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