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