Skip to content

Commit

Permalink
Add a configuration option to for the maximum allowed number of cookies
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1765815 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
markt-asf committed Oct 20, 2016
1 parent ce93867 commit 8de6d3b
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 12 deletions.
1 change: 1 addition & 0 deletions java/org/apache/coyote/http2/Constants.java
Expand Up @@ -27,4 +27,5 @@ public class Constants {
// Limits
static final int DEFAULT_MAX_HEADER_COUNT = 100;
static final int DEFAULT_MAX_HEADER_SIZE = 8 * 1024;
static final int DEFAULT_MAX_COOKIE_COUNT = 200;
}
12 changes: 12 additions & 0 deletions java/org/apache/coyote/http2/Http2Protocol.java
Expand Up @@ -64,6 +64,7 @@ public class Http2Protocol implements UpgradeProtocol {
// Limits
private Set<String> allowedTrailerHeaders =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
private int maxCookieCount = Constants.DEFAULT_MAX_COOKIE_COUNT;
private int maxHeaderCount = Constants.DEFAULT_MAX_HEADER_COUNT;
private int maxHeaderSize = Constants.DEFAULT_MAX_HEADER_SIZE;

Expand Down Expand Up @@ -107,6 +108,7 @@ public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter,
result.setMaxConcurrentStreamExecution(getMaxConcurrentStreamExecution());
result.setInitialWindowSize(getInitialWindowSize());
result.setAllowedTrailerHeaders(allowedTrailerHeaders);
result.setMaxCookieCount(getMaxCookieCount());
result.setMaxHeaderCount(getMaxHeaderCount());
result.setMaxHeaderSize(getMaxHeaderSize());
return result;
Expand Down Expand Up @@ -252,4 +254,14 @@ public void setMaxHeaderSize(int maxHeaderSize) {
public int getMaxHeaderSize() {
return maxHeaderSize;
}


public void setMaxCookieCount(int maxCookieCount) {
this.maxCookieCount = maxCookieCount;
}


public int getMaxCookieCount() {
return maxCookieCount;
}
}
11 changes: 11 additions & 0 deletions java/org/apache/coyote/http2/Http2UpgradeHandler.java
Expand Up @@ -147,6 +147,7 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH

// Limits
private Set<String> allowedTrailerHeaders = Collections.emptySet();
private int maxCookieCount = Constants.DEFAULT_MAX_COOKIE_COUNT;


Http2UpgradeHandler(Adapter adapter, Request coyoteRequest) {
Expand Down Expand Up @@ -1123,6 +1124,16 @@ public void setMaxHeaderSize(int maxHeaderSize) {
}


public void setMaxCookieCount(int maxCookieCount) {
this.maxCookieCount = maxCookieCount;
}


public int getMaxCookieCount() {
return maxCookieCount;
}


// ----------------------------------------------- Http2Parser.Input methods

@Override
Expand Down
2 changes: 2 additions & 0 deletions java/org/apache/coyote/http2/Stream.java
Expand Up @@ -96,6 +96,8 @@ class Stream extends AbstractStream implements HeaderEmitter {
this.coyoteResponse.setOutputBuffer(outputBuffer);
this.coyoteRequest.setResponse(coyoteResponse);
this.coyoteRequest.protocol().setString("HTTP/2.0");
// Configure HTTP/2 limits
this.coyoteRequest.getCookies().setLimit(handler.getMaxCookieCount());
}


Expand Down
122 changes: 110 additions & 12 deletions test/org/apache/coyote/http2/TestHttp2Limits.java
Expand Up @@ -18,9 +18,8 @@

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.junit.Assert;
Expand Down Expand Up @@ -170,7 +169,7 @@ private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPa
int maxHeaderCount, int maxHeaderSize, int delayms, int failMode) throws Exception {

// Build the custom headers
Map<String,String> customHeaders = new HashMap<>();
List<String[]> customHeaders = new ArrayList<>();
StringBuilder headerValue = new StringBuilder(headerSize);
// Does not need to be secure
Random r = new Random();
Expand All @@ -180,7 +179,7 @@ private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPa
}
String v = headerValue.toString();
for (int i = 0; i < headerCount; i++) {
customHeaders.put("X-TomcatTest" + i, v);
customHeaders.add(new String[] {"X-TomcatTest" + i, v});
}

enableHttp2();
Expand All @@ -205,9 +204,9 @@ private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPa
// Assumes at least one custom header and that all headers are the same
// length. These assumptions are valid for these tests.
ByteBuffer headersPayload = ByteBuffer.allocate(200 + (int) (customHeaders.size() *
customHeaders.values().iterator().next().length() * 1.2));
customHeaders.iterator().next()[1].length() * 1.2));

populateHeadersPayload(headersPayload, customHeaders);
populateHeadersPayload(headersPayload, customHeaders, "/simple");

Exception e = null;
try {
Expand Down Expand Up @@ -259,14 +258,14 @@ private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPa
}


private void populateHeadersPayload(ByteBuffer headersPayload, Map<String,String> customHeaders)
throws Exception {
private void populateHeadersPayload(ByteBuffer headersPayload, List<String[]> customHeaders,
String path) throws Exception {
MimeHeaders headers = new MimeHeaders();
headers.addValue(":method").setString("GET");
headers.addValue(":path").setString("/simple");
headers.addValue(":path").setString(path);
headers.addValue(":authority").setString("localhost:" + getPort());
for (Entry<String,String> customHeader : customHeaders.entrySet()) {
headers.addValue(customHeader.getKey()).setString(customHeader.getValue());
for (String[] customHeader : customHeaders) {
headers.addValue(customHeader[0]).setString(customHeader[1]);
}
State state = hpackEncoder.encode(headers, headersPayload);
if (state != State.COMPLETE) {
Expand Down Expand Up @@ -296,4 +295,103 @@ private void populateFrameHeader(byte[] frameHeader, int written, int left, int
// Stream id
ByteUtil.set31Bits(frameHeader, 5, streamId);
}


@Test
public void testCookieLimit1() throws Exception {
doTestCookieLimit(1, 0);
}


@Test
public void testCookieLimit2() throws Exception {
doTestCookieLimit(2, 0);
}


@Test
public void testCookieLimit100() throws Exception {
doTestCookieLimit(100, 0);
}


@Test
public void testCookieLimit100WithLimit50() throws Exception {
doTestCookieLimit(100, 50, 1);
}


@Test
public void testCookieLimit200() throws Exception {
doTestCookieLimit(200, 0);
}


@Test
public void testCookieLimit201() throws Exception {
doTestCookieLimit(201, 1);
}


private void doTestCookieLimit(int cookieCount, int failMode) throws Exception {
doTestCookieLimit(cookieCount, Constants.DEFAULT_MAX_COOKIE_COUNT, failMode);
}


private void doTestCookieLimit(int cookieCount, int maxCookieCount, int failMode)
throws Exception {

enableHttp2();

Http2Protocol http2Protocol =
(Http2Protocol) getTomcatInstance().getConnector().findUpgradeProtocols()[0];
http2Protocol.setMaxCookieCount(maxCookieCount);

configureAndStartWebApplication();
openClientConnection();
doHttpUpgrade();
sendClientPreface();
validateHttp2InitialResponse();

output.setTraceBody(true);

byte[] frameHeader = new byte[9];
ByteBuffer headersPayload = ByteBuffer.allocate(8192);

List<String[]> customHeaders = new ArrayList<>();
for (int i = 0; i < cookieCount; i++) {
customHeaders.add(new String[] {"Cookie", "a" + cookieCount + "=b" + cookieCount});
}

populateHeadersPayload(headersPayload, customHeaders, "/cookie");
populateFrameHeader(frameHeader, 0, headersPayload.limit(), headersPayload.limit(), 3);

writeFrame(frameHeader, headersPayload);

switch (failMode) {
case 0: {
parser.readFrame(true);
parser.readFrame(true);
parser.readFrame(true);
System.out.println(output.getTrace());
Assert.assertEquals(getCookieResponseTrace(3, cookieCount), output.getTrace());
break;
}
case 1: {
// Check status is 500
parser.readFrame(true);
Assert.assertTrue(output.getTrace(), output.getTrace().startsWith(
"3-HeadersStart\n3-Header-[:status]-[500]"));
output.clearTrace();
// Check EOS followed by reset is next
parser.readFrame(true);
parser.readFrame(true);
Assert.assertEquals("3-EndOfStream\n3-RST-[2]\n", output.getTrace());
break;
}
default: {
Assert.fail("Unknown failure mode specified");
}
}
}
}
5 changes: 5 additions & 0 deletions webapps/docs/changelog.xml
Expand Up @@ -122,6 +122,11 @@
that the read buffer is large enough for the header being processed.
(markt)
</fix>
<add>
Add configuration options to the HTTP/2 implementation to control the
maximum number of headers allowed, the maximum size of headers allowed
and the maximum number of cookies allowed. (markt)
</add>
</changelog>
</subsection>
<subsection name="Jasper">
Expand Down
6 changes: 6 additions & 0 deletions webapps/docs/config/http2.xml
Expand Up @@ -105,6 +105,12 @@
If not specified, the default value of <code>200</code> will be used.</p>
</attribute>

<attribute name="maxCookieCount" required="false">
<p>The maximum number of cookies that are permitted for a request. A value
of less than zero means no limit. If not specified, a default value of 200
will be used.</p>
</attribute>

<attribute name="maxHeaderCount" required="false">
<p>The maximum number of headers in a request that is allowed by the
container. A request that contains more headers than the specified limit
Expand Down

0 comments on commit 8de6d3b

Please sign in to comment.