From 01aded0eaaf12863a780ba8b7e46b7e19f7561b1 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 11 Mar 2016 08:41:37 -0700 Subject: [PATCH 1/2] Bumping up version jetty-version-maven-plugin to 1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6d8e037cd2d..4784d9fc9e3 100644 --- a/pom.xml +++ b/pom.xml @@ -268,7 +268,7 @@ org.eclipse.jetty.toolchain jetty-version-maven-plugin - 1.0.10 + 1.1 org.apache.maven.plugins From 5f2e2820f4595b22801906a5d5f1abacaff440dd Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 11 Mar 2016 22:49:12 +0100 Subject: [PATCH 2/2] Issue #423 (Duplicate Content-Length header not handled correctly) Fixed as required by the spec, rejecting the request. --- .../org/eclipse/jetty/http/HttpParser.java | 51 +++--- .../jetty/server/ContentLengthTest.java | 158 ++++++++++++++++++ 2 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/ContentLengthTest.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 3b56c18e392..12c7e2df9e3 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -78,8 +78,8 @@ import static org.eclipse.jetty.http.HttpTokens.TAB; *
*
RFC7230
(default) Compliance with RFC7230
*
RFC2616
Wrapped headers and HTTP/0.9 supported
- *
LEGACY
(aka STRICT) Adherence to Servlet Specification requirement for - * exact case of header names, bypassing the header caches, which are case insensitive, + *
LEGACY
(aka STRICT) Adherence to Servlet Specification requirement for + * exact case of header names, bypassing the header caches, which are case insensitive, * otherwise equivalent to RFC2616
*
* @see RFC 7230 @@ -221,10 +221,10 @@ public class HttpParser CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null)); CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null)); } - + private static HttpCompliance compliance() { - Boolean strict = Boolean.getBoolean(__STRICT); + Boolean strict = Boolean.getBoolean(__STRICT); return strict?HttpCompliance.LEGACY:HttpCompliance.RFC7230; } @@ -258,7 +258,7 @@ public class HttpParser { this(handler,maxHeaderBytes,strict?HttpCompliance.LEGACY:compliance()); } - + /* ------------------------------------------------------------------------------- */ @Deprecated public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict) @@ -271,7 +271,7 @@ public class HttpParser { this(handler,-1,compliance); } - + /* ------------------------------------------------------------------------------- */ public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance) { @@ -841,17 +841,13 @@ public class HttpParser switch (_header) { case CONTENT_LENGTH: - if (_endOfContent != EndOfContent.CHUNKED_CONTENT) + if (_endOfContent == EndOfContent.CONTENT_LENGTH) { - try - { - _contentLength=Long.parseLong(_valueString); - } - catch(NumberFormatException e) - { - LOG.ignore(e); - throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length"); - } + throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Duplicate Content-Length"); + } + else if (_endOfContent != EndOfContent.CHUNKED_CONTENT) + { + _contentLength=convertContentLength(_valueString); if (_contentLength <= 0) _endOfContent=EndOfContent.NO_CONTENT; else @@ -861,15 +857,16 @@ public class HttpParser case TRANSFER_ENCODING: if (_value==HttpHeaderValue.CHUNKED) + { _endOfContent=EndOfContent.CHUNKED_CONTENT; + _contentLength=-1; + } else { if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) _endOfContent=EndOfContent.CHUNKED_CONTENT; else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString())) - { throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad chunking"); - } } break; @@ -919,6 +916,18 @@ public class HttpParser _field=null; } + private long convertContentLength(String valueString) + { + try + { + return Long.parseLong(valueString); + } + catch(NumberFormatException e) + { + LOG.ignore(e); + throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Invalid Content-Length Value"); + } + } /* ------------------------------------------------------------------------------- */ /* @@ -970,12 +979,12 @@ public class HttpParser setState(State.HEADER_VALUE); break; } - + case HttpTokens.LINE_FEED: { // process previous header parsedHeader(); - + _contentPosition=0; // End of headers! @@ -1039,7 +1048,7 @@ public class HttpParser // process previous header parsedHeader(); - + // handle new header if (buffer.hasRemaining()) { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ContentLengthTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ContentLengthTest.java new file mode 100644 index 00000000000..a4d67b4c0cf --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ContentLengthTest.java @@ -0,0 +1,158 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse; +import org.eclipse.jetty.util.IO; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class ContentLengthTest +{ + private Server server; + private ServerConnector connector; + + private void startServer(Handler handler) throws Exception + { + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + server.setHandler(handler); + server.start(); + } + + @After + public void dispose() throws Exception + { + if (server != null) + server.stop(); + } + + @Test + public void testDuplicateContentLengthWithLargerAndCorrectValue() throws Exception + { + String content = "hello_world"; + testDuplicateContentLength(content, 2 * content.length(), content.length()); + } + + @Test + public void testDuplicateContentLengthWithCorrectAndLargerValue() throws Exception + { + String content = "hello_world"; + testDuplicateContentLength(content, content.length(), 2 * content.length()); + } + + private void testDuplicateContentLength(String content, long length1, long length2) throws Exception + { + startServer(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + } + }); + + try (Socket client = new Socket("localhost", connector.getLocalPort())) + { + String request = "" + + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Length: " + length1 + "\r\n" + + "Content-Length: " + length2 + "\r\n" + + "\r\n" + + content; + OutputStream output = client.getOutputStream(); + output.write(request.getBytes(StandardCharsets.UTF_8)); + output.flush(); + + SimpleHttpParser parser = new SimpleHttpParser(); + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); + SimpleHttpResponse response = parser.readResponse(reader); + + Assert.assertEquals(HttpStatus.BAD_REQUEST_400, Integer.parseInt(response.getCode())); + } + } + + @Test + public void testTransferEncodingChunkedBeforeContentLength() throws Exception + { + String content = "hello_world"; + testTransferEncodingChunkedAndContentLength(content, "Transfer-Encoding: chunked", "Content-Length: " + content.length()); + } + + @Test + public void testContentLengthBeforeTransferEncodingChunked() throws Exception + { + String content = "hello_world"; + testTransferEncodingChunkedAndContentLength(content, "Content-Length: " + content.length(), "Transfer-Encoding: chunked"); + } + + private void testTransferEncodingChunkedAndContentLength(String content, String header1, String header2) throws Exception + { + startServer(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String body = IO.toString(request.getInputStream()); + Assert.assertEquals(content, body); + } + }); + + try (Socket client = new Socket("localhost", connector.getLocalPort())) + { + String request = "" + + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + header1 + "\r\n" + + header2 + "\r\n" + + "\r\n" + + Integer.toHexString(content.length()) + "\r\n" + + content + + "0\r\n" + + "\r\n"; + OutputStream output = client.getOutputStream(); + output.write(request.getBytes(StandardCharsets.UTF_8)); + output.flush(); + + SimpleHttpParser parser = new SimpleHttpParser(); + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); + SimpleHttpResponse response = parser.readResponse(reader); + + Assert.assertEquals(HttpStatus.OK_200, Integer.parseInt(response.getCode())); + } + } +}