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