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