You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/09/04 21:31:28 UTC
(maven-resolver) branch agent/resolver-19x-bounded-error-body created (now 17eb5c9e1)
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a change to branch agent/resolver-19x-bounded-error-body
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
at 17eb5c9e1 Bound the error response body read by RFC9457Reporter on 1.9.x
This branch includes the following new commits:
new 17eb5c9e1 Bound the error response body read by RFC9457Reporter on 1.9.x
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven-resolver) 01/01: Bound the error response body read by RFC9457Reporter on 1.9.x
Posted by sl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a commit to branch agent/resolver-19x-bounded-error-body
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
commit 17eb5c9e19f4f797333fbc2091878ca713ac4c63
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Fri Sep 4 23:31:14 2026 +0200
Bound the error response body read by RFC9457Reporter on 1.9.x
Read at most 64 KiB of an error response body and fall back to the plain
HTTP status when the problem details cannot be parsed, instead of
buffering the whole entity and letting a malformed body surface as a
JSON parse error.
---
.../transport/http/RFC9457/RFC9457Reporter.java | 66 ++++++++++++++++++++--
.../eclipse/aether/transport/http/HttpServer.java | 22 ++++++++
.../aether/transport/http/HttpTransporterTest.java | 22 ++++++++
.../http/RFC9457/RFC9457ReporterTest.java | 48 ++++++++++++++++
4 files changed, 154 insertions(+), 4 deletions(-)
diff --git a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/RFC9457/RFC9457Reporter.java b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/RFC9457/RFC9457Reporter.java
index 562c48367..d88d090d0 100644
--- a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/RFC9457/RFC9457Reporter.java
+++ b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/RFC9457/RFC9457Reporter.java
@@ -18,17 +18,26 @@
*/
package org.eclipse.aether.transport.http.RFC9457;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.http.Header;
+import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.util.EntityUtils;
public class RFC9457Reporter {
public static final RFC9457Reporter INSTANCE = new RFC9457Reporter();
+ /**
+ * Maximum number of bytes read from an error response body. Problem details payloads are small, while the body
+ * of an error response is arbitrary remote data that the client may have transparently decompressed, so it is
+ * never buffered without a bound. Bodies larger than this limit are truncated, not rejected.
+ */
+ public static final int MAX_BODY_BYTES = 64 * 1024;
+
public boolean isRFC9457Message(CloseableHttpResponse response) {
Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
if (headers.length > 0) {
@@ -38,6 +47,11 @@ public class RFC9457Reporter {
return false;
}
+ /**
+ * Throws an {@link HttpRFC9457Exception} carrying the problem details of the given response. Returns normally
+ * when the body cannot be parsed as problem details (for example because it was truncated at
+ * {@link #MAX_BODY_BYTES}), in which case the caller is expected to report the plain HTTP status instead.
+ */
public void generateException(CloseableHttpResponse response) throws HttpRFC9457Exception {
int statusCode = getStatusCode(response);
String reasonPhrase = getReasonPhrase(response);
@@ -51,14 +65,58 @@ public class RFC9457Reporter {
}
if (body != null && !body.isEmpty()) {
- RFC9457Payload rfc9457Payload = RFC9457Parser.parse(body);
- throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload);
+ RFC9457Payload rfc9457Payload;
+ try {
+ rfc9457Payload = RFC9457Parser.parse(body);
+ } catch (RuntimeException ignore) {
+ // Malformed (possibly truncated, see MAX_BODY_BYTES) problem details must not change the error
+ // classification: leave it to the caller to report the plain HTTP status.
+ return;
+ }
+ if (rfc9457Payload != null) {
+ throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload);
+ }
+ return;
}
throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE);
}
private String getBody(final CloseableHttpResponse response) throws IOException {
- return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+ HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ return "";
+ }
+ InputStream is = entity.getContent();
+ if (is == null) {
+ return "";
+ }
+ try (InputStream stream = is) {
+ return readBody(stream);
+ }
+ }
+
+ /**
+ * Reads the given stream into a UTF-8 string, consuming at most {@link #MAX_BODY_BYTES} bytes. Any remaining
+ * bytes are left unread, so an oversized body is truncated instead of being buffered whole. The caller remains
+ * responsible for closing the stream.
+ *
+ * @param is The stream to read the body from, must not be {@code null}.
+ * @return The body as UTF-8 string, truncated to {@link #MAX_BODY_BYTES} bytes, never {@code null}.
+ * @throws IOException If reading the stream fails.
+ */
+ static String readBody(InputStream is) throws IOException {
+ ByteArrayOutputStream body = new ByteArrayOutputStream();
+ byte[] chunk = new byte[8 * 1024];
+ int remaining = MAX_BODY_BYTES;
+ while (remaining > 0) {
+ int read = is.read(chunk, 0, Math.min(chunk.length, remaining));
+ if (read < 0) {
+ break;
+ }
+ body.write(chunk, 0, read);
+ remaining -= read;
+ }
+ return body.toString(StandardCharsets.UTF_8.name());
}
private int getStatusCode(final CloseableHttpResponse response) {
diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java
index 2dd93d5c8..0c099bffc 100644
--- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java
+++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java
@@ -29,6 +29,7 @@ import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
@@ -40,6 +41,7 @@ import java.util.regex.Pattern;
import com.google.gson.Gson;
import org.eclipse.aether.transport.http.RFC9457.RFC9457Payload;
+import org.eclipse.aether.transport.http.RFC9457.RFC9457Reporter;
import org.eclipse.aether.util.ChecksumUtils;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -502,6 +504,26 @@ public class HttpServer {
if (HttpMethod.GET.is(req.getMethod())) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader(HttpHeader.CONTENT_TYPE.asString(), "application/problem+json");
+ if (path.endsWith("malformed.txt")) {
+ try (OutputStream outputStream = response.getOutputStream()) {
+ outputStream.write("{ this is not json".getBytes(StandardCharsets.UTF_8));
+ }
+ return;
+ }
+ if (path.endsWith("oversized.txt")) {
+ char[] detail = new char[RFC9457Reporter.MAX_BODY_BYTES + 4096];
+ Arrays.fill(detail, 'a');
+ RFC9457Payload oversized = new RFC9457Payload(
+ URI.create("https://example.com/probs/oversized"),
+ HttpServletResponse.SC_FORBIDDEN,
+ "Oversized",
+ new String(detail),
+ null);
+ try (OutputStream outputStream = response.getOutputStream()) {
+ outputStream.write(buildRFC9457Message(oversized).getBytes(StandardCharsets.UTF_8));
+ }
+ return;
+ }
RFC9457Payload rfc9457Payload;
if (path.endsWith("missing_fields.txt")) {
rfc9457Payload = new RFC9457Payload(null, null, null, null, null);
diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java
index 1f2ac03d0..762f41ab7 100644
--- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java
+++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java
@@ -1266,6 +1266,28 @@ public class HttpTransporterTest {
}
}
+ @Test
+ public void testGetRFC9457ResponseMalformed() throws Exception {
+ try {
+ transporter.get(new GetTask(URI.create("rfc9457/malformed.txt")));
+ fail("Expected error");
+ } catch (HttpResponseException e) {
+ assertEquals(HttpResponseException.class, e.getClass());
+ assertEquals(403, e.getStatusCode());
+ }
+ }
+
+ @Test
+ public void testGetRFC9457ResponseOversized() throws Exception {
+ try {
+ transporter.get(new GetTask(URI.create("rfc9457/oversized.txt")));
+ fail("Expected error");
+ } catch (HttpResponseException e) {
+ assertEquals(HttpResponseException.class, e.getClass());
+ assertEquals(403, e.getStatusCode());
+ }
+ }
+
@Test
public void testGetRFC9457ResponseWithMissingFields() throws Exception {
try {
diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/RFC9457/RFC9457ReporterTest.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/RFC9457/RFC9457ReporterTest.java
new file mode 100644
index 000000000..fd4ae552a
--- /dev/null
+++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/RFC9457/RFC9457ReporterTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.http.RFC9457;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class RFC9457ReporterTest {
+
+ @Test
+ public void testReadBodyTruncatesOversizedStream() throws IOException {
+ byte[] oversized = new byte[RFC9457Reporter.MAX_BODY_BYTES + 4096];
+ Arrays.fill(oversized, (byte) 'a');
+ ByteArrayInputStream is = new ByteArrayInputStream(oversized);
+ String body = RFC9457Reporter.readBody(is);
+ assertEquals(RFC9457Reporter.MAX_BODY_BYTES, body.length());
+ assertEquals(4096, is.available());
+ }
+
+ @Test
+ public void testReadBodyReadsSmallStreamCompletely() throws IOException {
+ String payload = "{\"title\":\"gone\"}";
+ String body = RFC9457Reporter.readBody(new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)));
+ assertEquals(payload, body);
+ }
+}