Index: PickyHttpParser.java =================================================================== RCS file: PickyHttpParser.java diff -N PickyHttpParser.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ PickyHttpParser.java 12 Dec 2003 12:58:34 -0000 @@ -0,0 +1,253 @@ +/* + * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpParser.java,v 1.8 2003/07/15 02:19:58 mbecke Exp $ + * $Revision: 1.8 $ + * $Date: 2003/07/15 02:19:58 $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * 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 + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient.contrib.utils; +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.HttpConstants; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.ProtocolException; +import org.apache.commons.httpclient.util.HttpParser; +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 treating unusually long response headers. + * + * The maximum line length is 4096 bytes/8-bit-characters. + * The maximum number of headers is 1000. + * The maximum header value length is 4096 characters. + * + * A HttpException is thrown if at least one of these limits is exceeded. + * + * @author Michael Becke + * @author Oleg Kalnichevski + * @author Christian Kohlschuetter + */ +public class PickyHttpParser implements HttpParser { + /** Singleton handling */ + private static HttpParser instance = null; + public static synchronized HttpParser getInstance() { + if(instance == null) { + instance = new PickyHttpParser(); + } + return instance; + } + + /** Log object for this class. */ + private final Log LOG = LogFactory.getLog(PickyHttpParser.class); + + private int lineLengthLimit = 4096; + private int headersLimit = 1000; + private int valueLengthLimit = 4096; + + /** + * Constructor for HttpParser. + */ + private PickyHttpParser() { } + + /* + * 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 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(); + } + + /* + * 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 + */ + public String readLine(InputStream inputStream) throws IOException { + LOG.trace("enter HttpParser.readLine()"); + 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 HttpConstants.getString(rawdata, 0, len - offset); + } + + /* + * 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 + */ + public Header[] parseHeaders(InputStream is) throws IOException, HttpException { + LOG.trace("enter HeaderParser.parseHeaders(HttpConnection, HeaderGroup)"); + + ArrayList headers = new ArrayList(); + String name = null; + StringBuffer value = null; + for (; ;) { + if(headers.size() > headersLimit) { + throw new HttpException("Too many headers (> "+headersLimit+")"); + } + String line = readLine(is); + 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()); + 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); + } + 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() > headersLimit) { + throw new HttpException("Too many headers (> "+headersLimit+")"); + } + } + + return (Header[]) headers.toArray(new Header[headers.size()]); + } +}