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