1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.transport.ssh.jsch;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14 import static org.junit.Assert.assertArrayEquals;
15 import static org.junit.Assert.assertEquals;
16 import static org.junit.Assert.assertFalse;
17 import static org.junit.Assert.assertNotNull;
18 import static org.junit.Assert.assertNotSame;
19 import static org.junit.Assert.assertNull;
20 import static org.junit.Assert.assertTrue;
21
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.time.Instant;
27 import java.util.concurrent.TimeUnit;
28
29 import org.eclipse.jgit.junit.RepositoryTestCase;
30 import org.eclipse.jgit.lib.Constants;
31 import org.eclipse.jgit.transport.SshConstants;
32 import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig.Host;
33 import org.eclipse.jgit.util.FS;
34 import org.eclipse.jgit.util.FileUtils;
35 import org.eclipse.jgit.util.SystemReader;
36 import org.junit.Before;
37 import org.junit.Test;
38
39 import com.jcraft.jsch.ConfigRepository;
40 import com.jcraft.jsch.ConfigRepository.Config;
41
42 public class OpenSshConfigTest extends RepositoryTestCase {
43 private File home;
44
45 private File configFile;
46
47 private OpenSshConfig osc;
48
49 @Override
50 @Before
51 public void setUp() throws Exception {
52 super.setUp();
53
54 home = new File(trash, "home");
55 FileUtils.mkdir(home);
56
57 configFile = new File(new File(home, ".ssh"), Constants.CONFIG);
58 FileUtils.mkdir(configFile.getParentFile());
59
60 mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit");
61 mockSystemReader.setProperty("TST_VAR", "TEST");
62 osc = new OpenSshConfig(home, configFile);
63 }
64
65 private void config(String data) throws IOException {
66 FS fs = FS.DETECTED;
67 long resolution = FS.getFileStoreAttributes(configFile.toPath())
68 .getFsTimestampResolution().toNanos();
69 Instant lastMtime = fs.lastModifiedInstant(configFile);
70 do {
71 try (final OutputStreamWriter fw = new OutputStreamWriter(
72 new FileOutputStream(configFile), UTF_8)) {
73 fw.write(data);
74 TimeUnit.NANOSECONDS.sleep(resolution);
75 } catch (InterruptedException e) {
76 Thread.interrupted();
77 }
78 } while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
79 }
80
81 @Test
82 public void testNoConfig() {
83 final Host h = osc.lookup("repo.or.cz");
84 assertNotNull(h);
85 assertEquals("repo.or.cz", h.getHostName());
86 assertEquals("jex_junit", h.getUser());
87 assertEquals(22, h.getPort());
88 assertEquals(1, h.getConnectionAttempts());
89 assertNull(h.getIdentityFile());
90 }
91
92 @Test
93 public void testSeparatorParsing() throws Exception {
94 config("Host\tfirst\n" +
95 "\tHostName\tfirst.tld\n" +
96 "\n" +
97 "Host second\n" +
98 " HostName\tsecond.tld\n" +
99 "Host=third\n" +
100 "HostName=third.tld\n\n\n" +
101 "\t Host = fourth\n\n\n" +
102 " \t HostName\t=fourth.tld\n" +
103 "Host\t = last\n" +
104 "HostName \t last.tld");
105 assertNotNull(osc.lookup("first"));
106 assertEquals("first.tld", osc.lookup("first").getHostName());
107 assertNotNull(osc.lookup("second"));
108 assertEquals("second.tld", osc.lookup("second").getHostName());
109 assertNotNull(osc.lookup("third"));
110 assertEquals("third.tld", osc.lookup("third").getHostName());
111 assertNotNull(osc.lookup("fourth"));
112 assertEquals("fourth.tld", osc.lookup("fourth").getHostName());
113 assertNotNull(osc.lookup("last"));
114 assertEquals("last.tld", osc.lookup("last").getHostName());
115 }
116
117 @Test
118 public void testQuoteParsing() throws Exception {
119 config("Host \"good\"\n" +
120 " HostName=\"good.tld\"\n" +
121 " Port=\"6007\"\n" +
122 " User=\"gooduser\"\n" +
123 "Host multiple unquoted and \"quoted\" \"hosts\"\n" +
124 " Port=\"2222\"\n" +
125 "Host \"spaced\"\n" +
126 "# Bad host name, but testing preservation of spaces\n" +
127 " HostName=\" spaced\ttld \"\n" +
128 "# Misbalanced quotes\n" +
129 "Host \"bad\"\n" +
130 "# OpenSSH doesn't allow this but ...\n" +
131 " HostName=bad.tld\"\n");
132 assertEquals("good.tld", osc.lookup("good").getHostName());
133 assertEquals("gooduser", osc.lookup("good").getUser());
134 assertEquals(6007, osc.lookup("good").getPort());
135 assertEquals(2222, osc.lookup("multiple").getPort());
136 assertEquals(2222, osc.lookup("quoted").getPort());
137 assertEquals(2222, osc.lookup("and").getPort());
138 assertEquals(2222, osc.lookup("unquoted").getPort());
139 assertEquals(2222, osc.lookup("hosts").getPort());
140 assertEquals(" spaced\ttld ", osc.lookup("spaced").getHostName());
141 assertEquals("bad.tld", osc.lookup("bad").getHostName());
142 }
143
144 @Test
145 public void testCaseInsensitiveKeyLookup() throws Exception {
146 config("Host orcz\n" + "Port 29418\n"
147 + "\tHostName repo.or.cz\nStrictHostKeyChecking yes\n");
148 final Host h = osc.lookup("orcz");
149 Config c = h.getConfig();
150 String exactCase = c.getValue("StrictHostKeyChecking");
151 assertEquals("yes", exactCase);
152 assertEquals(exactCase, c.getValue("stricthostkeychecking"));
153 assertEquals(exactCase, c.getValue("STRICTHOSTKEYCHECKING"));
154 assertEquals(exactCase, c.getValue("sTrIcThostKEYcheckING"));
155 assertNull(c.getValue("sTrIcThostKEYcheckIN"));
156 }
157
158 @Test
159 public void testAlias_DoesNotMatch() throws Exception {
160 config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
161 final Host h = osc.lookup("repo.or.cz");
162 assertNotNull(h);
163 assertEquals("repo.or.cz", h.getHostName());
164 assertEquals("jex_junit", h.getUser());
165 assertEquals(22, h.getPort());
166 assertNull(h.getIdentityFile());
167 final Host h2 = osc.lookup("orcz");
168 assertEquals("repo.or.cz", h.getHostName());
169 assertEquals("jex_junit", h.getUser());
170 assertEquals(29418, h2.getPort());
171 assertNull(h.getIdentityFile());
172 }
173
174 @Test
175 public void testAlias_OptionsSet() throws Exception {
176 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\tPort 2222\n"
177 + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n"
178 + "\tForwardX11 no\n");
179 final Host h = osc.lookup("orcz");
180 assertNotNull(h);
181 assertEquals("repo.or.cz", h.getHostName());
182 assertEquals("jex", h.getUser());
183 assertEquals(2222, h.getPort());
184 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
185 }
186
187 @Test
188 public void testAlias_OptionsKeywordCaseInsensitive() throws Exception {
189 config("hOsT orcz\n" + "\thOsTnAmE repo.or.cz\n" + "\tPORT 2222\n"
190 + "\tuser jex\n" + "\tidentityfile .ssh/id_jex\n"
191 + "\tForwardX11 no\n");
192 final Host h = osc.lookup("orcz");
193 assertNotNull(h);
194 assertEquals("repo.or.cz", h.getHostName());
195 assertEquals("jex", h.getUser());
196 assertEquals(2222, h.getPort());
197 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
198 }
199
200 @Test
201 public void testAlias_OptionsInherit() throws Exception {
202 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
203 + "\tHostName not.a.host.example.com\n" + "\tPort 2222\n"
204 + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n"
205 + "\tForwardX11 no\n");
206 final Host h = osc.lookup("orcz");
207 assertNotNull(h);
208 assertEquals("repo.or.cz", h.getHostName());
209 assertEquals("jex", h.getUser());
210 assertEquals(2222, h.getPort());
211 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
212 }
213
214 @Test
215 public void testAlias_PreferredAuthenticationsDefault() throws Exception {
216 final Host h = osc.lookup("orcz");
217 assertNotNull(h);
218 assertNull(h.getPreferredAuthentications());
219 }
220
221 @Test
222 public void testAlias_PreferredAuthentications() throws Exception {
223 config("Host orcz\n" + "\tPreferredAuthentications publickey\n");
224 final Host h = osc.lookup("orcz");
225 assertNotNull(h);
226 assertEquals("publickey", h.getPreferredAuthentications());
227 }
228
229 @Test
230 public void testAlias_InheritPreferredAuthentications() throws Exception {
231 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
232 + "\tPreferredAuthentications 'publickey, hostbased'\n");
233 final Host h = osc.lookup("orcz");
234 assertNotNull(h);
235 assertEquals("publickey,hostbased", h.getPreferredAuthentications());
236 }
237
238 @Test
239 public void testAlias_BatchModeDefault() throws Exception {
240 final Host h = osc.lookup("orcz");
241 assertNotNull(h);
242 assertFalse(h.isBatchMode());
243 }
244
245 @Test
246 public void testAlias_BatchModeYes() throws Exception {
247 config("Host orcz\n" + "\tBatchMode yes\n");
248 final Host h = osc.lookup("orcz");
249 assertNotNull(h);
250 assertTrue(h.isBatchMode());
251 }
252
253 @Test
254 public void testAlias_InheritBatchMode() throws Exception {
255 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
256 + "\tBatchMode yes\n");
257 final Host h = osc.lookup("orcz");
258 assertNotNull(h);
259 assertTrue(h.isBatchMode());
260 }
261
262 @Test
263 public void testAlias_ConnectionAttemptsDefault() throws Exception {
264 final Host h = osc.lookup("orcz");
265 assertNotNull(h);
266 assertEquals(1, h.getConnectionAttempts());
267 }
268
269 @Test
270 public void testAlias_ConnectionAttempts() throws Exception {
271 config("Host orcz\n" + "\tConnectionAttempts 5\n");
272 final Host h = osc.lookup("orcz");
273 assertNotNull(h);
274 assertEquals(5, h.getConnectionAttempts());
275 }
276
277 @Test
278 public void testAlias_invalidConnectionAttempts() throws Exception {
279 config("Host orcz\n" + "\tConnectionAttempts -1\n");
280 final Host h = osc.lookup("orcz");
281 assertNotNull(h);
282 assertEquals(1, h.getConnectionAttempts());
283 }
284
285 @Test
286 public void testAlias_badConnectionAttempts() throws Exception {
287 config("Host orcz\n" + "\tConnectionAttempts xxx\n");
288 final Host h = osc.lookup("orcz");
289 assertNotNull(h);
290 assertEquals(1, h.getConnectionAttempts());
291 }
292
293 @Test
294 public void testDefaultBlock() throws Exception {
295 config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n");
296 final Host h = osc.lookup("orcz");
297 assertNotNull(h);
298 assertEquals(5, h.getConnectionAttempts());
299 }
300
301 @Test
302 public void testHostCaseInsensitive() throws Exception {
303 config("hOsT orcz\nConnectionAttempts 3\n");
304 final Host h = osc.lookup("orcz");
305 assertNotNull(h);
306 assertEquals(3, h.getConnectionAttempts());
307 }
308
309 @Test
310 public void testListValueSingle() throws Exception {
311 config("Host orcz\nUserKnownHostsFile /foo/bar\n");
312 final ConfigRepository.Config c = osc.getConfig("orcz");
313 assertNotNull(c);
314 assertEquals("/foo/bar", c.getValue("UserKnownHostsFile"));
315 }
316
317 @Test
318 public void testListValueMultiple() throws Exception {
319
320 config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n");
321 final ConfigRepository.Config c = osc.getConfig("orcz");
322 assertNotNull(c);
323 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
324 "/foo/bar" },
325 c.getValues("UserKnownHostsFile"));
326 }
327
328 @Test
329 public void testRepeatedLookupsWithModification() throws Exception {
330 config("Host orcz\n" + "\tConnectionAttempts -1\n");
331 final Host h1 = osc.lookup("orcz");
332 assertNotNull(h1);
333 assertEquals(1, h1.getConnectionAttempts());
334 config("Host orcz\n" + "\tConnectionAttempts 5\n");
335 final Host h2 = osc.lookup("orcz");
336 assertNotNull(h2);
337 assertNotSame(h1, h2);
338 assertEquals(5, h2.getConnectionAttempts());
339 assertEquals(1, h1.getConnectionAttempts());
340 assertNotSame(h1.getConfig(), h2.getConfig());
341 }
342
343 @Test
344 public void testIdentityFile() throws Exception {
345 config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar");
346 final Host h = osc.lookup("orcz");
347 assertNotNull(h);
348 File f = h.getIdentityFile();
349 assertNotNull(f);
350
351 assertEquals(new File(home, "foo/ba z"), f);
352 final ConfigRepository.Config c = h.getConfig();
353
354 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
355 "/foo/bar" },
356 c.getValues("IdentityFile"));
357 }
358
359 @Test
360 public void testMultiIdentityFile() throws Exception {
361 config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz");
362 final Host h = osc.lookup("orcz");
363 assertNotNull(h);
364 File f = h.getIdentityFile();
365 assertNotNull(f);
366
367 assertEquals(new File(home, "foo/ba z"), f);
368 final ConfigRepository.Config c = h.getConfig();
369
370 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
371 "/foo/bar", "/foo/baz" },
372 c.getValues("IdentityFile"));
373 }
374
375 @Test
376 public void testNegatedPattern() throws Exception {
377 config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz");
378 final Host h = osc.lookup("repo.or.cz");
379 assertNotNull(h);
380 assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
381 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
382 h.getConfig().getValues("IdentityFile"));
383 }
384
385 @Test
386 public void testPattern() throws Exception {
387 config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
388 final Host h = osc.lookup("repo.or.cz");
389 assertNotNull(h);
390 assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
391 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
392 "/foo/baz" },
393 h.getConfig().getValues("IdentityFile"));
394 }
395
396 @Test
397 public void testMultiHost() throws Exception {
398 config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
399 final Host h1 = osc.lookup("repo.or.cz");
400 assertNotNull(h1);
401 assertEquals(new File(home, "foo/bar"), h1.getIdentityFile());
402 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
403 "/foo/baz" },
404 h1.getConfig().getValues("IdentityFile"));
405 final Host h2 = osc.lookup("orcz");
406 assertNotNull(h2);
407 assertEquals(new File(home, "foo/bar"), h2.getIdentityFile());
408 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
409 h2.getConfig().getValues("IdentityFile"));
410 }
411
412 @Test
413 public void testEqualsSign() throws Exception {
414 config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n");
415 final Host h = osc.lookup("orcz");
416 assertNotNull(h);
417 assertEquals(5, h.getConnectionAttempts());
418 assertEquals("foobar", h.getUser());
419 }
420
421 @Test
422 public void testMissingArgument() throws Exception {
423 config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n");
424 final Host h = osc.lookup("orcz");
425 assertNotNull(h);
426 assertEquals("foobar", h.getUser());
427 assertArrayEquals(new String[0], h.getConfig().getValues("SendEnv"));
428 assertNull(h.getIdentityFile());
429 assertNull(h.getConfig().getValue("ForwardX11"));
430 }
431
432 @Test
433 public void testHomeDirUserReplacement() throws Exception {
434 config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa");
435 final Host h = osc.lookup("orcz");
436 assertNotNull(h);
437 assertEquals(new File(new File(home, ".ssh"), "jex_junit_id_dsa"),
438 h.getIdentityFile());
439 }
440
441 @Test
442 public void testHostnameReplacement() throws Exception {
443 config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org");
444 final Host h = osc.lookup("orcz");
445 assertNotNull(h);
446 assertEquals("orcz.example.org", h.getHostName());
447 }
448
449 @Test
450 public void testRemoteUserReplacement() throws Exception {
451 config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n"
452 + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa");
453 final Host h = osc.lookup("orcz");
454 assertNotNull(h);
455 assertEquals(
456 new File(new File(home, ".ssh"),
457 "orcz.ex%20ample.org_foo_id_dsa"),
458 h.getIdentityFile());
459 }
460
461 @Test
462 public void testLocalhostFQDNReplacement() throws Exception {
463 String localhost = SystemReader.getInstance().getHostname();
464 config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa");
465 final Host h = osc.lookup("orcz");
466 assertNotNull(h);
467 assertEquals(
468 new File(new File(home, ".ssh"), localhost + "_id_dsa"),
469 h.getIdentityFile());
470 }
471
472 @Test
473 public void testPubKeyAcceptedAlgorithms() throws Exception {
474 config("Host=orcz\n\tPubkeyAcceptedAlgorithms ^ssh-rsa");
475 Host h = osc.lookup("orcz");
476 Config c = h.getConfig();
477 assertEquals("^ssh-rsa",
478 c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
479 assertEquals("^ssh-rsa", c.getValue("PubkeyAcceptedKeyTypes"));
480 }
481
482 @Test
483 public void testPubKeyAcceptedKeyTypes() throws Exception {
484 config("Host=orcz\n\tPubkeyAcceptedKeyTypes ^ssh-rsa");
485 Host h = osc.lookup("orcz");
486 Config c = h.getConfig();
487 assertEquals("^ssh-rsa",
488 c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
489 assertEquals("^ssh-rsa", c.getValue("PubkeyAcceptedKeyTypes"));
490 }
491
492 @Test
493 public void testEolComments() throws Exception {
494 config("#Comment\nHost=orcz #Comment\n\tPubkeyAcceptedAlgorithms ^ssh-rsa # Comment\n#Comment");
495 Host h = osc.lookup("orcz");
496 assertNotNull(h);
497 Config c = h.getConfig();
498 assertEquals("^ssh-rsa",
499 c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
500 }
501
502 @Test
503 public void testEnVarSubstitution() throws Exception {
504 config("Host orcz\nIdentityFile /tmp/${TST_VAR}\n"
505 + "CertificateFile /tmp/${}/foo\nUser ${TST_VAR}\nIdentityAgent /tmp/${TST_VAR/bar");
506 Host h = osc.lookup("orcz");
507 assertNotNull(h);
508 Config c = h.getConfig();
509 assertEquals("/tmp/TEST",
510 c.getValue(SshConstants.IDENTITY_FILE));
511
512 assertEquals("/tmp/${}/foo", c.getValue(SshConstants.CERTIFICATE_FILE));
513
514 assertEquals("${TST_VAR}", c.getValue(SshConstants.USER));
515 assertEquals("${TST_VAR}", h.getUser());
516
517 assertEquals("/tmp/${TST_VAR/bar",
518 c.getValue(SshConstants.IDENTITY_AGENT));
519 }
520
521 @Test
522 public void testNegativeMatch() throws Exception {
523 config("Host foo.bar !foobar.baz *.baz\n" + "Port 29418\n");
524 Host h = osc.lookup("foo.bar");
525 assertNotNull(h);
526 assertEquals(29418, h.getPort());
527 h = osc.lookup("foobar.baz");
528 assertNotNull(h);
529 assertEquals(22, h.getPort());
530 h = osc.lookup("foo.baz");
531 assertNotNull(h);
532 assertEquals(29418, h.getPort());
533 }
534
535 @Test
536 public void testNegativeMatch2() throws Exception {
537
538 config("Host foo.bar *.baz !foobar.baz\n" + "Port 29418\n");
539 Host h = osc.lookup("foo.bar");
540 assertNotNull(h);
541 assertEquals(29418, h.getPort());
542 h = osc.lookup("foobar.baz");
543 assertNotNull(h);
544 assertEquals(22, h.getPort());
545 h = osc.lookup("foo.baz");
546 assertNotNull(h);
547 assertEquals(29418, h.getPort());
548 }
549
550 @Test
551 public void testNoMatch() throws Exception {
552 config("Host !host1 !host2\n" + "Port 29418\n");
553 Host h = osc.lookup("host1");
554 assertNotNull(h);
555 assertEquals(22, h.getPort());
556 h = osc.lookup("host2");
557 assertNotNull(h);
558 assertEquals(22, h.getPort());
559 h = osc.lookup("host3");
560 assertNotNull(h);
561 assertEquals(22, h.getPort());
562 }
563
564 @Test
565 public void testMultipleMatch() throws Exception {
566 config("Host foo.bar\nPort 29418\nIdentityFile /foo\n\n"
567 + "Host *.bar\nPort 22\nIdentityFile /bar\n"
568 + "Host foo.bar\nPort 47\nIdentityFile /baz\n");
569 Host h = osc.lookup("foo.bar");
570 assertNotNull(h);
571 assertEquals(29418, h.getPort());
572 assertArrayEquals(new Object[] { "/foo", "/bar", "/baz" },
573 h.getConfig().getValues("IdentityFile"));
574 }
575
576 @Test
577 public void testWhitespace() throws Exception {
578 config("Host foo \tbar baz\nPort 29418\n");
579 Host h = osc.lookup("foo");
580 assertNotNull(h);
581 assertEquals(29418, h.getPort());
582 h = osc.lookup("bar");
583 assertNotNull(h);
584 assertEquals(29418, h.getPort());
585 h = osc.lookup("baz");
586 assertNotNull(h);
587 assertEquals(29418, h.getPort());
588 h = osc.lookup("\tbar");
589 assertNotNull(h);
590 assertEquals(22, h.getPort());
591 }
592 }