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