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