Skip to content

Commit bb07762

Browse files
authored
fix(api): hide jetty internals in oversized request 413 response (tronprotocol#6843)
1 parent 31af29f commit bb07762

2 files changed

Lines changed: 125 additions & 3 deletions

File tree

‎framework/src/main/java/org/tron/common/application/HttpService.java‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@
1616
package org.tron.common.application;
1717

1818
import com.google.common.annotations.VisibleForTesting;
19+
import java.io.IOException;
20+
import java.nio.ByteBuffer;
1921
import java.util.concurrent.CompletableFuture;
22+
import javax.servlet.RequestDispatcher;
23+
import javax.servlet.ServletException;
24+
import javax.servlet.http.HttpServletRequest;
25+
import javax.servlet.http.HttpServletResponse;
2026
import lombok.extern.slf4j.Slf4j;
27+
import org.eclipse.jetty.http.HttpStatus;
2128
import org.eclipse.jetty.server.ConnectionLimit;
29+
import org.eclipse.jetty.server.Request;
2230
import org.eclipse.jetty.server.Server;
31+
import org.eclipse.jetty.server.handler.ErrorHandler;
2332
import org.eclipse.jetty.server.handler.SizeLimitHandler;
2433
import org.eclipse.jetty.servlet.ServletContextHandler;
34+
import org.eclipse.jetty.util.BufferUtil;
2535
import org.tron.core.config.args.Args;
2636

2737
@Slf4j(topic = "rpc")
@@ -72,6 +82,7 @@ protected void initServer() {
7282
if (maxHttpConnectNumber > 0) {
7383
this.apiServer.addBean(new ConnectionLimit(maxHttpConnectNumber, this.apiServer));
7484
}
85+
this.apiServer.setErrorHandler(new OversizedRequestErrorHandler());
7586
}
7687

7788
protected ServletContextHandler initContextHandler() {
@@ -88,4 +99,30 @@ protected ServletContextHandler initContextHandler() {
8899
protected void addFilter(ServletContextHandler context) {
89100

90101
}
102+
103+
/**
104+
* For oversized requests (the 413 thrown by SizeLimitHandler during dispatch) logs the
105+
* detail server-side and returns the short, uniform bad-message page, instead of the
106+
* default error page that leaks the exception stack and internal request sizes. All
107+
* other errors keep Jetty's default handling.
108+
*/
109+
private static final class OversizedRequestErrorHandler extends ErrorHandler {
110+
111+
@Override
112+
public void handle(String target, Request baseRequest, HttpServletRequest request,
113+
HttpServletResponse response) throws IOException, ServletException {
114+
if (response.getStatus() == HttpStatus.PAYLOAD_TOO_LARGE_413) {
115+
Throwable cause = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
116+
logger.info("Reject oversized request, uri: {}, detail: {}",
117+
request.getRequestURI(), cause == null ? "413" : cause.getMessage());
118+
baseRequest.setHandled(true);
119+
ByteBuffer body = badMessageError(HttpStatus.PAYLOAD_TOO_LARGE_413,
120+
HttpStatus.getMessage(HttpStatus.PAYLOAD_TOO_LARGE_413),
121+
baseRequest.getResponse().getHttpFields());
122+
response.getOutputStream().write(BufferUtil.toArray(body));
123+
return;
124+
}
125+
super.handle(target, baseRequest, request, response);
126+
}
127+
}
91128
}

‎framework/src/test/java/org/tron/common/jetty/SizeLimitHandlerTest.java‎

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.tron.common.jetty;
22

33
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
45
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.net.Socket;
58
import java.net.URI;
9+
import java.nio.charset.StandardCharsets;
610
import java.util.concurrent.TimeUnit;
711
import java.util.stream.Collectors;
812
import javax.servlet.http.HttpServlet;
@@ -33,7 +37,7 @@
3337

3438
/**
3539
* Tests {@link org.eclipse.jetty.server.handler.SizeLimitHandler} body-size
36-
* enforcement configured in {@link HttpService#initContextHandler()}.
40+
* enforcement configured in {@link HttpService}.
3741
*
3842
* Covers: accept/reject by size, UTF-8 byte counting, independent limits
3943
* across HttpService instances, chunked transfer, and zero-limit behavior.
@@ -152,10 +156,69 @@ public void testHttpBodyWithinLimit() throws Exception {
152156
Assert.assertEquals(200, post(httpServerUri, new StringEntity("small body")));
153157
}
154158

159+
/**
160+
* An oversized request must return 413 carrying the short, uniform bad-message
161+
* page produced by the custom ErrorHandler in {@link HttpService} -
162+
* not the default Jetty error page that leaks the exception stack, class name
163+
* and the internal request size.
164+
*/
155165
@Test
156166
public void testHttpBodyExceedsLimit() throws Exception {
157-
Assert.assertEquals(413,
158-
post(httpServerUri, new StringEntity(repeat('a', HTTP_MAX_BODY_SIZE + 1))));
167+
HttpPost req = new HttpPost(httpServerUri);
168+
req.setEntity(new StringEntity(repeat('a', HTTP_MAX_BODY_SIZE + 1)));
169+
HttpResponse resp = client.execute(req);
170+
171+
String body = EntityUtils.toString(resp.getEntity());
172+
173+
// return value: 413
174+
Assert.assertEquals(413, resp.getStatusLine().getStatusCode());
175+
176+
// returned page: short uniform bad-message page with the generic reason
177+
Assert.assertTrue("should render the short bad-message page",
178+
body.contains("Bad Message 413"));
179+
Assert.assertTrue("reason should be the generic status message",
180+
body.contains("Payload Too Large"));
181+
182+
// must NOT leak Jetty internals
183+
Assert.assertFalse("must not leak exception class / stack frames",
184+
body.contains("org.eclipse.jetty"));
185+
}
186+
187+
/**
188+
* A malformed Content-Length is rejected by the HTTP parser (onBadMessage ->
189+
* ErrorHandler.badMessageError()), a different path from the 413 dispatch handler.
190+
* Confirms the custom ErrorHandler leaves other 4xx untouched: still the default
191+
* 400 bad-message page, not rerouted through the 413 branch.
192+
*/
193+
@Test
194+
public void testBadContentLengthReturnsDefault400() throws Exception {
195+
String raw = "POST / HTTP/1.1\r\n"
196+
+ "Host: localhost\r\n"
197+
+ "Content-Length: +450\r\n"
198+
+ "\r\n";
199+
String resp = sendRaw(httpServerUri, raw);
200+
201+
Assert.assertTrue("expected 400, got: " + firstLine(resp), resp.startsWith("HTTP/1.1 400"));
202+
Assert.assertTrue("should be the default bad-message page", resp.contains("Bad Message 400"));
203+
Assert.assertFalse("must not be rerouted through the 413 branch",
204+
resp.contains("Payload Too Large"));
205+
}
206+
207+
/**
208+
* A request-line URI longer than the request header buffer is rejected by the HTTP
209+
* parser (414), again via badMessageError(), not the 413 dispatch handler. Confirms it
210+
* is unaffected by the custom ErrorHandler.
211+
*/
212+
@Test
213+
public void testOversizedUriReturnsDefault414() throws Exception {
214+
String raw = "GET /" + repeat('a', 9000) + " HTTP/1.1\r\n" // request line > default 8KB
215+
+ "Host: localhost\r\n"
216+
+ "\r\n";
217+
String resp = sendRaw(httpServerUri, raw);
218+
219+
Assert.assertTrue("expected 414, got: " + firstLine(resp), resp.startsWith("HTTP/1.1 414"));
220+
Assert.assertFalse("must not be rerouted through the 413 branch",
221+
resp.contains("Payload Too Large"));
159222
}
160223

161224
@Test
@@ -324,4 +387,26 @@ private int post(URI uri, HttpEntity entity) throws Exception {
324387
private static String repeat(char c, int n) {
325388
return new String(new char[n]).replace('\0', c);
326389
}
390+
391+
/** Sends a raw HTTP request over a socket and returns the full response (until EOF). */
392+
private static String sendRaw(URI uri, String rawRequest) throws Exception {
393+
try (Socket socket = new Socket(uri.getHost(), uri.getPort())) {
394+
socket.getOutputStream().write(rawRequest.getBytes(StandardCharsets.US_ASCII));
395+
socket.getOutputStream().flush();
396+
ByteArrayOutputStream out = new ByteArrayOutputStream();
397+
InputStream in = socket.getInputStream();
398+
byte[] buf = new byte[4096];
399+
int n;
400+
while ((n = in.read(buf)) != -1) {
401+
out.write(buf, 0, n);
402+
}
403+
return new String(out.toByteArray(), StandardCharsets.UTF_8);
404+
}
405+
}
406+
407+
/** First line (status line) of a raw HTTP response. */
408+
private static String firstLine(String resp) {
409+
int idx = resp.indexOf("\r\n");
410+
return idx < 0 ? resp : resp.substring(0, idx);
411+
}
327412
}

0 commit comments

Comments
 (0)