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