Skip to content

Commit 8834049

Browse files
authored
Merge pull request #418 from apache/maintenance/FELIX-6776-Introduce-way-to-restrict-request-and-response-size
FELIX-6776 introduce way to restrict request and response size
2 parents 783f409 + ae80d28 commit 8834049

File tree

7 files changed

+251
-67
lines changed

7 files changed

+251
-67
lines changed

http/README.md

+56-54
Large diffs are not rendered by default.

http/base/pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
<version>1.0.0</version>
145145
<scope>provided</scope>
146146
</dependency>
147-
<!-- Testing -->
147+
<!-- Testing -->
148148
<dependency>
149149
<groupId>junit</groupId>
150150
<artifactId>junit</artifactId>
@@ -154,7 +154,7 @@
154154
<dependency>
155155
<groupId>org.mockito</groupId>
156156
<artifactId>mockito-core</artifactId>
157-
<version>5.7.0</version>
157+
<version>5.17.0</version>
158158
<scope>test</scope>
159159
</dependency>
160160
<dependency>

http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,23 @@ public ObjectClassDefinition getObjectClassDefinition( String id, String locale
203203
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_BUFFER_SIZE)));
204204

205205
adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_MAX_FORM_SIZE,
206-
"Maximum Form Size",
206+
"Maximum Form Size in bytes",
207207
"Size of Body for submitted form content. Default is 200KB.",
208208
204800,
209209
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_MAX_FORM_SIZE)));
210210

211+
adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_REQUEST_SIZE_LIMIT,
212+
"Maximum request size in bytes",
213+
"Maximum size of the request body in bytes. Default is unlimited.",
214+
204800,
215+
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_REQUEST_SIZE_LIMIT)));
216+
217+
adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT,
218+
"Maximum response size in bytes",
219+
"Maximum size of the response body in bytes. Default is unlimited.",
220+
204800,
221+
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT)));
222+
211223
adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP_PATH_EXCLUSIONS,
212224
"Path Exclusions",
213225
"Contains a list of context path prefixes. If a Web Application Bundle is started with a context path matching any " +

http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,15 @@ public final class JettyConfig
106106
/** Felix specific property to configure the request buffer size. Default is 24KB */
107107
public static final String FELIX_JETTY_RESPONSE_BUFFER_SIZE = "org.apache.felix.http.jetty.responseBufferSize";
108108

109-
/** Felix specific property to configure the max form size. Default is 200KB */
109+
/** Felix specific property to configure the max form size. Default is 200KB. */
110110
public static final String FELIX_JETTY_MAX_FORM_SIZE = "org.apache.felix.http.jetty.maxFormSize";
111111

112+
/** Felix specific property to configure the request size limit. Default is unlimited. See https://jetty.org/docs/jetty/12/programming-guide/server/http.html#handler-use-size-limit */
113+
public static final String FELIX_JETTY_REQUEST_SIZE_LIMIT = "org.apache.felix.http.jetty.requestSizeLimit";
114+
115+
/** Felix specific property to configure the response size limit. Default is unlimited. See https://jetty.org/docs/jetty/12/programming-guide/server/http.html#handler-use-size-limit */
116+
public static final String FELIX_JETTY_RESPONSE_SIZE_LIMIT = "org.apache.felix.http.jetty.responseSizeLimit";
117+
112118
/** Felix specific property to enable Jetty MBeans. Valid values are "true", "false". Default is false */
113119
public static final String FELIX_HTTP_MBEANS = "org.apache.felix.http.mbeans";
114120

@@ -493,6 +499,16 @@ public int getMaxFormSize()
493499
return getIntProperty(FELIX_JETTY_MAX_FORM_SIZE, 200 * 1024);
494500
}
495501

502+
public int getRequestSizeLimit()
503+
{
504+
return getIntProperty(FELIX_JETTY_REQUEST_SIZE_LIMIT, -1);
505+
}
506+
507+
public int getResponseSizeLimit()
508+
{
509+
return getIntProperty(FELIX_JETTY_RESPONSE_SIZE_LIMIT, -1);
510+
}
511+
496512
/**
497513
* Returns the configured session timeout in minutes or zero if not
498514
* configured.

http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java

+9
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.eclipse.jetty.server.ServerConnector;
5757
import org.eclipse.jetty.server.SslConnectionFactory;
5858
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
59+
import org.eclipse.jetty.server.handler.SizeLimitHandler;
5960
import org.eclipse.jetty.server.handler.StatisticsHandler;
6061
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
6162
import org.eclipse.jetty.session.HouseKeeper;
@@ -324,6 +325,14 @@ private void initializeJetty() throws Exception
324325
context.addServlet(holder, "/*");
325326
context.setMaxFormContentSize(this.config.getMaxFormSize());
326327

328+
int requestSizeLimit = this.config.getRequestSizeLimit();
329+
int responseSizeLimit = this.config.getResponseSizeLimit();
330+
if (requestSizeLimit > -1 || responseSizeLimit > -1) {
331+
// Use SizeLimitHandler to limit the size of the request body and response
332+
// -1 is unlimited
333+
context.setHandler(new SizeLimitHandler(requestSizeLimit, responseSizeLimit));
334+
}
335+
327336
if (this.config.isRegisterMBeans())
328337
{
329338
this.mbeanServerTracker = new MBeanServerTracker(this.context, this.server);

http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyMaxFormSizeIT.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,18 @@ public void testFormSizeLimit() throws Exception {
114114
formFieldsLimitExceeded.add(new Fields.Field("key", "valueoverlimit")); // over limit of 10 bytes
115115
ContentResponse responseExceeded = httpClient.FORM(uri, formFieldsLimitExceeded);
116116

117-
// HTTP 500 thrown, because req.getParameter("key") throws an IOEx
117+
// HTTP 500 thrown, because req.getParameter("key") throws an IOException
118118
assertEquals(500, responseExceeded.getStatus());
119119

120120
httpClient.close();
121121
}
122122

123-
static final class HelloWorldServlet extends HttpServlet {
124-
@Override
125-
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
126-
req.getParameter("key"); // this triggers the maxFormSize check
127-
resp.setStatus(200);
128-
resp.getWriter().write("OK");
129-
}
123+
static final class HelloWorldServlet extends HttpServlet {
124+
@Override
125+
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
126+
req.getParameter("key"); // this triggers the maxFormSize check
127+
resp.setStatus(200);
128+
resp.getWriter().write("OK");
129+
}
130130
}
131-
}
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.felix.http.jetty.it;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotNull;
21+
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
22+
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
23+
24+
import java.io.IOException;
25+
import java.net.URI;
26+
import java.util.Hashtable;
27+
import java.util.Map;
28+
29+
import javax.inject.Inject;
30+
import jakarta.servlet.Servlet;
31+
import jakarta.servlet.ServletException;
32+
import jakarta.servlet.http.HttpServlet;
33+
import jakarta.servlet.http.HttpServletRequest;
34+
import jakarta.servlet.http.HttpServletResponse;
35+
36+
import org.eclipse.jetty.client.ContentResponse;
37+
import org.eclipse.jetty.client.HttpClient;
38+
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
39+
import org.eclipse.jetty.util.Fields;
40+
import org.junit.Before;
41+
import org.junit.Test;
42+
import org.junit.runner.RunWith;
43+
import org.ops4j.pax.exam.Option;
44+
import org.ops4j.pax.exam.junit.PaxExam;
45+
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
46+
import org.ops4j.pax.exam.spi.reactors.PerClass;
47+
import org.osgi.framework.BundleContext;
48+
import org.osgi.service.http.HttpService;
49+
import org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants;
50+
51+
@RunWith(PaxExam.class)
52+
@ExamReactorStrategy(PerClass.class)
53+
public class JettySizeLimitHandlerIT extends AbstractJettyTestSupport {
54+
private static final int LIMIT_IN_BYTES = 10;
55+
56+
@Inject
57+
protected BundleContext bundleContext;
58+
59+
@Override
60+
protected Option[] additionalOptions() throws IOException {
61+
String jettyVersion = System.getProperty("jetty.version", JETTY_VERSION);
62+
return new Option[] {
63+
spifly(),
64+
65+
// bundles for the server side
66+
mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-webapp").version(jettyVersion),
67+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-ee").version(jettyVersion),
68+
mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-servlet").version(jettyVersion),
69+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-xml").version(jettyVersion),
70+
71+
// additional bundles for the client side
72+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").version(jettyVersion),
73+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-client").version(jettyVersion)
74+
};
75+
}
76+
77+
@Override
78+
protected Option felixHttpConfig(int httpPort) {
79+
return newConfiguration("org.apache.felix.http")
80+
.put("org.osgi.service.http.port", httpPort)
81+
.put("org.apache.felix.http.jetty.requestSizeLimit", LIMIT_IN_BYTES) // 10 bytes limit for the request
82+
.put("org.apache.felix.http.jetty.responseSizeLimit", LIMIT_IN_BYTES) // 10 bytes limit for the response
83+
.asOption();
84+
}
85+
86+
@Before
87+
public void setup(){
88+
assertNotNull(bundleContext);
89+
bundleContext.registerService(Servlet.class, new HelloWorldServletWithinLimit(), new Hashtable<>(Map.of(
90+
HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/withinlimit/*"
91+
)));
92+
bundleContext.registerService(Servlet.class, new HelloWorldServletExceedingLimit(), new Hashtable<>(Map.of(
93+
HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/exceedinglimit/*"
94+
)));
95+
}
96+
97+
98+
@Test
99+
public void testRequestResponseLimits() throws Exception {
100+
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP();
101+
HttpClient httpClient = new HttpClient(transport);
102+
httpClient.start();
103+
104+
Object value = bundleContext.getServiceReference(HttpService.class).getProperty("org.osgi.service.http.port");
105+
int httpPort = Integer.parseInt((String) value);
106+
107+
Fields formFields = new Fields();
108+
formFields.add(new Fields.Field("key", "value")); // under 10 bytes
109+
ContentResponse responseWithinLimit = httpClient.FORM(new URI(String.format("http://localhost:%d/withinlimit/a", httpPort)), formFields);
110+
111+
// Request limit ok, response limit ok
112+
assertEquals(200, responseWithinLimit.getStatus());
113+
assertEquals("OK", responseWithinLimit.getContentAsString());
114+
115+
// Request limit ok, response limit exceeded
116+
// org.eclipse.jetty.http.HttpException$RuntimeException: 500: Response body is too large: 17>10
117+
ContentResponse responseExceedingLimit = httpClient.FORM(new URI(String.format("http://localhost:%d/exceedinglimit/a", httpPort)), formFields);
118+
assertEquals(500, responseExceedingLimit.getStatus());
119+
120+
Fields formFieldsLimitExceeded = new Fields();
121+
formFieldsLimitExceeded.add(new Fields.Field("key", "valueoverlimit")); // over limit of 10 bytes
122+
ContentResponse responseExceeded = httpClient.FORM(new URI(String.format("http://localhost:%d/withinlimit/a", httpPort)), formFieldsLimitExceeded);
123+
124+
// Request limit exceeded, HTTP 413 directly from Jetty
125+
assertEquals(413, responseExceeded.getStatus());
126+
127+
httpClient.close();
128+
}
129+
130+
static final class HelloWorldServletWithinLimit extends HttpServlet {
131+
@Override
132+
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
133+
resp.setStatus(200);
134+
resp.getWriter().write("OK");
135+
}
136+
}
137+
138+
static final class HelloWorldServletExceedingLimit extends HttpServlet {
139+
@Override
140+
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
141+
resp.setStatus(200);
142+
resp.getWriter().write("responseoverlimit");
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)