Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ParameterLimitValve to enforce request parameter limits for specific URLs #753

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions java/org/apache/catalina/connector/Request.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ public class Request implements HttpServletRequest {
*/
public Request(Connector connector, org.apache.coyote.Request coyoteRequest) {
this.connector = connector;
if (connector != null) {
this.maxParameterCount = connector.getMaxParameterCount();
}
this.coyoteRequest = coyoteRequest;
inputBuffer = new InputBuffer(coyoteRequest);
}
Expand Down Expand Up @@ -410,6 +413,10 @@ public org.apache.coyote.Request getCoyoteRequest() {

private HttpServletRequest applicationRequest = null;

/**
* The maximum number of request parameters
*/
private int maxParameterCount = -1;

// --------------------------------------------------------- Public Methods

Expand Down Expand Up @@ -440,6 +447,11 @@ public void recycle() {
userPrincipal = null;
subject = null;
parametersParsed = false;
if (connector != null ) {
maxParameterCount = connector.getMaxParameterCount();
} else {
maxParameterCount = -1;
}
if (parts != null) {
for (Part part : parts) {
try {
Expand Down Expand Up @@ -828,6 +840,14 @@ public void setServerPort(int port) {
coyoteRequest.setServerPort(port);
}

/**
* Set the maximum number of request parameters (GET plus POST) for a single request
*
* @param maxParameterCount The maximum number of request parameters
*/
public void setMaxParameterCount(int maxParameterCount) {
this.maxParameterCount = maxParameterCount;
}

// ------------------------------------------------- ServletRequest Methods

Expand Down Expand Up @@ -2428,7 +2448,6 @@ private void parseParts() {
}
}

int maxParameterCount = getConnector().getMaxParameterCount();
Parameters parameters = coyoteRequest.getParameters();
parameters.setLimit(maxParameterCount);

Expand Down Expand Up @@ -2767,8 +2786,6 @@ protected void doParseParameters() {

Parameters parameters = coyoteRequest.getParameters();

// Set this every time in case limit has been changed via JMX
int maxParameterCount = getConnector().getMaxParameterCount();
if (parts != null && maxParameterCount > 0) {
maxParameterCount -= parts.size();
}
Expand Down
119 changes: 119 additions & 0 deletions java/org/apache/catalina/valves/ParameterLimitValve.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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.apache.catalina.valves;


import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import jakarta.servlet.ServletException;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;


/**
* This is a concrete implementation of {@link ValveBase} that enforces a limit on the number of HTTP request parameters.
* The features of this implementation include:
* <ul>
* <li>URL-specific parameter limits that can be defined using regular expressions</li>
* <li>Configurable through Tomcat's <code>server.xml</code> or <code>context.xml</code></li>
* </ul>
* <p>
* The default limit, specified by Connector's value, applies to all requests unless a more specific
* URL pattern is matched. URL patterns and their corresponding limits can be configured via a regular expression
* mapping through the <code>urlPatternLimits</code> attribute.
* </p>
* <p>
* The Valve checks each incoming request and enforces the appropriate limit. If a request exceeds the allowed number
* of parameters, a <code>400 Bad Request</code> response is returned.
* </p>
* <p>
* Example configuration in <code>context.xml</code>:
* <pre>
* {@code
* <Context>
* <Valve className="org.apache.catalina.valves.ParameterLimitValve"
* urlPatternLimits="/api/.*=150,/admin/.*=50" />
* </Context>
* }
* </pre>
* <p>
* The configuration allows for flexible control over different sections of your application, such as applying higher
* limits for API endpoints and stricter limits for admin areas.
* </p>
*
* @author Dimitris Soumis
*/

public class ParameterLimitValve extends ValveBase {

/**
* Map for URL-specific limits
*/
private final Map<Pattern, Integer> urlPatternLimits = new HashMap<>();

/**
* Set the mapping of URL patterns to their corresponding parameter limits. The format of the input string should
* be a comma-separated list of URL pattern and parameter limit pairs, where the pattern is a regular expression.
* <p>
* Example: <code>/api/.*=150,/admin/.*=50</code>
*
* @param urlPatternConfig A string containing URL pattern to parameter limit mappings, in the format "pattern=limit"
*/

public void setUrlPatternLimits(String urlPatternConfig) {
String[] urlLimitPairs = urlPatternConfig.split(",");
for (String pair : urlLimitPairs) {
String[] urlLimit = pair.split("=");
Pattern pattern = Pattern.compile(urlLimit[0]);
int limit = Integer.parseInt(urlLimit[1]);
urlPatternLimits.put(pattern, Integer.valueOf(limit));
}
}

/**
* Invoke the next Valve in the sequence. Before invoking, checks if any of the defined patterns
* matches the URI of the request and if it does, enforces the corresponding parameter limit for the request.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String requestURI = request.getRequestURI();

// Iterate over the URL patterns and apply corresponding limits
for (Map.Entry<Pattern, Integer> entry : urlPatternLimits.entrySet()) {
Pattern pattern = entry.getKey();
int limit = entry.getValue().intValue();

if (pattern.matcher(requestURI).matches()) {
request.setMaxParameterCount(limit);
break;
}
}

// Continue processing the request
getNext().invoke(request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public Manager getManager() {
context.setParent(host);

Connector connector = EasyMock.createNiceMock(Connector.class);
EasyMock.replay(connector);
Request req = new Request(connector, null) {
@Override
public Context getContext() {
Expand All @@ -116,7 +117,6 @@ public Context getContext() {
};
req.setRequestedSessionId("invalidSession");
HttpServletRequest request = new RequestFacade(req);
EasyMock.replay(connector);
requestCachingSessionListener.request = request;

manager.setContext(context);
Expand Down
Loading
Loading