This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch bugfix/fix-preemptive-proxy-auth in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
commit 7afb9feff2e01393859dd721a98a5d24c2af4d4e Author: Konrad Windszus <[email protected]> AuthorDate: Mon Jan 19 09:22:11 2026 +0100 Fix preemptive proxy authentication in JDK Client --- maven-resolver-test-http/pom.xml | 16 ++ .../aether/internal/test/util/http/HttpServer.java | 243 +++++++++++++++++---- .../test/util/http/HttpTransporterTest.java | 68 +++++- .../src/main/resources/compressible-file.xml | 42 ++++ .../transport/apache/ApacheTransporterTest.java | 9 +- .../maven-resolver-transport-jdk/pom.xml | 4 + .../maven-resolver-transport-jdk11/pom.xml | 4 + .../aether/transport/jdk/JdkTransporter.java | 13 +- .../aether/transport/jdk/JdkTransporterTest.java | 6 + maven-resolver-transport-jdk-parent/pom.xml | 5 + maven-resolver-transport-jetty/pom.xml | 14 +- .../transport/jetty/JettyTransporterTest.java | 7 + 12 files changed, 367 insertions(+), 64 deletions(-) diff --git a/maven-resolver-test-http/pom.xml b/maven-resolver-test-http/pom.xml index 9617490fa..647a15216 100644 --- a/maven-resolver-test-http/pom.xml +++ b/maven-resolver-test-http/pom.xml @@ -81,6 +81,18 @@ <groupId>org.eclipse.jetty.compression</groupId> <artifactId>jetty-compression-server</artifactId> </dependency> + <dependency> + <groupId>org.eclipse.jetty.compression</groupId> + <artifactId>jetty-compression-zstandard</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty.compression</groupId> + <artifactId>jetty-compression-brotli</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty.compression</groupId> + <artifactId>jetty-compression-gzip</artifactId> + </dependency> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> @@ -98,6 +110,10 @@ <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> 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 0ca20510c..ecffdd118 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 @@ -19,19 +19,19 @@ package org.eclipse.aether.internal.test.util.http; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; +import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,11 +43,19 @@ import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory; import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper; import org.eclipse.aether.spi.connector.transport.http.RFC9457.RFC9457Payload; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.compression.server.CompressionConfig; +import org.eclipse.jetty.compression.server.CompressionHandler; import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.pathmap.MatchedResource; +import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; @@ -58,8 +66,8 @@ import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,12 +80,16 @@ public class HttpServer { private final String path; - private final Map<String, String> headers; + private final Map<String, String> requestHeaders; - public LogEntry(String method, String path, Map<String, String> headers) { + private Map<String, String> responseHeaders; + + CountDownLatch responseHeadersAvailableSignal = new CountDownLatch(1); + + public LogEntry(String method, String path, Map<String, String> requestHeaders) { this.method = method; this.path = path; - this.headers = headers; + this.requestHeaders = requestHeaders; } public String getMethod() { @@ -88,8 +100,29 @@ public class HttpServer { return path; } - public Map<String, String> getHeaders() { - return headers; + public Map<String, String> getRequestHeaders() { + return requestHeaders; + } + + /** + * This method blocks until the response headers are available. + * @return the response headers + */ + public Map<String, String> getResponseHeaders() { + try { + if (!responseHeadersAvailableSignal.await(30, java.util.concurrent.TimeUnit.SECONDS)) { + throw new IllegalStateException("Timeout waiting for response headers to be available"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Interrupted while waiting for response headers to be available", e); + } + return responseHeaders; + } + + public void setResponseHeaders(Map<String, String> responseHeaders) { + this.responseHeaders = responseHeaders; + responseHeadersAvailableSignal.countDown(); } @Override @@ -290,15 +323,15 @@ public class HttpServer { server = new Server(); httpConnector = new ServerConnector(server); server.addConnector(httpConnector); - server.setHandler(new Handler.Sequence( + + server.setHandler(new LogHandler(new CompressionEnforcingHandler(new Handler.Sequence( new ConnectionClosingHandler(), new ServerErrorHandler(), - new LogHandler(), new ProxyAuthHandler(), new AuthHandler(), new RedirectHandler(), new RepoHandler(), - new RFC9457Handler())); + new RFC9457Handler())))); server.start(); return this; @@ -313,6 +346,111 @@ public class HttpServer { } } + private class CompressionEnforcingHandler extends CompressionHandler { + // duplicate of CompressionHandler.pathConfigs which is private + private final PathMappings<CompressionConfig> pathConfigs = new PathMappings<>(); + + CompressionEnforcingHandler(Handler handler) { + super(handler); + this.putConfiguration( + "/br/*", + CompressionConfig.builder().compressIncludeEncoding("br").build()); + this.putConfiguration( + "/zstd/*", + CompressionConfig.builder().compressIncludeEncoding("zstd").build()); + this.putConfiguration( + "/gzip/*", + CompressionConfig.builder().compressIncludeEncoding("gzip").build()); + this.putConfiguration( + "/deflate/*", + CompressionConfig.builder() + .compressIncludeEncoding("deflate") + .build()); + } + + @Override + public CompressionConfig putConfiguration(PathSpec pathSpec, CompressionConfig config) { + // deliberately not set it in the super class yet + return pathConfigs.put(pathSpec, config); + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + Handler next = getHandler(); + if (next == null) { + return false; + } + String pathInContext = Request.getPathInContext(request); + MatchedResource<CompressionConfig> matchedConfig = this.pathConfigs.getMatched(pathInContext); + if (matchedConfig == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("skipping compression: path {} has no matching compression config", pathInContext); + } + // No configuration, skip + return next.handle(request, response, callback); + } + + // set the matched config in the super class for further processing, but for all paths + // no need to reset it later as this handler is not used among multiple requests + super.putConfiguration(PathSpec.from("/*"), matchedConfig.getResource()); + // first path segment determines the encoding, remove it from the request path for further processing + return super.handle(new StripLeadingPathSegmentsRequestWrapper(request, 1), response, callback); + } + } + + private static class StripLeadingPathSegmentsRequestWrapper extends Request.Wrapper { + private final HttpURI modifiedURI; + + StripLeadingPathSegmentsRequestWrapper(Request wrapped, int segmentsToStrip) { + super(wrapped); + this.modifiedURI = stripPathSegments(wrapped.getHttpURI(), segmentsToStrip); + } + + private static HttpURI stripPathSegments(HttpURI originalURI, int segmentsToStrip) { + if (segmentsToStrip <= 0) { + return originalURI; + } + + String originalPath = originalURI.getPath(); + if (originalPath == null || originalPath.isEmpty()) { + return originalURI; + } + + // Split path into segments + String[] segments = originalPath.split("/"); + StringBuilder newPath = new StringBuilder(); + + // Skip empty first segment (from leading /) and the specified number of segments + int skipCount = 0; + for (int i = 0; i < segments.length; i++) { + if (segments[i].isEmpty() && i == 0) { + // Skip leading empty segment from leading / + continue; + } + if (skipCount < segmentsToStrip) { + skipCount++; + continue; + } + newPath.append("/").append(segments[i]); + } + + // If we stripped everything, return root path + if (newPath.isEmpty()) { + newPath.append("/"); + } + + // Build new URI with modified path + return org.eclipse.jetty.http.HttpURI.build(originalURI) + .path(newPath.toString()) + .asImmutable(); + } + + @Override + public HttpURI getHttpURI() { + return modifiedURI; + } + } + private class ConnectionClosingHandler extends Handler.Abstract { @Override @@ -330,29 +468,48 @@ public class HttpServer { if (serverErrorsBeforeWorks.getAndDecrement() > 0) { response.setStatus(serverErrorStatusCode); writeResponseBodyMessage(request, response, "Oops, come back later!"); - callback.succeeded(); return true; } return false; } } - private class LogHandler extends Handler.Abstract { + private class LogHandler extends Handler.Wrapper { + + LogHandler(Handler handler) { + super(handler); + } + @Override - public boolean handle(Request req, Response response, Callback callback) { + public boolean handle(Request req, Response response, Callback callback) throws Exception { + LOGGER.info( "{} {}{}", req.getMethod(), req.getHttpURI().getDecodedPath(), req.getHttpURI().getQuery() != null ? "?" + req.getHttpURI().getQuery() : ""); - Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (HttpField header : req.getHeaders()) { - headers.put(header.getName(), header.getValueList().stream().collect(Collectors.joining(", "))); + Map<String, String> requestHeaders = + toUnmodifiableMap(req.getHeaders()); // capture request headers before other handlers modify them + LogEntry logEntry = new LogEntry(req.getMethod(), req.getHttpURI().getPathQuery(), requestHeaders); + logEntries.add(logEntry); + // prevent closing the response before logging (assume all writes are synchronous for simplicity) + boolean result = super.handle(req, response, callback); + // capture response headers after other handlers modified them + // at this point in time the connection may have been already closed (i.e. last chunk already sent) + logEntry.setResponseHeaders(toUnmodifiableMap(response.getHeaders())); + if (result) { + callback.succeeded(); } - logEntries.add(new LogEntry( - req.getMethod(), req.getHttpURI().getPathQuery(), Collections.unmodifiableMap(headers))); - return false; + return result; + } + + Map<String, String> toUnmodifiableMap(HttpFields headers) { + Map<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (HttpField header : headers) { + map.put(header.getName(), header.getValueList().stream().collect(Collectors.joining(", "))); + } + return Collections.unmodifiableMap(map); } } @@ -370,7 +527,6 @@ public class HttpServer { if (ExpectContinue.FAIL.equals(expectContinue) && req.getHeaders().get(HttpHeader.EXPECT) != null) { response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); writeResponseBodyMessage(req, response, "Expectation was set to fail"); - callback.succeeded(); return true; } @@ -379,14 +535,12 @@ public class HttpServer { if (!file.isFile() || path.endsWith("/")) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); writeResponseBodyMessage(req, response, "Not found"); - callback.succeeded(); return true; } long ifUnmodifiedSince = req.getHeaders().getDateField(HttpHeader.IF_UNMODIFIED_SINCE); if (ifUnmodifiedSince != -1L && file.lastModified() > ifUnmodifiedSince) { response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); writeResponseBodyMessage(req, response, "Precondition failed"); - callback.succeeded(); return true; } long offset = 0L; @@ -398,14 +552,12 @@ public class HttpServer { if (offset >= file.length()) { response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); writeResponseBodyMessage(req, response, "Range not satisfiable"); - callback.succeeded(); return true; } } String encoding = req.getHeaders().get(HttpHeader.ACCEPT_ENCODING); if ((encoding != null && !"identity".equals(encoding)) || ifUnmodifiedSince == -1L) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - callback.succeeded(); return true; } } @@ -428,27 +580,27 @@ public class HttpServer { } } if (HttpMethod.HEAD.is(req.getMethod())) { - callback.succeeded(); return true; } - try (FileInputStream is = new FileInputStream(file); - OutputStream os = Response.asBufferedOutputStream(req, response)) { - if (offset > 0L) { - long skipped = is.skip(offset); - while (skipped < offset && is.read() >= 0) { - skipped++; - } - } - IO.copy(is, os); + Content.Source contentSource = + Content.Source.from(new ByteBufferPool.Sized(null), file.toPath(), offset, -1); + try (Blocker.Callback fileReadCallback = Blocker.callback()) { + Content.copy(contentSource, response, fileReadCallback); + fileReadCallback.block(); } } else if (HttpMethod.PUT.is(req.getMethod())) { if (!webDav) { file.getParentFile().mkdirs(); } if (file.getParentFile().exists()) { - try (InputStream is = Content.Source.asInputStream(req); - FileOutputStream os = new FileOutputStream(file)) { - IO.copy(is, os); + try (SeekableByteChannel channel = Files.newByteChannel( + file.toPath(), + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING); + Blocker.Callback fileWriteCallback = Blocker.callback()) { + Content.copy(req, Content.Sink.from(channel), fileWriteCallback); + fileWriteCallback.block(); } catch (IOException e) { file.delete(); throw e; @@ -474,14 +626,15 @@ public class HttpServer { } else { response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } - callback.succeeded(); return true; } } private void writeResponseBodyMessage(Request request, Response response, String message) throws IOException { - try (OutputStream outputStream = Response.asBufferedOutputStream(request, response)) { - outputStream.write(message.getBytes(StandardCharsets.UTF_8)); + // write synchronously to avoid closing the response too early + try (Blocker.Callback callback = Blocker.callback()) { + Content.Sink.write(response, false, message, callback); + callback.block(); } } @@ -510,7 +663,6 @@ public class HttpServer { } writeResponseBodyMessage(req, response, buildRFC9457Message(rfc9457Payload)); } - callback.succeeded(); return true; } } @@ -542,7 +694,6 @@ public class HttpServer { location.append("/repo").append(path.substring(9)); Response.sendRedirect( req, response, callback, HttpServletResponse.SC_MOVED_PERMANENTLY, location.toString(), false); - callback.succeeded(); return true; } } @@ -562,7 +713,6 @@ public class HttpServer { } response.getHeaders().add(HttpHeader.WWW_AUTHENTICATE, "Basic realm=\"Test-Realm\""); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - callback.succeeded(); return true; } return false; @@ -579,7 +729,6 @@ public class HttpServer { } response.getHeaders().add(HttpHeader.PROXY_AUTHENTICATE, "basic realm=\"Test-Realm\""); response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED); - callback.succeeded(); return true; } else { return false; 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 index 6f8e65294..316657d43 100644 --- 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 @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Stream; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.DefaultRepositoryCache; @@ -66,6 +67,8 @@ 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 org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -74,12 +77,13 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * Common set of tests against Http transporter. */ @SuppressWarnings({"checkstyle:MethodName"}) -public class HttpTransporterTest { +public abstract class HttpTransporterTest { protected static final Path KEY_STORE_PATH = Paths.get("target/keystore"); @@ -192,6 +196,9 @@ public class HttpTransporterTest { 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"); + try (InputStream is = getCompressibleFileStream()) { + Files.copy(is, repoDir.toPath().resolve("compressible-file.xml")); + } File resumable = new File(repoDir, "resume.txt"); TestFileUtils.writeString(resumable, "resumable"); resumable.setLastModified(System.currentTimeMillis() - 90 * 1000); @@ -199,6 +206,10 @@ public class HttpTransporterTest { newTransporter(httpServer.getHttpUrl()); } + private static InputStream getCompressibleFileStream() { + return HttpTransporterTest.class.getClassLoader().getResourceAsStream("compressible-file.xml"); + } + @AfterEach protected void tearDown() throws Exception { if (transporter != null) { @@ -431,15 +442,48 @@ public class HttpTransporterTest { assertEquals(OLD_FILE_TIMESTAMP, file.lastModified()); } - @Test - protected void testGet_CompressionUsedWithPom() throws Exception { - File file = TestFileUtils.createTempFile("pom"); - GetTask task = new GetTask(URI.create("repo/artifact.pom")).setDataPath(file.toPath()); + /** + * Provides compression algorithms supported by the transporter implementation. + * This should be the string value passed in the {@code Accept-Encoding} header. + * + * @return stream of supported compression algorithm names + * @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding#directives">Accept-Encoding directives</a> + */ + protected abstract Stream<String> supportedCompressionAlgorithms(); + + @ParameterizedTest + // DEFLATE isn't supported by Jetty server (https://github.com/jetty/jetty.project/issues/280) + @ValueSource(strings = {"br", "gzip", "zstd"}) + protected void testGet_WithCompression(String encoding) throws Exception { + assumeTrue( + supportedCompressionAlgorithms().anyMatch(supported -> supported.equals(encoding)), + () -> "Transporter does not support compression algorithm: " + encoding); + RecordingTransportListener listener = new RecordingTransportListener(); + // requires a file with at least 48/50 bytes (otherwise compression is disabled, + // https://github.com/jetty/jetty.project/blob/2264d3d9f9586f3e5e9040fba779ed72e931cb46/jetty-core/jetty-compression/jetty-compression-brotli/src/main/java/org/eclipse/jetty/compression/brotli/BrotliCompression.java#L61) + GetTask task = new GetTask(URI.create(encoding + "/repo/compressible-file.xml")).setListener(listener); transporter.get(task); - String acceptEncoding = httpServer.getLogEntries().get(0).getHeaders().get("Accept-Encoding"); + String acceptEncoding = + httpServer.getLogEntries().get(0).getRequestHeaders().get("Accept-Encoding"); assertNotNull(acceptEncoding, "Missing Accept-Encoding header when retrieving pom"); - // support either gzip or deflate as the transporter implementation may vary - assertTrue(acceptEncoding.contains("gzip") || acceptEncoding.contains("deflate")); + assertTrue(acceptEncoding.contains(encoding)); + // check original response header sent by server (client transparently handles compression and removes it) + // see https://issues.apache.org/jira/browse/HTTPCORE-792 + // and https://github.com/mizosoft/methanol/issues/182 + for (HttpServer.LogEntry log : httpServer.getLogEntries()) { + assertEquals(encoding, log.getResponseHeaders().get("Content-Encoding")); + } + String expectedResourceData; + try (InputStream is = getCompressibleFileStream()) { + expectedResourceData = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } + assertEquals(expectedResourceData, task.getDataString()); + assertEquals(0L, listener.getDataOffset()); + // data length is unknown as chunked transfer encoding is used with compression + assertEquals(-1, listener.getDataLength()); + assertEquals(1, listener.getStartedCount()); + assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); + assertEquals(task.getDataString(), listener.getBaos().toString(StandardCharsets.UTF_8)); } @Test @@ -1235,7 +1279,7 @@ public class HttpTransporterTest { 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")); + assertEquals("SomeTest/1.0", log.getRequestHeaders().get("User-Agent")); } } @@ -1251,7 +1295,7 @@ public class HttpTransporterTest { assertEquals(1, httpServer.getLogEntries().size()); for (HttpServer.LogEntry log : httpServer.getLogEntries()) { for (Map.Entry<String, String> entry : headers.entrySet()) { - assertEquals(entry.getValue(), log.getHeaders().get(entry.getKey()), entry.getKey()); + assertEquals(entry.getValue(), log.getRequestHeaders().get(entry.getKey()), entry.getKey()); } } } @@ -1321,8 +1365,8 @@ public class HttpTransporterTest { 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")); + assertNotNull(httpServer.getLogEntries().get(0).getRequestHeaders().get("Authorization")); + assertNotNull(httpServer.getLogEntries().get(0).getRequestHeaders().get("Proxy-Authorization")); } @Test diff --git a/maven-resolver-test-http/src/main/resources/compressible-file.xml b/maven-resolver-test-http/src/main/resources/compressible-file.xml new file mode 100644 index 000000000..706a316f5 --- /dev/null +++ b/maven-resolver-test-http/src/main/resources/compressible-file.xml @@ -0,0 +1,42 @@ +<!-- + * 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. + --> +<metadata modelVersion="1.1.0"> +<groupId>org.apache.maven.plugins</groupId> +<artifactId>maven-jar-plugin</artifactId> +<version>3.0.3-SNAPSHOT</version> +<versioning> +<snapshot> +<timestamp>20171206.232134</timestamp> +<buildNumber>2496</buildNumber> +</snapshot> +<lastUpdated>20171206232134</lastUpdated> +<snapshotVersions> +<snapshotVersion> +<extension>jar</extension> +<value>3.0.3-20171206.232134-2496</value> +<updated>20171206232134</updated> +</snapshotVersion> +<snapshotVersion> +<extension>pom</extension> +<value>3.0.3-20171206.232134-2496</value> +<updated>20171206232134</updated> +</snapshotVersion> +</snapshotVersions> +</versioning> +</metadata> \ No newline at end of file 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 027d605cd..d08168773 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 @@ -21,6 +21,7 @@ package org.eclipse.aether.transport.apache; import java.io.File; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; import org.apache.http.pool.ConnPoolControl; import org.apache.http.pool.PoolStats; @@ -47,6 +48,11 @@ class ApacheTransporterTest extends HttpTransporterTest { super(() -> new ApacheTransporterFactory(standardChecksumExtractor(), new PathProcessorSupport())); } + @Override + protected Stream<String> supportedCompressionAlgorithms() { + return Stream.of("gzip", "deflate"); + } + @Override @Disabled @Test @@ -86,7 +92,8 @@ class ApacheTransporterTest extends HttpTransporterTest { assertTrue(listener.getProgressedCount() > 0, "Count: " + listener.getProgressedCount()); assertEquals("upload", TestFileUtils.readString(new File(repoDir, "dir1/dir2/file.txt"))); - assertEquals(5, httpServer.getLogEntries().size()); + assertEquals( + 5, httpServer.getLogEntries().size(), "Expected 5 requests but got: " + httpServer.getLogEntries()); assertEquals("OPTIONS", httpServer.getLogEntries().get(0).getMethod()); assertEquals("MKCOL", httpServer.getLogEntries().get(1).getMethod()); assertEquals("/repo/dir1/dir2/", httpServer.getLogEntries().get(1).getPath()); diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml index ca0d4061a..e5cad9189 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml @@ -58,6 +58,10 @@ <groupId>com.github.mizosoft.methanol</groupId> <artifactId>methanol</artifactId> </dependency> + <dependency> + <groupId>com.github.mizosoft.methanol</groupId> + <artifactId>methanol-brotli</artifactId> + </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml index 40ee490da..0e86cddfe 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/pom.xml @@ -57,6 +57,10 @@ <groupId>com.github.mizosoft.methanol</groupId> <artifactId>methanol</artifactId> </dependency> + <dependency> + <groupId>com.github.mizosoft.methanol</groupId> + <artifactId>methanol-brotli</artifactId> + </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java index f5b2b29a4..0cef3ccdf 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java @@ -162,6 +162,8 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte private PasswordAuthentication serverAuthentication; + private PasswordAuthentication proxyAuthentication; + JdkTransporter( RepositorySystemSession session, RemoteRepository repository, @@ -459,6 +461,12 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte requestBuilder.setHeader( "Authorization", getBasicAuthValue(serverAuthentication.getUserName(), serverAuthentication.getPassword())); + // also send proxy authentication pre-emptively if configured + if (proxyAuthentication != null) { + requestBuilder.setHeader( + "Proxy-Authorization", + getBasicAuthValue(proxyAuthentication.getUserName(), proxyAuthentication.getPassword())); + } } } @@ -575,9 +583,8 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte String username = proxyAuthContext.get(AuthenticationContext.USERNAME); String password = proxyAuthContext.get(AuthenticationContext.PASSWORD); - authentications.put( - Authenticator.RequestorType.PROXY, - new PasswordAuthentication(username, password.toCharArray())); + proxyAuthentication = new PasswordAuthentication(username, password.toCharArray()); + authentications.put(Authenticator.RequestorType.PROXY, proxyAuthentication); } } } diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java index c480e8f55..cf37e4c2d 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java @@ -20,6 +20,7 @@ package org.eclipse.aether.transport.jdk; import java.net.ConnectException; import java.net.URI; +import java.util.stream.Stream; import org.eclipse.aether.internal.impl.DefaultPathProcessor; import org.eclipse.aether.internal.test.util.TestUtils; @@ -42,6 +43,11 @@ import static org.junit.jupiter.api.Assertions.fail; */ class JdkTransporterTest extends HttpTransporterTest { + @Override + protected Stream<String> supportedCompressionAlgorithms() { + return Stream.of("gzip", "deflate", "br"); + } + @Override @Disabled @Test diff --git a/maven-resolver-transport-jdk-parent/pom.xml b/maven-resolver-transport-jdk-parent/pom.xml index 2122794b5..36e5f24a3 100644 --- a/maven-resolver-transport-jdk-parent/pom.xml +++ b/maven-resolver-transport-jdk-parent/pom.xml @@ -45,6 +45,11 @@ <artifactId>methanol</artifactId> <version>1.9.0</version> </dependency> + <dependency> + <groupId>com.github.mizosoft.methanol</groupId> + <artifactId>methanol-brotli</artifactId> + <version>1.9.0</version> + </dependency> </dependencies> </dependencyManagement> </project> diff --git a/maven-resolver-transport-jetty/pom.xml b/maven-resolver-transport-jetty/pom.xml index c398e2901..2413c0c2b 100644 --- a/maven-resolver-transport-jetty/pom.xml +++ b/maven-resolver-transport-jetty/pom.xml @@ -72,7 +72,19 @@ <groupId>org.eclipse.jetty.http2</groupId> <artifactId>jetty-http2-client-transport</artifactId> </dependency> - + <!-- supported compressions --> + <dependency> + <groupId>org.eclipse.jetty.compression</groupId> + <artifactId>jetty-compression-gzip</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty.compression</groupId> + <artifactId>jetty-compression-brotli</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty.compression</groupId> + <artifactId>jetty-compression-zstandard</artifactId> + </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> 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 index 94bfaa92e..4917ad734 100644 --- 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 @@ -18,6 +18,8 @@ */ package org.eclipse.aether.transport.jetty; +import java.util.stream.Stream; + import org.eclipse.aether.internal.test.util.http.HttpTransporterTest; import org.eclipse.aether.spi.io.PathProcessorSupport; import org.junit.jupiter.api.Disabled; @@ -28,6 +30,11 @@ import org.junit.jupiter.api.Test; */ class JettyTransporterTest extends HttpTransporterTest { + @Override + protected Stream<String> supportedCompressionAlgorithms() { + return Stream.of("gzip", "deflate", "br", "zstd"); + } + @Override @Disabled @Test
