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