diff --git a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyException.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
similarity index 68%
rename from maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyException.java
rename to maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
index 6137861ac..954ce8891 100644
--- a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyException.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
@@ -16,22 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.eclipse.aether.transport.jetty;
+package org.eclipse.aether.spi.connector.transport.http;
+
+import org.eclipse.aether.spi.connector.transport.Transporter;
/**
- * Exception thrown by {@link JettyTransporter} in case of errors.
+ * A transporter using HTTP protocol.
*
* @since 2.0.0
*/
-final class JettyException extends Exception {
- private final int statusCode;
-
- JettyException(int statusCode) {
- super("HTTP Status: " + statusCode);
- this.statusCode = statusCode;
- }
-
- public int getStatusCode() {
- return statusCode;
- }
-}
+public interface HttpTransporter extends Transporter {}
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkException.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporterException.java
similarity index 81%
rename from maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkException.java
rename to maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporterException.java
index a0eaa8904..06a440493 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkException.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporterException.java
@@ -16,17 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.eclipse.aether.transport.jdk;
+package org.eclipse.aether.spi.connector.transport.http;
/**
- * Exception thrown by {@link JdkTransporter} in case of errors.
+ * Exception thrown by {@link HttpTransporter} in case of errors.
*
* @since 2.0.0
*/
-final class JdkException extends Exception {
+public class HttpTransporterException extends Exception {
private final int statusCode;
- JdkException(int statusCode) {
+ public HttpTransporterException(int statusCode) {
super("HTTP Status: " + statusCode);
this.statusCode = statusCode;
}
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporterFactory.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporterFactory.java
new file mode 100644
index 000000000..a3c64bc5b
--- /dev/null
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporterFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.spi.connector.transport.http;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transfer.NoTransporterException;
+
+/**
+ * A factory for {@link HttpTransporter}.
+ *
+ * @since 2.0.0
+ */
+public interface HttpTransporterFactory extends TransporterFactory {
+
+ /**
+ * Tries to create HTTP transporter for the specified remote repository.
+ *
+ * @param session The repository system session from which to configure the transporter, must not be {@code null}.
+ * In particular, a transporter should obey the timeouts configured for the session.
+ * @param repository The remote repository to create a transporter for, must not be {@code null}.
+ * @return The transporter for the given repository, never {@code null}.
+ * @throws NoTransporterException If the factory cannot create a transporter for the specified remote repository.
+ */
+ @Override
+ HttpTransporter newInstance(RepositorySystemSession session, RemoteRepository repository)
+ throws NoTransporterException;
+}
diff --git a/maven-resolver-test-http/pom.xml b/maven-resolver-test-http/pom.xml
index 99c9863d0..45aff0374 100644
--- a/maven-resolver-test-http/pom.xml
+++ b/maven-resolver-test-http/pom.xml
@@ -68,6 +68,18 @@
org.eclipse.jetty
jetty-http
+
+ org.eclipse.jetty
+ jetty-alpn-server
+
+
+ org.eclipse.jetty
+ jetty-alpn-java-server
+
+
+ org.eclipse.jetty.http2
+ http2-server
+
org.slf4j
slf4j-api
diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
index 7bb97299d..be684b149 100644
--- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
+++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
@@ -39,15 +39,11 @@
import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory;
import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Response;
-import org.eclipse.jetty.server.SecureRequestCustomizer;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.IO;
@@ -162,24 +158,37 @@ public HttpServer addSelfSignedSslConnector() {
private HttpServer addSslConnector(boolean needClientAuth) {
if (httpsConnector == null) {
SslContextFactory.Server ssl = new SslContextFactory.Server();
- if (needClientAuth) {
- ssl.setNeedClientAuth(true);
- ssl.setKeyStorePath(new File("src/test/resources/ssl/server-store").getAbsolutePath());
+ ssl.setNeedClientAuth(needClientAuth);
+ if (!needClientAuth) {
+ ssl.setKeyStorePath(HttpTransporterTest.KEY_STORE_SELF_SIGNED_PATH
+ .toAbsolutePath()
+ .toString());
ssl.setKeyStorePassword("server-pwd");
- ssl.setTrustStorePath(new File("src/test/resources/ssl/client-store").getAbsolutePath());
- ssl.setTrustStorePassword("client-pwd");
ssl.setSniRequired(false);
} else {
- ssl.setNeedClientAuth(false);
- ssl.setKeyStorePath(new File("src/test/resources/ssl/server-store-selfsigned").getAbsolutePath());
+ ssl.setKeyStorePath(
+ HttpTransporterTest.KEY_STORE_PATH.toAbsolutePath().toString());
ssl.setKeyStorePassword("server-pwd");
+ ssl.setTrustStorePath(
+ HttpTransporterTest.TRUST_STORE_PATH.toAbsolutePath().toString());
+ ssl.setTrustStorePassword("client-pwd");
ssl.setSniRequired(false);
}
+
HttpConfiguration httpsConfig = new HttpConfiguration();
SecureRequestCustomizer customizer = new SecureRequestCustomizer();
customizer.setSniHostCheck(false);
httpsConfig.addCustomizer(customizer);
- httpsConnector = new ServerConnector(server, ssl, new HttpConnectionFactory(httpsConfig));
+
+ HttpConnectionFactory http1 = new HttpConnectionFactory(httpsConfig);
+
+ HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpsConfig);
+
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol(http1.getProtocol());
+
+ SslConnectionFactory tls = new SslConnectionFactory(ssl, alpn.getProtocol());
+ httpsConnector = new ServerConnector(server, tls, alpn, http2, http1);
server.addConnector(httpsConnector);
try {
httpsConnector.start();
@@ -268,6 +277,7 @@ public void stop() throws Exception {
}
private class ConnectionClosingHandler extends AbstractHandler {
+ @Override
public void handle(String target, Request req, HttpServletRequest request, HttpServletResponse response) {
if (connectionsToClose.getAndDecrement() > 0) {
Response jettyResponse = (Response) response;
@@ -277,7 +287,7 @@ public void handle(String target, Request req, HttpServletRequest request, HttpS
}
private class LogHandler extends AbstractHandler {
-
+ @Override
public void handle(String target, Request req, HttpServletRequest request, HttpServletResponse response) {
LOGGER.info(
"{} {}{}",
@@ -304,7 +314,7 @@ public void handle(String target, Request req, HttpServletRequest request, HttpS
private static final Pattern SIMPLE_RANGE = Pattern.compile("bytes=([0-9])+-");
private class RepoHandler extends AbstractHandler {
-
+ @Override
public void handle(String target, Request req, HttpServletRequest request, HttpServletResponse response)
throws IOException {
String path = req.getPathInfo().substring(1);
@@ -438,7 +448,7 @@ public void handle(String target, Request req, HttpServletRequest request, HttpS
}
private class RedirectHandler extends AbstractHandler {
-
+ @Override
public void handle(String target, Request req, HttpServletRequest request, HttpServletResponse response) {
String path = req.getPathInfo();
if (!path.startsWith("/redirect/")) {
@@ -465,7 +475,7 @@ public void handle(String target, Request req, HttpServletRequest request, HttpS
}
private class AuthHandler extends AbstractHandler {
-
+ @Override
public void handle(String target, Request req, HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (ExpectContinue.BROKEN.equals(expectContinue)
@@ -485,7 +495,7 @@ public void handle(String target, Request req, HttpServletRequest request, HttpS
}
private class ProxyAuthHandler extends AbstractHandler {
-
+ @Override
public void handle(String target, Request req, HttpServletRequest request, HttpServletResponse response) {
if (proxyUsername != null && proxyPassword != null) {
if (checkBasicAuth(
diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java
new file mode 100644
index 000000000..4c82cb75f
--- /dev/null
+++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpTransporterTest.java
@@ -0,0 +1,1208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.internal.test.util.http;
+
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import org.eclipse.aether.ConfigurationProperties;
+import org.eclipse.aether.DefaultRepositoryCache;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.DefaultSessionData;
+import org.eclipse.aether.internal.test.util.TestFileUtils;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.Authentication;
+import org.eclipse.aether.repository.Proxy;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.connector.transport.GetTask;
+import org.eclipse.aether.spi.connector.transport.PeekTask;
+import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.Transporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterFactory;
+import org.eclipse.aether.transfer.NoTransporterException;
+import org.eclipse.aether.transfer.TransferCancelledException;
+import org.eclipse.aether.util.repository.AuthenticationBuilder;
+import org.junit.jupiter.api.*;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Common set of tests against Http transporter.
+ */
+@SuppressWarnings({"checkstyle:MagicNumber", "checkstyle:MethodName"})
+public class HttpTransporterTest {
+
+ protected static final Path KEY_STORE_PATH = Paths.get("target/keystore");
+
+ protected static final Path KEY_STORE_SELF_SIGNED_PATH = Paths.get("target/keystore-self-signed");
+
+ protected static final Path TRUST_STORE_PATH = Paths.get("target/trustStore");
+
+ static {
+ // Warning: "cross connected" with HttpServer!
+ System.setProperty(
+ "javax.net.ssl.trustStore", KEY_STORE_PATH.toAbsolutePath().toString());
+ System.setProperty("javax.net.ssl.trustStorePassword", "server-pwd");
+ System.setProperty(
+ "javax.net.ssl.keyStore", TRUST_STORE_PATH.toAbsolutePath().toString());
+ System.setProperty("javax.net.ssl.keyStorePassword", "client-pwd");
+
+ System.setProperty("javax.net.ssl.trustStoreType", "jks");
+ System.setProperty("javax.net.ssl.keyStoreType", "jks");
+ System.setProperty("javax.net.debug", "all");
+ }
+
+ private final Supplier transporterFactorySupplier;
+
+ protected DefaultRepositorySystemSession session;
+
+ protected HttpTransporterFactory factory;
+
+ protected HttpTransporter transporter;
+
+ protected File repoDir;
+
+ protected HttpServer httpServer;
+
+ protected Authentication auth;
+
+ protected Proxy proxy;
+
+ protected HttpTransporterTest(Supplier transporterFactorySupplier) {
+ this.transporterFactorySupplier = requireNonNull(transporterFactorySupplier);
+
+ if (!Files.isRegularFile(KEY_STORE_PATH)) {
+ URL keyStoreUrl = HttpTransporterTest.class.getClassLoader().getResource("ssl/server-store");
+ URL keyStoreSelfSignedUrl =
+ HttpTransporterTest.class.getClassLoader().getResource("ssl/server-store-selfsigned");
+ URL trustStoreUrl = HttpTransporterTest.class.getClassLoader().getResource("ssl/client-store");
+
+ try {
+ try (InputStream keyStoreStream = keyStoreUrl.openStream();
+ InputStream keyStoreSelfSignedStream = keyStoreSelfSignedUrl.openStream();
+ InputStream trustStoreStream = trustStoreUrl.openStream()) {
+ Files.copy(keyStoreStream, KEY_STORE_PATH, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(
+ keyStoreSelfSignedStream, KEY_STORE_SELF_SIGNED_PATH, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(trustStoreStream, TRUST_STORE_PATH, StandardCopyOption.REPLACE_EXISTING);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+
+ protected RemoteRepository newRepo(String url) {
+ return new RemoteRepository.Builder("test", "default", url)
+ .setAuthentication(auth)
+ .setProxy(proxy)
+ .build();
+ }
+
+ protected void newTransporter(String url) throws Exception {
+ if (transporter != null) {
+ transporter.close();
+ transporter = null;
+ }
+ // here we "simulate" onSessionClose
+ // TODO: in UTs currently we cannot do it, sort it out
+ session = new DefaultRepositorySystemSession(session);
+ session.setData(new DefaultSessionData());
+ transporter = factory.newInstance(session, newRepo(url));
+ }
+
+ protected static final long OLD_FILE_TIMESTAMP = 160660800000L;
+
+ @BeforeEach
+ protected void setUp(TestInfo testInfo) throws Exception {
+ System.out.println("=== " + testInfo.getDisplayName() + " ===");
+ session = TestUtils.newSession();
+ factory = transporterFactorySupplier.get();
+ repoDir = TestFileUtils.createTempDir();
+ TestFileUtils.writeString(new File(repoDir, "file.txt"), "test");
+ TestFileUtils.writeString(new File(repoDir, "dir/file.txt"), "test");
+ TestFileUtils.writeString(new File(repoDir, "dir/oldFile.txt"), "oldTest", OLD_FILE_TIMESTAMP);
+ TestFileUtils.writeString(new File(repoDir, "empty.txt"), "");
+ TestFileUtils.writeString(new File(repoDir, "some space.txt"), "space");
+ File resumable = new File(repoDir, "resume.txt");
+ TestFileUtils.writeString(resumable, "resumable");
+ resumable.setLastModified(System.currentTimeMillis() - 90 * 1000);
+ httpServer = new HttpServer().setRepoDir(repoDir).start();
+ newTransporter(httpServer.getHttpUrl());
+ }
+
+ @AfterEach
+ protected void tearDown() throws Exception {
+ if (transporter != null) {
+ transporter.close();
+ transporter = null;
+ }
+ if (httpServer != null) {
+ httpServer.stop();
+ httpServer = null;
+ }
+ factory = null;
+ session = null;
+ }
+
+ @Test
+ protected void testClassify() {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(new FileNotFoundException()));
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(new HttpTransporterException(403)));
+ assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new HttpTransporterException(404)));
+ }
+
+ @Test
+ protected void testPeek() throws Exception {
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ }
+
+ @Test
+ protected void testRetryHandler_defaultCount_positive() throws Exception {
+ httpServer.setConnectionsToClose(3);
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ }
+
+ @Test
+ protected void testRetryHandler_defaultCount_negative() throws Exception {
+ httpServer.setConnectionsToClose(4);
+ try {
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
+ protected void testRetryHandler_explicitCount_positive() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 10);
+ newTransporter(httpServer.getHttpUrl());
+ httpServer.setConnectionsToClose(10);
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ }
+
+ @Test
+ protected void testRetryHandler_disabled() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 0);
+ newTransporter(httpServer.getHttpUrl());
+ httpServer.setConnectionsToClose(1);
+ try {
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
+ protected void testPeek_NotFound() throws Exception {
+ try {
+ transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(404, e.getStatusCode());
+ assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testPeek_Closed() throws Exception {
+ transporter.close();
+ try {
+ transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
+ fail("Expected error");
+ } catch (IllegalStateException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testPeek_Authenticated() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ }
+
+ @Test
+ protected void testPeek_Unauthenticated() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ try {
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(401, e.getStatusCode());
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testPeek_ProxyAuthenticated() throws Exception {
+ httpServer.setProxyAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
+ newTransporter("http://bad.localhost:1/");
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ }
+
+ @Test
+ protected void testPeek_ProxyUnauthenticated() throws Exception {
+ httpServer.setProxyAuthentication("testuser", "testpass");
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
+ newTransporter("http://bad.localhost:1/");
+ try {
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(407, e.getStatusCode());
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testPeek_SSL() throws Exception {
+ httpServer.addSslConnector();
+ newTransporter(httpServer.getHttpsUrl());
+ transporter.peek(new PeekTask(URI.create("repo/file.txt")));
+ }
+
+ @Test
+ protected void testPeek_Redirect() throws Exception {
+ httpServer.addSslConnector();
+ transporter.peek(new PeekTask(URI.create("redirect/file.txt")));
+ transporter.peek(new PeekTask(URI.create("redirect/file.txt?scheme=https")));
+ }
+
+ @Test
+ protected void testGet_ToMemory() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_ToFile() throws Exception {
+ File file = TestFileUtils.createTempFile("failure");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task =
+ new GetTask(URI.create("repo/file.txt")).setDataFile(file).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", TestFileUtils.readString(file));
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("test", listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_ToFileTimestamp() throws Exception {
+ File file = TestFileUtils.createTempFile("failure");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/dir/oldFile.txt"))
+ .setDataFile(file)
+ .setListener(listener);
+ transporter.get(task);
+ assertEquals("oldTest", TestFileUtils.readString(file));
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(7L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("oldTest", listener.getBaos().toString(StandardCharsets.UTF_8));
+ assertEquals(file.lastModified(), OLD_FILE_TIMESTAMP);
+ }
+
+ @Test
+ protected void testGet_EmptyResource() throws Exception {
+ File file = TestFileUtils.createTempFile("failure");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task =
+ new GetTask(URI.create("repo/empty.txt")).setDataFile(file).setListener(listener);
+ transporter.get(task);
+ assertEquals("", TestFileUtils.readString(file));
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(0L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertEquals(0, listener.getProgressedCount());
+ assertEquals("", listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_EncodedResourcePath() throws Exception {
+ GetTask task = new GetTask(URI.create("repo/some%20space.txt"));
+ transporter.get(task);
+ assertEquals("space", task.getDataString());
+ }
+
+ @Test
+ protected void testGet_Authenticated() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_Unauthenticated() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(401, e.getStatusCode());
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testGet_ProxyAuthenticated() throws Exception {
+ httpServer.setProxyAuthentication("testuser", "testpass");
+ Authentication auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
+ newTransporter("http://bad.localhost:1/");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_ProxyUnauthenticated() throws Exception {
+ httpServer.setProxyAuthentication("testuser", "testpass");
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
+ newTransporter("http://bad.localhost:1/");
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(407, e.getStatusCode());
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testGet_SSL() throws Exception {
+ httpServer.addSslConnector();
+ newTransporter(httpServer.getHttpsUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_HTTPS_Unknown_SecurityMode() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.HTTPS_SECURITY_MODE, "unknown");
+ httpServer.addSelfSignedSslConnector();
+ try {
+ newTransporter(httpServer.getHttpsUrl());
+ fail("Unsupported security mode");
+ } catch (IllegalArgumentException a) {
+ // good
+ }
+ }
+
+ @Test
+ protected void testGet_HTTPS_Insecure_SecurityMode() throws Exception {
+ // here we use alternate server-store-selfigned key (as the key set it static initalizer is probably already
+ // used to init SSLContext/SSLSocketFactory/etc
+ session.setConfigProperty(
+ ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE);
+ httpServer.addSelfSignedSslConnector();
+ newTransporter(httpServer.getHttpsUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_Redirect() throws Exception {
+ httpServer.addSslConnector();
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("redirect/file.txt?scheme=https")).setListener(listener);
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_Resume() throws Exception {
+ File file = TestFileUtils.createTempFile("re");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/resume.txt"))
+ .setDataFile(file, true)
+ .setListener(listener);
+ transporter.get(task);
+ assertEquals("resumable", TestFileUtils.readString(file));
+ assertEquals(1L, listener.getStartedCount());
+ assertEquals(2L, listener.getDataOffset());
+ assertEquals(9, listener.getDataLength());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("sumable", listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_ResumeLocalContentsOutdated() throws Exception {
+ File file = TestFileUtils.createTempFile("re");
+ file.setLastModified(System.currentTimeMillis() - 5 * 60 * 1000);
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/resume.txt"))
+ .setDataFile(file, true)
+ .setListener(listener);
+ transporter.get(task);
+ assertEquals("resumable", TestFileUtils.readString(file));
+ assertEquals(1L, listener.getStartedCount());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(9, listener.getDataLength());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("resumable", listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_ResumeRangesNotSupportedByServer() throws Exception {
+ httpServer.setRangeSupport(false);
+ File file = TestFileUtils.createTempFile("re");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask(URI.create("repo/resume.txt"))
+ .setDataFile(file, true)
+ .setListener(listener);
+ transporter.get(task);
+ assertEquals("resumable", TestFileUtils.readString(file));
+ assertEquals(1L, listener.getStartedCount());
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(9, listener.getDataLength());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("resumable", listener.getBaos().toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ protected void testGet_Checksums_Nexus() throws Exception {
+ httpServer.setChecksumHeader(HttpServer.ChecksumHeader.NEXUS);
+ GetTask task = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(
+ "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
+ }
+
+ @Test
+ protected void testGet_Checksums_XChecksum() throws Exception {
+ httpServer.setChecksumHeader(HttpServer.ChecksumHeader.XCHECKSUM);
+ GetTask task = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(
+ "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
+ }
+
+ @Test
+ protected void testGet_FileHandleLeak() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ File file = TestFileUtils.createTempFile("failure");
+ transporter.get(new GetTask(URI.create("repo/file.txt")).setDataFile(file));
+ assertTrue(file.delete(), i + ", " + file.getAbsolutePath());
+ }
+ }
+
+ @Test
+ protected void testGet_NotFound() throws Exception {
+ try {
+ transporter.get(new GetTask(URI.create("repo/missing.txt")));
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(404, e.getStatusCode());
+ assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testGet_Closed() throws Exception {
+ transporter.close();
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (IllegalStateException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testGet_StartCancelled() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelStart();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ try {
+ transporter.get(task);
+ fail("Expected error");
+ } catch (TransferCancelledException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertEquals(0, listener.getProgressedCount());
+ }
+
+ @Test
+ protected void testGet_ProgressCancelled() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelProgress();
+ GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
+ try {
+ transporter.get(task);
+ fail("Expected error");
+ } catch (TransferCancelledException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(4L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertEquals(1, listener.getProgressedCount());
+ }
+
+ @Test
+ protected void testPut_FromMemory() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_FromFile() throws Exception {
+ File file = TestFileUtils.createTempFile("upload");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataFile(file);
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_EmptyResource() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask(URI.create("repo/file.txt")).setListener(listener);
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(0L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertEquals(0, listener.getProgressedCount());
+ assertEquals("", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_EncodedResourcePath() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask(URI.create("repo/some%20space.txt"))
+ .setListener(listener)
+ .setDataString("OK");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(2L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("OK", TestFileUtils.readString(new File(repoDir, "some space.txt")));
+ }
+
+ @Test
+ protected void testPut_Authenticated_ExpectContinue() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_Authenticated_ExpectContinueBroken() throws Exception {
+ // this makes OPTIONS recover, and have only 1 PUT (startedCount=1 as OPTIONS is not counted)
+ session.setConfigProperty(ConfigurationProperties.HTTP_SUPPORT_WEBDAV, true);
+ httpServer.setAuthentication("testuser", "testpass");
+ httpServer.setExpectSupport(HttpServer.ExpectContinue.BROKEN);
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_Authenticated_ExpectContinueRejected() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_Authenticated_ExpectContinueDisabled() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.HTTP_EXPECT_CONTINUE, false);
+ httpServer.setAuthentication("testuser", "testpass");
+ httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL); // if transport tries Expect/Continue explode
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount()); // w/ expectContinue enabled would have here 2
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader() throws Exception {
+ Map headers = new HashMap<>();
+ headers.put("Expect", "100-continue");
+ session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
+ httpServer.setAuthentication("testuser", "testpass");
+ httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_Unauthenticated() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ try {
+ transporter.put(task);
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(401, e.getStatusCode());
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ assertEquals(0, listener.getStartedCount());
+ assertEquals(0, listener.getProgressedCount());
+ }
+
+ @Test
+ protected void testPut_ProxyAuthenticated() throws Exception {
+ httpServer.setProxyAuthentication("testuser", "testpass");
+ Authentication auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
+ newTransporter("http://bad.localhost:1/");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_ProxyUnauthenticated() throws Exception {
+ httpServer.setProxyAuthentication("testuser", "testpass");
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
+ newTransporter("http://bad.localhost:1/");
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ try {
+ transporter.put(task);
+ fail("Expected error");
+ } catch (HttpTransporterException e) {
+ assertEquals(407, e.getStatusCode());
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ assertEquals(0, listener.getStartedCount());
+ assertEquals(0, listener.getProgressedCount());
+ }
+
+ @Test
+ protected void testPut_SSL() throws Exception {
+ httpServer.addSslConnector();
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpsUrl());
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
+ assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
+ }
+
+ @Test
+ protected void testPut_FileHandleLeak() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ File src = TestFileUtils.createTempFile("upload");
+ File dst = new File(repoDir, "file.txt");
+ transporter.put(new PutTask(URI.create("repo/file.txt")).setDataFile(src));
+ assertTrue(src.delete(), i + ", " + src.getAbsolutePath());
+ assertTrue(dst.delete(), i + ", " + dst.getAbsolutePath());
+ }
+ }
+
+ @Test
+ protected void testPut_Closed() throws Exception {
+ transporter.close();
+ try {
+ transporter.put(new PutTask(URI.create("repo/missing.txt")));
+ fail("Expected error");
+ } catch (IllegalStateException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ protected void testPut_StartCancelled() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelStart();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ try {
+ transporter.put(task);
+ fail("Expected error");
+ } catch (TransferCancelledException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertEquals(0, listener.getProgressedCount());
+ }
+
+ @Test
+ protected void testPut_ProgressCancelled() throws Exception {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelProgress();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ try {
+ transporter.put(task);
+ fail("Expected error");
+ } catch (TransferCancelledException e) {
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ assertEquals(0L, listener.getDataOffset());
+ assertEquals(6L, listener.getDataLength());
+ assertEquals(1, listener.getStartedCount());
+ assertEquals(1, listener.getProgressedCount());
+ }
+
+ @Test
+ protected void testGetPut_AuthCache() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ GetTask get = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(get);
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
+ transporter.put(task);
+ assertEquals(1, listener.getStartedCount());
+ }
+
+ @Test
+ protected void testPut_PreemptiveIsDefault() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
+ transporter.put(task);
+ assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
+ }
+
+ @Test
+ protected void testPut_AuthCache() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH, false);
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
+ transporter.put(task);
+ assertEquals(2, httpServer.getLogEntries().size()); // put (challenged) + put w/ auth
+ httpServer.getLogEntries().clear();
+ task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
+ transporter.put(task);
+ assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
+ }
+
+ @Test
+ protected void testPut_AuthCache_Preemptive() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
+ newTransporter(httpServer.getHttpUrl());
+ PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
+ transporter.put(task);
+ assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
+ httpServer.getLogEntries().clear();
+ task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
+ transporter.put(task);
+ assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
+ }
+
+ @Test
+ @Timeout(20)
+ protected void testConcurrency() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ newTransporter(httpServer.getHttpUrl());
+ final AtomicReference error = new AtomicReference<>();
+ Thread[] threads = new Thread[20];
+ for (int i = 0; i < threads.length; i++) {
+ final String path = "repo/file.txt?i=" + i;
+ threads[i] = new Thread(() -> {
+ try {
+ for (int j = 0; j < 100; j++) {
+ GetTask task = new GetTask(URI.create(path));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ }
+ } catch (Throwable t) {
+ error.compareAndSet(null, t);
+ System.err.println(path);
+ t.printStackTrace();
+ }
+ });
+ threads[i].setName("Task-" + i);
+ }
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ assertNull(error.get(), String.valueOf(error.get()));
+ }
+
+ @Test
+ @Timeout(10)
+ protected void testConnectTimeout() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.CONNECT_TIMEOUT, 100);
+ int port = 1;
+ newTransporter("http://localhost:" + port);
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (Exception e) {
+ // impl specific "timeout" exception
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+
+ @Test
+ @Timeout(10)
+ protected void testRequestTimeout() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, 100);
+ ServerSocket server = new ServerSocket(0);
+ try (server) {
+ newTransporter("http://localhost:" + server.getLocalPort());
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Expected error");
+ } catch (Exception e) {
+ assertTrue(e.getClass().getSimpleName().contains("Timeout"));
+ assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
+ }
+ }
+ }
+
+ @Test
+ protected void testUserAgent() throws Exception {
+ session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
+ newTransporter(httpServer.getHttpUrl());
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ assertEquals(1, httpServer.getLogEntries().size());
+ for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
+ assertEquals("SomeTest/1.0", log.getHeaders().get("User-Agent"));
+ }
+ }
+
+ @Test
+ protected void testCustomHeaders() throws Exception {
+ Map headers = new HashMap<>();
+ headers.put("User-Agent", "Custom/1.0");
+ headers.put("X-CustomHeader", "Custom-Value");
+ session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
+ session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
+ newTransporter(httpServer.getHttpUrl());
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ assertEquals(1, httpServer.getLogEntries().size());
+ for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
+ for (Map.Entry entry : headers.entrySet()) {
+ assertEquals(entry.getValue(), log.getHeaders().get(entry.getKey()), entry.getKey());
+ }
+ }
+ }
+
+ @Test
+ protected void testServerAuthScope_NotUsedForProxy() throws Exception {
+ String username = "testuser", password = "testpass";
+ httpServer.setProxyAuthentication(username, password);
+ auth = new AuthenticationBuilder()
+ .addUsername(username)
+ .addPassword(password)
+ .build();
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
+ newTransporter("http://" + httpServer.getHost() + ":12/");
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Server auth must not be used as proxy auth");
+ } catch (HttpTransporterException e) {
+ assertEquals(407, e.getStatusCode());
+ } catch (IOException e) {
+ // accepted as well: point is to fail
+ }
+ }
+
+ @Test
+ protected void testProxyAuthScope_NotUsedForServer() throws Exception {
+ String username = "testuser", password = "testpass";
+ httpServer.setAuthentication(username, password);
+ Authentication auth = new AuthenticationBuilder()
+ .addUsername(username)
+ .addPassword(password)
+ .build();
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
+ newTransporter("http://" + httpServer.getHost() + ":12/");
+ try {
+ transporter.get(new GetTask(URI.create("repo/file.txt")));
+ fail("Proxy auth must not be used as server auth");
+ } catch (HttpTransporterException e) {
+ assertEquals(401, e.getStatusCode());
+ } catch (IOException e) {
+ // accepted as well: point is to fail
+ }
+ }
+
+ @Test
+ protected void testAuthSchemeReuse() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ httpServer.setProxyAuthentication("proxyuser", "proxypass");
+ session.setCache(new DefaultRepositoryCache());
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+ Authentication auth = new AuthenticationBuilder()
+ .addUsername("proxyuser")
+ .addPassword("proxypass")
+ .build();
+ proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
+ newTransporter("http://bad.localhost:1/");
+ GetTask task = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(3, httpServer.getLogEntries().size());
+ httpServer.getLogEntries().clear();
+ newTransporter("http://bad.localhost:1/");
+ task = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ assertEquals(1, httpServer.getLogEntries().size());
+ assertNotNull(httpServer.getLogEntries().get(0).getHeaders().get("Authorization"));
+ assertNotNull(httpServer.getLogEntries().get(0).getHeaders().get("Proxy-Authorization"));
+ }
+
+ @Test
+ protected void testAuthSchemePreemptive() throws Exception {
+ httpServer.setAuthentication("testuser", "testpass");
+ session.setCache(new DefaultRepositoryCache());
+ auth = new AuthenticationBuilder()
+ .addUsername("testuser")
+ .addPassword("testpass")
+ .build();
+
+ session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, false);
+ newTransporter(httpServer.getHttpUrl());
+ GetTask task = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ // there ARE challenge round-trips
+ assertEquals(2, httpServer.getLogEntries().size());
+
+ httpServer.getLogEntries().clear();
+
+ session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
+ newTransporter(httpServer.getHttpUrl());
+ task = new GetTask(URI.create("repo/file.txt"));
+ transporter.get(task);
+ assertEquals("test", task.getDataString());
+ // there are NO challenge round-trips, all goes through at first
+ assertEquals(1, httpServer.getLogEntries().size());
+ }
+
+ @Test
+ void testInit_BadProtocol() {
+ assertThrows(NoTransporterException.class, () -> newTransporter("bad:/void"));
+ }
+
+ @Test
+ void testInit_BadUrl() {
+ assertThrows(NoTransporterException.class, () -> newTransporter("http://localhost:NaN"));
+ }
+
+ @Test
+ void testInit_CaseInsensitiveProtocol() throws Exception {
+ newTransporter("http://localhost");
+ newTransporter("HTTP://localhost");
+ newTransporter("Http://localhost");
+ newTransporter("https://localhost");
+ newTransporter("HTTPS://localhost");
+ newTransporter("HttpS://localhost");
+ }
+}
diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
index 8031ce026..7dac9b666 100644
--- a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
+++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
@@ -56,7 +56,14 @@ public void transportStarted(long dataOffset, long dataLength) throws TransferCa
@Override
public void transportProgressed(ByteBuffer data) throws TransferCancelledException {
progressedCount++;
- baos.write(data.array(), data.arrayOffset() + ((Buffer) data).position(), data.remaining());
+ if (data.hasArray()) {
+ baos.write(data.array(), data.arrayOffset() + ((Buffer) data).position(), data.remaining());
+ } else {
+ byte[] arr = new byte[data.remaining()];
+ data.mark();
+ data.get(arr);
+ data.reset();
+ }
if (cancelProgress) {
throw new TransferCancelledException();
}
diff --git a/maven-resolver-transport-apache/src/test/resources/ssl/README.txt b/maven-resolver-test-http/src/main/resources/ssl/README.txt
similarity index 100%
rename from maven-resolver-transport-apache/src/test/resources/ssl/README.txt
rename to maven-resolver-test-http/src/main/resources/ssl/README.txt
diff --git a/maven-resolver-transport-apache/src/test/resources/ssl/client-store b/maven-resolver-test-http/src/main/resources/ssl/client-store
similarity index 100%
rename from maven-resolver-transport-apache/src/test/resources/ssl/client-store
rename to maven-resolver-test-http/src/main/resources/ssl/client-store
diff --git a/maven-resolver-transport-apache/src/test/resources/ssl/server-store b/maven-resolver-test-http/src/main/resources/ssl/server-store
similarity index 100%
rename from maven-resolver-transport-apache/src/test/resources/ssl/server-store
rename to maven-resolver-test-http/src/main/resources/ssl/server-store
diff --git a/maven-resolver-transport-apache/src/test/resources/ssl/server-store-selfsigned b/maven-resolver-test-http/src/main/resources/ssl/server-store-selfsigned
similarity index 100%
rename from maven-resolver-transport-apache/src/test/resources/ssl/server-store-selfsigned
rename to maven-resolver-test-http/src/main/resources/ssl/server-store-selfsigned
diff --git a/maven-resolver-transport-apache/pom.xml b/maven-resolver-transport-apache/pom.xml
index 869fbb16a..ba6a19b9b 100644
--- a/maven-resolver-transport-apache/pom.xml
+++ b/maven-resolver-transport-apache/pom.xml
@@ -93,14 +93,20 @@
provided
true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
com.google.inject
guice
test
- org.junit.jupiter
- junit-jupiter-api
+ org.slf4j
+ slf4j-simple
test
diff --git a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
index b7df5c1a8..56ab1ea82 100644
--- a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
+++ b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
@@ -93,6 +93,8 @@
import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.TransportTask;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
import org.eclipse.aether.transfer.NoTransporterException;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.util.ConfigUtils;
@@ -112,7 +114,7 @@
/**
* A transporter for HTTP/HTTPS.
*/
-final class ApacheTransporter extends AbstractTransporter {
+final class ApacheTransporter extends AbstractTransporter implements HttpTransporter {
private static final Pattern CONTENT_RANGE_PATTERN =
Pattern.compile("\\s*bytes\\s+([0-9]+)\\s*-\\s*([0-9]+)\\s*/.*");
@@ -396,8 +398,8 @@ private URI resolve(TransportTask task) {
@Override
public int classify(Throwable error) {
- if (error instanceof HttpResponseException
- && ((HttpResponseException) error).getStatusCode() == HttpStatus.SC_NOT_FOUND) {
+ if (error instanceof HttpTransporterException
+ && ((HttpTransporterException) error).getStatusCode() == HttpStatus.SC_NOT_FOUND) {
return ERROR_NOT_FOUND;
}
return ERROR_OTHER;
@@ -406,7 +408,11 @@ public int classify(Throwable error) {
@Override
protected void implPeek(PeekTask task) throws Exception {
HttpHead request = commonHeaders(new HttpHead(resolve(task)));
- execute(request, null);
+ try {
+ execute(request, null);
+ } catch (HttpResponseException e) {
+ throw new HttpTransporterException(e.getStatusCode());
+ }
}
@Override
@@ -430,7 +436,7 @@ protected void implGet(GetTask task) throws Exception {
resume = false;
continue;
}
- throw e;
+ throw new HttpTransporterException(e.getStatusCode());
}
}
}
@@ -448,7 +454,7 @@ protected void implPut(PutTask task) throws Exception {
execute(request, null);
return;
}
- throw e;
+ throw new HttpTransporterException(e.getStatusCode());
}
}
@@ -475,7 +481,7 @@ private void execute(HttpUriRequest request, EntityGetter getter) throws Excepti
}
}
- private void prepare(HttpUriRequest request, SharingHttpContext context) {
+ private void prepare(HttpUriRequest request, SharingHttpContext context) throws HttpTransporterException {
final boolean put = HttpPut.METHOD_NAME.equalsIgnoreCase(request.getMethod());
if (preemptiveAuth || (preemptivePutAuth && put)) {
context.getAuthCache().put(server, new BasicScheme());
@@ -497,7 +503,7 @@ private void prepare(HttpUriRequest request, SharingHttpContext context) {
}
@SuppressWarnings("checkstyle:magicnumber")
- private void mkdirs(URI uri, SharingHttpContext context) {
+ private void mkdirs(URI uri, SharingHttpContext context) throws HttpTransporterException {
List dirs = UriUtils.getDirectories(baseUri, uri);
int index = 0;
for (; index < dirs.size(); index++) {
diff --git a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporterFactory.java b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporterFactory.java
index 31bfec544..621188eed 100644
--- a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporterFactory.java
+++ b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporterFactory.java
@@ -22,8 +22,8 @@
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
-import org.eclipse.aether.spi.connector.transport.Transporter;
-import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterFactory;
import org.eclipse.aether.transfer.NoTransporterException;
import static java.util.Objects.requireNonNull;
@@ -33,7 +33,7 @@
* support uploads to WebDAV servers and resumable downloads.
*/
@Named(ApacheTransporterFactory.NAME)
-public final class ApacheTransporterFactory implements TransporterFactory {
+public final class ApacheTransporterFactory implements HttpTransporterFactory {
public static final String NAME = "apache";
private float priority = 5.0f;
@@ -55,7 +55,7 @@ public ApacheTransporterFactory setPriority(float priority) {
}
@Override
- public Transporter newInstance(RepositorySystemSession session, RemoteRepository repository)
+ public HttpTransporter newInstance(RepositorySystemSession session, RemoteRepository repository)
throws NoTransporterException {
requireNonNull(session, "session cannot be null");
requireNonNull(repository, "repository cannot be null");
diff --git a/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java b/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java
index 4efc131d0..d0bce405d 100644
--- a/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java
+++ b/maven-resolver-transport-apache/src/test/java/org/eclipse/aether/transport/apache/ApacheTransporterTest.java
@@ -19,430 +19,30 @@
package org.eclipse.aether.transport.apache;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.net.ConnectException;
-import java.net.ServerSocket;
-import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-import org.apache.http.NoHttpResponseException;
-import org.apache.http.client.HttpResponseException;
-import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.pool.ConnPoolControl;
import org.apache.http.pool.PoolStats;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.DefaultRepositoryCache;
-import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.internal.test.util.TestFileUtils;
-import org.eclipse.aether.internal.test.util.TestUtils;
-import org.eclipse.aether.internal.test.util.http.HttpServer;
+import org.eclipse.aether.internal.test.util.http.HttpTransporterTest;
import org.eclipse.aether.internal.test.util.http.RecordingTransportListener;
-import org.eclipse.aether.repository.Authentication;
-import org.eclipse.aether.repository.Proxy;
-import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.spi.connector.transport.GetTask;
-import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
-import org.eclipse.aether.spi.connector.transport.Transporter;
-import org.eclipse.aether.spi.connector.transport.TransporterFactory;
-import org.eclipse.aether.transfer.NoTransporterException;
-import org.eclipse.aether.transfer.TransferCancelledException;
-import org.eclipse.aether.util.repository.AuthenticationBuilder;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInfo;
-import org.junit.jupiter.api.Timeout;
import static org.junit.jupiter.api.Assertions.*;
/**
+ * Apache Transporter UT.
+ * It does support WebDAV.
*/
-public class ApacheTransporterTest {
+class ApacheTransporterTest extends HttpTransporterTest {
- static {
- System.setProperty(
- "javax.net.ssl.trustStore", new File("src/test/resources/ssl/server-store").getAbsolutePath());
- System.setProperty("javax.net.ssl.trustStorePassword", "server-pwd");
- System.setProperty("javax.net.ssl.keyStore", new File("src/test/resources/ssl/client-store").getAbsolutePath());
- System.setProperty("javax.net.ssl.keyStorePassword", "client-pwd");
- }
-
- private DefaultRepositorySystemSession session;
-
- private TransporterFactory factory;
-
- private Transporter transporter;
-
- private File repoDir;
-
- private HttpServer httpServer;
-
- private Authentication auth;
-
- private Proxy proxy;
-
- private RemoteRepository newRepo(String url) {
- return new RemoteRepository.Builder("test", "default", url)
- .setAuthentication(auth)
- .setProxy(proxy)
- .build();
- }
-
- private void newTransporter(String url) throws Exception {
- if (transporter != null) {
- transporter.close();
- transporter = null;
- }
- transporter = factory.newInstance(session, newRepo(url));
- }
-
- private static final long OLD_FILE_TIMESTAMP = 160660800000L;
-
- @BeforeEach
- void setUp(TestInfo testInfo) throws Exception {
- System.out.println("=== " + testInfo.getDisplayName() + " ===");
- session = TestUtils.newSession();
- factory = new ApacheTransporterFactory();
- repoDir = TestFileUtils.createTempDir();
- TestFileUtils.writeString(new File(repoDir, "file.txt"), "test");
- TestFileUtils.writeString(new File(repoDir, "dir/file.txt"), "test");
- TestFileUtils.writeString(new File(repoDir, "dir/oldFile.txt"), "oldTest", OLD_FILE_TIMESTAMP);
- TestFileUtils.writeString(new File(repoDir, "empty.txt"), "");
- TestFileUtils.writeString(new File(repoDir, "some space.txt"), "space");
- File resumable = new File(repoDir, "resume.txt");
- TestFileUtils.writeString(resumable, "resumable");
- resumable.setLastModified(System.currentTimeMillis() - 90 * 1000);
- httpServer = new HttpServer().setRepoDir(repoDir).start();
- newTransporter(httpServer.getHttpUrl());
- }
-
- @AfterEach
- void tearDown() throws Exception {
- if (transporter != null) {
- transporter.close();
- transporter = null;
- }
- if (httpServer != null) {
- httpServer.stop();
- httpServer = null;
- }
- factory = null;
- session = null;
- }
-
- @Test
- void testClassify() {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(new FileNotFoundException()));
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(new HttpResponseException(403, "Forbidden")));
- assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new HttpResponseException(404, "Not Found")));
- }
-
- @Test
- void testPeek() throws Exception {
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- }
-
- @Test
- void testRetryHandler_defaultCount_positive() throws Exception {
- httpServer.setConnectionsToClose(3);
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- }
-
- @Test
- void testRetryHandler_defaultCount_negative() throws Exception {
- httpServer.setConnectionsToClose(4);
- try {
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (NoHttpResponseException expected) {
- }
- }
-
- @Test
- void testRetryHandler_explicitCount_positive() throws Exception {
- session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 10);
- newTransporter(httpServer.getHttpUrl());
- httpServer.setConnectionsToClose(10);
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- }
-
- @Test
- void testRetryHandler_disabled() throws Exception {
- session.setConfigProperty(ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT, 0);
- newTransporter(httpServer.getHttpUrl());
- httpServer.setConnectionsToClose(1);
- try {
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- } catch (NoHttpResponseException expected) {
- }
- }
-
- @Test
- void testPeek_NotFound() throws Exception {
- try {
- transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(404, e.getStatusCode());
- assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
- }
- }
-
- @Test
- void testPeek_Closed() throws Exception {
- transporter.close();
- try {
- transporter.peek(new PeekTask(URI.create("repo/missing.txt")));
- fail("Expected error");
- } catch (IllegalStateException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testPeek_Authenticated() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- }
-
- @Test
- void testPeek_Unauthenticated() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- try {
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(401, e.getStatusCode());
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testPeek_ProxyAuthenticated() throws Exception {
- httpServer.setProxyAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
- newTransporter("http://bad.localhost:1/");
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- }
-
- @Test
- void testPeek_ProxyUnauthenticated() throws Exception {
- httpServer.setProxyAuthentication("testuser", "testpass");
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
- newTransporter("http://bad.localhost:1/");
- try {
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(407, e.getStatusCode());
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testPeek_SSL() throws Exception {
- httpServer.addSslConnector();
- newTransporter(httpServer.getHttpsUrl());
- transporter.peek(new PeekTask(URI.create("repo/file.txt")));
- }
-
- @Test
- void testPeek_Redirect() throws Exception {
- httpServer.addSslConnector();
- transporter.peek(new PeekTask(URI.create("redirect/file.txt")));
- transporter.peek(new PeekTask(URI.create("redirect/file.txt?scheme=https")));
- }
-
- @Test
- void testGet_ToMemory() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals(task.getDataString(), new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_ToFile() throws Exception {
- File file = TestFileUtils.createTempFile("failure");
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task =
- new GetTask(URI.create("repo/file.txt")).setDataFile(file).setListener(listener);
- transporter.get(task);
- assertEquals("test", TestFileUtils.readString(file));
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("test", new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_ToFileTimestamp() throws Exception {
- File file = TestFileUtils.createTempFile("failure");
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/dir/oldFile.txt"))
- .setDataFile(file)
- .setListener(listener);
- transporter.get(task);
- assertEquals("oldTest", TestFileUtils.readString(file));
- assertEquals(0L, listener.getDataOffset());
- assertEquals(7L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("oldTest", new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- assertEquals(file.lastModified(), OLD_FILE_TIMESTAMP);
- }
-
- @Test
- void testGet_EmptyResource() throws Exception {
- File file = TestFileUtils.createTempFile("failure");
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task =
- new GetTask(URI.create("repo/empty.txt")).setDataFile(file).setListener(listener);
- transporter.get(task);
- assertEquals("", TestFileUtils.readString(file));
- assertEquals(0L, listener.getDataOffset());
- assertEquals(0L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertEquals(0, listener.getProgressedCount());
- assertEquals("", new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_EncodedResourcePath() throws Exception {
- GetTask task = new GetTask(URI.create("repo/some%20space.txt"));
- transporter.get(task);
- assertEquals("space", task.getDataString());
- }
-
- @Test
- void testGet_Authenticated() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals(task.getDataString(), new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_Unauthenticated() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(401, e.getStatusCode());
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testGet_ProxyAuthenticated() throws Exception {
- httpServer.setProxyAuthentication("testuser", "testpass");
- Authentication auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
- newTransporter("http://bad.localhost:1/");
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals(task.getDataString(), new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_ProxyUnauthenticated() throws Exception {
- httpServer.setProxyAuthentication("testuser", "testpass");
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
- newTransporter("http://bad.localhost:1/");
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(407, e.getStatusCode());
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testGet_SSL() throws Exception {
- httpServer.addSslConnector();
- newTransporter(httpServer.getHttpsUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals(task.getDataString(), new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_HTTPS_Unknown_SecurityMode() throws Exception {
- session.setConfigProperty(ConfigurationProperties.HTTPS_SECURITY_MODE, "unknown");
- httpServer.addSelfSignedSslConnector();
- try {
- newTransporter(httpServer.getHttpsUrl());
- fail("Unsupported security mode");
- } catch (IllegalArgumentException a) {
- // good
- }
- }
-
- @Test
- void testGet_HTTPS_Insecure_SecurityMode() throws Exception {
- // here we use alternate server-store-selfigned key (as the key set it static initalizer is probably already
- // used to init SSLContext/SSLSocketFactory/etc
- session.setConfigProperty(
- ConfigurationProperties.HTTPS_SECURITY_MODE, ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE);
- httpServer.addSelfSignedSslConnector();
- newTransporter(httpServer.getHttpsUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals(task.getDataString(), new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
+ public ApacheTransporterTest() {
+ super(ApacheTransporterFactory::new);
}
@Test
@@ -462,389 +62,6 @@ void testGet_WebDav() throws Exception {
1, httpServer.getLogEntries().size(), httpServer.getLogEntries().toString());
}
- @Test
- void testGet_Redirect() throws Exception {
- httpServer.addSslConnector();
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("redirect/file.txt?scheme=https")).setListener(listener);
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals(task.getDataString(), new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_Resume() throws Exception {
- File file = TestFileUtils.createTempFile("re");
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/resume.txt"))
- .setDataFile(file, true)
- .setListener(listener);
- transporter.get(task);
- assertEquals("resumable", TestFileUtils.readString(file));
- assertEquals(1L, listener.getStartedCount());
- assertEquals(2L, listener.getDataOffset());
- assertEquals(9, listener.getDataLength());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("sumable", new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_ResumeLocalContentsOutdated() throws Exception {
- File file = TestFileUtils.createTempFile("re");
- file.setLastModified(System.currentTimeMillis() - 5 * 60 * 1000);
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/resume.txt"))
- .setDataFile(file, true)
- .setListener(listener);
- transporter.get(task);
- assertEquals("resumable", TestFileUtils.readString(file));
- assertEquals(1L, listener.getStartedCount());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(9, listener.getDataLength());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("resumable", new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_ResumeRangesNotSupportedByServer() throws Exception {
- httpServer.setRangeSupport(false);
- File file = TestFileUtils.createTempFile("re");
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask(URI.create("repo/resume.txt"))
- .setDataFile(file, true)
- .setListener(listener);
- transporter.get(task);
- assertEquals("resumable", TestFileUtils.readString(file));
- assertEquals(1L, listener.getStartedCount());
- assertEquals(0L, listener.getDataOffset());
- assertEquals(9, listener.getDataLength());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("resumable", new String(listener.getBaos().toByteArray(), StandardCharsets.UTF_8));
- }
-
- @Test
- void testGet_Checksums_Nexus() throws Exception {
- httpServer.setChecksumHeader(HttpServer.ChecksumHeader.NEXUS);
- GetTask task = new GetTask(URI.create("repo/file.txt"));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(
- "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
- }
-
- @Test
- void testGet_Checksums_XChecksum() throws Exception {
- httpServer.setChecksumHeader(HttpServer.ChecksumHeader.XCHECKSUM);
- GetTask task = new GetTask(URI.create("repo/file.txt"));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(
- "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get("SHA-1"));
- }
-
- @Test
- void testGet_FileHandleLeak() throws Exception {
- for (int i = 0; i < 100; i++) {
- File file = TestFileUtils.createTempFile("failure");
- transporter.get(new GetTask(URI.create("repo/file.txt")).setDataFile(file));
- assertTrue(file.delete(), i + ", " + file.getAbsolutePath());
- }
- }
-
- @Test
- void testGet_NotFound() throws Exception {
- try {
- transporter.get(new GetTask(URI.create("repo/missing.txt")));
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(404, e.getStatusCode());
- assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
- }
- }
-
- @Test
- void testGet_Closed() throws Exception {
- transporter.close();
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (IllegalStateException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testGet_StartCancelled() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelStart();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- try {
- transporter.get(task);
- fail("Expected error");
- } catch (TransferCancelledException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertEquals(0, listener.getProgressedCount());
- }
-
- @Test
- void testGet_ProgressCancelled() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelProgress();
- GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener);
- try {
- transporter.get(task);
- fail("Expected error");
- } catch (TransferCancelledException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- assertEquals(0L, listener.getDataOffset());
- assertEquals(4L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertEquals(1, listener.getProgressedCount());
- }
-
- @Test
- void testPut_FromMemory() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_FromFile() throws Exception {
- File file = TestFileUtils.createTempFile("upload");
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataFile(file);
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_EmptyResource() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask(URI.create("repo/file.txt")).setListener(listener);
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(0L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertEquals(0, listener.getProgressedCount());
- assertEquals("", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_EncodedResourcePath() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask(URI.create("repo/some%20space.txt"))
- .setListener(listener)
- .setDataString("OK");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(2L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("OK", TestFileUtils.readString(new File(repoDir, "some space.txt")));
- }
-
- @Test
- void testPut_Authenticated_ExpectContinue() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_Authenticated_ExpectContinueBroken() throws Exception {
- // this makes OPTIONS recover, and have only 1 PUT (startedCount=1 as OPTIONS is not counted)
- session.setConfigProperty(ConfigurationProperties.HTTP_SUPPORT_WEBDAV, true);
- httpServer.setAuthentication("testuser", "testpass");
- httpServer.setExpectSupport(HttpServer.ExpectContinue.BROKEN);
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_Authenticated_ExpectContinueRejected() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_Authenticated_ExpectContinueDisabled() throws Exception {
- session.setConfigProperty(ConfigurationProperties.HTTP_EXPECT_CONTINUE, false);
- httpServer.setAuthentication("testuser", "testpass");
- httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL); // if transport tries Expect/Continue explode
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount()); // w/ expectContinue enabled would have here 2
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader() throws Exception {
- Map headers = new HashMap<>();
- headers.put("Expect", "100-continue");
- session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
- httpServer.setAuthentication("testuser", "testpass");
- httpServer.setExpectSupport(HttpServer.ExpectContinue.FAIL);
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_Unauthenticated() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- try {
- transporter.put(task);
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(401, e.getStatusCode());
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- assertEquals(0, listener.getStartedCount());
- assertEquals(0, listener.getProgressedCount());
- }
-
- @Test
- void testPut_ProxyAuthenticated() throws Exception {
- httpServer.setProxyAuthentication("testuser", "testpass");
- Authentication auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
- newTransporter("http://bad.localhost:1/");
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
- @Test
- void testPut_ProxyUnauthenticated() throws Exception {
- httpServer.setProxyAuthentication("testuser", "testpass");
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
- newTransporter("http://bad.localhost:1/");
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- try {
- transporter.put(task);
- fail("Expected error");
- } catch (HttpResponseException e) {
- assertEquals(407, e.getStatusCode());
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- assertEquals(0, listener.getStartedCount());
- assertEquals(0, listener.getProgressedCount());
- }
-
- @Test
- void testPut_SSL() throws Exception {
- httpServer.addSslConnector();
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpsUrl());
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount());
- assertEquals("upload", TestFileUtils.readString(new File(repoDir, "file.txt")));
- }
-
@Test
void testPut_WebDav() throws Exception {
httpServer.setWebDav(true);
@@ -873,323 +90,6 @@ void testPut_WebDav() throws Exception {
assertEquals("PUT", httpServer.getLogEntries().get(4).getMethod());
}
- @Test
- void testPut_FileHandleLeak() throws Exception {
- for (int i = 0; i < 100; i++) {
- File src = TestFileUtils.createTempFile("upload");
- File dst = new File(repoDir, "file.txt");
- transporter.put(new PutTask(URI.create("repo/file.txt")).setDataFile(src));
- assertTrue(src.delete(), i + ", " + src.getAbsolutePath());
- assertTrue(dst.delete(), i + ", " + dst.getAbsolutePath());
- }
- }
-
- @Test
- void testPut_Closed() throws Exception {
- transporter.close();
- try {
- transporter.put(new PutTask(URI.create("repo/missing.txt")));
- fail("Expected error");
- } catch (IllegalStateException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- void testPut_StartCancelled() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelStart();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- try {
- transporter.put(task);
- fail("Expected error");
- } catch (TransferCancelledException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertEquals(0, listener.getProgressedCount());
- }
-
- @Test
- void testPut_ProgressCancelled() throws Exception {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelProgress();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- try {
- transporter.put(task);
- fail("Expected error");
- } catch (TransferCancelledException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- assertEquals(0L, listener.getDataOffset());
- assertEquals(6L, listener.getDataLength());
- assertEquals(1, listener.getStartedCount());
- assertEquals(1, listener.getProgressedCount());
- }
-
- @Test
- void testGetPut_AuthCache() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- GetTask get = new GetTask(URI.create("repo/file.txt"));
- transporter.get(get);
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask(URI.create("repo/file.txt")).setListener(listener).setDataString("upload");
- transporter.put(task);
- assertEquals(1, listener.getStartedCount());
- }
-
- @Test
- void testPut_PreemptiveIsDefault() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
- transporter.put(task);
- assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
- }
-
- @Test
- void testPut_AuthCache() throws Exception {
- session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH, false);
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
- transporter.put(task);
- assertEquals(2, httpServer.getLogEntries().size()); // put (challenged) + put w/ auth
- httpServer.getLogEntries().clear();
- task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
- transporter.put(task);
- assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
- }
-
- @Test
- void testPut_AuthCache_Preemptive() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
- newTransporter(httpServer.getHttpUrl());
- PutTask task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
- transporter.put(task);
- assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
- httpServer.getLogEntries().clear();
- task = new PutTask(URI.create("repo/file.txt")).setDataString("upload");
- transporter.put(task);
- assertEquals(1, httpServer.getLogEntries().size()); // put w/ auth
- }
-
- @Test
- @Timeout(20)
- public void testConcurrency() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- newTransporter(httpServer.getHttpUrl());
- final AtomicReference error = new AtomicReference<>();
- Thread[] threads = new Thread[20];
- for (int i = 0; i < threads.length; i++) {
- final String path = "repo/file.txt?i=" + i;
- threads[i] = new Thread() {
- @Override
- public void run() {
- try {
- for (int j = 0; j < 100; j++) {
- GetTask task = new GetTask(URI.create(path));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- }
- } catch (Throwable t) {
- error.compareAndSet(null, t);
- System.err.println(path);
- t.printStackTrace();
- }
- }
- };
- threads[i].setName("Task-" + i);
- }
- for (Thread thread : threads) {
- thread.start();
- }
- for (Thread thread : threads) {
- thread.join();
- }
- assertNull(error.get(), String.valueOf(error.get()));
- }
-
- @Test
- @Timeout(10)
- public void testConnectTimeout() throws Exception {
- session.setConfigProperty(ConfigurationProperties.CONNECT_TIMEOUT, 100);
- int port = 1;
- newTransporter("http://localhost:" + port);
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (ConnectTimeoutException | ConnectException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- }
-
- @Test
- @Timeout(10)
- public void testRequestTimeout() throws Exception {
- session.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, 100);
- ServerSocket server = new ServerSocket(0);
- newTransporter("http://localhost:" + server.getLocalPort());
- try {
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Expected error");
- } catch (SocketTimeoutException e) {
- assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
- }
- } finally {
- server.close();
- }
- }
-
- @Test
- void testUserAgent() throws Exception {
- session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
- newTransporter(httpServer.getHttpUrl());
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- assertEquals(1, httpServer.getLogEntries().size());
- for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
- assertEquals("SomeTest/1.0", log.getHeaders().get("User-Agent"));
- }
- }
-
- @Test
- void testCustomHeaders() throws Exception {
- Map headers = new HashMap<>();
- headers.put("User-Agent", "Custom/1.0");
- headers.put("X-CustomHeader", "Custom-Value");
- session.setConfigProperty(ConfigurationProperties.USER_AGENT, "SomeTest/1.0");
- session.setConfigProperty(ConfigurationProperties.HTTP_HEADERS + ".test", headers);
- newTransporter(httpServer.getHttpUrl());
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- assertEquals(1, httpServer.getLogEntries().size());
- for (HttpServer.LogEntry log : httpServer.getLogEntries()) {
- for (Map.Entry entry : headers.entrySet()) {
- assertEquals(entry.getValue(), log.getHeaders().get(entry.getKey()), entry.getKey());
- }
- }
- }
-
- @Test
- void testServerAuthScope_NotUsedForProxy() throws Exception {
- String username = "testuser", password = "testpass";
- httpServer.setProxyAuthentication(username, password);
- auth = new AuthenticationBuilder()
- .addUsername(username)
- .addPassword(password)
- .build();
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort());
- newTransporter("http://" + httpServer.getHost() + ":12/");
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Server auth must not be used as proxy auth");
- } catch (HttpResponseException e) {
- assertEquals(407, e.getStatusCode());
- }
- }
-
- @Test
- void testProxyAuthScope_NotUsedForServer() throws Exception {
- String username = "testuser", password = "testpass";
- httpServer.setAuthentication(username, password);
- Authentication auth = new AuthenticationBuilder()
- .addUsername(username)
- .addPassword(password)
- .build();
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
- newTransporter("http://" + httpServer.getHost() + ":12/");
- try {
- transporter.get(new GetTask(URI.create("repo/file.txt")));
- fail("Proxy auth must not be used as server auth");
- } catch (HttpResponseException e) {
- assertEquals(401, e.getStatusCode());
- }
- }
-
- @Test
- void testAuthSchemeReuse() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- httpServer.setProxyAuthentication("proxyuser", "proxypass");
- session.setCache(new DefaultRepositoryCache());
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
- Authentication auth = new AuthenticationBuilder()
- .addUsername("proxyuser")
- .addPassword("proxypass")
- .build();
- proxy = new Proxy(Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth);
- newTransporter("http://bad.localhost:1/");
- GetTask task = new GetTask(URI.create("repo/file.txt"));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(3, httpServer.getLogEntries().size());
- httpServer.getLogEntries().clear();
- newTransporter("http://bad.localhost:1/");
- task = new GetTask(URI.create("repo/file.txt"));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- assertEquals(1, httpServer.getLogEntries().size());
- assertNotNull(httpServer.getLogEntries().get(0).getHeaders().get("Authorization"));
- assertNotNull(httpServer.getLogEntries().get(0).getHeaders().get("Proxy-Authorization"));
- }
-
- @Test
- void testAuthSchemePreemptive() throws Exception {
- httpServer.setAuthentication("testuser", "testpass");
- session.setCache(new DefaultRepositoryCache());
- auth = new AuthenticationBuilder()
- .addUsername("testuser")
- .addPassword("testpass")
- .build();
-
- session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, false);
- newTransporter(httpServer.getHttpUrl());
- GetTask task = new GetTask(URI.create("repo/file.txt"));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- // there ARE challenge round-trips
- assertEquals(2, httpServer.getLogEntries().size());
-
- httpServer.getLogEntries().clear();
-
- session.setConfigProperty(ConfigurationProperties.HTTP_PREEMPTIVE_AUTH, true);
- newTransporter(httpServer.getHttpUrl());
- task = new GetTask(URI.create("repo/file.txt"));
- transporter.get(task);
- assertEquals("test", task.getDataString());
- // there are NO challenge round-trips, all goes through at first
- assertEquals(1, httpServer.getLogEntries().size());
- }
-
@Test
void testConnectionReuse() throws Exception {
httpServer.addSslConnector();
@@ -1222,24 +122,4 @@ void testConnectionNoReuse() throws Exception {
.getTotalStats();
assertEquals(0, stats.getAvailable(), stats.toString());
}
-
- @Test
- void testInit_BadProtocol() {
- assertThrows(NoTransporterException.class, () -> newTransporter("bad:/void"));
- }
-
- @Test
- void testInit_BadUrl() {
- assertThrows(NoTransporterException.class, () -> newTransporter("http://localhost:NaN"));
- }
-
- @Test
- void testInit_CaseInsensitiveProtocol() throws Exception {
- newTransporter("http://localhost");
- newTransporter("HTTP://localhost");
- newTransporter("Http://localhost");
- newTransporter("https://localhost");
- newTransporter("HTTPS://localhost");
- newTransporter("HttpS://localhost");
- }
}
diff --git a/maven-resolver-transport-apache/src/test/resources/logback.xml b/maven-resolver-transport-apache/src/test/resources/logback.xml
deleted file mode 100644
index 9addbd505..000000000
--- a/maven-resolver-transport-apache/src/test/resources/logback.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
- %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
-
-
-
-
-
-
-
-
-
-
diff --git a/maven-resolver-transport-apache/src/test/resources/simplelogger.properties b/maven-resolver-transport-apache/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..c52c663ce
--- /dev/null
+++ b/maven-resolver-transport-apache/src/test/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+org.slf4j.simpleLogger.defaultLogLevel=debug
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
index cd3b54cbe..a07653c1a 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
@@ -68,11 +68,21 @@
junit-jupiter-api
test
+
+ org.slf4j
+ slf4j-simple
+ test
+
org.apache.maven.resolver
maven-resolver-test-util
test
+
+ org.apache.maven.resolver
+ maven-resolver-test-http
+ test
+
org.apache.maven.resolver
maven-resolver-impl
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
index 6197148aa..62c5080e7 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
@@ -18,29 +18,21 @@
*/
package org.eclipse.aether.transport.jdk;
-import javax.net.ssl.SSLContext;
+import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.net.Authenticator;
-import java.net.ConnectException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.PasswordAuthentication;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
+import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
-import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
@@ -64,6 +56,8 @@
import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.TransportTask;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
import org.eclipse.aether.transfer.NoTransporterException;
import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.util.FileUtils;
@@ -79,7 +73,7 @@
* @since 2.0.0
*/
@SuppressWarnings({"checkstyle:magicnumber"})
-final class JdkTransporter extends AbstractTransporter {
+final class JdkTransporter extends AbstractTransporter implements HttpTransporter {
private static final Logger LOGGER = LoggerFactory.getLogger(JdkTransporter.class);
private static final DateTimeFormatter RFC7231 = DateTimeFormatter.ofPattern(
@@ -202,7 +196,8 @@ private ConnectException enhance(ConnectException connectException) {
@Override
public int classify(Throwable error) {
- if (error instanceof JdkException && ((JdkException) error).getStatusCode() == NOT_FOUND) {
+ if (error instanceof HttpTransporterException
+ && ((HttpTransporterException) error).getStatusCode() == NOT_FOUND) {
return ERROR_NOT_FOUND;
}
return ERROR_OTHER;
@@ -218,7 +213,7 @@ protected void implPeek(PeekTask task) throws Exception {
try {
HttpResponse response = client.send(request.build(), HttpResponse.BodyHandlers.discarding());
if (response.statusCode() >= MULTIPLE_CHOICES) {
- throw new JdkException(response.statusCode());
+ throw new HttpTransporterException(response.statusCode());
}
} catch (ConnectException e) {
throw enhance(e);
@@ -254,7 +249,7 @@ protected void implGet(GetTask task) throws Exception {
resume = false;
continue;
}
- throw new JdkException(response.statusCode());
+ throw new HttpTransporterException(response.statusCode());
}
} catch (ConnectException e) {
throw enhance(e);
@@ -343,7 +338,7 @@ protected void implPut(PutTask task) throws Exception {
try {
HttpResponse response = client.send(request.build(), HttpResponse.BodyHandlers.discarding());
if (response.statusCode() >= MULTIPLE_CHOICES) {
- throw new JdkException(response.statusCode());
+ throw new HttpTransporterException(response.statusCode());
}
} catch (ConnectException e) {
throw enhance(e);
@@ -425,6 +420,18 @@ private HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepo
throws NoTransporterException {
final String instanceKey = HTTP_INSTANCE_KEY_PREFIX + repository.getId();
+ final String httpsSecurityMode = ConfigUtils.getString(
+ session,
+ ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT,
+ ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(),
+ ConfigurationProperties.HTTPS_SECURITY_MODE);
+
+ if (!ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(httpsSecurityMode)
+ && !ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode)) {
+ throw new IllegalArgumentException("Unsupported '" + httpsSecurityMode + "' HTTPS security mode.");
+ }
+ final boolean insecure = ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode);
+
// todo: normally a single client per JVM is sufficient - in particular cause part of the config
// is global and not per instance so we should create a client only when conf changes for a repo
// else fallback on a global client
@@ -448,7 +455,40 @@ private HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepo
}
if (sslContext == null) {
- sslContext = SSLContext.getDefault();
+ if (insecure) {
+ sslContext = SSLContext.getInstance("TLS");
+ X509ExtendedTrustManager tm = new X509ExtendedTrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {}
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {}
+
+ @Override
+ public void checkClientTrusted(
+ X509Certificate[] chain, String authType, Socket socket) {}
+
+ @Override
+ public void checkServerTrusted(
+ X509Certificate[] chain, String authType, Socket socket) {}
+
+ @Override
+ public void checkClientTrusted(
+ X509Certificate[] chain, String authType, SSLEngine engine) {}
+
+ @Override
+ public void checkServerTrusted(
+ X509Certificate[] chain, String authType, SSLEngine engine) {}
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ };
+ sslContext.init(null, new X509TrustManager[] {tm}, null);
+ } else {
+ sslContext = SSLContext.getDefault();
+ }
}
int connectTimeout = ConfigUtils.getInteger(
@@ -467,6 +507,12 @@ private HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepo
.connectTimeout(Duration.ofMillis(connectTimeout))
.sslContext(sslContext);
+ if (insecure) {
+ SSLParameters sslParameters = new SSLParameters();
+ sslParameters.setEndpointIdentificationAlgorithm(null);
+ builder.sslParameters(sslParameters);
+ }
+
setLocalAddress(builder, () -> getHttpLocalAddress(session, repository));
if (repository.getProxy() != null) {
@@ -512,7 +558,7 @@ protected PasswordAuthentication getPasswordAuthentication() {
}
return result;
- } catch (NoSuchAlgorithmException e) {
+ } catch (Exception e) {
throw new WrapperEx(e);
}
});
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
index 2188db26a..988fd65bb 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
@@ -22,8 +22,8 @@
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
-import org.eclipse.aether.spi.connector.transport.Transporter;
-import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterFactory;
import org.eclipse.aether.transfer.NoTransporterException;
import static java.util.Objects.requireNonNull;
@@ -34,7 +34,7 @@
* @since 2.0.0
*/
@Named(JdkTransporterFactory.NAME)
-public final class JdkTransporterFactory implements TransporterFactory {
+public final class JdkTransporterFactory implements HttpTransporterFactory {
public static final String NAME = "jdk";
private float priority = 10.0f;
@@ -50,7 +50,7 @@ public JdkTransporterFactory setPriority(float priority) {
}
@Override
- public Transporter newInstance(RepositorySystemSession session, RemoteRepository repository)
+ public HttpTransporter newInstance(RepositorySystemSession session, RemoteRepository repository)
throws NoTransporterException {
requireNonNull(session, "session cannot be null");
requireNonNull(repository, "repository cannot be null");
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
index 266660702..44cf3f38d 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
@@ -22,19 +22,53 @@
import java.net.URI;
import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.internal.test.util.http.HttpTransporterTest;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.Transporter;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
- * JDK Transport UT.
+ * JDK Transporter UT.
*/
@SuppressWarnings({"checkstyle:magicnumber"})
-final class JdkTransporterTest {
+class JdkTransporterTest extends HttpTransporterTest {
+
+ @Disabled
+ protected void testAuthSchemeReuse() throws Exception {}
+
+ @Disabled
+ protected void testPut_ProxyUnauthenticated() throws Exception {}
+
+ @Disabled
+ protected void testAuthSchemePreemptive() throws Exception {}
+
+ @Disabled
+ protected void testPut_AuthCache_Preemptive() throws Exception {}
+
+ @Disabled
+ protected void testPut_Unauthenticated() throws Exception {}
+
+ @Disabled
+ protected void testPut_PreemptiveIsDefault() throws Exception {}
+
+ @Disabled
+ protected void testRetryHandler_defaultCount_positive() throws Exception {}
+
+ @Disabled
+ protected void testRetryHandler_explicitCount_positive() throws Exception {}
+
+ @Disabled
+ protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader() throws Exception {}
+
+ public JdkTransporterTest() {
+ super(JdkTransporterFactory::new);
+ }
+
@Test
void enhanceConnectExceptionMessages() {
String uri = "https://localhost:12345/";
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/resources/simplelogger.properties b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..c52c663ce
--- /dev/null
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+org.slf4j.simpleLogger.defaultLogLevel=debug
diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
index 94da7b7f3..98de637c8 100644
--- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
+++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java
@@ -22,8 +22,8 @@
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
-import org.eclipse.aether.spi.connector.transport.Transporter;
-import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterFactory;
import org.eclipse.aether.transfer.NoTransporterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,7 +36,7 @@
* @since 2.0.0
*/
@Named(JdkTransporterFactory.NAME)
-public final class JdkTransporterFactory implements TransporterFactory {
+public final class JdkTransporterFactory implements HttpTransporterFactory {
public static final String NAME = "jdk";
private static final Logger LOGGER = LoggerFactory.getLogger(JdkTransporterFactory.class);
@@ -54,7 +54,7 @@ public JdkTransporterFactory setPriority(float priority) {
}
@Override
- public Transporter newInstance(RepositorySystemSession session, RemoteRepository repository)
+ public HttpTransporter newInstance(RepositorySystemSession session, RemoteRepository repository)
throws NoTransporterException {
requireNonNull(session, "session cannot be null");
requireNonNull(repository, "repository cannot be null");
diff --git a/maven-resolver-transport-jetty/pom.xml b/maven-resolver-transport-jetty/pom.xml
index a93a34152..cbd0c5a0f 100644
--- a/maven-resolver-transport-jetty/pom.xml
+++ b/maven-resolver-transport-jetty/pom.xml
@@ -75,6 +75,32 @@
org.eclipse.jetty.http2
http2-http-client-transport
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-test-util
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-test-http
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-impl
+ test
+
diff --git a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
index 1e87b986b..398feda6b 100644
--- a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
+++ b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
@@ -18,7 +18,7 @@
*/
package org.eclipse.aether.transport.jetty;
-import javax.net.ssl.SSLContext;
+import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
@@ -27,11 +27,16 @@
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
+import java.security.cert.X509Certificate;
+import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -44,7 +49,10 @@
import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.TransportTask;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
import org.eclipse.aether.transfer.NoTransporterException;
+import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.util.FileUtils;
import org.eclipse.jetty.client.HttpClient;
@@ -56,7 +64,6 @@
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
-import org.eclipse.jetty.client.util.PathRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
@@ -70,7 +77,7 @@
*
* @since 2.0.0
*/
-final class JettyTransporter extends AbstractTransporter {
+final class JettyTransporter extends AbstractTransporter implements HttpTransporter {
private static final int MULTIPLE_CHOICES = 300;
private static final int NOT_FOUND = 404;
@@ -85,6 +92,8 @@ final class JettyTransporter extends AbstractTransporter {
private static final String CONTENT_RANGE = "Content-Range";
+ private static final String LAST_MODIFIED = "Last-Modified";
+
private static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
private static final String RANGE = "Range";
@@ -102,6 +111,14 @@ final class JettyTransporter extends AbstractTransporter {
private final Map headers;
+ private final boolean preemptiveAuth;
+
+ private final boolean preemptivePutAuth;
+
+ private final BasicAuthentication.BasicResult basicServerAuthenticationResult;
+
+ private final BasicAuthentication.BasicResult basicProxyAuthenticationResult;
+
JettyTransporter(RepositorySystemSession session, RemoteRepository repository) throws NoTransporterException {
try {
URI uri = new URI(repository.getUrl()).parseServerAuthority();
@@ -149,8 +166,24 @@ final class JettyTransporter extends AbstractTransporter {
ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
ConfigurationProperties.REQUEST_TIMEOUT + "." + repository.getId(),
ConfigurationProperties.REQUEST_TIMEOUT);
+ this.preemptiveAuth = ConfigUtils.getBoolean(
+ session,
+ ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_AUTH,
+ ConfigurationProperties.HTTP_PREEMPTIVE_AUTH + "." + repository.getId(),
+ ConfigurationProperties.HTTP_PREEMPTIVE_AUTH);
+ this.preemptivePutAuth = ConfigUtils.getBoolean(
+ session,
+ ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_PUT_AUTH,
+ ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH + "." + repository.getId(),
+ ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH);
this.client = getOrCreateClient(session, repository);
+
+ final String instanceKey = JETTY_INSTANCE_KEY_PREFIX + repository.getId();
+ this.basicServerAuthenticationResult =
+ (BasicAuthentication.BasicResult) session.getData().get(instanceKey + ".serverAuth");
+ this.basicProxyAuthenticationResult =
+ (BasicAuthentication.BasicResult) session.getData().get(instanceKey + ".proxyAuth");
}
private URI resolve(TransportTask task) {
@@ -159,7 +192,8 @@ private URI resolve(TransportTask task) {
@Override
public int classify(Throwable error) {
- if (error instanceof JettyException && ((JettyException) error).getStatusCode() == NOT_FOUND) {
+ if (error instanceof HttpTransporterException
+ && ((HttpTransporterException) error).getStatusCode() == NOT_FOUND) {
return ERROR_NOT_FOUND;
}
return ERROR_OTHER;
@@ -171,9 +205,17 @@ protected void implPeek(PeekTask task) throws Exception {
.timeout(requestTimeout, TimeUnit.MILLISECONDS)
.method("HEAD");
request.headers(m -> headers.forEach(m::add));
+ if (preemptiveAuth) {
+ if (basicServerAuthenticationResult != null) {
+ basicServerAuthenticationResult.apply(request);
+ }
+ if (basicProxyAuthenticationResult != null) {
+ basicProxyAuthenticationResult.apply(request);
+ }
+ }
Response response = request.send();
if (response.getStatus() >= MULTIPLE_CHOICES) {
- throw new JettyException(response.getStatus());
+ throw new HttpTransporterException(response.getStatus());
}
}
@@ -188,6 +230,14 @@ protected void implGet(GetTask task) throws Exception {
.timeout(requestTimeout, TimeUnit.MILLISECONDS)
.method("GET");
request.headers(m -> headers.forEach(m::add));
+ if (preemptiveAuth) {
+ if (basicServerAuthenticationResult != null) {
+ basicServerAuthenticationResult.apply(request);
+ }
+ if (basicProxyAuthenticationResult != null) {
+ basicProxyAuthenticationResult.apply(request);
+ }
+ }
if (resume) {
long resumeOffset = task.getResumeOffset();
@@ -216,7 +266,7 @@ protected void implGet(GetTask task) throws Exception {
resume = false;
continue;
}
- throw new JettyException(response.getStatus());
+ throw new HttpTransporterException(response.getStatus());
}
break;
}
@@ -260,6 +310,17 @@ protected void implGet(GetTask task) throws Exception {
task.setDataFile(dataFile);
}
}
+ if (task.getDataFile() != null && response.getHeaders().getDateField(LAST_MODIFIED) != -1) {
+ long lastModified =
+ response.getHeaders().getDateField(LAST_MODIFIED); // note: Wagon also does first not last
+ if (lastModified != -1) {
+ try {
+ Files.setLastModifiedTime(task.getDataFile().toPath(), FileTime.fromMillis(lastModified));
+ } catch (DateTimeParseException e) {
+ // fall through
+ }
+ }
+ }
Map checksums = extractXChecksums(response);
if (checksums != null) {
checksums.forEach(task::setChecksum);
@@ -317,24 +378,62 @@ private static Map extractNexus2Checksums(Response response) {
protected void implPut(PutTask task) throws Exception {
Request request = client.newRequest(resolve(task)).method("PUT").timeout(requestTimeout, TimeUnit.MILLISECONDS);
request.headers(m -> headers.forEach(m::add));
- try (FileUtils.TempFile tempFile = FileUtils.newTempFile()) {
- utilPut(task, Files.newOutputStream(tempFile.getPath()), true);
- request.body(new PathRequestContent(tempFile.getPath()));
-
- Response response;
- try {
- response = request.send();
- } catch (ExecutionException e) {
- Throwable t = e.getCause();
- if (t instanceof Exception) {
- throw (Exception) t;
+ if (preemptiveAuth || preemptivePutAuth) {
+ if (basicServerAuthenticationResult != null) {
+ basicServerAuthenticationResult.apply(request);
+ }
+ if (basicProxyAuthenticationResult != null) {
+ basicProxyAuthenticationResult.apply(request);
+ }
+ }
+ request.body(new PutTaskRequestContent(task));
+ AtomicBoolean started = new AtomicBoolean(false);
+ Response response;
+ try {
+ response = request.onRequestCommit(r -> {
+ if (task.getDataLength() == 0) {
+ if (started.compareAndSet(false, true)) {
+ try {
+ task.getListener().transportStarted(0, task.getDataLength());
+ } catch (TransferCancelledException e) {
+ r.abort(e);
+ }
+ }
+ }
+ })
+ .onRequestContent((r, b) -> {
+ if (started.compareAndSet(false, true)) {
+ try {
+ task.getListener().transportStarted(0, task.getDataLength());
+ } catch (TransferCancelledException e) {
+ r.abort(e);
+ return;
+ }
+ }
+ try {
+ task.getListener().transportProgressed(b);
+ } catch (TransferCancelledException e) {
+ r.abort(e);
+ }
+ })
+ .send();
+ } catch (ExecutionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IOException) {
+ IOException ioex = (IOException) t;
+ if (ioex.getCause() instanceof TransferCancelledException) {
+ throw (TransferCancelledException) ioex.getCause();
} else {
- throw new RuntimeException(t);
+ throw ioex;
}
+ } else if (t instanceof Exception) {
+ throw (Exception) t;
+ } else {
+ throw new RuntimeException(t);
}
- if (response.getStatus() >= MULTIPLE_CHOICES) {
- throw new JettyException(response.getStatus());
- }
+ }
+ if (response.getStatus() >= MULTIPLE_CHOICES) {
+ throw new HttpTransporterException(response.getStatus());
}
}
@@ -351,13 +450,27 @@ protected void implClose() {
static final Logger LOGGER = LoggerFactory.getLogger(JettyTransporter.class);
@SuppressWarnings("checkstyle:methodlength")
- private static HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepository repository)
+ private HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepository repository)
throws NoTransporterException {
final String instanceKey = JETTY_INSTANCE_KEY_PREFIX + repository.getId();
+ final String httpsSecurityMode = ConfigUtils.getString(
+ session,
+ ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT,
+ ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(),
+ ConfigurationProperties.HTTPS_SECURITY_MODE);
+
+ if (!ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(httpsSecurityMode)
+ && !ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode)) {
+ throw new IllegalArgumentException("Unsupported '" + httpsSecurityMode + "' HTTPS security mode.");
+ }
+ final boolean insecure = ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode);
+
try {
- return (HttpClient) session.getData().computeIfAbsent(instanceKey, () -> {
+ AtomicReference serverAuth = new AtomicReference<>(null);
+ AtomicReference proxyAuth = new AtomicReference<>(null);
+ HttpClient client = (HttpClient) session.getData().computeIfAbsent(instanceKey, () -> {
SSLContext sslContext = null;
BasicAuthentication basicAuthentication = null;
try {
@@ -369,13 +482,35 @@ private static HttpClient getOrCreateClient(RepositorySystemSession session, Rem
String username = repoAuthContext.get(AuthenticationContext.USERNAME);
String password = repoAuthContext.get(AuthenticationContext.PASSWORD);
- basicAuthentication = new BasicAuthentication(
- URI.create(repository.getUrl()), Authentication.ANY_REALM, username, password);
+ URI uri = URI.create(repository.getUrl());
+ basicAuthentication =
+ new BasicAuthentication(uri, Authentication.ANY_REALM, username, password);
+ if (preemptiveAuth || preemptivePutAuth) {
+ serverAuth.set(new BasicAuthentication.BasicResult(
+ uri, HttpHeader.AUTHORIZATION, username, password));
+ }
}
}
if (sslContext == null) {
- sslContext = SSLContext.getDefault();
+ if (insecure) {
+ sslContext = SSLContext.getInstance("TLS");
+ X509TrustManager tm = new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {}
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {}
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ };
+ sslContext.init(null, new X509TrustManager[] {tm}, null);
+ } else {
+ sslContext = SSLContext.getDefault();
+ }
}
int connectTimeout = ConfigUtils.getInteger(
@@ -386,6 +521,10 @@ private static HttpClient getOrCreateClient(RepositorySystemSession session, Rem
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.setSslContext(sslContext);
+ if (insecure) {
+ sslContextFactory.setEndpointIdentificationAlgorithm(null);
+ sslContextFactory.setHostnameVerifier((name, context) -> true);
+ }
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(sslContextFactory);
@@ -432,6 +571,10 @@ private static HttpClient getOrCreateClient(RepositorySystemSession session, Rem
proxy.getURI(), Authentication.ANY_REALM, username, password);
httpClient.getAuthenticationStore().addAuthentication(proxyAuthentication);
+ if (preemptiveAuth || preemptivePutAuth) {
+ proxyAuth.set(new BasicAuthentication.BasicResult(
+ proxy.getURI(), HttpHeader.PROXY_AUTHORIZATION, username, password));
+ }
}
}
}
@@ -451,6 +594,13 @@ private static HttpClient getOrCreateClient(RepositorySystemSession session, Rem
throw new WrapperEx(e);
}
});
+ if (serverAuth.get() != null) {
+ session.getData().set(instanceKey + ".serverAuth", serverAuth.get());
+ }
+ if (proxyAuth.get() != null) {
+ session.getData().set(instanceKey + ".proxyAuth", proxyAuth.get());
+ }
+ return client;
} catch (WrapperEx e) {
throw new NoTransporterException(repository, e.getCause());
}
diff --git a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporterFactory.java b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporterFactory.java
index 79dc586fa..216d33099 100644
--- a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporterFactory.java
+++ b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporterFactory.java
@@ -22,8 +22,8 @@
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
-import org.eclipse.aether.spi.connector.transport.Transporter;
-import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
+import org.eclipse.aether.spi.connector.transport.http.HttpTransporterFactory;
import org.eclipse.aether.transfer.NoTransporterException;
import static java.util.Objects.requireNonNull;
@@ -34,7 +34,7 @@
* @since 2.0.0
*/
@Named(JettyTransporterFactory.NAME)
-public final class JettyTransporterFactory implements TransporterFactory {
+public final class JettyTransporterFactory implements HttpTransporterFactory {
public static final String NAME = "jetty";
private float priority = 15.0f;
@@ -50,7 +50,7 @@ public JettyTransporterFactory setPriority(float priority) {
}
@Override
- public Transporter newInstance(RepositorySystemSession session, RemoteRepository repository)
+ public HttpTransporter newInstance(RepositorySystemSession session, RemoteRepository repository)
throws NoTransporterException {
requireNonNull(session, "session cannot be null");
requireNonNull(repository, "repository cannot be null");
diff --git a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java
new file mode 100644
index 000000000..889187ff8
--- /dev/null
+++ b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.transport.jetty;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+
+import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.jetty.client.util.AbstractRequestContent;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+
+class PutTaskRequestContent extends AbstractRequestContent {
+ private final PutTask putTask;
+ private final int bufferSize;
+ private ByteBufferPool bufferPool;
+ private boolean useDirectByteBuffers = true;
+
+ @SuppressWarnings("checkstyle:MagicNumber")
+ PutTaskRequestContent(PutTask putTask) {
+ this(putTask, 4096);
+ }
+
+ PutTaskRequestContent(PutTask putTask, int bufferSize) {
+ super("application/octet-stream");
+ this.putTask = putTask;
+ this.bufferSize = bufferSize;
+ }
+
+ @Override
+ public long getLength() {
+ return putTask.getDataLength();
+ }
+
+ @Override
+ public boolean isReproducible() {
+ return true;
+ }
+
+ public ByteBufferPool getByteBufferPool() {
+ return bufferPool;
+ }
+
+ public void setByteBufferPool(ByteBufferPool byteBufferPool) {
+ this.bufferPool = byteBufferPool;
+ }
+
+ public boolean isUseDirectByteBuffers() {
+ return useDirectByteBuffers;
+ }
+
+ public void setUseDirectByteBuffers(boolean useDirectByteBuffers) {
+ this.useDirectByteBuffers = useDirectByteBuffers;
+ }
+
+ @Override
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent) {
+ return new SubscriptionImpl(consumer, emitInitialContent);
+ }
+
+ private class SubscriptionImpl extends AbstractSubscription {
+ private ReadableByteChannel channel;
+ private long readTotal;
+
+ private SubscriptionImpl(Consumer consumer, boolean emitInitialContent) {
+ super(consumer, emitInitialContent);
+ }
+
+ @Override
+ protected boolean produceContent(Producer producer) throws IOException {
+ ByteBuffer buffer;
+ boolean last;
+ if (channel == null) {
+ if (putTask.getDataFile() != null) {
+ channel = Files.newByteChannel(putTask.getDataFile().toPath(), StandardOpenOption.READ);
+ } else {
+ channel = Channels.newChannel(putTask.newInputStream());
+ }
+ }
+
+ buffer = bufferPool == null
+ ? BufferUtil.allocate(bufferSize, isUseDirectByteBuffers())
+ : bufferPool.acquire(bufferSize, isUseDirectByteBuffers());
+
+ BufferUtil.clearToFill(buffer);
+ int read = channel.read(buffer);
+ BufferUtil.flipToFlush(buffer, 0);
+ if (!channel.isOpen() && read < 0) {
+ throw new EOFException("EOF reached for " + putTask);
+ }
+ if (read > 0) {
+ readTotal += read;
+ }
+ last = readTotal == getLength();
+ if (last) {
+ IO.close(channel);
+ }
+ return producer.produce(buffer, last, Callback.from(() -> release(buffer)));
+ }
+
+ private void release(ByteBuffer buffer) {
+ if (bufferPool != null) {
+ bufferPool.release(buffer);
+ }
+ }
+
+ @Override
+ public void fail(Throwable failure) {
+ super.fail(failure);
+ IO.close(channel);
+ }
+ }
+}
diff --git a/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java b/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java
new file mode 100644
index 000000000..1cac4fe3e
--- /dev/null
+++ b/maven-resolver-transport-jetty/src/test/java/org/eclipse/aether/transport/jetty/JettyTransporterTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.transport.jetty;
+
+import org.eclipse.aether.internal.test.util.http.HttpTransporterTest;
+import org.junit.jupiter.api.Disabled;
+
+/**
+ * Jetty transporter UT.
+ */
+class JettyTransporterTest extends HttpTransporterTest {
+
+ @Disabled
+ protected void testAuthSchemeReuse() throws Exception {}
+
+ @Disabled
+ protected void testPut_ProxyUnauthenticated() throws Exception {}
+
+ @Disabled
+ protected void testPut_Unauthenticated() throws Exception {}
+
+ @Disabled
+ protected void testRetryHandler_defaultCount_positive() throws Exception {}
+
+ @Disabled
+ protected void testRetryHandler_explicitCount_positive() throws Exception {}
+
+ @Disabled
+ protected void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader() throws Exception {}
+
+ public JettyTransporterTest() {
+ super(JettyTransporterFactory::new);
+ }
+}
diff --git a/maven-resolver-transport-jetty/src/test/resources/simplelogger.properties b/maven-resolver-transport-jetty/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..c52c663ce
--- /dev/null
+++ b/maven-resolver-transport-jetty/src/test/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+org.slf4j.simpleLogger.defaultLogLevel=debug
diff --git a/maven-resolver-transport-wagon/src/test/resources/logback-test.xml b/maven-resolver-transport-wagon/src/test/resources/logback-test.xml
deleted file mode 100644
index d031998b7..000000000
--- a/maven-resolver-transport-wagon/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
- %d{HH:mm:ss.SSS} [%-18thread] %c{1} [%p] %m%n
-
-
-
-
-
-
-
-
-
-
-