michael@0: /* michael@0: * ==================================================================== michael@0: * Licensed to the Apache Software Foundation (ASF) under one michael@0: * or more contributor license agreements. See the NOTICE file michael@0: * distributed with this work for additional information michael@0: * regarding copyright ownership. The ASF licenses this file michael@0: * to you under the Apache License, Version 2.0 (the michael@0: * "License"); you may not use this file except in compliance michael@0: * with the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, michael@0: * software distributed under the License is distributed on an michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY michael@0: * KIND, either express or implied. See the License for the michael@0: * specific language governing permissions and limitations michael@0: * under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.impl.io; michael@0: michael@0: import java.io.IOException; michael@0: import java.util.ArrayList; michael@0: import java.util.List; michael@0: michael@0: import ch.boye.httpclientandroidlib.Header; michael@0: import ch.boye.httpclientandroidlib.HttpException; michael@0: import ch.boye.httpclientandroidlib.HttpMessage; michael@0: import ch.boye.httpclientandroidlib.ParseException; michael@0: import ch.boye.httpclientandroidlib.ProtocolException; michael@0: import ch.boye.httpclientandroidlib.io.HttpMessageParser; michael@0: import ch.boye.httpclientandroidlib.io.SessionInputBuffer; michael@0: import ch.boye.httpclientandroidlib.message.LineParser; michael@0: import ch.boye.httpclientandroidlib.message.BasicLineParser; michael@0: import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; michael@0: import ch.boye.httpclientandroidlib.params.HttpParams; michael@0: import ch.boye.httpclientandroidlib.util.CharArrayBuffer; michael@0: michael@0: /** michael@0: * Abstract base class for HTTP message parsers that obtain input from michael@0: * an instance of {@link SessionInputBuffer}. michael@0: *

michael@0: * The following parameters can be used to customize the behavior of this michael@0: * class: michael@0: *

michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: public abstract class AbstractMessageParser implements HttpMessageParser { michael@0: michael@0: private static final int HEAD_LINE = 0; michael@0: private static final int HEADERS = 1; michael@0: michael@0: private final SessionInputBuffer sessionBuffer; michael@0: private final int maxHeaderCount; michael@0: private final int maxLineLen; michael@0: private final List headerLines; michael@0: protected final LineParser lineParser; michael@0: michael@0: private int state; michael@0: private HttpMessage message; michael@0: michael@0: /** michael@0: * Creates an instance of this class. michael@0: * michael@0: * @param buffer the session input buffer. michael@0: * @param parser the line parser. michael@0: * @param params HTTP parameters. michael@0: */ michael@0: public AbstractMessageParser( michael@0: final SessionInputBuffer buffer, michael@0: final LineParser parser, michael@0: final HttpParams params) { michael@0: super(); michael@0: if (buffer == null) { michael@0: throw new IllegalArgumentException("Session input buffer may not be null"); michael@0: } michael@0: if (params == null) { michael@0: throw new IllegalArgumentException("HTTP parameters may not be null"); michael@0: } michael@0: this.sessionBuffer = buffer; michael@0: this.maxHeaderCount = params.getIntParameter( michael@0: CoreConnectionPNames.MAX_HEADER_COUNT, -1); michael@0: this.maxLineLen = params.getIntParameter( michael@0: CoreConnectionPNames.MAX_LINE_LENGTH, -1); michael@0: this.lineParser = (parser != null) ? parser : BasicLineParser.DEFAULT; michael@0: this.headerLines = new ArrayList(); michael@0: this.state = HEAD_LINE; michael@0: } michael@0: michael@0: /** michael@0: * Parses HTTP headers from the data receiver stream according to the generic michael@0: * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. michael@0: * michael@0: * @param inbuffer Session input buffer michael@0: * @param maxHeaderCount maximum number of headers allowed. If the number michael@0: * of headers received from the data stream exceeds maxCount value, an michael@0: * IOException will be thrown. Setting this parameter to a negative value michael@0: * or zero will disable the check. michael@0: * @param maxLineLen maximum number of characters for a header line, michael@0: * including the continuation lines. Setting this parameter to a negative michael@0: * value or zero will disable the check. michael@0: * @return array of HTTP headers michael@0: * @param parser line parser to use. Can be null, in which case michael@0: * the default implementation of this interface will be used. michael@0: * michael@0: * @throws IOException in case of an I/O error michael@0: * @throws HttpException in case of HTTP protocol violation michael@0: */ michael@0: public static Header[] parseHeaders( michael@0: final SessionInputBuffer inbuffer, michael@0: int maxHeaderCount, michael@0: int maxLineLen, michael@0: LineParser parser) michael@0: throws HttpException, IOException { michael@0: if (parser == null) { michael@0: parser = BasicLineParser.DEFAULT; michael@0: } michael@0: List headerLines = new ArrayList(); michael@0: return parseHeaders(inbuffer, maxHeaderCount, maxLineLen, parser, headerLines); michael@0: } michael@0: michael@0: /** michael@0: * Parses HTTP headers from the data receiver stream according to the generic michael@0: * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. michael@0: * michael@0: * @param inbuffer Session input buffer michael@0: * @param maxHeaderCount maximum number of headers allowed. If the number michael@0: * of headers received from the data stream exceeds maxCount value, an michael@0: * IOException will be thrown. Setting this parameter to a negative value michael@0: * or zero will disable the check. michael@0: * @param maxLineLen maximum number of characters for a header line, michael@0: * including the continuation lines. Setting this parameter to a negative michael@0: * value or zero will disable the check. michael@0: * @param parser line parser to use. michael@0: * @param headerLines List of header lines. This list will be used to store michael@0: * intermediate results. This makes it possible to resume parsing of michael@0: * headers in case of a {@link java.io.InterruptedIOException}. michael@0: * michael@0: * @return array of HTTP headers michael@0: * michael@0: * @throws IOException in case of an I/O error michael@0: * @throws HttpException in case of HTTP protocol violation michael@0: * michael@0: * @since 4.1 michael@0: */ michael@0: public static Header[] parseHeaders( michael@0: final SessionInputBuffer inbuffer, michael@0: int maxHeaderCount, michael@0: int maxLineLen, michael@0: final LineParser parser, michael@0: final List headerLines) michael@0: throws HttpException, IOException { michael@0: michael@0: if (inbuffer == null) { michael@0: throw new IllegalArgumentException("Session input buffer may not be null"); michael@0: } michael@0: if (parser == null) { michael@0: throw new IllegalArgumentException("Line parser may not be null"); michael@0: } michael@0: if (headerLines == null) { michael@0: throw new IllegalArgumentException("Header line list may not be null"); michael@0: } michael@0: michael@0: CharArrayBuffer current = null; michael@0: CharArrayBuffer previous = null; michael@0: for (;;) { michael@0: if (current == null) { michael@0: current = new CharArrayBuffer(64); michael@0: } else { michael@0: current.clear(); michael@0: } michael@0: int l = inbuffer.readLine(current); michael@0: if (l == -1 || current.length() < 1) { michael@0: break; michael@0: } michael@0: // Parse the header name and value michael@0: // Check for folded headers first michael@0: // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 michael@0: // discussion on folded headers michael@0: if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) { michael@0: // we have continuation folded header michael@0: // so append value michael@0: int i = 0; michael@0: while (i < current.length()) { michael@0: char ch = current.charAt(i); michael@0: if (ch != ' ' && ch != '\t') { michael@0: break; michael@0: } michael@0: i++; michael@0: } michael@0: if (maxLineLen > 0 michael@0: && previous.length() + 1 + current.length() - i > maxLineLen) { michael@0: throw new IOException("Maximum line length limit exceeded"); michael@0: } michael@0: previous.append(' '); michael@0: previous.append(current, i, current.length() - i); michael@0: } else { michael@0: headerLines.add(current); michael@0: previous = current; michael@0: current = null; michael@0: } michael@0: if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) { michael@0: throw new IOException("Maximum header count exceeded"); michael@0: } michael@0: } michael@0: Header[] headers = new Header[headerLines.size()]; michael@0: for (int i = 0; i < headerLines.size(); i++) { michael@0: CharArrayBuffer buffer = (CharArrayBuffer) headerLines.get(i); michael@0: try { michael@0: headers[i] = parser.parseHeader(buffer); michael@0: } catch (ParseException ex) { michael@0: throw new ProtocolException(ex.getMessage()); michael@0: } michael@0: } michael@0: return headers; michael@0: } michael@0: michael@0: /** michael@0: * Subclasses must override this method to generate an instance of michael@0: * {@link HttpMessage} based on the initial input from the session buffer. michael@0: *

michael@0: * Usually this method is expected to read just the very first line or michael@0: * the very first valid from the data stream and based on the input generate michael@0: * an appropriate instance of {@link HttpMessage}. michael@0: * michael@0: * @param sessionBuffer the session input buffer. michael@0: * @return HTTP message based on the input from the session buffer. michael@0: * @throws IOException in case of an I/O error. michael@0: * @throws HttpException in case of HTTP protocol violation. michael@0: * @throws ParseException in case of a parse error. michael@0: */ michael@0: protected abstract HttpMessage parseHead(SessionInputBuffer sessionBuffer) michael@0: throws IOException, HttpException, ParseException; michael@0: michael@0: public HttpMessage parse() throws IOException, HttpException { michael@0: int st = this.state; michael@0: switch (st) { michael@0: case HEAD_LINE: michael@0: try { michael@0: this.message = parseHead(this.sessionBuffer); michael@0: } catch (ParseException px) { michael@0: throw new ProtocolException(px.getMessage(), px); michael@0: } michael@0: this.state = HEADERS; michael@0: //$FALL-THROUGH$ michael@0: case HEADERS: michael@0: Header[] headers = AbstractMessageParser.parseHeaders( michael@0: this.sessionBuffer, michael@0: this.maxHeaderCount, michael@0: this.maxLineLen, michael@0: this.lineParser, michael@0: this.headerLines); michael@0: this.message.setHeaders(headers); michael@0: HttpMessage result = this.message; michael@0: this.message = null; michael@0: this.headerLines.clear(); michael@0: this.state = HEAD_LINE; michael@0: return result; michael@0: default: michael@0: throw new IllegalStateException("Inconsistent parser state"); michael@0: } michael@0: } michael@0: michael@0: }