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: * The following parameters can be used to customize the behavior of this michael@0: * class: michael@0: *
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: }