001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.io;
029
030import java.io.IOException;
031import java.util.ArrayList;
032import java.util.List;
033
034import org.apache.http.Header;
035import org.apache.http.HttpException;
036import org.apache.http.HttpMessage;
037import org.apache.http.MessageConstraintException;
038import org.apache.http.ParseException;
039import org.apache.http.ProtocolException;
040import org.apache.http.config.MessageConstraints;
041import org.apache.http.io.HttpMessageParser;
042import org.apache.http.io.SessionInputBuffer;
043import org.apache.http.message.BasicLineParser;
044import org.apache.http.message.LineParser;
045import org.apache.http.params.HttpParamConfig;
046import org.apache.http.params.HttpParams;
047import org.apache.http.util.Args;
048import org.apache.http.util.CharArrayBuffer;
049
050/**
051 * Abstract base class for HTTP message parsers that obtain input from
052 * an instance of {@link SessionInputBuffer}.
053 *
054 * @since 4.0
055 */
056@SuppressWarnings("deprecation")
057public abstract class AbstractMessageParser<T extends HttpMessage> implements HttpMessageParser<T> {
058
059    private static final int HEAD_LINE    = 0;
060    private static final int HEADERS      = 1;
061
062    private final SessionInputBuffer sessionBuffer;
063    private final MessageConstraints messageConstraints;
064    private final List<CharArrayBuffer> headerLines;
065    protected final LineParser lineParser;
066
067    private int state;
068    private T message;
069
070    /**
071     * Creates an instance of AbstractMessageParser.
072     *
073     * @param buffer the session input buffer.
074     * @param parser the line parser.
075     * @param params HTTP parameters.
076     *
077     * @deprecated (4.3) use {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer,
078     *   LineParser, MessageConstraints)}
079     */
080    @Deprecated
081    public AbstractMessageParser(
082            final SessionInputBuffer buffer,
083            final LineParser parser,
084            final HttpParams params) {
085        super();
086        Args.notNull(buffer, "Session input buffer");
087        Args.notNull(params, "HTTP parameters");
088        this.sessionBuffer = buffer;
089        this.messageConstraints = HttpParamConfig.getMessageConstraints(params);
090        this.lineParser = (parser != null) ? parser : BasicLineParser.INSTANCE;
091        this.headerLines = new ArrayList<CharArrayBuffer>();
092        this.state = HEAD_LINE;
093    }
094
095    /**
096     * Creates new instance of AbstractMessageParser.
097     *
098     * @param buffer the session input buffer.
099     * @param lineParser the line parser. If {@code null} {@link BasicLineParser#INSTANCE}
100     *   will be used.
101     * @param constraints the message constraints. If {@code null}
102     *   {@link MessageConstraints#DEFAULT} will be used.
103     *
104     * @since 4.3
105     */
106    public AbstractMessageParser(
107            final SessionInputBuffer buffer,
108            final LineParser lineParser,
109            final MessageConstraints constraints) {
110        super();
111        this.sessionBuffer = Args.notNull(buffer, "Session input buffer");
112        this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE;
113        this.messageConstraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
114        this.headerLines = new ArrayList<CharArrayBuffer>();
115        this.state = HEAD_LINE;
116    }
117
118    /**
119     * Parses HTTP headers from the data receiver stream according to the generic
120     * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
121     *
122     * @param inbuffer Session input buffer
123     * @param maxHeaderCount maximum number of headers allowed. If the number
124     *  of headers received from the data stream exceeds maxCount value, an
125     *  IOException will be thrown. Setting this parameter to a negative value
126     *  or zero will disable the check.
127     * @param maxLineLen maximum number of characters for a header line,
128     *  including the continuation lines. Setting this parameter to a negative
129     *  value or zero will disable the check.
130     * @return array of HTTP headers
131     * @param parser line parser to use. Can be {@code null}, in which case
132     *  the default implementation of this interface will be used.
133     *
134     * @throws IOException in case of an I/O error
135     * @throws HttpException in case of HTTP protocol violation
136     */
137    public static Header[] parseHeaders(
138            final SessionInputBuffer inbuffer,
139            final int maxHeaderCount,
140            final int maxLineLen,
141            final LineParser parser) throws HttpException, IOException {
142        final List<CharArrayBuffer> headerLines = new ArrayList<CharArrayBuffer>();
143        return parseHeaders(inbuffer, maxHeaderCount, maxLineLen,
144                parser != null ? parser : BasicLineParser.INSTANCE,
145                headerLines);
146    }
147
148    /**
149     * Parses HTTP headers from the data receiver stream according to the generic
150     * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
151     *
152     * @param inbuffer Session input buffer
153     * @param maxHeaderCount maximum number of headers allowed. If the number
154     *  of headers received from the data stream exceeds maxCount value, an
155     *  IOException will be thrown. Setting this parameter to a negative value
156     *  or zero will disable the check.
157     * @param maxLineLen maximum number of characters for a header line,
158     *  including the continuation lines. Setting this parameter to a negative
159     *  value or zero will disable the check.
160     * @param parser line parser to use.
161     * @param headerLines List of header lines. This list will be used to store
162     *   intermediate results. This makes it possible to resume parsing of
163     *   headers in case of a {@link java.io.InterruptedIOException}.
164     *
165     * @return array of HTTP headers
166     *
167     * @throws IOException in case of an I/O error
168     * @throws HttpException in case of HTTP protocol violation
169     *
170     * @since 4.1
171     */
172    public static Header[] parseHeaders(
173            final SessionInputBuffer inbuffer,
174            final int maxHeaderCount,
175            final int maxLineLen,
176            final LineParser parser,
177            final List<CharArrayBuffer> headerLines) throws HttpException, IOException {
178        Args.notNull(inbuffer, "Session input buffer");
179        Args.notNull(parser, "Line parser");
180        Args.notNull(headerLines, "Header line list");
181
182        CharArrayBuffer current = null;
183        CharArrayBuffer previous = null;
184        for (;;) {
185            if (current == null) {
186                current = new CharArrayBuffer(64);
187            } else {
188                current.clear();
189            }
190            final int l = inbuffer.readLine(current);
191            if (l == -1 || current.length() < 1) {
192                break;
193            }
194            // Parse the header name and value
195            // Check for folded headers first
196            // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
197            // discussion on folded headers
198            if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) {
199                // we have continuation folded header
200                // so append value
201                int i = 0;
202                while (i < current.length()) {
203                    final char ch = current.charAt(i);
204                    if (ch != ' ' && ch != '\t') {
205                        break;
206                    }
207                    i++;
208                }
209                if (maxLineLen > 0
210                        && previous.length() + 1 + current.length() - i > maxLineLen) {
211                    throw new MessageConstraintException("Maximum line length limit exceeded");
212                }
213                previous.append(' ');
214                previous.append(current, i, current.length() - i);
215            } else {
216                headerLines.add(current);
217                previous = current;
218                current = null;
219            }
220            if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) {
221                throw new MessageConstraintException("Maximum header count exceeded");
222            }
223        }
224        final Header[] headers = new Header[headerLines.size()];
225        for (int i = 0; i < headerLines.size(); i++) {
226            final CharArrayBuffer buffer = headerLines.get(i);
227            try {
228                headers[i] = parser.parseHeader(buffer);
229            } catch (final ParseException ex) {
230                throw new ProtocolException(ex.getMessage());
231            }
232        }
233        return headers;
234    }
235
236    /**
237     * Subclasses must override this method to generate an instance of
238     * {@link HttpMessage} based on the initial input from the session buffer.
239     * <p>
240     * Usually this method is expected to read just the very first line or
241     * the very first valid from the data stream and based on the input generate
242     * an appropriate instance of {@link HttpMessage}.
243     *
244     * @param sessionBuffer the session input buffer.
245     * @return HTTP message based on the input from the session buffer.
246     * @throws IOException in case of an I/O error.
247     * @throws HttpException in case of HTTP protocol violation.
248     * @throws ParseException in case of a parse error.
249     */
250    protected abstract T parseHead(SessionInputBuffer sessionBuffer)
251        throws IOException, HttpException, ParseException;
252
253    @Override
254    public T parse() throws IOException, HttpException {
255        final int st = this.state;
256        switch (st) {
257        case HEAD_LINE:
258            try {
259                this.message = parseHead(this.sessionBuffer);
260            } catch (final ParseException px) {
261                throw new ProtocolException(px.getMessage(), px);
262            }
263            this.state = HEADERS;
264            //$FALL-THROUGH$
265        case HEADERS:
266            final Header[] headers = AbstractMessageParser.parseHeaders(
267                    this.sessionBuffer,
268                    this.messageConstraints.getMaxHeaderCount(),
269                    this.messageConstraints.getMaxLineLength(),
270                    this.lineParser,
271                    this.headerLines);
272            this.message.setHeaders(headers);
273            final T result = this.message;
274            this.message = null;
275            this.headerLines.clear();
276            this.state = HEAD_LINE;
277            return result;
278        default:
279            throw new IllegalStateException("Inconsistent parser state");
280        }
281    }
282
283}