View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.transport.http;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.net.ConnectException;
24  import java.net.ServerSocket;
25  import java.net.SocketTimeoutException;
26  import java.net.URI;
27  import java.nio.charset.StandardCharsets;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.concurrent.atomic.AtomicReference;
31  
32  import org.apache.http.NoHttpResponseException;
33  import org.apache.http.client.HttpResponseException;
34  import org.apache.http.conn.ConnectTimeoutException;
35  import org.apache.http.pool.ConnPoolControl;
36  import org.apache.http.pool.PoolStats;
37  import org.eclipse.aether.ConfigurationProperties;
38  import org.eclipse.aether.DefaultRepositoryCache;
39  import org.eclipse.aether.DefaultRepositorySystemSession;
40  import org.eclipse.aether.internal.test.util.TestFileUtils;
41  import org.eclipse.aether.internal.test.util.TestUtils;
42  import org.eclipse.aether.repository.Authentication;
43  import org.eclipse.aether.repository.Proxy;
44  import org.eclipse.aether.repository.RemoteRepository;
45  import org.eclipse.aether.spi.connector.transport.GetTask;
46  import org.eclipse.aether.spi.connector.transport.PeekTask;
47  import org.eclipse.aether.spi.connector.transport.PutTask;
48  import org.eclipse.aether.spi.connector.transport.Transporter;
49  import org.eclipse.aether.spi.connector.transport.TransporterFactory;
50  import org.eclipse.aether.transfer.NoTransporterException;
51  import org.eclipse.aether.transfer.TransferCancelledException;
52  import org.eclipse.aether.transport.http.RFC9457.HttpRFC9457Exception;
53  import org.eclipse.aether.util.repository.AuthenticationBuilder;
54  import org.junit.After;
55  import org.junit.Before;
56  import org.junit.Rule;
57  import org.junit.Test;
58  import org.junit.rules.TestName;
59  
60  import static org.junit.Assert.assertEquals;
61  import static org.junit.Assert.assertNotNull;
62  import static org.junit.Assert.assertNull;
63  import static org.junit.Assert.assertTrue;
64  import static org.junit.Assert.fail;
65  
66  /**
67   */
68  public class HttpTransporterTest {
69  
70      static {
71          System.setProperty(
72                  "javax.net.ssl.trustStore", new File("src/test/resources/ssl/server-store").getAbsolutePath());
73          System.setProperty("javax.net.ssl.trustStorePassword", "server-pwd");
74          System.setProperty("javax.net.ssl.keyStore", new File("src/test/resources/ssl/client-store").getAbsolutePath());
75          System.setProperty("javax.net.ssl.keyStorePassword", "client-pwd");
76      }
77  
78      @Rule
79      public TestName testName = new TestName();
80  
81      private DefaultRepositorySystemSession session;
82  
83      private TransporterFactory factory;
84  
85      private Transporter transporter;
86  
87      private File repoDir;
88  
89      private HttpServer httpServer;
90  
91      private Authentication auth;
92  
93      private Proxy proxy;
94  
95      private RemoteRepository newRepo(String url) {
96          return new RemoteRepository.Builder("test", "default", url)
97                  .setAuthentication(auth)
98                  .setProxy(proxy)
99                  .build();
100     }
101 
102     private void newTransporter(String url) throws Exception {
103         if (transporter != null) {
104             transporter.close();
105             transporter = null;
106         }
107         transporter = factory.newInstance(session, newRepo(url));
108     }
109 
110     private static final long OLD_FILE_TIMESTAMP = 160660800000L;
111 
112     @Before
113     public void setUp() throws Exception {
114         System.out.println("=== " + testName.getMethodName() + " ===");
115         session = TestUtils.newSession();
116         factory = new HttpTransporterFactory();
117         repoDir = TestFileUtils.createTempDir();
118         TestFileUtils.writeString(new File(repoDir, "file.txt"), "test");
119         TestFileUtils.writeString(new File(repoDir, "dir/file.txt"), "test");
120         TestFileUtils.writeString(new File(repoDir, "dir/oldFile.txt"), "oldTest", OLD_FILE_TIMESTAMP);
121         TestFileUtils.writeString(new File(repoDir, "empty.txt"), "");
122         TestFileUtils.writeString(new File(repoDir, "some space.txt"), "space");
123         File resumable = new File(repoDir, "resume.txt");
124         TestFileUtils.writeString(resumable, "resumable");
125         resumable.setLastModified(System.currentTimeMillis() - 90 * 1000);
126         httpServer = new HttpServer().setRepoDir(repoDir).start();
127         newTransporter(httpServer.getHttpUrl());
128     }
129 
130     @After
131     public void tearDown() throws Exception {
132         if (transporter != null) {
133             transporter.close();
134             transporter = null;
135         }
136         if (httpServer != null) {
137             httpServer.stop();
138             httpServer = null;
139         }
140         factory = null;
141         session = null;
142     }
143 
144     @Test
145     public void testClassify() {
146         assertEquals(Transporter.ERROR_OTHER, transporter.classify(new FileNotFoundException()));
147         assertEquals(Transporter.ERROR_OTHER, transporter.classify(new HttpResponseException(403, "Forbidden")));
148         assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new HttpResponseException(404, "Not Found")));
149     }
150 
151     @Test
152     public void testPeek() throws Exception {
153         transporter.peek(new PeekTask(URI.create("repo/file.txt")));
154     }
155 
156     @Test
157     public void testRetryHandlerdefaultCountpositive() throws Exception {
158         httpServer.setConnectionsToClose(3);
159         transporter.peek(new PeekTask(URI.create("repo/file.txt")));
160     }
161 
162     @Test
163     public void testRetryHandlerdefaultCountnegative() throws Exception {
164         httpServer.setConnectionsToClose(4);
165         try {
166             transporter.peek(new PeekTask(URI.create("repo/file.txt")));
167             fail("Expected error");
168         } catch (NoHttpResponseException expected) {
169         }
170     }
171 
172     @Test
173     public void testRetryHandlerexplicitCountpositive() throws Exception {
174         session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 10);
175         newTransporter(httpServer.getHttpUrl());
176         httpServer.setConnectionsToClose(10);
177         transporter.peek(new PeekTask(URI.create("repo/file.txt")));
178     }
179 
180     @Test
181     public void testRetryHandlerdisabled() throws Exception {
182         session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 0);
183         newTransporter(httpServer.getHttpUrl());
184         httpServer.setConnectionsToClose(1);
185         try {
186             transporter.peek(new PeekTask(URI.create("repo/file.txt")));
187         } catch (NoHttpResponseException expected) {
188         }
189     }
190 
191     @Test
192     public void testPeekNotFound() throws Exception {
193         try {
194             transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
195             fail("Expected error");
196         } catch (HttpResponseException e) {
197             assertEquals(404, e.getStatusCode());
198             assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
199         }
200     }
201 
202     @Test
203     public void testPeekClosed() throws Exception {
204         transporter.close();
205         try {
206             transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
207             fail("Expected error");
208         } catch (IllegalStateException e) {
209             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
210         }
211     }
212 
213     @Test
214     public void testPeekAuthenticated() throws Exception {
215         httpServer.setAuthentication("testuser", "testpass");
216         auth = new AuthenticationBuilder()
217                 .addUsername("testuser")
218                 .addPassword("testpass")
219                 .build();
220         newTransporter(httpServer.getHttpUrl());
221         transporter.peek(new PeekTask(URI.create("repo/file.txt")));
222     }
223 
224     @Test
225     public void testPeekUnauthenticated() throws Exception {
226         httpServer.setAuthentication("testuser", "testpass");
227         try {
228             transporter.peek(new PeekTask(URI.create("repo/file.txt")));
229             fail("Expected error");
230         } catch (HttpResponseException e) {
231             assertEquals(401, e.getStatusCode());
232             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
233         }
234     }
235 
236     @Test
237     public void testPeekProxyAuthenticated() throws Exception {
238         httpServer.setProxyAuthentication("testuser", "testpass");
239         auth = new AuthenticationBuilder()
240                 .addUsername("testuser")
241                 .addPassword("testpass")
242                 .build();
243         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
244         newTransporter("http://bad.localhost:1/");
245         transporter.peek(new PeekTask(URI.create("repo/file.txt")));
246     }
247 
248     @Test
249     public void testPeekProxyUnauthenticated() throws Exception {
250         httpServer.setProxyAuthentication("testuser", "testpass");
251         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
252         newTransporter("http://bad.localhost:1/");
253         try {
254             transporter.peek(new PeekTask(URI.create("repo/file.txt")));
255             fail("Expected error");
256         } catch (HttpResponseException e) {
257             assertEquals(407, e.getStatusCode());
258             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
259         }
260     }
261 
262     @Test
263     public void testPeekSSL() throws Exception {
264         httpServer.addSslConnector();
265         newTransporter(httpServer.getHttpsUrl());
266         transporter.peek(new PeekTask(URI.create("repo/file.txt")));
267     }
268 
269     @Test
270     public void testPeekRedirect() throws Exception {
271         httpServer.addSslConnector();
272         transporter.peek(new PeekTask(URI.create("redirect/file.txt")));
273         transporter.peek(new PeekTask(URI.create("redirect/file.txt?scheme=https")));
274     }
275 
276     @Test
277     public void testGetToMemory() throws Exception {
278         RecordingTransportListener listener = new RecordingTransportListener();
279         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
280         transporter.get(task);
281         assertEquals("test", task.getDataString());
282         assertEquals(0L, listener.dataOffset);
283         assertEquals(4L, listener.dataLength);
284         assertEquals(1, listener.startedCount);
285         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
286         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
287     }
288 
289     @Test
290     public void testGetToFile() throws Exception {
291         File file = TestFileUtils.createTempFile("failure");
292         RecordingTransportListener listener = new RecordingTransportListener();
293         GetTask task =
294                 new GetTask(URI.create("repo/file.txt")).setDataFile(file).setListener(listener);
295         transporter.get(task);
296         assertEquals("test", TestFileUtils.readString(file));
297         assertEquals(0L, listener.dataOffset);
298         assertEquals(4L, listener.dataLength);
299         assertEquals(1, listener.startedCount);
300         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
301         assertEquals("test", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
302     }
303 
304     @Test
305     public void testGetToFileTimestamp() throws Exception {
306         File file = TestFileUtils.createTempFile("failure");
307         RecordingTransportListener listener = new RecordingTransportListener();
308         GetTask task = new GetTask(URI.create("repo/dir/oldFile.txt"))
309                 .setDataFile(file)
310                 .setListener(listener);
311         transporter.get(task);
312         assertEquals("oldTest", TestFileUtils.readString(file));
313         assertEquals(0L, listener.dataOffset);
314         assertEquals(7L, listener.dataLength);
315         assertEquals(1, listener.startedCount);
316         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
317         assertEquals("oldTest", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
318         assertEquals(file.lastModified(), OLD_FILE_TIMESTAMP);
319     }
320 
321     @Test
322     public void testGetEmptyResource() throws Exception {
323         File file = TestFileUtils.createTempFile("failure");
324         RecordingTransportListener listener = new RecordingTransportListener();
325         GetTask task =
326                 new GetTask(URI.create("repo/empty.txt")).setDataFile(file).setListener(listener);
327         transporter.get(task);
328         assertEquals("", TestFileUtils.readString(file));
329         assertEquals(0L, listener.dataOffset);
330         assertEquals(0L, listener.dataLength);
331         assertEquals(1, listener.startedCount);
332         assertEquals(0, listener.progressedCount);
333         assertEquals("", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
334     }
335 
336     @Test
337     public void testGetEncodedResourcePath() throws Exception {
338         GetTask task = new GetTask(URI.create("repo/some%20space.txt"));
339         transporter.get(task);
340         assertEquals("space", task.getDataString());
341     }
342 
343     @Test
344     public void testGetAuthenticated() throws Exception {
345         httpServer.setAuthentication("testuser", "testpass");
346         auth = new AuthenticationBuilder()
347                 .addUsername("testuser")
348                 .addPassword("testpass")
349                 .build();
350         newTransporter(httpServer.getHttpUrl());
351         RecordingTransportListener listener = new RecordingTransportListener();
352         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
353         transporter.get(task);
354         assertEquals("test", task.getDataString());
355         assertEquals(0L, listener.dataOffset);
356         assertEquals(4L, listener.dataLength);
357         assertEquals(1, listener.startedCount);
358         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
359         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
360     }
361 
362     @Test
363     public void testGetUnauthenticated() throws Exception {
364         httpServer.setAuthentication("testuser", "testpass");
365         try {
366             transporter.get(new GetTask(URI.create("repo/file.txt")));
367             fail("Expected error");
368         } catch (HttpResponseException e) {
369             assertEquals(401, e.getStatusCode());
370             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
371         }
372     }
373 
374     @Test
375     public void testGetProxyAuthenticated() throws Exception {
376         httpServer.setProxyAuthentication("testuser", "testpass");
377         Authentication auth = new AuthenticationBuilder()
378                 .addUsername("testuser")
379                 .addPassword("testpass")
380                 .build();
381         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
382         newTransporter("http://bad.localhost:1/");
383         RecordingTransportListener listener = new RecordingTransportListener();
384         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
385         transporter.get(task);
386         assertEquals("test", task.getDataString());
387         assertEquals(0L, listener.dataOffset);
388         assertEquals(4L, listener.dataLength);
389         assertEquals(1, listener.startedCount);
390         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
391         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
392     }
393 
394     @Test
395     public void testGetProxyUnauthenticated() throws Exception {
396         httpServer.setProxyAuthentication("testuser", "testpass");
397         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
398         newTransporter("http://bad.localhost:1/");
399         try {
400             transporter.get(new GetTask(URI.create("repo/file.txt")));
401             fail("Expected error");
402         } catch (HttpResponseException e) {
403             assertEquals(407, e.getStatusCode());
404             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
405         }
406     }
407 
408     @Test
409     public void testGetSSL() throws Exception {
410         httpServer.addSslConnector();
411         newTransporter(httpServer.getHttpsUrl());
412         RecordingTransportListener listener = new RecordingTransportListener();
413         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
414         transporter.get(task);
415         assertEquals("test", task.getDataString());
416         assertEquals(0L, listener.dataOffset);
417         assertEquals(4L, listener.dataLength);
418         assertEquals(1, listener.startedCount);
419         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
420         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
421     }
422 
423     @Test
424     public void testGetHTTPSUnknownSecurityMode() throws Exception {
425         session.setConfigProperty("aether.connector.https.securityMode", "unknown");
426         httpServer.addSelfSignedSslConnector();
427         try {
428             newTransporter(httpServer.getHttpsUrl());
429             fail("Unsupported security mode");
430         } catch (IllegalArgumentException a) {
431             // good
432         }
433     }
434 
435     @Test
436     public void testGetHTTPSInsecureSecurityMode() throws Exception {
437         // here we use alternate server-store-selfigned key (as the key set it static initalizer is probably already
438         // used to init SSLContext/SSLSocketFactory/etc
439         session.setConfigProperty(
440                 "aether.connector.https.securityMode", ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE);
441         httpServer.addSelfSignedSslConnector();
442         newTransporter(httpServer.getHttpsUrl());
443         RecordingTransportListener listener = new RecordingTransportListener();
444         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
445         transporter.get(task);
446         assertEquals("test", task.getDataString());
447         assertEquals(0L, listener.dataOffset);
448         assertEquals(4L, listener.dataLength);
449         assertEquals(1, listener.startedCount);
450         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
451         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
452     }
453 
454     @Test
455     public void testGetWebDav() throws Exception {
456         httpServer.setWebDav(true);
457         RecordingTransportListener listener = new RecordingTransportListener();
458         GetTask task = new GetTask(URI.create("repo/dir/file.txt")).setListener(listener);
459         ((HttpTransporter) transporter).getState().setWebDav(true);
460         transporter.get(task);
461         assertEquals("test", task.getDataString());
462         assertEquals(0L, listener.dataOffset);
463         assertEquals(4L, listener.dataLength);
464         assertEquals(1, listener.startedCount);
465         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
466         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
467         assertEquals(
468                 httpServer.getLogEntries().toString(),
469                 1,
470                 httpServer.getLogEntries().size());
471     }
472 
473     @Test
474     public void testGetRedirect() throws Exception {
475         httpServer.addSslConnector();
476         RecordingTransportListener listener = new RecordingTransportListener();
477         GetTask task = new GetTask(URI.create("redirect/file.txt?scheme=https")).setListener(listener);
478         transporter.get(task);
479         assertEquals("test", task.getDataString());
480         assertEquals(0L, listener.dataOffset);
481         assertEquals(4L, listener.dataLength);
482         assertEquals(1, listener.startedCount);
483         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
484         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
485     }
486 
487     @Test
488     public void testGetResume() throws Exception {
489         File file = TestFileUtils.createTempFile("re");
490         RecordingTransportListener listener = new RecordingTransportListener();
491         GetTask task = new GetTask(URI.create("repo/resume.txt"))
492                 .setDataFile(file, true)
493                 .setListener(listener);
494         transporter.get(task);
495         assertEquals("resumable", TestFileUtils.readString(file));
496         assertEquals(1L, listener.startedCount);
497         assertEquals(2L, listener.dataOffset);
498         assertEquals(9, listener.dataLength);
499         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
500         assertEquals("sumable", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
501     }
502 
503     @Test
504     public void testGetResumeLocalContentsOutdated() throws Exception {
505         File file = TestFileUtils.createTempFile("re");
506         file.setLastModified(System.currentTimeMillis() - 5 * 60 * 1000);
507         RecordingTransportListener listener = new RecordingTransportListener();
508         GetTask task = new GetTask(URI.create("repo/resume.txt"))
509                 .setDataFile(file, true)
510                 .setListener(listener);
511         transporter.get(task);
512         assertEquals("resumable", TestFileUtils.readString(file));
513         assertEquals(1L, listener.startedCount);
514         assertEquals(0L, listener.dataOffset);
515         assertEquals(9, listener.dataLength);
516         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
517         assertEquals("resumable", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
518     }
519 
520     @Test
521     public void testGetResumeRangesNotSupportedByServer() throws Exception {
522         httpServer.setRangeSupport(false);
523         File file = TestFileUtils.createTempFile("re");
524         RecordingTransportListener listener = new RecordingTransportListener();
525         GetTask task = new GetTask(URI.create("repo/resume.txt"))
526                 .setDataFile(file, true)
527                 .setListener(listener);
528         transporter.get(task);
529         assertEquals("resumable", TestFileUtils.readString(file));
530         assertEquals(1L, listener.startedCount);
531         assertEquals(0L, listener.dataOffset);
532         assertEquals(9, listener.dataLength);
533         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
534         assertEquals("resumable", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
535     }
536 
537     @Test
538     public void testGetChecksumsNexus() throws Exception {
539         httpServer.setChecksumHeader(HttpServer.ChecksumHeader.NEXUS);
540         GetTask task = new GetTask(URI.create("repo/file.txt"));
541         transporter.get(task);
542         assertEquals("test", task.getDataString());
543         assertEquals(
544                 "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
545     }
546 
547     @Test
548     public void testGetChecksumsXChecksum() throws Exception {
549         httpServer.setChecksumHeader(HttpServer.ChecksumHeader.XCHECKSUM);
550         GetTask task = new GetTask(URI.create("repo/file.txt"));
551         transporter.get(task);
552         assertEquals("test", task.getDataString());
553         assertEquals(
554                 "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
555     }
556 
557     @Test
558     public void testGetFileHandleLeak() throws Exception {
559         for (int i = 0; i < 100; i++) {
560             File file = TestFileUtils.createTempFile("failure");
561             transporter.get(new GetTask(URI.create("repo/file.txt")).setDataFile(file));
562             assertTrue(i + ", " + file.getAbsolutePath(), file.delete());
563         }
564     }
565 
566     @Test
567     public void testGetNotFound() throws Exception {
568         try {
569             transporter.get(new GetTask(URI.create("repo/missing.txt")));
570             fail("Expected error");
571         } catch (HttpResponseException e) {
572             assertEquals(404, e.getStatusCode());
573             assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
574         }
575     }
576 
577     @Test
578     public void testGetClosed() throws Exception {
579         transporter.close();
580         try {
581             transporter.get(new GetTask(URI.create("repo/file.txt")));
582             fail("Expected error");
583         } catch (IllegalStateException e) {
584             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
585         }
586     }
587 
588     @Test
589     public void testGetStartCancelled() throws Exception {
590         RecordingTransportListener listener = new RecordingTransportListener();
591         listener.cancelStart = true;
592         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
593         try {
594             transporter.get(task);
595             fail("Expected error");
596         } catch (TransferCancelledException e) {
597             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
598         }
599         assertEquals(0L, listener.dataOffset);
600         assertEquals(4L, listener.dataLength);
601         assertEquals(1, listener.startedCount);
602         assertEquals(0, listener.progressedCount);
603     }
604 
605     @Test
606     public void testGetProgressCancelled() throws Exception {
607         RecordingTransportListener listener = new RecordingTransportListener();
608         listener.cancelProgress = true;
609         GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
610         try {
611             transporter.get(task);
612             fail("Expected error");
613         } catch (TransferCancelledException e) {
614             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
615         }
616         assertEquals(0L, listener.dataOffset);
617         assertEquals(4L, listener.dataLength);
618         assertEquals(1, listener.startedCount);
619         assertEquals(1, listener.progressedCount);
620     }
621 
622     @Test
623     public void testPutFromMemory() throws Exception {
624         RecordingTransportListener listener = new RecordingTransportListener();
625         PutTask task =
626                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
627         transporter.put(task);
628         assertEquals(0L, listener.dataOffset);
629         assertEquals(6L, listener.dataLength);
630         assertEquals(1, listener.startedCount);
631         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
632         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
633     }
634 
635     @Test
636     public void testPutFromFile() throws Exception {
637         File file = TestFileUtils.createTempFile("upload");
638         RecordingTransportListener listener = new RecordingTransportListener();
639         PutTask task =
640                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataFile(file);
641         transporter.put(task);
642         assertEquals(0L, listener.dataOffset);
643         assertEquals(6L, listener.dataLength);
644         assertEquals(1, listener.startedCount);
645         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
646         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
647     }
648 
649     @Test
650     public void testPutEmptyResource() throws Exception {
651         RecordingTransportListener listener = new RecordingTransportListener();
652         PutTask task = new PutTask(URI.create("repo/file.txt")).setListener(listener);
653         transporter.put(task);
654         assertEquals(0L, listener.dataOffset);
655         assertEquals(0L, listener.dataLength);
656         assertEquals(1, listener.startedCount);
657         assertEquals(0, listener.progressedCount);
658         assertEquals("", TestFileUtils.readString(new File(repoDir, "file.txt")));
659     }
660 
661     @Test
662     public void testPutEncodedResourcePath() throws Exception {
663         RecordingTransportListener listener = new RecordingTransportListener();
664         PutTask task = new PutTask(URI.create("repo/some%20space.txt"))
665                 .setListener(listener)
666                 .setDataString("OK");
667         transporter.put(task);
668         assertEquals(0L, listener.dataOffset);
669         assertEquals(2L, listener.dataLength);
670         assertEquals(1, listener.startedCount);
671         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
672         assertEquals("OK", TestFileUtils.readString(new File(repoDir, "some space.txt")));
673     }
674 
675     @Test
676     public void testPutAuthenticatedExpectContinue() throws Exception {
677         httpServer.setAuthentication("testuser", "testpass");
678         auth = new AuthenticationBuilder()
679                 .addUsername("testuser")
680                 .addPassword("testpass")
681                 .build();
682         newTransporter(httpServer.getHttpUrl());
683         RecordingTransportListener listener = new RecordingTransportListener();
684         PutTask task =
685                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
686         transporter.put(task);
687         assertEquals(0L, listener.dataOffset);
688         assertEquals(6L, listener.dataLength);
689         assertEquals(1, listener.startedCount);
690         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
691         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
692     }
693 
694     @Test
695     public void testPutAuthenticatedExpectContinueBroken() throws Exception {
696         // this makes OPTIONS recover, and have only 1 PUT (startedCount=1 as OPTIONS is not counted)
697         session.setConfigProperty(HttpTransporter.SUPPORT_WEBDAV, true);
698         httpServer.setAuthentication("testuser", "testpass");
699         httpServer.setExpectSupport(HttpServer.ExpectContinue.BROKEN);
700         auth = new AuthenticationBuilder()
701                 .addUsername("testuser")
702                 .addPassword("testpass")
703                 .build();
704         newTransporter(httpServer.getHttpUrl());
705         RecordingTransportListener listener = new RecordingTransportListener();
706         PutTask task =
707                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
708         transporter.put(task);
709         assertEquals(0L, listener.dataOffset);
710         assertEquals(6L, listener.dataLength);
711         assertEquals(1, listener.startedCount);
712         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
713         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
714     }
715 
716     @Test
717     public void testPutAuthenticatedExpectContinueRejected() throws Exception {
718         httpServer.setAuthentication("testuser", "testpass");
719         httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
720         auth = new AuthenticationBuilder()
721                 .addUsername("testuser")
722                 .addPassword("testpass")
723                 .build();
724         newTransporter(httpServer.getHttpUrl());
725         RecordingTransportListener listener = new RecordingTransportListener();
726         PutTask task =
727                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
728         transporter.put(task);
729         assertEquals(0L, listener.dataOffset);
730         assertEquals(6L, listener.dataLength);
731         assertEquals(1, listener.startedCount);
732         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
733         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
734     }
735 
736     @Test
737     public void testPutAuthenticatedExpectContinueDisabled() throws Exception {
738         session.setConfigProperty(ConfigurationProperties.HTTP_EXPECT_CONTINUE, false);
739         httpServer.setAuthentication("testuser", "testpass");
740         httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL); // if transport tries Expect/Continue explode
741         auth = new AuthenticationBuilder()
742                 .addUsername("testuser")
743                 .addPassword("testpass")
744                 .build();
745         newTransporter(httpServer.getHttpUrl());
746         RecordingTransportListener listener = new RecordingTransportListener();
747         PutTask task =
748                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
749         transporter.put(task);
750         assertEquals(0L, listener.dataOffset);
751         assertEquals(6L, listener.dataLength);
752         assertEquals(1, listener.startedCount); // w/ expectContinue enabled would have here 2
753         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
754         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
755     }
756 
757     @Test
758     public void testPutAuthenticatedExpectContinueRejectedExplicitlyConfiguredHeader() throws Exception {
759         Map<String, String> headers = new HashMap<>();
760         headers.put("Expect", "100-continue");
761         session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
762         httpServer.setAuthentication("testuser", "testpass");
763         httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
764         auth = new AuthenticationBuilder()
765                 .addUsername("testuser")
766                 .addPassword("testpass")
767                 .build();
768         newTransporter(httpServer.getHttpUrl());
769         RecordingTransportListener listener = new RecordingTransportListener();
770         PutTask task =
771                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
772         transporter.put(task);
773         assertEquals(0L, listener.dataOffset);
774         assertEquals(6L, listener.dataLength);
775         assertEquals(1, listener.startedCount);
776         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
777         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
778     }
779 
780     @Test
781     public void testPutUnauthenticated() throws Exception {
782         httpServer.setAuthentication("testuser", "testpass");
783         RecordingTransportListener listener = new RecordingTransportListener();
784         PutTask task =
785                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
786         try {
787             transporter.put(task);
788             fail("Expected error");
789         } catch (HttpResponseException e) {
790             assertEquals(401, e.getStatusCode());
791             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
792         }
793         assertEquals(0, listener.startedCount);
794         assertEquals(0, listener.progressedCount);
795     }
796 
797     @Test
798     public void testPutProxyAuthenticated() throws Exception {
799         httpServer.setProxyAuthentication("testuser", "testpass");
800         Authentication auth = new AuthenticationBuilder()
801                 .addUsername("testuser")
802                 .addPassword("testpass")
803                 .build();
804         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
805         newTransporter("http://bad.localhost:1/");
806         RecordingTransportListener listener = new RecordingTransportListener();
807         PutTask task =
808                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
809         transporter.put(task);
810         assertEquals(0L, listener.dataOffset);
811         assertEquals(6L, listener.dataLength);
812         assertEquals(1, listener.startedCount);
813         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
814         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
815     }
816 
817     @Test
818     public void testPutProxyUnauthenticated() throws Exception {
819         httpServer.setProxyAuthentication("testuser", "testpass");
820         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
821         newTransporter("http://bad.localhost:1/");
822         RecordingTransportListener listener = new RecordingTransportListener();
823         PutTask task =
824                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
825         try {
826             transporter.put(task);
827             fail("Expected error");
828         } catch (HttpResponseException e) {
829             assertEquals(407, e.getStatusCode());
830             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
831         }
832         assertEquals(0, listener.startedCount);
833         assertEquals(0, listener.progressedCount);
834     }
835 
836     @Test
837     public void testPutSSL() throws Exception {
838         httpServer.addSslConnector();
839         httpServer.setAuthentication("testuser", "testpass");
840         auth = new AuthenticationBuilder()
841                 .addUsername("testuser")
842                 .addPassword("testpass")
843                 .build();
844         newTransporter(httpServer.getHttpsUrl());
845         RecordingTransportListener listener = new RecordingTransportListener();
846         PutTask task =
847                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
848         transporter.put(task);
849         assertEquals(0L, listener.dataOffset);
850         assertEquals(6L, listener.dataLength);
851         assertEquals(1, listener.startedCount);
852         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
853         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
854     }
855 
856     @Test
857     public void testPutWebDav() throws Exception {
858         httpServer.setWebDav(true);
859         session.setConfigProperty(HttpTransporter.SUPPORT_WEBDAV, true);
860         newTransporter(httpServer.getHttpUrl());
861 
862         RecordingTransportListener listener = new RecordingTransportListener();
863         PutTask task = new PutTask(URI.create("repo/dir1/dir2/file.txt"))
864                 .setListener(listener)
865                 .setDataString("upload");
866         transporter.put(task);
867         assertEquals(0L, listener.dataOffset);
868         assertEquals(6L, listener.dataLength);
869         assertEquals(1, listener.startedCount);
870         assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0);
871         assertEquals("upload", TestFileUtils.readString(new File(repoDir, "dir1/dir2/file.txt")));
872 
873         assertEquals(5, httpServer.getLogEntries().size());
874         assertEquals("OPTIONS", httpServer.getLogEntries().get(0).method);
875         assertEquals("MKCOL", httpServer.getLogEntries().get(1).method);
876         assertEquals("/repo/dir1/dir2/", httpServer.getLogEntries().get(1).path);
877         assertEquals("MKCOL", httpServer.getLogEntries().get(2).method);
878         assertEquals("/repo/dir1/", httpServer.getLogEntries().get(2).path);
879         assertEquals("MKCOL", httpServer.getLogEntries().get(3).method);
880         assertEquals("/repo/dir1/dir2/", httpServer.getLogEntries().get(3).path);
881         assertEquals("PUT", httpServer.getLogEntries().get(4).method);
882     }
883 
884     @Test
885     public void testPutFileHandleLeak() throws Exception {
886         for (int i = 0; i < 100; i++) {
887             File src = TestFileUtils.createTempFile("upload");
888             File dst = new File(repoDir, "file.txt");
889             transporter.put(new PutTask(URI.create("repo/file.txt")).setDataFile(src));
890             assertTrue(i + ", " + src.getAbsolutePath(), src.delete());
891             assertTrue(i + ", " + dst.getAbsolutePath(), dst.delete());
892         }
893     }
894 
895     @Test
896     public void testPutClosed() throws Exception {
897         transporter.close();
898         try {
899             transporter.put(new PutTask(URI.create("repo/missing.txt")));
900             fail("Expected error");
901         } catch (IllegalStateException e) {
902             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
903         }
904     }
905 
906     @Test
907     public void testPutStartCancelled() throws Exception {
908         RecordingTransportListener listener = new RecordingTransportListener();
909         listener.cancelStart = true;
910         PutTask task =
911                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
912         try {
913             transporter.put(task);
914             fail("Expected error");
915         } catch (TransferCancelledException e) {
916             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
917         }
918         assertEquals(0L, listener.dataOffset);
919         assertEquals(6L, listener.dataLength);
920         assertEquals(1, listener.startedCount);
921         assertEquals(0, listener.progressedCount);
922     }
923 
924     @Test
925     public void testPutProgressCancelled() throws Exception {
926         RecordingTransportListener listener = new RecordingTransportListener();
927         listener.cancelProgress = true;
928         PutTask task =
929                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
930         try {
931             transporter.put(task);
932             fail("Expected error");
933         } catch (TransferCancelledException e) {
934             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
935         }
936         assertEquals(0L, listener.dataOffset);
937         assertEquals(6L, listener.dataLength);
938         assertEquals(1, listener.startedCount);
939         assertEquals(1, listener.progressedCount);
940     }
941 
942     @Test
943     public void testGetPutAuthCache() throws Exception {
944         httpServer.setAuthentication("testuser", "testpass");
945         auth = new AuthenticationBuilder()
946                 .addUsername("testuser")
947                 .addPassword("testpass")
948                 .build();
949         newTransporter(httpServer.getHttpUrl());
950         GetTask get = new GetTask(URI.create("repo/file.txt"));
951         transporter.get(get);
952         RecordingTransportListener listener = new RecordingTransportListener();
953         PutTask task =
954                 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
955         transporter.put(task);
956         assertEquals(1, listener.startedCount);
957     }
958 
959     @Test
960     public void testPutPreemptiveIsDefault() throws Exception {
961         httpServer.setAuthentication("testuser", "testpass");
962         auth = new AuthenticationBuilder()
963                 .addUsername("testuser")
964                 .addPassword("testpass")
965                 .build();
966         newTransporter(httpServer.getHttpUrl());
967         PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
968         transporter.put(task);
969         assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
970     }
971 
972     @Test
973     public void testPutAuthCache() throws Exception {
974         session.setConfigProperty(HttpTransporter.PREEMPTIVE_PUT_AUTH, false);
975         httpServer.setAuthentication("testuser", "testpass");
976         auth = new AuthenticationBuilder()
977                 .addUsername("testuser")
978                 .addPassword("testpass")
979                 .build();
980         newTransporter(httpServer.getHttpUrl());
981         PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
982         transporter.put(task);
983         assertEquals(2, httpServer.getLogEntries().size()); // put (challenged) + put w/ auth
984         httpServer.getLogEntries().clear();
985         task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
986         transporter.put(task);
987         assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
988     }
989 
990     @Test
991     public void testPutAuthCachePreemptive() throws Exception {
992         httpServer.setAuthentication("testuser", "testpass");
993         auth = new AuthenticationBuilder()
994                 .addUsername("testuser")
995                 .addPassword("testpass")
996                 .build();
997         session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
998         newTransporter(httpServer.getHttpUrl());
999         PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1000         transporter.put(task);
1001         assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
1002         httpServer.getLogEntries().clear();
1003         task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1004         transporter.put(task);
1005         assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
1006     }
1007 
1008     @Test(timeout = 20000L)
1009     public void testConcurrency() throws Exception {
1010         httpServer.setAuthentication("testuser", "testpass");
1011         auth = new AuthenticationBuilder()
1012                 .addUsername("testuser")
1013                 .addPassword("testpass")
1014                 .build();
1015         newTransporter(httpServer.getHttpUrl());
1016         final AtomicReference<Throwable> error = new AtomicReference<>();
1017         Thread[] threads = new Thread[20];
1018         for (int i = 0; i < threads.length; i++) {
1019             final String path = "repo/file.txt?i=" + i;
1020             threads[i] = new Thread() {
1021                 @Override
1022                 public void run() {
1023                     try {
1024                         for (int j = 0; j < 100; j++) {
1025                             GetTask task = new GetTask(URI.create(path));
1026                             transporter.get(task);
1027                             assertEquals("test", task.getDataString());
1028                         }
1029                     } catch (Throwable t) {
1030                         error.compareAndSet(null, t);
1031                         System.err.println(path);
1032                         t.printStackTrace();
1033                     }
1034                 }
1035             };
1036             threads[i].setName("Task-" + i);
1037         }
1038         for (Thread thread : threads) {
1039             thread.start();
1040         }
1041         for (Thread thread : threads) {
1042             thread.join();
1043         }
1044         assertNull(String.valueOf(error.get()), error.get());
1045     }
1046 
1047     @Test(timeout = 1000L)
1048     public void testConnectTimeout() throws Exception {
1049         session.setConfigProperty(ConfigurationProperties.CONNECT_TIMEOUT, 100);
1050         int port = 1;
1051         newTransporter("http://localhost:" + port);
1052         try {
1053             transporter.get(new GetTask(URI.create("repo/file.txt")));
1054             fail("Expected error");
1055         } catch (ConnectTimeoutException | ConnectException e) {
1056             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1057         }
1058     }
1059 
1060     @Test(timeout = 1000L)
1061     public void testRequestTimeout() throws Exception {
1062         session.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, 100);
1063         ServerSocket server = new ServerSocket(0);
1064         newTransporter("http://localhost:" + server.getLocalPort());
1065         try {
1066             try {
1067                 transporter.get(new GetTask(URI.create("repo/file.txt")));
1068                 fail("Expected error");
1069             } catch (SocketTimeoutException e) {
1070                 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1071             }
1072         } finally {
1073             server.close();
1074         }
1075     }
1076 
1077     @Test
1078     public void testUserAgent() throws Exception {
1079         session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
1080         newTransporter(httpServer.getHttpUrl());
1081         transporter.get(new GetTask(URI.create("repo/file.txt")));
1082         assertEquals(1, httpServer.getLogEntries().size());
1083         for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
1084             assertEquals("SomeTest/1.0", log.headers.get("User-Agent"));
1085         }
1086     }
1087 
1088     @Test
1089     public void testCustomHeaders() throws Exception {
1090         Map<String, String> headers = new HashMap<>();
1091         headers.put("User-Agent", "Custom/1.0");
1092         headers.put("X-CustomHeader", "Custom-Value");
1093         session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
1094         session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
1095         newTransporter(httpServer.getHttpUrl());
1096         transporter.get(new GetTask(URI.create("repo/file.txt")));
1097         assertEquals(1, httpServer.getLogEntries().size());
1098         for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
1099             for (Map.Entry<String, String> entry : headers.entrySet()) {
1100                 assertEquals(entry.getKey(), entry.getValue(), log.headers.get(entry.getKey()));
1101             }
1102         }
1103     }
1104 
1105     @Test
1106     public void testServerAuthScopeNotUsedForProxy() throws Exception {
1107         String username = "testuser", password = "testpass";
1108         httpServer.setProxyAuthentication(username, password);
1109         auth = new AuthenticationBuilder()
1110                 .addUsername(username)
1111                 .addPassword(password)
1112                 .build();
1113         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
1114         newTransporter("http://" + httpServer.getHost() + ":12/");
1115         try {
1116             transporter.get(new GetTask(URI.create("repo/file.txt")));
1117             fail("Server auth must not be used as proxy auth");
1118         } catch (HttpResponseException e) {
1119             assertEquals(407, e.getStatusCode());
1120         }
1121     }
1122 
1123     @Test
1124     public void testProxyAuthScopeNotUsedForServer() throws Exception {
1125         String username = "testuser", password = "testpass";
1126         httpServer.setAuthentication(username, password);
1127         Authentication auth = new AuthenticationBuilder()
1128                 .addUsername(username)
1129                 .addPassword(password)
1130                 .build();
1131         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
1132         newTransporter("http://" + httpServer.getHost() + ":12/");
1133         try {
1134             transporter.get(new GetTask(URI.create("repo/file.txt")));
1135             fail("Proxy auth must not be used as server auth");
1136         } catch (HttpResponseException e) {
1137             assertEquals(401, e.getStatusCode());
1138         }
1139     }
1140 
1141     @Test
1142     public void testAuthSchemeReuse() throws Exception {
1143         httpServer.setAuthentication("testuser", "testpass");
1144         httpServer.setProxyAuthentication("proxyuser", "proxypass");
1145         session.setCache(new DefaultRepositoryCache());
1146         auth = new AuthenticationBuilder()
1147                 .addUsername("testuser")
1148                 .addPassword("testpass")
1149                 .build();
1150         Authentication auth = new AuthenticationBuilder()
1151                 .addUsername("proxyuser")
1152                 .addPassword("proxypass")
1153                 .build();
1154         proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
1155         newTransporter("http://bad.localhost:1/");
1156         GetTask task = new GetTask(URI.create("repo/file.txt"));
1157         transporter.get(task);
1158         assertEquals("test", task.getDataString());
1159         assertEquals(3, httpServer.getLogEntries().size());
1160         httpServer.getLogEntries().clear();
1161         newTransporter("http://bad.localhost:1/");
1162         task = new GetTask(URI.create("repo/file.txt"));
1163         transporter.get(task);
1164         assertEquals("test", task.getDataString());
1165         assertEquals(1, httpServer.getLogEntries().size());
1166         assertNotNull(httpServer.getLogEntries().get(0).headers.get("Authorization"));
1167         assertNotNull(httpServer.getLogEntries().get(0).headers.get("Proxy-Authorization"));
1168     }
1169 
1170     @Test
1171     public void testAuthSchemePreemptive() throws Exception {
1172         httpServer.setAuthentication("testuser", "testpass");
1173         session.setCache(new DefaultRepositoryCache());
1174         auth = new AuthenticationBuilder()
1175                 .addUsername("testuser")
1176                 .addPassword("testpass")
1177                 .build();
1178 
1179         session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, false);
1180         newTransporter(httpServer.getHttpUrl());
1181         GetTask task = new GetTask(URI.create("repo/file.txt"));
1182         transporter.get(task);
1183         assertEquals("test", task.getDataString());
1184         // there ARE challenge round-trips
1185         assertEquals(2, httpServer.getLogEntries().size());
1186 
1187         httpServer.getLogEntries().clear();
1188 
1189         session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
1190         newTransporter(httpServer.getHttpUrl());
1191         task = new GetTask(URI.create("repo/file.txt"));
1192         transporter.get(task);
1193         assertEquals("test", task.getDataString());
1194         // there are NO challenge round-trips, all goes through at first
1195         assertEquals(1, httpServer.getLogEntries().size());
1196     }
1197 
1198     @Test
1199     public void testConnectionReuse() throws Exception {
1200         httpServer.addSslConnector();
1201         session.setCache(new DefaultRepositoryCache());
1202         for (int i = 0; i < 3; i++) {
1203             newTransporter(httpServer.getHttpsUrl());
1204             GetTask task = new GetTask(URI.create("repo/file.txt"));
1205             transporter.get(task);
1206             assertEquals("test", task.getDataString());
1207         }
1208         PoolStats stats = ((ConnPoolControl<?>)
1209                         ((HttpTransporter) transporter).getState().getConnectionManager())
1210                 .getTotalStats();
1211         assertEquals(stats.toString(), 1, stats.getAvailable());
1212     }
1213 
1214     @Test
1215     public void testConnectionNoReuse() throws Exception {
1216         httpServer.addSslConnector();
1217         session.setCache(new DefaultRepositoryCache());
1218         session.setConfigProperty(ConfigurationProperties.HTTP_REUSE_CONNECTIONS, false);
1219         for (int i = 0; i < 3; i++) {
1220             newTransporter(httpServer.getHttpsUrl());
1221             GetTask task = new GetTask(URI.create("repo/file.txt"));
1222             transporter.get(task);
1223             assertEquals("test", task.getDataString());
1224         }
1225         PoolStats stats = ((ConnPoolControl<?>)
1226                         ((HttpTransporter) transporter).getState().getConnectionManager())
1227                 .getTotalStats();
1228         assertEquals(stats.toString(), 0, stats.getAvailable());
1229     }
1230 
1231     @Test(expected = NoTransporterException.class)
1232     public void testInitBadProtocol() throws Exception {
1233         newTransporter("bad:/void");
1234     }
1235 
1236     @Test(expected = NoTransporterException.class)
1237     public void testInitBadUrl() throws Exception {
1238         newTransporter("http://localhost:NaN");
1239     }
1240 
1241     @Test
1242     public void testInitCaseInsensitiveProtocol() throws Exception {
1243         newTransporter("http://localhost");
1244         newTransporter("HTTP://localhost");
1245         newTransporter("Http://localhost");
1246         newTransporter("https://localhost");
1247         newTransporter("HTTPS://localhost");
1248         newTransporter("HttpS://localhost");
1249     }
1250 
1251     @Test
1252     public void testGetRFC9457Response() throws Exception {
1253         try {
1254             transporter.get(new GetTask(URI.create("rfc9457/file.txt")));
1255             fail("Expected error");
1256         } catch (HttpRFC9457Exception e) {
1257             assertEquals(403, e.getStatusCode());
1258             assertEquals(e.getPayload().getType(), URI.create("https://example.com/probs/out-of-credit"));
1259             assertEquals(403, e.getPayload().getStatus().intValue());
1260             assertEquals("You do not have enough credit.", e.getPayload().getTitle());
1261             assertEquals(
1262                     "Your current balance is 30, but that costs 50.",
1263                     e.getPayload().getDetail());
1264             assertEquals(e.getPayload().getInstance(), URI.create("/account/12345/msgs/abc"));
1265         }
1266     }
1267 
1268     @Test
1269     public void testGetRFC9457ResponseWithMissingFields() throws Exception {
1270         try {
1271             transporter.get(new GetTask(URI.create("rfc9457/missing_fields.txt")));
1272             fail("Expected error");
1273         } catch (HttpRFC9457Exception e) {
1274             assertEquals(403, e.getStatusCode());
1275             assertEquals(e.getPayload().getType(), URI.create("about:blank"));
1276             assertNull(e.getPayload().getStatus());
1277             assertNull(e.getPayload().getTitle());
1278             assertNull(e.getPayload().getDetail());
1279             assertNull(e.getPayload().getInstance());
1280         }
1281     }
1282 }