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