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