Index: src/java/org/apache/commons/httpclient/ChunkedInputStream.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v retrieving revision 1.22 diff -u -r1.22 ChunkedInputStream.java --- src/java/org/apache/commons/httpclient/ChunkedInputStream.java 18 Apr 2004 23:51:34 -0000 1.22 +++ src/java/org/apache/commons/httpclient/ChunkedInputStream.java 18 Jul 2004 19:13:21 -0000 @@ -301,7 +301,7 @@ private void parseTrailerHeaders() throws IOException { Header[] footers = null; try { - footers = HttpParser.parseHeaders(in, + footers = method.getParams().getHttpParser().parseHeaders(in, method.getParams().getHttpElementCharset()); } catch(HttpException e) { LOG.error("Error parsing trailer headers", e); Index: src/java/org/apache/commons/httpclient/HttpConnection.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v retrieving revision 1.96 diff -u -r1.96 HttpConnection.java --- src/java/org/apache/commons/httpclient/HttpConnection.java 5 Jul 2004 22:46:58 -0000 1.96 +++ src/java/org/apache/commons/httpclient/HttpConnection.java 18 Jul 2004 19:13:25 -0000 @@ -1024,7 +1024,7 @@ LOG.trace("enter HttpConnection.readLine()"); assertOpen(); - return HttpParser.readLine(inputStream); + return getParams().getHttpParser().readLine(inputStream); } /** @@ -1044,7 +1044,7 @@ LOG.trace("enter HttpConnection.readLine()"); assertOpen(); - return HttpParser.readLine(inputStream, charset); + return getParams().getHttpParser().readLine(inputStream, charset); } /** Index: src/java/org/apache/commons/httpclient/HttpMethodBase.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.210 diff -u -r1.210 HttpMethodBase.java --- src/java/org/apache/commons/httpclient/HttpMethodBase.java 5 Jul 2004 22:46:58 -0000 1.210 +++ src/java/org/apache/commons/httpclient/HttpMethodBase.java 18 Jul 2004 19:13:33 -0000 @@ -1746,7 +1746,7 @@ getResponseHeaderGroup().clear(); - Header[] headers = HttpParser.parseHeaders( + Header[] headers = getParams().getHttpParser().parseHeaders( conn.getResponseInputStream(), getParams().getHttpElementCharset()); if (Wire.HEADER_WIRE.enabled()) { for (int i = 0; i < headers.length; i++) { Index: src/java/org/apache/commons/httpclient/HttpParser.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpParser.java,v retrieving revision 1.12 diff -u -r1.12 HttpParser.java --- src/java/org/apache/commons/httpclient/HttpParser.java 13 May 2004 04:03:25 -0000 1.12 +++ src/java/org/apache/commons/httpclient/HttpParser.java 18 Jul 2004 19:13:33 -0000 @@ -31,12 +31,8 @@ import java.io.IOException; import java.io.InputStream; -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import org.apache.commons.httpclient.util.EncodingUtil; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.commons.httpclient.util.DefaultHttpParser; /** * A utility class for parsing http header values. @@ -45,17 +41,17 @@ * @author Oleg Kalnichevski * * @since 2.0beta1 + * @deprecated Use {@link org.apache.commons.httpclient.util.HttpParser} instead */ public class HttpParser { - /** Log object for this class. */ - private static final Log LOG = LogFactory.getLog(HttpParser.class); - /** * Constructor for HttpParser. */ private HttpParser() { } + private static final org.apache.commons.httpclient.util.HttpParser PARSER = DefaultHttpParser.INSTANCE; + /** * Return byte array from an (unchunked) input stream. * Stop reading when "\n" terminator encountered @@ -69,20 +65,7 @@ * @return a byte array from the stream */ public static byte[] readRawLine(InputStream inputStream) throws IOException { - LOG.trace("enter HttpParser.readRawLine()"); - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - int ch; - while ((ch = inputStream.read()) >= 0) { - buf.write(ch); - if (ch == '\n') { - break; - } - } - if (buf.size() == 0) { - return null; - } - return buf.toByteArray(); + return PARSER.readRawLine(inputStream); } /** @@ -100,24 +83,7 @@ * @since 3.0 */ public static String readLine(InputStream inputStream, String charset) throws IOException { - LOG.trace("enter HttpParser.readLine(InputStream, String)"); - byte[] rawdata = readRawLine(inputStream); - if (rawdata == null) { - return null; - } - int len = rawdata.length; - int offset = 0; - if (len > 0) { - if (rawdata[len - 1] == '\n') { - offset++; - if (len > 1) { - if (rawdata[len - 2] == '\r') { - offset++; - } - } - } - } - return EncodingUtil.getString(rawdata, 0, len - offset, charset); + return PARSER.readLine(inputStream, charset); } /** @@ -135,8 +101,7 @@ */ public static String readLine(InputStream inputStream) throws IOException { - LOG.trace("enter HttpParser.readLine(InputStream)"); - return readLine(inputStream, "US-ASCII"); + return PARSER.readLine(inputStream); } /** @@ -154,52 +119,7 @@ * @since 3.0 */ public static Header[] parseHeaders(InputStream is, String charset) throws IOException, HttpException { - LOG.trace("enter HeaderParser.parseHeaders(InputStream, String)"); - - ArrayList headers = new ArrayList(); - String name = null; - StringBuffer value = null; - for (; ;) { - String line = HttpParser.readLine(is, charset); - if ((line == null) || (line.length() < 1)) { - break; - } - - // Parse the header name and value - // Check for folded headers first - // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 - // discussion on folded headers - if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) { - // we have continuation folded header - // so append value - if (value != null) { - value.append(' '); - value.append(line.trim()); - } - } else { - // make sure we save the previous name,value pair if present - if (name != null) { - headers.add(new Header(name, value.toString())); - } - - // Otherwise we should have normal HTTP header line - // Parse the header name and value - int colon = line.indexOf(":"); - if (colon < 0) { - throw new ProtocolException("Unable to parse header: " + line); - } - name = line.substring(0, colon).trim(); - value = new StringBuffer(line.substring(colon + 1).trim()); - } - - } - - // make sure we save the last name,value pair if present - if (name != null) { - headers.add(new Header(name, value.toString())); - } - - return (Header[]) headers.toArray(new Header[headers.size()]); + return PARSER.parseHeaders(is, charset); } /** @@ -216,7 +136,6 @@ * @deprecated use #parseHeaders(InputStream, String) */ public static Header[] parseHeaders(InputStream is) throws IOException, HttpException { - LOG.trace("enter HeaderParser.parseHeaders(InputStream, String)"); - return parseHeaders(is, "US-ASCII"); + return PARSER.parseHeaders(is); } } Index: src/java/org/apache/commons/httpclient/params/DefaultHttpParams.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/DefaultHttpParams.java,v retrieving revision 1.8 diff -u -r1.8 DefaultHttpParams.java --- src/java/org/apache/commons/httpclient/params/DefaultHttpParams.java 13 May 2004 04:01:22 -0000 1.8 +++ src/java/org/apache/commons/httpclient/params/DefaultHttpParams.java 18 Jul 2004 19:13:34 -0000 @@ -32,6 +32,8 @@ import java.io.Serializable; import java.util.HashMap; +import org.apache.commons.httpclient.util.DefaultHttpParser; +import org.apache.commons.httpclient.util.HttpParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -250,4 +252,28 @@ clone.setDefaults(this.defaults); return clone; } + + /** + * Defines the HttpParser instance to be used by default. + *
+ * By default, this is {@link org.apache.commons.httpclient.util.DefaultHttpParser}. + *
+ * + * This parameter expects a value of type {@link org.apache.commons.httpclient.util.HttpParser} + */ + public static final String HTTP_PARSER_INSTANCE = "http.parser.instance"; + + /** + * Returns the currently used HttpParser instance. + * + * @return HttpParser instance + */ + public HttpParser getHttpParser() { + HttpParser parser = (HttpParser)getParameter(HTTP_PARSER_INSTANCE); + if(parser == null) { + parser = DefaultHttpParser.INSTANCE; + } + return parser; + } + } Index: src/java/org/apache/commons/httpclient/params/HttpMethodParams.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpMethodParams.java,v retrieving revision 1.13 diff -u -r1.13 HttpMethodParams.java --- src/java/org/apache/commons/httpclient/params/HttpMethodParams.java 13 May 2004 04:01:22 -0000 1.13 +++ src/java/org/apache/commons/httpclient/params/HttpMethodParams.java 18 Jul 2004 19:13:35 -0000 @@ -227,7 +227,7 @@ * * @see java.net.SocketOptions#SO_TIMEOUT */ - public static final String SO_TIMEOUT = "http.socket.timeout"; + public static final String SO_TIMEOUT = "http.socket.timeout"; /** * Creates a new collection of parameters with the collection returned @@ -427,5 +427,4 @@ setParameters(PROTOCOL_STRICTNESS_PARAMETERS, new Boolean(false)); setIntParameter(STATUS_LINE_GARBAGE_LIMIT, Integer.MAX_VALUE); } - } Index: src/test/org/apache/commons/httpclient/SimpleHttpConnection.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConnection.java,v retrieving revision 1.18 diff -u -r1.18 SimpleHttpConnection.java --- src/test/org/apache/commons/httpclient/SimpleHttpConnection.java 22 Feb 2004 18:08:49 -0000 1.18 +++ src/test/org/apache/commons/httpclient/SimpleHttpConnection.java 18 Jul 2004 19:13:37 -0000 @@ -40,6 +40,7 @@ import java.util.Vector; import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.util.DefaultHttpParser; import org.apache.commons.httpclient.util.EncodingUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -169,16 +170,16 @@ } public void write(byte[] data) - throws IOException, IllegalStateException, HttpRecoverableException { + throws IOException, IllegalStateException, HttpException { } public void writeLine() - throws IOException, IllegalStateException, HttpRecoverableException { + throws IOException, IllegalStateException, HttpException { } public String readLine(String charset) throws IOException, IllegalStateException { - String str = HttpParser.readLine(inputStream, charset); + String str = DefaultHttpParser.INSTANCE.readLine(inputStream, charset); log.debug("read: " + str); return str; } @@ -188,7 +189,7 @@ */ public String readLine() throws IOException, IllegalStateException { - String str = HttpParser.readLine(inputStream); + String str = DefaultHttpParser.INSTANCE.readLine(inputStream); log.debug("read: " + str); return str; } Index: src/test/org/apache/commons/httpclient/TestHttpParser.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpParser.java,v retrieving revision 1.3 diff -u -r1.3 TestHttpParser.java --- src/test/org/apache/commons/httpclient/TestHttpParser.java 22 Feb 2004 18:08:49 -0000 1.3 +++ src/test/org/apache/commons/httpclient/TestHttpParser.java 18 Jul 2004 19:13:37 -0000 @@ -33,7 +33,13 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import junit.framework.*; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.apache.commons.httpclient.util.DefaultHttpParser; +import org.apache.commons.httpclient.util.HttpParser; +import org.apache.commons.httpclient.util.PickyHttpParser; /** * Simple tests for {@link HttpParser}. @@ -62,18 +68,24 @@ return new TestSuite(TestHttpParser.class); } - public void testReadHttpLine() throws Exception { + public void testReadHttpLine_DefaultHttpParser() throws Exception { + testReadHttpLine(DefaultHttpParser.INSTANCE); + } + public void testReadHttpLine_PickyHttpParser() throws Exception { + testReadHttpLine(PickyHttpParser.INSTANCE); + } + private void testReadHttpLine(HttpParser parser) throws Exception { InputStream instream = new ByteArrayInputStream( "\r\r\nstuff\r\n".getBytes(HTTP_ELEMENT_CHARSET)); - assertEquals("\r", HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); - assertEquals("stuff", HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); - assertEquals(null, HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals("\r", parser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals("stuff", parser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals(null, parser.readLine(instream, HTTP_ELEMENT_CHARSET)); instream = new ByteArrayInputStream( "\n\r\nstuff\r\n".getBytes("US-ASCII")); - assertEquals("", HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); - assertEquals("", HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); - assertEquals("stuff", HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); - assertEquals(null, HttpParser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals("", parser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals("", parser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals("stuff", parser.readLine(instream, HTTP_ELEMENT_CHARSET)); + assertEquals(null, parser.readLine(instream, HTTP_ELEMENT_CHARSET)); } } Index: src/test/org/apache/commons/httpclient/TestNoHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v retrieving revision 1.38 diff -u -r1.38 TestNoHost.java --- src/test/org/apache/commons/httpclient/TestNoHost.java 11 May 2004 20:43:55 -0000 1.38 +++ src/test/org/apache/commons/httpclient/TestNoHost.java 18 Jul 2004 19:13:37 -0000 @@ -86,6 +86,7 @@ suite.addTest(TestHttpVersion.suite()); suite.addTest(TestEffectiveHttpVersion.suite()); suite.addTest(TestHttpParser.suite()); + suite.addTest(TestPickyHttpParser.suite()); suite.addTest(TestBadContentLength.suite()); suite.addTest(TestEquals.suite()); suite.addTestSuite(TestIdleConnectionTimeout.class); Index: src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java,v retrieving revision 1.9 diff -u -r1.9 SimpleHttpServerConnection.java --- src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java 13 Jun 2004 20:22:20 -0000 1.9 +++ src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java 18 Jul 2004 19:13:37 -0000 @@ -37,8 +37,8 @@ import java.io.UnsupportedEncodingException; import java.net.Socket; -import org.apache.commons.httpclient.HttpParser; import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.util.DefaultHttpParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -141,7 +141,7 @@ private void readRequest() throws IOException { String line; do { - line = HttpParser.readLine(in, HTTP_ELEMENT_CHARSET); + line = DefaultHttpParser.INSTANCE.readLine(in, HTTP_ELEMENT_CHARSET); } while (line != null && line.length() == 0); if (line == null) { @@ -153,7 +153,7 @@ try { request = new SimpleRequest( RequestLine.parseLine(line), - HttpParser.parseHeaders(in, HTTP_ELEMENT_CHARSET), + DefaultHttpParser.INSTANCE.parseHeaders(in, HTTP_ELEMENT_CHARSET), null); } catch (IOException e) { connectionClose(); Index: src/java/org/apache/commons/httpclient/util/DefaultHttpParser.java =================================================================== RCS file: src/java/org/apache/commons/httpclient/util/DefaultHttpParser.java diff -N src/java/org/apache/commons/httpclient/util/DefaultHttpParser.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/commons/httpclient/util/DefaultHttpParser.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,225 @@ +/* + * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpParser.java,v 1.12 2004/05/13 04:03:25 mbecke Exp $ + * $Revision: 1.12 $ + * $Date: 2004/05/13 04:03:25 $ + * + * ==================================================================== + * + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + *null is returned
+ *
+ * @param inputStream the stream to read from
+ *
+ * @throws IOException if an I/O problem occurs
+ * @return a byte array from the stream
+ */
+ public byte[] readRawLine(InputStream inputStream) throws IOException {
+ LOG.trace("enter HttpParser.readRawLine()");
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ int ch;
+ while ((ch = inputStream.read()) >= 0) {
+ buf.write(ch);
+ if (ch == '\n') {
+ break;
+ }
+ }
+ if (buf.size() == 0) {
+ return null;
+ }
+ return buf.toByteArray();
+ }
+
+ /**
+ * Read up to "\n" from an (unchunked) input stream.
+ * If the stream ends before the line terminator is found,
+ * the last part of the string will still be returned.
+ * If no input data available, null is returned
+ *
+ * @param inputStream the stream to read from
+ * @param charset charset of HTTP protocol elements
+ *
+ * @throws IOException if an I/O problem occurs
+ * @return a line from the stream
+ *
+ * @since 3.0
+ */
+ public String readLine(InputStream inputStream, String charset) throws IOException {
+ LOG.trace("enter HttpParser.readLine(InputStream, String)");
+ byte[] rawdata = readRawLine(inputStream);
+ if (rawdata == null) {
+ return null;
+ }
+ int len = rawdata.length;
+ int offset = 0;
+ if (len > 0) {
+ if (rawdata[len - 1] == '\n') {
+ offset++;
+ if (len > 1) {
+ if (rawdata[len - 2] == '\r') {
+ offset++;
+ }
+ }
+ }
+ }
+ return EncodingUtil.getString(rawdata, 0, len - offset, charset);
+ }
+
+ /**
+ * Read up to "\n" from an (unchunked) input stream.
+ * If the stream ends before the line terminator is found,
+ * the last part of the string will still be returned.
+ * If no input data available, null is returned
+ *
+ * @param inputStream the stream to read from
+ *
+ * @throws IOException if an I/O problem occurs
+ * @return a line from the stream
+ *
+ * @deprecated use #readLine(InputStream, String)
+ */
+
+ public String readLine(InputStream inputStream) throws IOException {
+ LOG.trace("enter HttpParser.readLine(InputStream)");
+ return readLine(inputStream, "US-ASCII");
+ }
+
+ /**
+ * Parses headers from the given stream. Headers with the same name are not
+ * combined.
+ *
+ * @param is the stream to read headers from
+ * @param charset the charset to use for reading the data
+ *
+ * @return an array of headers in the order in which they were parsed
+ *
+ * @throws IOException if an IO error occurs while reading from the stream
+ * @throws HttpException if there is an error parsing a header value
+ *
+ * @since 3.0
+ */
+ public Header[] parseHeaders(InputStream is, String charset) throws IOException, HttpException {
+ LOG.trace("enter HeaderParser.parseHeaders(InputStream, String)");
+
+ ArrayList headers = new ArrayList();
+ String name = null;
+ StringBuffer value = null;
+ for (; ;) {
+ String line = readLine(is, charset);
+ if ((line == null) || (line.length() < 1)) {
+ break;
+ }
+
+ // Parse the header name and value
+ // Check for folded headers first
+ // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
+ // discussion on folded headers
+ if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) {
+ // we have continuation folded header
+ // so append value
+ if (value != null) {
+ value.append(' ');
+ value.append(line.trim());
+ }
+ } else {
+ // make sure we save the previous name,value pair if present
+ if (name != null) {
+ headers.add(new Header(name, value.toString()));
+ }
+
+ // Otherwise we should have normal HTTP header line
+ // Parse the header name and value
+ int colon = line.indexOf(":");
+ if (colon < 0) {
+ throw new ProtocolException("Unable to parse header: " + line);
+ }
+ name = line.substring(0, colon).trim();
+ value = new StringBuffer(line.substring(colon + 1).trim());
+ }
+
+ }
+
+ // make sure we save the last name,value pair if present
+ if (name != null) {
+ headers.add(new Header(name, value.toString()));
+ }
+
+ return (Header[]) headers.toArray(new Header[headers.size()]);
+ }
+
+ /**
+ * Parses headers from the given stream. Headers with the same name are not
+ * combined.
+ *
+ * @param is the stream to read headers from
+ *
+ * @return an array of headers in the order in which they were parsed
+ *
+ * @throws IOException if an IO error occurs while reading from the stream
+ * @throws HttpException if there is an error parsing a header value
+ *
+ * @deprecated use #parseHeaders(InputStream, String)
+ */
+ public Header[] parseHeaders(InputStream is) throws IOException, HttpException {
+ LOG.trace("enter HeaderParser.parseHeaders(InputStream, String)");
+ return parseHeaders(is, "US-ASCII");
+ }
+}
Index: src/java/org/apache/commons/httpclient/util/HttpParser.java
===================================================================
RCS file: src/java/org/apache/commons/httpclient/util/HttpParser.java
diff -N src/java/org/apache/commons/httpclient/util/HttpParser.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/java/org/apache/commons/httpclient/util/HttpParser.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,91 @@
+package org.apache.commons.httpclient.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpException;
+
+/**
+ * An interface for utility classes for parsing http header values.
+ *
+ * @author Michael Becke
+ * @author Oleg Kalnichevski
+ * @author Christian Kohlschuetter
+ */
+public interface HttpParser {
+ /**
+ * Return byte array from an (unchunked) input stream.
+ * Stop reading when "\n" terminator encountered
+ * If the stream ends before the line terminator is found,
+ * the last part of the string will still be returned.
+ * If no input data available, null is returned
+ *
+ * @param inputStream the stream to read from
+ *
+ * @throws IOException if an I/O problem occurs
+ * @return a byte array from the stream
+ */
+ public abstract byte[] readRawLine(InputStream inputStream)
+ throws IOException;
+
+ /**
+ * Read up to "\n" from an (unchunked) input stream.
+ * If the stream ends before the line terminator is found,
+ * the last part of the string will still be returned.
+ * If no input data available, null is returned
+ *
+ * @param inputStream the stream to read from
+ * @param charset The charset to be used for converting
+ *
+ * @throws IOException if an I/O problem occurs
+ * @return a line from the stream
+ */
+ public abstract String readLine(InputStream inputStream, String charset)
+ throws IOException;
+
+ /**
+ * Read up to "\n" from an (unchunked) input stream.
+ * If the stream ends before the line terminator is found,
+ * the last part of the string will still be returned.
+ * If no input data available, null is returned
+ *
+ * @param inputStream the stream to read from
+ *
+ * @throws IOException if an I/O problem occurs
+ * @return a line from the stream
+ * @deprecated use #readLine(InputStream, String)
+ */
+ public abstract String readLine(InputStream inputStream) throws IOException;
+
+ /**
+ * Parses headers from the given stream. Headers with the same name are not
+ * combined.
+ *
+ * @param is the stream to read headers from
+ *
+ * @return an array of headers in the order in which they were parsed
+ *
+ * @throws IOException if an IO error occurs while reading from the stream
+ * @throws HttpException if there is an error parsing a header value
+ * @deprecated Use #parseHeaders(InputStream, String)
+ */
+ public abstract Header[] parseHeaders(InputStream is)
+ throws IOException, HttpException;
+
+ /**
+ * Parses headers from the given stream. Headers with the same name are not
+ * combined.
+ *
+ * @param is the stream to read headers from
+ * @param charset the charset to use for reading the data
+ *
+ * @return an array of headers in the order in which they were parsed
+ *
+ * @throws IOException if an IO error occurs while reading from the stream
+ * @throws HttpException if there is an error parsing a header value
+ *
+ * @since 3.0
+ */
+ public abstract Header[] parseHeaders(InputStream is, String charset) throws IOException, HttpException;
+}
Index: src/java/org/apache/commons/httpclient/util/PickyHttpParser.java
===================================================================
RCS file: src/java/org/apache/commons/httpclient/util/PickyHttpParser.java
diff -N src/java/org/apache/commons/httpclient/util/PickyHttpParser.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/java/org/apache/commons/httpclient/util/PickyHttpParser.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,173 @@
+package org.apache.commons.httpclient.util;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.ProtocolException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A utility class for parsing http header values.
+ *
+ * This implementation differs from {@link DefaultHttpParser}
+ * in specially treating unusually long response headers.
+ *
+ * There is a default instance available with preset limits, and you
+ * can define limits by creating your own instances.
+ *
+ * A HttpException is thrown if at least one of these limits is exceeded.
+ *
+ * @author Christian Kohlschuetter
+ * @author Michael Becke
+ * @author Oleg Kalnichevski
+ */
+public class PickyHttpParser extends DefaultHttpParser {
+ /**
+ * The default PickyHttpParser instance with the follwing settings:
+ *
+ * Maximum length of an HTTP header name: 256
+ * Maximum length of an HTTP header line: 64k
+ * Maximum length of an HTTP header value (can span over several lines): 256k
+ * Maximum total length of a request/response header section: 1M
+ * Maximum number of headers: 1000
+ */
+ public static final HttpParser INSTANCE = new PickyHttpParser();
+
+ /** Log object for this class. */
+ private final Log LOG = LogFactory.getLog(PickyHttpParser.class);
+
+ /** Maximum length of an HTTP header name */
+ private final int nameLengthLimit;
+
+ /** Maximum length of an HTTP header value (can span over several lines) */
+ private final int valueLengthLimit;
+
+ /** Maximum length of an HTTP header line */
+ private final int lineLengthLimit;
+
+ /** Maximum number of headers */
+ private final int numHeadersLimit;
+
+ /** Maximum total length of a request/response header section */
+ private final int headersLengthLimit;
+
+ private PickyHttpParser() {
+ this(256, 64*1024, 256 * 1024, 1 * 1024 * 1024, 1000);
+ }
+
+ /**
+ * Creates a new custom PickyHttpParser
+ *
+ * @param nameLengthLimit Maximum length of an HTTP header name
+ * @param lineLengthLimit Maximum length of an HTTP header line
+ * @param valueLengthLimit Maximum length of an HTTP header value (can span over several lines)
+ * @param headersLengthLimit Maximum total length of a request/response header section
+ * @param numHeadersLimit Maximum number of headers
+ */
+ public PickyHttpParser(int nameLengthLimit, int lineLengthLimit, int valueLengthLimit, int headersLengthLimit, int numHeadersLimit) {
+ super();
+ this.nameLengthLimit = nameLengthLimit + 1; // including the colon
+ this.lineLengthLimit = lineLengthLimit;
+ this.valueLengthLimit = valueLengthLimit;
+ this.headersLengthLimit = headersLengthLimit;
+ this.numHeadersLimit = numHeadersLimit;
+ }
+
+ public byte[] readRawLine(InputStream inputStream) throws IOException {
+ LOG.trace("enter HttpParser.readRawLine()");
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ int ch;
+ int n = 0;
+ while ((ch = inputStream.read()) >= 0) {
+ buf.write(ch);
+ n++;
+ if (ch == '\n') {
+ break;
+ }
+ if(n > lineLengthLimit) {
+ throw new HttpException("Line too long (> "+lineLengthLimit+" bytes)");
+ }
+ }
+ if (buf.size() == 0) {
+ return null;
+ }
+ return buf.toByteArray();
+ }
+
+ public Header[] parseHeaders(InputStream is, String charset) throws IOException, HttpException {
+ LOG.trace("enter HeaderParser.parseHeaders(InputStream, String)");
+
+ ArrayList headers = new ArrayList();
+ String name = null;
+ StringBuffer value = null;
+
+ int headersLength = 0;
+
+ while(true) {
+ if(headers.size() > numHeadersLimit) {
+ throw new HttpException("Too many headers (> "+numHeadersLimit+")");
+ }
+ String line = readLine(is, charset);
+ if ((line == null) || (line.length() < 1)) {
+ break;
+ }
+
+ headersLength += line.length();
+ if(headersLength > headersLengthLimit) {
+ throw new HttpException("Header section too long (> "+headersLengthLimit+")");
+ }
+
+ // Parse the header name and value
+ // Check for folded headers first
+ // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
+ // discussion on folded headers
+ if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) {
+ // we have continuation folded header
+ // so append value
+ if (value != null) {
+ value.append(' ');
+ value.append(line.trim());
+ if(value.length() > valueLengthLimit) {
+ throw new HttpException("Header value too long (> "+valueLengthLimit+")");
+ }
+ }
+ } else {
+ // make sure we save the previous name,value pair if present
+ if (name != null) {
+ headers.add(new Header(name, value.toString()));
+ }
+
+ // Otherwise we should have normal HTTP header line
+ // Parse the header name and value
+ int colon = line.indexOf(":");
+ if (colon < 0) {
+ throw new ProtocolException("Unable to parse header: " + line);
+ }
+ if (colon > nameLengthLimit) {
+ throw new ProtocolException("Header name too long (> "+nameLengthLimit+")");
+ }
+ name = line.substring(0, colon).trim();
+ value = new StringBuffer(line.substring(colon + 1).trim());
+ if(value.length() > valueLengthLimit) {
+ throw new HttpException("Header value too long (> "+valueLengthLimit+")");
+ }
+ }
+
+ }
+
+ // make sure we save the last name,value pair if present
+ if (name != null) {
+ headers.add(new Header(name, value.toString()));
+ if(headers.size() > numHeadersLimit) {
+ throw new HttpException("Too many headers (> "+numHeadersLimit+")");
+ }
+ }
+
+ return (Header[]) headers.toArray(new Header[headers.size()]);
+ }
+}
Index: src/test/org/apache/commons/httpclient/TestPickyHttpParser.java
===================================================================
RCS file: src/test/org/apache/commons/httpclient/TestPickyHttpParser.java
diff -N src/test/org/apache/commons/httpclient/TestPickyHttpParser.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/test/org/apache/commons/httpclient/TestPickyHttpParser.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,300 @@
+package org.apache.commons.httpclient;
+
+import java.io.IOException;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.httpclient.server.HttpRequestHandler;
+import org.apache.commons.httpclient.server.RequestLine;
+import org.apache.commons.httpclient.server.ResponseWriter;
+import org.apache.commons.httpclient.server.SimpleHttpServer;
+import org.apache.commons.httpclient.server.SimpleHttpServerConnection;
+import org.apache.commons.httpclient.server.SimpleRequest;
+import org.apache.commons.httpclient.util.PickyHttpParser;
+import org.apache.commons.httpclient.util.TimeoutController;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests HttpClient's behaviour when receiving bad response headers
+ * using the {@link PickyHttpParser}.
+ *
+ * A very simple HTTP Server will be setup on a free local port during testing, which + * returns bad/malicious response headers. + *
+ * + * @author Christian Kohlschuetter + */ +public class TestPickyHttpParser extends TestNoHost { + private static final Log LOG = LogFactory.getLog(SimpleHttpServer.class); + + private HttpClient client = null; + private SimpleHttpServer server = null; + + // ------------------------------------------------------------ Constructor + public TestPickyHttpParser(String testName) { + super(testName); + } + + // ------------------------------------------------------------------- Main + public static void main(String args[]) { + String[] testCaseName = { TestPickyHttpParser.class.getName()}; + junit.textui.TestRunner.main(testCaseName); + } + + // ------------------------------------------------------- TestCase Methods + + public static Test suite() { + return new TestSuite(TestPickyHttpParser.class); + } + + // ----------------------------------------------------------- Test Methods + + public void setUp() throws IOException { + /* + * uncomment to enable wire log + */ + /* + System.setProperty( + "org.apache.commons.logging.Log", + "org.apache.commons.logging.impl.SimpleLog"); + System.setProperty( + "org.apache.commons.logging.simplelog.showdatetime", + "true"); + System.setProperty( + "org.apache.commons.logging.simplelog.log.httpclient.wire", + "debug"); + System.setProperty( + "org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", + "debug"); + */ + client = new HttpClient(); + + // Uncomment the following line to see DefaultHttpParser failing all the tests + client.getParams().setParameter(HttpMethodParams.HTTP_PARSER_INSTANCE, PickyHttpParser.INSTANCE); + + server = new SimpleHttpServer(); // use arbitrary port + server.setRequestHandler(new MyHttpRequestHandler()); + } + + public void tearDown() throws IOException { + client = null; + + server.destroy(); + } + + /* + public void testForDebuggingOnly() + throws InterruptedException { + while (server.isRunning()) { + Thread.sleep(100); + } + } + */ + + /** + * HttpClient connects to the test server and performs a + * request. + * + * The server responds to the request and sends a HTTP/1.0 200 OK + * followed by a Header with infinite number of data. + * + * Expected behavior: + * HTTPClient should detect that the header line is too long and + * throw a HttpException. + * + * @throws IOException + */ + public void testLongHeaderLine() throws IOException { + client.getParams().makeLenient(); + + final long waitMax = 3 * 1000; // [ms] + FetchJob fj = + new FetchJob( + client, + "http://" + + server.getLocalAddress() + + ":" + + server.getLocalPort() + + "/long_line"); + + try { + TimeoutController.execute(fj, waitMax); + } catch (TimeoutController.TimeoutException e) { + throw new IOException("FetchJob timeout"); + } + + Throwable t = fj.getThrowable(); + if (t != null) { + if (t instanceof HttpException) { + LOG.info("Received expected HttpException", t); + return; + } + fail("Unexpected error in FetchJob: " + t.toString()); + } else { + fail("Did not receive expected HttpException"); + } + } + + /** + * HttpClient connects to the test server and performs a + * request. + * + * The server responds to the request and sends a HTTP/1.0 200 OK + * followed by an infinite number of Headers. + * + * Expected behavior: + * HTTPClient should detect that too many headers were received and + * throw a HttpException. + * + * @throws IOException + */ + public void testInfiniteHeaderLines() throws IOException { + client.getParams().makeLenient(); + + final long waitMax = 3 * 1000; // [ms] + FetchJob fj = + new FetchJob( + client, + "http://" + + server.getLocalAddress() + + ":" + + server.getLocalPort() + + "/infinite_lines"); + + try { + TimeoutController.execute(fj, waitMax); + } catch (TimeoutController.TimeoutException e) { + throw new IOException("FetchJob timeout"); + } + + Throwable t = fj.getThrowable(); + if (t != null) { + if (t instanceof HttpException) { + LOG.info("Received expected HttpException", t); + return; + } + fail("Unexpected error in FetchJob: " + t.toString()); + } else { + fail("Did not receive expected HttpException"); + } + } + + /** + * HttpClient connects to the test server and performs a + * request. + * + * The server responds to the request and sends a HTTP/1.0 200 OK + * followed by a Header with infinite number of folded lines. + * + * Expected behavior: + * HTTPClient should detect that the Header is too long and + * throw a HttpException. + * + * @throws IOException + */ + public void testInfinitelyFoldedHeaderLine() throws IOException { + client.getParams().makeLenient(); + + final long waitMax = 3 * 1000; // [ms] + FetchJob fj = + new FetchJob( + client, + "http://" + + server.getLocalAddress() + + ":" + + server.getLocalPort() + + "/infinitely_folded_header"); + + try { + TimeoutController.execute(fj, waitMax); + } catch (TimeoutController.TimeoutException e) { + throw new IOException("FetchJob timeout"); + } + + Throwable t = fj.getThrowable(); + if (t != null) { + if (t instanceof HttpException) { + LOG.info("Received expected HttpException", t); + return; + } + fail("Unexpected error in FetchJob: " + t.toString()); + } else { + fail("Did not receive expected HttpException"); + } + } + + private class MyHttpRequestHandler implements HttpRequestHandler { + public boolean processRequest(SimpleHttpServerConnection conn, SimpleRequest request) + throws IOException { + RequestLine requestLine = request.getRequestLine(); + ResponseWriter out = conn.getWriter(); + if ("GET".equals(requestLine.getMethod())) { + + if ("/long_line".equals(requestLine.getUri())) { + out.println("HTTP/1.1 200 OK"); + out.println("Content-Type: text/html"); + out.print("Nonsense: "); + while (true) { + out.print("12345"); + out.flush(); + } + } else if ("/infinite_lines".equals(requestLine.getUri())) { + out.println("HTTP/1.1 200 OK"); + out.println("Content-Type: text/html"); + while (true) { + out.println("Nonsense: 12345"); + out.flush(); + } + } else if ("/infinitely_folded_header".equals(requestLine.getUri())) { + out.println("HTTP/1.1 200 OK"); + out.println("Content-Type: text/html"); + out.print("Nonsense: "); + while (true) { + for(int i=0;i<8;i++) { + out.print("0123456789"); + } + out.println(); + out.flush(); + out.print(" "); + } + } + } + + return false; + } + } + + private class FetchJob implements Runnable { + private Throwable throwable = null; + private HttpClient client; + private String uri; + + public FetchJob(HttpClient client, String uri) { + this.client = client; + this.uri = uri; + } + + public Throwable getThrowable() { + return throwable; + } + + public void run() { + GetMethod method = null; + try { + method = new GetMethod(uri); + client.executeMethod(method); + } catch (Throwable t) { + throwable = t; + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + } + +}