1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.test.util.http;
20
21 import javax.net.ssl.KeyManagerFactory;
22 import javax.net.ssl.SSLContext;
23 import javax.net.ssl.TrustManagerFactory;
24
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.UncheckedIOException;
30 import java.net.ServerSocket;
31 import java.net.URI;
32 import java.net.URL;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.nio.file.StandardCopyOption;
38 import java.security.KeyStore;
39 import java.security.NoSuchAlgorithmException;
40 import java.util.Enumeration;
41 import java.util.HashMap;
42 import java.util.Map;
43 import java.util.concurrent.atomic.AtomicReference;
44 import java.util.function.Supplier;
45 import java.util.stream.Stream;
46
47 import org.eclipse.aether.ConfigurationProperties;
48 import org.eclipse.aether.DefaultRepositoryCache;
49 import org.eclipse.aether.DefaultRepositorySystemSession;
50 import org.eclipse.aether.DefaultSessionData;
51 import org.eclipse.aether.internal.impl.transport.http.DefaultChecksumExtractor;
52 import org.eclipse.aether.internal.impl.transport.http.Nx2ChecksumExtractor;
53 import org.eclipse.aether.internal.impl.transport.http.XChecksumExtractor;
54 import org.eclipse.aether.internal.test.util.TestFileUtils;
55 import org.eclipse.aether.internal.test.util.TestLocalRepositoryManager;
56 import org.eclipse.aether.repository.Authentication;
57 import org.eclipse.aether.repository.Proxy;
58 import org.eclipse.aether.repository.RemoteRepository;
59 import org.eclipse.aether.spi.connector.transport.GetTask;
60 import org.eclipse.aether.spi.connector.transport.PeekTask;
61 import org.eclipse.aether.spi.connector.transport.PutTask;
62 import org.eclipse.aether.spi.connector.transport.Transporter;
63 import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
64 import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractorStrategy;
65 import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
66 import org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
67 import org.eclipse.aether.spi.connector.transport.http.HttpTransporterFactory;
68 import org.eclipse.aether.spi.connector.transport.http.RFC9457.HttpRFC9457Exception;
69 import org.eclipse.aether.transfer.NoTransporterException;
70 import org.eclipse.aether.transfer.TransferCancelledException;
71 import org.eclipse.aether.util.repository.AuthenticationBuilder;
72 import org.junit.jupiter.api.AfterAll;
73 import org.junit.jupiter.api.AfterEach;
74 import org.junit.jupiter.api.BeforeAll;
75 import org.junit.jupiter.api.BeforeEach;
76 import org.junit.jupiter.api.Test;
77 import org.junit.jupiter.api.TestInfo;
78 import org.junit.jupiter.api.Timeout;
79 import org.junit.jupiter.params.ParameterizedTest;
80 import org.junit.jupiter.params.provider.ValueSource;
81
82 import static java.util.Objects.requireNonNull;
83 import static org.junit.jupiter.api.Assertions.assertEquals;
84 import static org.junit.jupiter.api.Assertions.assertNotNull;
85 import static org.junit.jupiter.api.Assertions.assertNull;
86 import static org.junit.jupiter.api.Assertions.assertThrows;
87 import static org.junit.jupiter.api.Assertions.assertTrue;
88 import static org.junit.jupiter.api.Assertions.fail;
89 import static org.junit.jupiter.api.Assumptions.assumeTrue;
90
91
92
93
94 @SuppressWarnings({"checkstyle:MethodName"})
95 public abstract class HttpTransporterTest {
96
97 protected static final Path KEY_STORE_PATH = Paths.get("target/keystore");
98
99 protected static final Path KEY_STORE_SELF_SIGNED_PATH = Paths.get("target/keystore-self-signed");
100
101 protected static final Path TRUST_STORE_PATH = Paths.get("target/trustStore");
102
103 protected static SSLContext defaultSslContext;
104
105 static {
106
107
108 }
109
110 @BeforeAll
111 protected static void beforeAll() throws NoSuchAlgorithmException {
112
113 URL keyStoreUrl = HttpTransporterTest.class.getClassLoader().getResource("ssl/server-store");
114 URL keyStoreSelfSignedUrl =
115 HttpTransporterTest.class.getClassLoader().getResource("ssl/server-store-selfsigned");
116 URL trustStoreUrl = HttpTransporterTest.class.getClassLoader().getResource("ssl/client-store");
117
118 try {
119 try (InputStream keyStoreStream = keyStoreUrl.openStream();
120 InputStream keyStoreSelfSignedStream = keyStoreSelfSignedUrl.openStream();
121 InputStream trustStoreStream = trustStoreUrl.openStream()) {
122 Files.copy(keyStoreStream, KEY_STORE_PATH, StandardCopyOption.REPLACE_EXISTING);
123 Files.copy(keyStoreSelfSignedStream, KEY_STORE_SELF_SIGNED_PATH, StandardCopyOption.REPLACE_EXISTING);
124 Files.copy(trustStoreStream, TRUST_STORE_PATH, StandardCopyOption.REPLACE_EXISTING);
125 }
126 } catch (IOException e) {
127 throw new UncheckedIOException(e);
128 }
129
130
131 defaultSslContext = SSLContext.getDefault();
132 SSLContext.setDefault(createSSLContext());
133 }
134
135 @AfterAll
136 protected static void afterAll() {
137 if (defaultSslContext != null) {
138 SSLContext.setDefault(defaultSslContext);
139 }
140 }
141
142
143
144
145
146
147
148
149 protected static SSLContext createSSLContext() {
150 try {
151
152 KeyStore customTrustStore = KeyStore.getInstance("jks");
153 try (InputStream is = Files.newInputStream(KEY_STORE_PATH)) {
154 customTrustStore.load(is, "server-pwd".toCharArray());
155 }
156
157
158 KeyStore customKeyStore = KeyStore.getInstance("jks");
159 try (InputStream is = Files.newInputStream(TRUST_STORE_PATH)) {
160 customKeyStore.load(is, "client-pwd".toCharArray());
161 }
162
163
164 KeyStore defaultTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
165 Path defaultTrustStorePath = Path.of(System.getProperty("java.home"), "lib", "security", "cacerts");
166 if (Files.exists(defaultTrustStorePath)) {
167 try (InputStream is = Files.newInputStream(defaultTrustStorePath)) {
168 defaultTrustStore.load(is, "changeit".toCharArray());
169 }
170 } else {
171 defaultTrustStore.load(null, null);
172 }
173 Enumeration<String> aliases = customTrustStore.aliases();
174 while (aliases.hasMoreElements()) {
175 String alias = aliases.nextElement();
176 defaultTrustStore.setCertificateEntry("custom-trust-" + alias, customTrustStore.getCertificate(alias));
177 }
178
179
180 KeyStore mergedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
181 mergedKeyStore.load(null, null);
182 Enumeration<String> keyAliases = customKeyStore.aliases();
183 while (keyAliases.hasMoreElements()) {
184 String alias = keyAliases.nextElement();
185 if (customKeyStore.isKeyEntry(alias)) {
186 mergedKeyStore.setKeyEntry(
187 alias,
188 customKeyStore.getKey(alias, "client-pwd".toCharArray()),
189 "client-pwd".toCharArray(),
190 customKeyStore.getCertificateChain(alias));
191 } else {
192 mergedKeyStore.setCertificateEntry(alias, customKeyStore.getCertificate(alias));
193 }
194 }
195
196 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
197 tmf.init(defaultTrustStore);
198
199 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
200 kmf.init(mergedKeyStore, "client-pwd".toCharArray());
201
202 SSLContext sslContext = SSLContext.getInstance("TLS");
203 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
204 return sslContext;
205 } catch (Exception e) {
206 throw new RuntimeException("Failed to create SSLContext", e);
207 }
208 }
209
210 private final Supplier<HttpTransporterFactory> transporterFactorySupplier;
211
212 protected DefaultRepositorySystemSession session;
213
214 protected HttpTransporterFactory factory;
215
216 protected HttpTransporter transporter;
217
218 protected Runnable closer;
219
220 protected File repoDir;
221
222 protected HttpServer httpServer;
223
224 protected Authentication auth;
225
226 protected Proxy proxy;
227
228 protected HttpTransporterTest(Supplier<HttpTransporterFactory> transporterFactorySupplier) {
229 this.transporterFactorySupplier = requireNonNull(transporterFactorySupplier);
230 }
231
232 protected static ChecksumExtractor standardChecksumExtractor() {
233 HashMap<String, ChecksumExtractorStrategy> strategies = new HashMap<>();
234 strategies.put("1", new Nx2ChecksumExtractor());
235 strategies.put("2", new XChecksumExtractor());
236 return new DefaultChecksumExtractor(strategies);
237 }
238
239 protected RemoteRepository newRepo(String url) {
240 return new RemoteRepository.Builder("test", "default", url)
241 .setAuthentication(auth)
242 .setProxy(proxy)
243 .build();
244 }
245
246 protected void newTransporter(String url) throws Exception {
247 if (transporter != null) {
248 transporter.close();
249 transporter = null;
250 }
251 if (closer != null) {
252 closer.run();
253 closer = null;
254 }
255 session = new DefaultRepositorySystemSession(session);
256 session.setData(new DefaultSessionData());
257 transporter = factory.newInstance(session, newRepo(url));
258 }
259
260 protected static final long OLD_FILE_TIMESTAMP = 160660800000L;
261
262
263 private static final int SC_TOO_MANY_REQUESTS = 429;
264
265 @BeforeEach
266 protected void setUp(TestInfo testInfo) throws Exception {
267 System.out.println("=== " + testInfo.getDisplayName() + " ===");
268 session = new DefaultRepositorySystemSession(h -> {
269 this.closer = h;
270 return true;
271 });
272 session.setLocalRepositoryManager(new TestLocalRepositoryManager());
273 factory = transporterFactorySupplier.get();
274 repoDir = TestFileUtils.createTempDir();
275 TestFileUtils.writeString(new File(repoDir, "file.txt"), "test");
276 TestFileUtils.writeString(new File(repoDir, "artifact.pom"), "<xml>pom</xml>");
277 TestFileUtils.writeString(new File(repoDir, "dir/file.txt"), "test");
278 TestFileUtils.writeString(new File(repoDir, "dir/oldFile.txt"), "oldTest", OLD_FILE_TIMESTAMP);
279 TestFileUtils.writeString(new File(repoDir, "empty.txt"), "");
280 TestFileUtils.writeString(new File(repoDir, "some space.txt"), "space");
281 try (InputStream is = getCompressibleFileStream()) {
282 Files.copy(is, repoDir.toPath().resolve("compressible-file.xml"));
283 }
284 File resumable = new File(repoDir, "resume.txt");
285 TestFileUtils.writeString(resumable, "resumable");
286 resumable.setLastModified(System.currentTimeMillis() - 90 * 1000);
287 httpServer = new HttpServer().setRepoDir(repoDir).start();
288 newTransporter(httpServer.getHttpUrl());
289 }
290
291 private static InputStream getCompressibleFileStream() {
292 return HttpTransporterTest.class.getClassLoader().getResourceAsStream("compressible-file.xml");
293 }
294
295 @AfterEach
296 protected void tearDown() throws Exception {
297 if (transporter != null) {
298 transporter.close();
299 transporter = null;
300 }
301 if (closer != null) {
302 closer.run();
303 closer = null;
304 }
305 if (httpServer != null) {
306 httpServer.stop();
307 httpServer = null;
308 }
309 factory = null;
310 session = null;
311 }
312
313
314
315
316
317 protected boolean supportsPreemptiveAuth() {
318 return true;
319 }
320
321 @Test
322 protected void testClassify() {
323 assertEquals(Transporter.ERROR_OTHER, transporter.classify(new FileNotFoundException()));
324 assertEquals(Transporter.ERROR_OTHER, transporter.classify(new HttpTransporterException(403)));
325 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new HttpTransporterException(404)));
326 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new HttpTransporterException(410)));
327 }
328
329 @Test
330 protected void testPeek() throws Exception {
331 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
332 }
333
334 @Test
335 protected void testPeek_DoesNotAcceptRfc9457() throws Exception {
336
337 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
338 String accept = httpServer.getLogEntries().get(0).getRequestHeaders().get("Accept");
339 assertNull(accept, "No accept header expected for HEAD request, but was: " + accept);
340 }
341
342 @Test
343 protected void testRetryHandler_defaultCount_positive() throws Exception {
344 httpServer.setConnectionsToClose(3);
345 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
346 }
347
348 @Test
349 protected void testRetryHandler_defaultCount_negative() throws Exception {
350 httpServer.setConnectionsToClose(4);
351 try {
352 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
353 fail("Expected error");
354 } catch (Exception expected) {
355 }
356 }
357
358 @Test
359 protected void testRetryHandler_tooManyRequests_explicitCount_positive() throws Exception {
360
361 session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 1);
362 int retryIntervalMs = 500;
363 session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL, retryIntervalMs);
364 newTransporter(httpServer.getHttpUrl());
365 httpServer.setServerErrorsBeforeWorks(1, SC_TOO_MANY_REQUESTS);
366 long startTime = System.currentTimeMillis();
367 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
368 assertTrue(
369 System.currentTimeMillis() - startTime >= retryIntervalMs,
370 "Expected back off delay of at least " + retryIntervalMs);
371 }
372
373 @Test
374 protected void testRetryHandler_tooManyRequests_explicitCount_negative() throws Exception {
375
376 session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 3);
377 int retryIntervalMs = 100;
378 session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL, retryIntervalMs);
379 newTransporter(httpServer.getHttpUrl());
380 httpServer.setServerErrorsBeforeWorks(4, SC_TOO_MANY_REQUESTS);
381 long startTime = System.currentTimeMillis();
382 try {
383 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
384 fail("Expected error");
385 } catch (Exception expected) {
386 }
387
388 long expectedMinimumDuration = retryIntervalMs * (1 + 2 + 3);
389 assertTrue(
390 System.currentTimeMillis() - startTime >= expectedMinimumDuration,
391 "Expected back off delay of at least " + expectedMinimumDuration);
392 }
393
394 @Test
395 protected void testRetryHandler_explicitCount_positive() throws Exception {
396 session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 10);
397 newTransporter(httpServer.getHttpUrl());
398 httpServer.setConnectionsToClose(10);
399 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
400 }
401
402 @Test
403 protected void testRetryHandler_disabled() throws Exception {
404 session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 0);
405 newTransporter(httpServer.getHttpUrl());
406 httpServer.setConnectionsToClose(1);
407 try {
408 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
409 } catch (Exception expected) {
410 }
411 }
412
413 @Test
414 protected void testPeek_NotFound() throws Exception {
415 try {
416 transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
417 fail("Expected error");
418 } catch (HttpTransporterException e) {
419 assertEquals(404, e.getStatusCode());
420 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
421 }
422 }
423
424 @Test
425 protected void testPeek_Closed() throws Exception {
426 transporter.close();
427 try {
428 transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
429 fail("Expected error");
430 } catch (IllegalStateException e) {
431 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
432 }
433 }
434
435 @Test
436 protected void testPeek_Authenticated() throws Exception {
437 httpServer.setAuthentication("testuser", "testpass");
438 auth = new AuthenticationBuilder()
439 .addUsername("testuser")
440 .addPassword("testpass")
441 .build();
442 newTransporter(httpServer.getHttpUrl());
443 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
444 }
445
446 @Test
447 protected void testPeek_Unauthenticated() throws Exception {
448 httpServer.setAuthentication("testuser", "testpass");
449 try {
450 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
451 fail("Expected error");
452 } catch (HttpTransporterException e) {
453 assertEquals(401, e.getStatusCode());
454 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
455 }
456 }
457
458 @Test
459 protected void testPeek_ProxyAuthenticated() throws Exception {
460 httpServer.setProxyAuthentication("testuser", "testpass");
461 auth = new AuthenticationBuilder()
462 .addUsername("testuser")
463 .addPassword("testpass")
464 .build();
465 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
466 newTransporter("http://bad.localhost:1/");
467 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
468 }
469
470 @Test
471 protected void testPeek_ProxyUnauthenticated() throws Exception {
472 httpServer.setProxyAuthentication("testuser", "testpass");
473 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
474 newTransporter("http://bad.localhost:1/");
475 try {
476 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
477 fail("Expected error");
478 } catch (HttpTransporterException e) {
479 assertEquals(407, e.getStatusCode());
480 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
481 }
482 }
483
484 @Test
485 protected void testPeek_SSL() throws Exception {
486 httpServer.addSslConnector();
487 newTransporter(httpServer.getHttpsUrl());
488 transporter.peek(new PeekTask(URI.create("repo/file.txt")));
489 }
490
491 @Test
492 protected void testPeek_Redirect() throws Exception {
493 httpServer.addSslConnector();
494 transporter.peek(new PeekTask(URI.create("redirect/file.txt")));
495 transporter.peek(new PeekTask(URI.create("redirect/file.txt?scheme=https")));
496 }
497
498 @Test
499 protected void testGet_ToMemory() throws Exception {
500 RecordingTransportListener listener = new RecordingTransportListener();
501 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
502 transporter.get(task);
503 assertEquals("test", task.getDataString());
504 assertEquals(0L, listener.getDataOffset());
505 assertEquals(4L, listener.getDataLength());
506 assertEquals(1, listener.getStartedCount());
507 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
508 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
509 }
510
511 @Test
512 protected void testGet_ToFile() throws Exception {
513 File file = TestFileUtils.createTempFile("failure");
514 RecordingTransportListener listener = new RecordingTransportListener();
515 GetTask task = new GetTask(URI.create("repo/file.txt"))
516 .setDataPath(file.toPath())
517 .setListener(listener);
518 transporter.get(task);
519 assertEquals("test", TestFileUtils.readString(file));
520 assertEquals(0L, listener.getDataOffset());
521 assertEquals(4L, listener.getDataLength());
522 assertEquals(1, listener.getStartedCount());
523 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
524 assertEquals("test", listener.getBaos().toString(StandardCharsets.UTF_8));
525 }
526
527 @Test
528 protected void testGet_ToFileTimestamp() throws Exception {
529 File file = TestFileUtils.createTempFile("failure");
530 RecordingTransportListener listener = new RecordingTransportListener();
531 GetTask task = new GetTask(URI.create("repo/dir/oldFile.txt"))
532 .setDataPath(file.toPath())
533 .setListener(listener);
534 transporter.get(task);
535 assertEquals("oldTest", TestFileUtils.readString(file));
536 assertEquals(0L, listener.getDataOffset());
537 assertEquals(7L, listener.getDataLength());
538 assertEquals(1, listener.getStartedCount());
539 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
540 assertEquals("oldTest", listener.getBaos().toString(StandardCharsets.UTF_8));
541 assertEquals(OLD_FILE_TIMESTAMP, file.lastModified());
542 }
543
544 @Test
545 protected void testGet_AcceptsRfc9457() throws Exception {
546 GetTask task = new GetTask(URI.create("repo/file.txt"));
547 transporter.get(task);
548 String accept = httpServer.getLogEntries().get(0).getRequestHeaders().get("Accept");
549 assertNotNull(accept, "Missing Accept header when retrieving artifact");
550 assertTrue(
551 accept.contains("application/problem+json"),
552 "Expected Accept header to contain application/problem+json, but was: " + accept);
553 }
554
555 @Test
556 protected void testGet_ParseRfc9457() throws Exception {
557
558 newTransporter("https://repo.maven.apache.org");
559 try {
560
561 GetTask task = new GetTask(URI.create("cdn-cgi/error/1020"));
562 transporter.get(task);
563 fail("Should have throw HttpRFC9457Exception");
564 } catch (HttpRFC9457Exception e) {
565
566 assertEquals(403, e.getStatusCode());
567 assertEquals("Error 1020: Access denied", e.getPayload().getTitle());
568 assertEquals(
569 "The request was blocked by a Cloudflare firewall rule configured by the site owner.",
570 e.getPayload().getDetail());
571 }
572 }
573
574
575
576
577
578
579
580
581 protected abstract Stream<String> supportedCompressionAlgorithms();
582
583 @ParameterizedTest
584
585 @ValueSource(strings = {"br", "gzip", "zstd"})
586 protected void testGet_WithCompression(String encoding) throws Exception {
587 assumeTrue(
588 supportedCompressionAlgorithms().anyMatch(supported -> supported.equals(encoding)),
589 () -> "Transporter does not support compression algorithm: " + encoding);
590 RecordingTransportListener listener = new RecordingTransportListener();
591
592
593 GetTask task = new GetTask(URI.create(encoding + "/repo/compressible-file.xml")).setListener(listener);
594 transporter.get(task);
595 String acceptEncoding =
596 httpServer.getLogEntries().get(0).getRequestHeaders().get("Accept-Encoding");
597 assertNotNull(acceptEncoding, "Missing Accept-Encoding header when retrieving pom");
598 assertTrue(acceptEncoding.contains(encoding));
599
600
601
602 for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
603 assertEquals(encoding, log.getResponseHeaders().get("Content-Encoding"));
604 }
605 String expectedResourceData;
606 try (InputStream is = getCompressibleFileStream()) {
607 expectedResourceData = new String(is.readAllBytes(), StandardCharsets.UTF_8);
608 }
609 assertEquals(expectedResourceData, task.getDataString());
610 assertEquals(0L, listener.getDataOffset());
611
612 assertEquals(-1, listener.getDataLength());
613 assertEquals(1, listener.getStartedCount());
614 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
615 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
616 }
617
618 @Test
619 protected void testGet_EmptyResource() throws Exception {
620 File file = TestFileUtils.createTempFile("failure");
621 RecordingTransportListener listener = new RecordingTransportListener();
622 GetTask task = new GetTask(URI.create("repo/empty.txt"))
623 .setDataPath(file.toPath())
624 .setListener(listener);
625 transporter.get(task);
626 assertEquals("", TestFileUtils.readString(file));
627 assertEquals(0L, listener.getDataOffset());
628 assertEquals(0L, listener.getDataLength());
629 assertEquals(1, listener.getStartedCount());
630 assertEquals(0, listener.getProgressedCount());
631 assertEquals("", listener.getBaos().toString(StandardCharsets.UTF_8));
632 }
633
634 @Test
635 protected void testGet_EncodedResourcePath() throws Exception {
636 GetTask task = new GetTask(URI.create("repo/some%20space.txt"));
637 transporter.get(task);
638 assertEquals("space", task.getDataString());
639 }
640
641 @Test
642 protected void testGet_Authenticated() throws Exception {
643 httpServer.setAuthentication("testuser", "testpass");
644 auth = new AuthenticationBuilder()
645 .addUsername("testuser")
646 .addPassword("testpass")
647 .build();
648 newTransporter(httpServer.getHttpUrl());
649 RecordingTransportListener listener = new RecordingTransportListener();
650 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
651 transporter.get(task);
652 assertEquals("test", task.getDataString());
653 assertEquals(0L, listener.getDataOffset());
654 assertEquals(4L, listener.getDataLength());
655 assertEquals(1, listener.getStartedCount());
656 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
657 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
658 }
659
660 @Test
661 protected void testGet_Unauthenticated() throws Exception {
662 httpServer.setAuthentication("testuser", "testpass");
663 try {
664 transporter.get(new GetTask(URI.create("repo/file.txt")));
665 fail("Expected error");
666 } catch (HttpTransporterException e) {
667 assertEquals(401, e.getStatusCode());
668 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
669 }
670 }
671
672 @Test
673 protected void testGet_ProxyAuthenticated() throws Exception {
674 httpServer.setProxyAuthentication("testuser", "testpass");
675 Authentication auth = new AuthenticationBuilder()
676 .addUsername("testuser")
677 .addPassword("testpass")
678 .build();
679 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
680 newTransporter("http://bad.localhost:1/");
681 RecordingTransportListener listener = new RecordingTransportListener();
682 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
683 transporter.get(task);
684 assertEquals("test", task.getDataString());
685 assertEquals(0L, listener.getDataOffset());
686 assertEquals(4L, listener.getDataLength());
687 assertEquals(1, listener.getStartedCount());
688 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
689 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
690 }
691
692 @Test
693 protected void testGet_ProxyUnauthenticated() throws Exception {
694 httpServer.setProxyAuthentication("testuser", "testpass");
695 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
696 newTransporter("http://bad.localhost:1/");
697 try {
698 transporter.get(new GetTask(URI.create("repo/file.txt")));
699 fail("Expected error");
700 } catch (HttpTransporterException e) {
701 assertEquals(407, e.getStatusCode());
702 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
703 }
704 }
705
706 @Test
707 protected void testGet_RFC9457Response() throws Exception {
708 try {
709 transporter.get(new GetTask(URI.create("rfc9457/file.txt")));
710 fail("Expected error");
711 } catch (HttpRFC9457Exception e) {
712 assertEquals(403, e.getStatusCode());
713 assertEquals(e.getPayload().getType(), URI.create("https://example.com/probs/out-of-credit"));
714 assertEquals(403, e.getPayload().getStatus());
715 assertEquals("You do not have enough credit.", e.getPayload().getTitle());
716 assertEquals(
717 "Your current balance is 30, but that costs 50.",
718 e.getPayload().getDetail());
719 assertEquals(URI.create("/account/12345/msgs/abc"), e.getPayload().getInstance());
720 }
721 }
722
723 @Test
724 protected void testGet_RFC9457Response_with_missing_fields() throws Exception {
725 try {
726 transporter.get(new GetTask(URI.create("rfc9457/missing_fields.txt")));
727 fail("Expected error");
728 } catch (HttpRFC9457Exception e) {
729 assertEquals(403, e.getStatusCode());
730 assertEquals(e.getPayload().getType(), URI.create("about:blank"));
731 assertNull(e.getPayload().getStatus());
732 assertNull(e.getPayload().getTitle());
733 assertNull(e.getPayload().getDetail());
734 assertNull(e.getPayload().getInstance());
735 }
736 }
737
738 @Test
739 protected void testGet_SSL() throws Exception {
740 httpServer.addSslConnector();
741 newTransporter(httpServer.getHttpsUrl());
742 RecordingTransportListener listener = new RecordingTransportListener();
743 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
744 transporter.get(task);
745 assertEquals("test", task.getDataString());
746 assertEquals(0L, listener.getDataOffset());
747 assertEquals(4L, listener.getDataLength());
748 assertEquals(1, listener.getStartedCount());
749 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
750 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
751 }
752
753 @Test
754 protected void testGet_SSL_WithServerErrors() throws Exception {
755 httpServer.setServerErrorsBeforeWorks(1);
756 httpServer.addSslConnector();
757 newTransporter(httpServer.getHttpsUrl());
758 for (int i = 1; i < 3; i++) {
759 try {
760 RecordingTransportListener listener = new RecordingTransportListener();
761 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
762 transporter.get(task);
763 assertEquals("test", task.getDataString());
764 assertEquals(0L, listener.getDataOffset());
765 assertEquals(4L, listener.getDataLength());
766 assertEquals(1, listener.getStartedCount());
767 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
768 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
769 } catch (HttpTransporterException e) {
770 assertEquals(500, e.getStatusCode());
771 }
772 }
773 }
774
775 @Test
776 protected void testGet_HTTPS_Unknown_SecurityMode() throws Exception {
777 session.setConfigProperty(ConfigurationProperties.HTTPS_SECURITY_MODE, "unknown");
778 httpServer.addSelfSignedSslConnector();
779 try {
780 newTransporter(httpServer.getHttpsUrl());
781 fail("Unsupported security mode");
782 } catch (IllegalArgumentException a) {
783
784 }
785 }
786
787 @Test
788 protected void testGet_HTTPS_Insecure_SecurityMode() throws Exception {
789
790
791 session.setConfigProperty(
792 ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE);
793 httpServer.addSelfSignedSslConnector();
794 newTransporter(httpServer.getHttpsUrl());
795 RecordingTransportListener listener = new RecordingTransportListener();
796 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
797 transporter.get(task);
798 assertEquals("test", task.getDataString());
799 assertEquals(0L, listener.getDataOffset());
800 assertEquals(4L, listener.getDataLength());
801 assertEquals(1, listener.getStartedCount());
802 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
803 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
804 }
805
806 @Test
807 protected void testGet_HTTPS_HTTP2Only_Insecure_SecurityMode() throws Exception {
808
809
810 enableHttp2Protocol();
811 session.setConfigProperty(
812 ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE);
813 httpServer.addSelfSignedSslConnectorHttp2Only();
814 newTransporter(httpServer.getHttpsUrl());
815 RecordingTransportListener listener = new RecordingTransportListener();
816 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
817 transporter.get(task);
818 assertEquals("test", task.getDataString());
819 assertEquals(0L, listener.getDataOffset());
820 assertEquals(4L, listener.getDataLength());
821 assertEquals(1, listener.getStartedCount());
822 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
823 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
824 }
825
826 protected void enableHttp2Protocol() {}
827
828 @Test
829 protected void testGet_Redirect() throws Exception {
830 httpServer.addSslConnector();
831 RecordingTransportListener listener = new RecordingTransportListener();
832 GetTask task = new GetTask(URI.create("redirect/file.txt?scheme=https")).setListener(listener);
833 transporter.get(task);
834 assertEquals("test", task.getDataString());
835 assertEquals(0L, listener.getDataOffset());
836 assertEquals(4L, listener.getDataLength());
837 assertEquals(1, listener.getStartedCount());
838 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
839 assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
840 }
841
842 @Test
843 protected void testGet_Resume() throws Exception {
844 File file = TestFileUtils.createTempFile("re");
845 RecordingTransportListener listener = new RecordingTransportListener();
846 GetTask task = new GetTask(URI.create("repo/resume.txt"))
847 .setDataPath(file.toPath(), true)
848 .setListener(listener);
849 transporter.get(task);
850 assertEquals("resumable", TestFileUtils.readString(file));
851 assertEquals(1L, listener.getStartedCount());
852 assertEquals(2L, listener.getDataOffset());
853 assertEquals(9, listener.getDataLength());
854 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
855 assertEquals("sumable", listener.getBaos().toString(StandardCharsets.UTF_8));
856 }
857
858 @Test
859 protected void testGet_ResumeLocalContentsOutdated() throws Exception {
860 File file = TestFileUtils.createTempFile("re");
861 file.setLastModified(System.currentTimeMillis() - 5 * 60 * 1000);
862 RecordingTransportListener listener = new RecordingTransportListener();
863 GetTask task = new GetTask(URI.create("repo/resume.txt"))
864 .setDataPath(file.toPath(), true)
865 .setListener(listener);
866 transporter.get(task);
867 assertEquals("resumable", TestFileUtils.readString(file));
868 assertEquals(1L, listener.getStartedCount());
869 assertEquals(0L, listener.getDataOffset());
870 assertEquals(9, listener.getDataLength());
871 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
872 assertEquals("resumable", listener.getBaos().toString(StandardCharsets.UTF_8));
873 }
874
875 @Test
876 protected void testGet_ResumeRangesNotSupportedByServer() throws Exception {
877 httpServer.setRangeSupport(false);
878 File file = TestFileUtils.createTempFile("re");
879 RecordingTransportListener listener = new RecordingTransportListener();
880 GetTask task = new GetTask(URI.create("repo/resume.txt"))
881 .setDataPath(file.toPath(), true)
882 .setListener(listener);
883 transporter.get(task);
884 assertEquals("resumable", TestFileUtils.readString(file));
885 assertEquals(1L, listener.getStartedCount());
886 assertEquals(0L, listener.getDataOffset());
887 assertEquals(9, listener.getDataLength());
888 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
889 assertEquals("resumable", listener.getBaos().toString(StandardCharsets.UTF_8));
890 }
891
892 @Test
893 protected void testGet_Checksums_Nexus() throws Exception {
894 httpServer.setChecksumHeader(HttpServer.ChecksumHeader.NEXUS);
895 GetTask task = new GetTask(URI.create("repo/file.txt"));
896 transporter.get(task);
897 assertEquals("test", task.getDataString());
898 assertEquals(
899 "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
900 }
901
902 @Test
903 protected void testGet_Checksums_XChecksum() throws Exception {
904 httpServer.setChecksumHeader(HttpServer.ChecksumHeader.XCHECKSUM);
905 GetTask task = new GetTask(URI.create("repo/file.txt"));
906 transporter.get(task);
907 assertEquals("test", task.getDataString());
908 assertEquals(
909 "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
910 }
911
912 @Test
913 protected void testGet_FileHandleLeak() throws Exception {
914 for (int i = 0; i < 100; i++) {
915 File file = TestFileUtils.createTempFile("failure");
916 transporter.get(new GetTask(URI.create("repo/file.txt")).setDataPath(file.toPath()));
917 assertTrue(file.delete(), i + ", " + file.getAbsolutePath());
918 }
919 }
920
921 @Test
922 protected void testGet_NotFound() throws Exception {
923 try {
924 transporter.get(new GetTask(URI.create("repo/missing.txt")));
925 fail("Expected error");
926 } catch (HttpTransporterException e) {
927 assertEquals(404, e.getStatusCode());
928 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
929 }
930 }
931
932 @Test
933 protected void testGet_Closed() throws Exception {
934 transporter.close();
935 try {
936 transporter.get(new GetTask(URI.create("repo/file.txt")));
937 fail("Expected error");
938 } catch (IllegalStateException e) {
939 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
940 }
941 }
942
943 @Test
944 protected void testGet_StartCancelled() throws Exception {
945 RecordingTransportListener listener = new RecordingTransportListener();
946 listener.cancelStart();
947 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
948 try {
949 transporter.get(task);
950 fail("Expected error");
951 } catch (TransferCancelledException e) {
952 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
953 }
954 assertEquals(0L, listener.getDataOffset());
955 assertEquals(4L, listener.getDataLength());
956 assertEquals(1, listener.getStartedCount());
957 assertEquals(0, listener.getProgressedCount());
958 }
959
960 @Test
961 protected void testGet_ProgressCancelled() throws Exception {
962 RecordingTransportListener listener = new RecordingTransportListener();
963 listener.cancelProgress();
964 GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
965 try {
966 transporter.get(task);
967 fail("Expected error");
968 } catch (TransferCancelledException e) {
969 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
970 }
971 assertEquals(0L, listener.getDataOffset());
972 assertEquals(4L, listener.getDataLength());
973 assertEquals(1, listener.getStartedCount());
974 assertEquals(1, listener.getProgressedCount());
975 }
976
977 @Test
978 protected void testPut_FromMemory() throws Exception {
979 RecordingTransportListener listener = new RecordingTransportListener();
980 String payload = "upload";
981 PutTask task =
982 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString(payload);
983 transporter.put(task);
984 assertEquals(0L, listener.getDataOffset());
985 assertEquals(6L, listener.getDataLength());
986 assertEquals(1, listener.getStartedCount());
987 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
988 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
989 assertEquals(
990 String.valueOf(payload.getBytes(StandardCharsets.UTF_8).length),
991 httpServer.getLogEntries().get(0).getRequestHeaders().get("Content-Length"));
992 }
993
994 @Test
995 protected void testPut_AcceptsRfc9457() throws Exception {
996 String payload = "upload";
997 PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString(payload);
998 transporter.put(task);
999 String accept = httpServer.getLogEntries().get(0).getRequestHeaders().get("Accept");
1000 assertNotNull(accept, "Missing Accept header when retrieving artifact");
1001 assertTrue(
1002 accept.contains("application/problem+json"),
1003 "Expected Accept header to contain application/problem+json, but was: " + accept);
1004 }
1005
1006 @Test
1007 protected void testPut_FromFile() throws Exception {
1008 File file = TestFileUtils.createTempFile("upload");
1009 RecordingTransportListener listener = new RecordingTransportListener();
1010 PutTask task =
1011 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataPath(file.toPath());
1012 transporter.put(task);
1013 assertEquals(0L, listener.getDataOffset());
1014 assertEquals(6L, listener.getDataLength());
1015 assertEquals(1, listener.getStartedCount());
1016 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1017 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1018 assertEquals(
1019 String.valueOf(file.length()),
1020 httpServer.getLogEntries().get(0).getRequestHeaders().get("Content-Length"));
1021 }
1022
1023 @Test
1024 protected void testPut_EmptyResource() throws Exception {
1025 RecordingTransportListener listener = new RecordingTransportListener();
1026 PutTask task = new PutTask(URI.create("repo/file.txt")).setListener(listener);
1027 transporter.put(task);
1028 assertEquals(0L, listener.getDataOffset());
1029 assertEquals(0L, listener.getDataLength());
1030
1031 assertTrue(
1032 listener.getStartedCount() <= 1,
1033 "The transport should be started at most once but was started " + listener.getStartedCount()
1034 + " times");
1035 assertEquals(0, listener.getProgressedCount());
1036 assertEquals("", TestFileUtils.readString(new File(repoDir, "file.txt")));
1037 }
1038
1039 @Test
1040 protected void testPut_EncodedResourcePath() throws Exception {
1041 RecordingTransportListener listener = new RecordingTransportListener();
1042 PutTask task = new PutTask(URI.create("repo/some%20space.txt"))
1043 .setListener(listener)
1044 .setDataString("OK");
1045 transporter.put(task);
1046 assertEquals(0L, listener.getDataOffset());
1047 assertEquals(2L, listener.getDataLength());
1048 assertEquals(1, listener.getStartedCount());
1049 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1050 assertEquals("OK", TestFileUtils.readString(new File(repoDir, "some space.txt")));
1051 }
1052
1053 @Test
1054 protected void testPut_Authenticated_ExpectContinue() throws Exception {
1055 httpServer.setAuthentication("testuser", "testpass");
1056 auth = new AuthenticationBuilder()
1057 .addUsername("testuser")
1058 .addPassword("testpass")
1059 .build();
1060 newTransporter(httpServer.getHttpUrl());
1061 RecordingTransportListener listener = new RecordingTransportListener();
1062 PutTask task =
1063 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1064 transporter.put(task);
1065 assertEquals(0L, listener.getDataOffset());
1066 assertEquals(6L, listener.getDataLength());
1067 assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount());
1068 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1069 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1070 }
1071
1072 @Test
1073 protected void testPut_Authenticated_ExpectContinueBroken() throws Exception {
1074
1075 session.setConfigProperty(ConfigurationProperties.HTTP_SUPPORT_WEBDAV, true);
1076 httpServer.setAuthentication("testuser", "testpass");
1077 httpServer.setExpectSupport(HttpServer.ExpectContinue.BROKEN);
1078 auth = new AuthenticationBuilder()
1079 .addUsername("testuser")
1080 .addPassword("testpass")
1081 .build();
1082 newTransporter(httpServer.getHttpUrl());
1083 RecordingTransportListener listener = new RecordingTransportListener();
1084 PutTask task =
1085 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1086 transporter.put(task);
1087 assertEquals(0L, listener.getDataOffset());
1088 assertEquals(6L, listener.getDataLength());
1089 assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount());
1090 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1091 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1092 }
1093
1094 @Test
1095 protected void testPut_Authenticated_ExpectContinueRejected() throws Exception {
1096 httpServer.setAuthentication("testuser", "testpass");
1097 httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
1098 auth = new AuthenticationBuilder()
1099 .addUsername("testuser")
1100 .addPassword("testpass")
1101 .build();
1102 newTransporter(httpServer.getHttpUrl());
1103 RecordingTransportListener listener = new RecordingTransportListener();
1104 PutTask task =
1105 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1106 transporter.put(task);
1107 assertEquals(0L, listener.getDataOffset());
1108 assertEquals(6L, listener.getDataLength());
1109 assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount());
1110 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1111 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1112 }
1113
1114 @Test
1115 protected void testPut_Authenticated_ExpectContinueDisabled() throws Exception {
1116 session.setConfigProperty(ConfigurationProperties.HTTP_EXPECT_CONTINUE, false);
1117 httpServer.setAuthentication("testuser", "testpass");
1118 httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
1119 auth = new AuthenticationBuilder()
1120 .addUsername("testuser")
1121 .addPassword("testpass")
1122 .build();
1123 newTransporter(httpServer.getHttpUrl());
1124 RecordingTransportListener listener = new RecordingTransportListener();
1125 PutTask task =
1126 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1127 transporter.put(task);
1128 assertEquals(0L, listener.getDataOffset());
1129 assertEquals(6L, listener.getDataLength());
1130 assertEquals(
1131 supportsPreemptiveAuth() ? 1 : 2,
1132 listener.getStartedCount());
1133 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1134 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1135 }
1136
1137 @Test
1138 protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader() throws Exception {
1139 Map<String, String> headers = new HashMap<>();
1140 headers.put("Expect", "100-continue");
1141 session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
1142 httpServer.setAuthentication("testuser", "testpass");
1143 httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
1144 auth = new AuthenticationBuilder()
1145 .addUsername("testuser")
1146 .addPassword("testpass")
1147 .build();
1148 newTransporter(httpServer.getHttpUrl());
1149 RecordingTransportListener listener = new RecordingTransportListener();
1150 PutTask task =
1151 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1152 transporter.put(task);
1153 assertEquals(0L, listener.getDataOffset());
1154 assertEquals(6L, listener.getDataLength());
1155 assertEquals(1, listener.getStartedCount());
1156 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1157 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1158 }
1159
1160 @Test
1161 protected void testPut_Unauthenticated() throws Exception {
1162 httpServer.setAuthentication("testuser", "testpass");
1163 RecordingTransportListener listener = new RecordingTransportListener();
1164 PutTask task =
1165 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1166 try {
1167 transporter.put(task);
1168 fail("Expected error");
1169 } catch (HttpTransporterException e) {
1170 assertEquals(401, e.getStatusCode());
1171 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1172 }
1173 assertEquals(0, listener.getStartedCount());
1174 assertEquals(0, listener.getProgressedCount());
1175 }
1176
1177 @Test
1178 protected void testPut_ProxyAuthenticated() throws Exception {
1179 httpServer.setProxyAuthentication("testuser", "testpass");
1180 Authentication auth = new AuthenticationBuilder()
1181 .addUsername("testuser")
1182 .addPassword("testpass")
1183 .build();
1184 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
1185 newTransporter("http://bad.localhost:1/");
1186 RecordingTransportListener listener = new RecordingTransportListener();
1187 PutTask task =
1188 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1189 transporter.put(task);
1190 assertEquals(0L, listener.getDataOffset());
1191 assertEquals(6L, listener.getDataLength());
1192 assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount());
1193 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1194 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1195 }
1196
1197 @Test
1198 protected void testPut_ProxyUnauthenticated() throws Exception {
1199 httpServer.setProxyAuthentication("testuser", "testpass");
1200 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
1201 newTransporter("http://bad.localhost:1/");
1202 RecordingTransportListener listener = new RecordingTransportListener();
1203 PutTask task =
1204 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1205 try {
1206 transporter.put(task);
1207 fail("Expected error");
1208 } catch (HttpTransporterException e) {
1209 assertEquals(407, e.getStatusCode());
1210 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1211 }
1212 assertEquals(0, listener.getStartedCount());
1213 assertEquals(0, listener.getProgressedCount());
1214 }
1215
1216 @Test
1217 protected void testPut_SSL() throws Exception {
1218 httpServer.addSslConnector();
1219 httpServer.setAuthentication("testuser", "testpass");
1220 auth = new AuthenticationBuilder()
1221 .addUsername("testuser")
1222 .addPassword("testpass")
1223 .build();
1224 newTransporter(httpServer.getHttpsUrl());
1225 RecordingTransportListener listener = new RecordingTransportListener();
1226 PutTask task =
1227 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1228 transporter.put(task);
1229 assertEquals(0L, listener.getDataOffset());
1230 assertEquals(6L, listener.getDataLength());
1231 assertEquals(supportsPreemptiveAuth() ? 1 : 2, listener.getStartedCount());
1232 assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
1233 assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
1234 }
1235
1236 @Test
1237 protected void testPut_FileHandleLeak() throws Exception {
1238 for (int i = 0; i < 100; i++) {
1239 File src = TestFileUtils.createTempFile("upload");
1240 File dst = new File(repoDir, "file.txt");
1241 transporter.put(new PutTask(URI.create("repo/file.txt")).setDataPath(src.toPath()));
1242 assertTrue(src.delete(), i + ", " + src.getAbsolutePath());
1243 assertTrue(dst.delete(), i + ", " + dst.getAbsolutePath());
1244 }
1245 }
1246
1247 @Test
1248 protected void testPut_Closed() throws Exception {
1249 transporter.close();
1250 try {
1251 transporter.put(new PutTask(URI.create("repo/missing.txt")));
1252 fail("Expected error");
1253 } catch (IllegalStateException e) {
1254 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1255 }
1256 }
1257
1258 @Test
1259 protected void testPut_StartCancelled() throws Exception {
1260 RecordingTransportListener listener = new RecordingTransportListener();
1261 listener.cancelStart();
1262 PutTask task =
1263 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1264 try {
1265 transporter.put(task);
1266 fail("Expected error");
1267 } catch (TransferCancelledException e) {
1268 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1269 }
1270 assertEquals(0L, listener.getDataOffset());
1271 assertEquals(6L, listener.getDataLength());
1272 assertEquals(1, listener.getStartedCount());
1273 assertEquals(0, listener.getProgressedCount());
1274 }
1275
1276 @Test
1277 protected void testPut_ProgressCancelled() throws Exception {
1278 RecordingTransportListener listener = new RecordingTransportListener();
1279 listener.cancelProgress();
1280 PutTask task =
1281 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1282 try {
1283 transporter.put(task);
1284 fail("Expected error");
1285 } catch (TransferCancelledException e) {
1286 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1287 }
1288 assertEquals(0L, listener.getDataOffset());
1289 assertEquals(6L, listener.getDataLength());
1290 assertEquals(1, listener.getStartedCount());
1291 assertEquals(1, listener.getProgressedCount());
1292 }
1293
1294 @Test
1295 protected void testGetPut_AuthCache() throws Exception {
1296 httpServer.setAuthentication("testuser", "testpass");
1297 auth = new AuthenticationBuilder()
1298 .addUsername("testuser")
1299 .addPassword("testpass")
1300 .build();
1301 newTransporter(httpServer.getHttpUrl());
1302 GetTask get = new GetTask(URI.create("repo/file.txt"));
1303 transporter.get(get);
1304 RecordingTransportListener listener = new RecordingTransportListener();
1305 PutTask task =
1306 new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
1307 transporter.put(task);
1308 assertEquals(1, listener.getStartedCount());
1309 }
1310
1311 @Test
1312 protected void testPut_PreemptiveIsDefault() throws Exception {
1313 httpServer.setAuthentication("testuser", "testpass");
1314 auth = new AuthenticationBuilder()
1315 .addUsername("testuser")
1316 .addPassword("testpass")
1317 .build();
1318 newTransporter(httpServer.getHttpUrl());
1319 PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1320 transporter.put(task);
1321 assertEquals(
1322 supportsPreemptiveAuth() ? 1 : 2, httpServer.getLogEntries().size());
1323 }
1324
1325 @Test
1326 protected void testPut_AuthCache() throws Exception {
1327 session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH, false);
1328 httpServer.setAuthentication("testuser", "testpass");
1329 auth = new AuthenticationBuilder()
1330 .addUsername("testuser")
1331 .addPassword("testpass")
1332 .build();
1333 newTransporter(httpServer.getHttpUrl());
1334 PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1335 transporter.put(task);
1336 assertEquals(2, httpServer.getLogEntries().size());
1337 httpServer.getLogEntries().clear();
1338 task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1339 transporter.put(task);
1340 assertEquals(1, httpServer.getLogEntries().size());
1341 }
1342
1343 @Test
1344 protected void testPut_AuthCache_Preemptive() throws Exception {
1345 httpServer.setAuthentication("testuser", "testpass");
1346 auth = new AuthenticationBuilder()
1347 .addUsername("testuser")
1348 .addPassword("testpass")
1349 .build();
1350 session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
1351 newTransporter(httpServer.getHttpUrl());
1352 PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1353 transporter.put(task);
1354 assertEquals(
1355 supportsPreemptiveAuth() ? 1 : 2, httpServer.getLogEntries().size());
1356 httpServer.getLogEntries().clear();
1357 task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
1358 transporter.put(task);
1359 assertEquals(1, httpServer.getLogEntries().size());
1360 }
1361
1362 @Test
1363 @Timeout(20)
1364 protected void testConcurrency() throws Exception {
1365 httpServer.setAuthentication("testuser", "testpass");
1366 auth = new AuthenticationBuilder()
1367 .addUsername("testuser")
1368 .addPassword("testpass")
1369 .build();
1370 newTransporter(httpServer.getHttpUrl());
1371 final AtomicReference<Throwable> error = new AtomicReference<>();
1372 Thread[] threads = new Thread[20];
1373 for (int i = 0; i < threads.length; i++) {
1374 final String path = "repo/file.txt?i=" + i;
1375 threads[i] = new Thread(() -> {
1376 try {
1377 for (int j = 0; j < 100; j++) {
1378 GetTask task = new GetTask(URI.create(path));
1379 transporter.get(task);
1380 assertEquals("test", task.getDataString());
1381 }
1382 } catch (Throwable t) {
1383 error.compareAndSet(null, t);
1384 System.err.println(path);
1385 t.printStackTrace();
1386 }
1387 });
1388 threads[i].setName("Task-" + i);
1389 }
1390 for (Thread thread : threads) {
1391 thread.start();
1392 }
1393 for (Thread thread : threads) {
1394 thread.join();
1395 }
1396 assertNull(error.get(), String.valueOf(error.get()));
1397 }
1398
1399 @Test
1400 @Timeout(10)
1401 protected void testConnectTimeout() throws Exception {
1402 session.setConfigProperty(ConfigurationProperties.CONNECT_TIMEOUT, 100);
1403 int port = 1;
1404 newTransporter("http://localhost:" + port);
1405 try {
1406 transporter.get(new GetTask(URI.create("repo/file.txt")));
1407 fail("Expected error");
1408 } catch (Exception e) {
1409
1410 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1411 }
1412 }
1413
1414 @Test
1415 @Timeout(10)
1416 protected void testRequestTimeout() throws Exception {
1417 session.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, 100);
1418 ServerSocket server = new ServerSocket(0);
1419 try (server) {
1420 newTransporter("http://localhost:" + server.getLocalPort());
1421 try {
1422 transporter.get(new GetTask(URI.create("repo/file.txt")));
1423 fail("Expected error");
1424 } catch (Exception e) {
1425 assertTrue(e.getClass().getSimpleName().contains("Timeout"));
1426 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
1427 }
1428 }
1429 }
1430
1431 @Test
1432 protected void testUserAgent() throws Exception {
1433 session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
1434 newTransporter(httpServer.getHttpUrl());
1435 transporter.get(new GetTask(URI.create("repo/file.txt")));
1436 assertEquals(1, httpServer.getLogEntries().size());
1437 for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
1438 assertEquals("SomeTest/1.0", log.getRequestHeaders().get("User-Agent"));
1439 }
1440 }
1441
1442 @Test
1443 protected void testCustomHeaders() throws Exception {
1444 Map<String, String> headers = new HashMap<>();
1445 headers.put("User-Agent", "Custom/1.0");
1446 headers.put("X-CustomHeader", "Custom-Value");
1447 session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
1448 session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
1449 newTransporter(httpServer.getHttpUrl());
1450 transporter.get(new GetTask(URI.create("repo/file.txt")));
1451 assertEquals(1, httpServer.getLogEntries().size());
1452 for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
1453 for (Map.Entry<String, String> entry : headers.entrySet()) {
1454 assertEquals(entry.getValue(), log.getRequestHeaders().get(entry.getKey()), entry.getKey());
1455 }
1456 }
1457 }
1458
1459 @Test
1460 protected void testServerAuthScope_NotUsedForProxy() throws Exception {
1461 String username = "testuser", password = "testpass";
1462 httpServer.setProxyAuthentication(username, password);
1463 auth = new AuthenticationBuilder()
1464 .addUsername(username)
1465 .addPassword(password)
1466 .build();
1467 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
1468 newTransporter("http://" + httpServer.getHost() + ":12/");
1469 try {
1470 transporter.get(new GetTask(URI.create("repo/file.txt")));
1471 fail("Server auth must not be used as proxy auth");
1472 } catch (HttpTransporterException e) {
1473 assertEquals(407, e.getStatusCode());
1474 } catch (IOException e) {
1475
1476 }
1477 }
1478
1479 @Test
1480 protected void testProxyAuthScope_NotUsedForServer() throws Exception {
1481 String username = "testuser", password = "testpass";
1482 httpServer.setAuthentication(username, password);
1483 Authentication auth = new AuthenticationBuilder()
1484 .addUsername(username)
1485 .addPassword(password)
1486 .build();
1487 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
1488 newTransporter("http://" + httpServer.getHost() + ":12/");
1489 try {
1490 transporter.get(new GetTask(URI.create("repo/file.txt")));
1491 fail("Proxy auth must not be used as server auth");
1492 } catch (HttpTransporterException e) {
1493 assertEquals(401, e.getStatusCode());
1494 } catch (IOException e) {
1495
1496 }
1497 }
1498
1499 @Test
1500 protected void testAuthSchemeReuse() throws Exception {
1501 httpServer.setAuthentication("testuser", "testpass");
1502 httpServer.setProxyAuthentication("proxyuser", "proxypass");
1503 session.setCache(new DefaultRepositoryCache());
1504 auth = new AuthenticationBuilder()
1505 .addUsername("testuser")
1506 .addPassword("testpass")
1507 .build();
1508 Authentication auth = new AuthenticationBuilder()
1509 .addUsername("proxyuser")
1510 .addPassword("proxypass")
1511 .build();
1512 proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
1513 newTransporter("http://bad.localhost:1/");
1514 GetTask task = new GetTask(URI.create("repo/file.txt"));
1515 transporter.get(task);
1516 assertEquals("test", task.getDataString());
1517 assertEquals(3, httpServer.getLogEntries().size());
1518 httpServer.getLogEntries().clear();
1519 newTransporter("http://bad.localhost:1/");
1520 task = new GetTask(URI.create("repo/file.txt"));
1521 transporter.get(task);
1522 assertEquals("test", task.getDataString());
1523 assertEquals(1, httpServer.getLogEntries().size());
1524 assertNotNull(httpServer.getLogEntries().get(0).getRequestHeaders().get("Authorization"));
1525 assertNotNull(httpServer.getLogEntries().get(0).getRequestHeaders().get("Proxy-Authorization"));
1526 }
1527
1528 @Test
1529 protected void testAuthSchemePreemptive() throws Exception {
1530 httpServer.setAuthentication("testuser", "testpass");
1531 session.setCache(new DefaultRepositoryCache());
1532 auth = new AuthenticationBuilder()
1533 .addUsername("testuser")
1534 .addPassword("testpass")
1535 .build();
1536
1537 session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, false);
1538 newTransporter(httpServer.getHttpUrl());
1539 GetTask task = new GetTask(URI.create("repo/file.txt"));
1540 transporter.get(task);
1541 assertEquals("test", task.getDataString());
1542
1543 assertEquals(2, httpServer.getLogEntries().size());
1544
1545 httpServer.getLogEntries().clear();
1546
1547 session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
1548 newTransporter(httpServer.getHttpUrl());
1549 task = new GetTask(URI.create("repo/file.txt"));
1550 transporter.get(task);
1551 assertEquals("test", task.getDataString());
1552
1553 assertEquals(
1554 supportsPreemptiveAuth() ? 1 : 2, httpServer.getLogEntries().size());
1555 }
1556
1557 @Test
1558 void testInit_BadProtocol() {
1559 assertThrows(NoTransporterException.class, () -> newTransporter("bad:/void"));
1560 }
1561
1562 @Test
1563 void testInit_BadUrl() {
1564 assertThrows(NoTransporterException.class, () -> newTransporter("http://localhost:NaN"));
1565 }
1566
1567 @Test
1568 void testInit_CaseInsensitiveProtocol() throws Exception {
1569 newTransporter("http://localhost");
1570 newTransporter("HTTP://localhost");
1571 newTransporter("Http://localhost");
1572 newTransporter("https://localhost");
1573 newTransporter("HTTPS://localhost");
1574 newTransporter("HttpS://localhost");
1575 }
1576 }