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