View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.io;
29  
30  import java.io.IOException;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import org.apache.http.Header;
35  import org.apache.http.HttpException;
36  import org.apache.http.HttpMessage;
37  import org.apache.http.MessageConstraintException;
38  import org.apache.http.ParseException;
39  import org.apache.http.ProtocolException;
40  import org.apache.http.annotation.NotThreadSafe;
41  import org.apache.http.config.MessageConstraints;
42  import org.apache.http.io.HttpMessageParser;
43  import org.apache.http.io.SessionInputBuffer;
44  import org.apache.http.message.BasicLineParser;
45  import org.apache.http.message.LineParser;
46  import org.apache.http.params.HttpParamConfig;
47  import org.apache.http.params.HttpParams;
48  import org.apache.http.util.Args;
49  import org.apache.http.util.CharArrayBuffer;
50  
51  /**
52   * Abstract base class for HTTP message parsers that obtain input from
53   * an instance of {@link SessionInputBuffer}.
54   *
55   * @since 4.0
56   */
57  @SuppressWarnings("deprecation")
58  @NotThreadSafe
59  public abstract class AbstractMessageParser<T extends HttpMessage> implements HttpMessageParser<T> {
60  
61      private static final int HEAD_LINE    = 0;
62      private static final int HEADERS      = 1;
63  
64      private final SessionInputBuffer sessionBuffer;
65      private final MessageConstraints messageConstraints;
66      private final List<CharArrayBuffer> headerLines;
67      protected final LineParser lineParser;
68  
69      private int state;
70      private T message;
71  
72      /**
73       * Creates an instance of AbstractMessageParser.
74       *
75       * @param buffer the session input buffer.
76       * @param parser the line parser.
77       * @param params HTTP parameters.
78       *
79       * @deprecated (4.3) use {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer,
80       *   LineParser, MessageConstraints)}
81       */
82      @Deprecated
83      public AbstractMessageParser(
84              final SessionInputBuffer buffer,
85              final LineParser parser,
86              final HttpParams params) {
87          super();
88          Args.notNull(buffer, "Session input buffer");
89          Args.notNull(params, "HTTP parameters");
90          this.sessionBuffer = buffer;
91          this.messageConstraints = HttpParamConfig.getMessageConstraints(params);
92          this.lineParser = (parser != null) ? parser : BasicLineParser.INSTANCE;
93          this.headerLines = new ArrayList<CharArrayBuffer>();
94          this.state = HEAD_LINE;
95      }
96  
97      /**
98       * Creates new instance of AbstractMessageParser.
99       *
100      * @param buffer the session input buffer.
101      * @param lineParser the line parser. If <code>null</code> {@link BasicLineParser#INSTANCE}
102      *   will be used.
103      * @param constraints the message constraints. If <code>null</code>
104      *   {@link MessageConstraints#DEFAULT} will be used.
105      *
106      * @since 4.3
107      */
108     public AbstractMessageParser(
109             final SessionInputBuffer buffer,
110             final LineParser lineParser,
111             final MessageConstraints constraints) {
112         super();
113         this.sessionBuffer = Args.notNull(buffer, "Session input buffer");
114         this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE;
115         this.messageConstraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
116         this.headerLines = new ArrayList<CharArrayBuffer>();
117         this.state = HEAD_LINE;
118     }
119 
120     /**
121      * Parses HTTP headers from the data receiver stream according to the generic
122      * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
123      *
124      * @param inbuffer Session input buffer
125      * @param maxHeaderCount maximum number of headers allowed. If the number
126      *  of headers received from the data stream exceeds maxCount value, an
127      *  IOException will be thrown. Setting this parameter to a negative value
128      *  or zero will disable the check.
129      * @param maxLineLen maximum number of characters for a header line,
130      *  including the continuation lines. Setting this parameter to a negative
131      *  value or zero will disable the check.
132      * @return array of HTTP headers
133      * @param parser line parser to use. Can be <code>null</code>, in which case
134      *  the default implementation of this interface will be used.
135      *
136      * @throws IOException in case of an I/O error
137      * @throws HttpException in case of HTTP protocol violation
138      */
139     public static Header[] parseHeaders(
140             final SessionInputBuffer inbuffer,
141             final int maxHeaderCount,
142             final int maxLineLen,
143             final LineParser parser) throws HttpException, IOException {
144         final List<CharArrayBuffer> headerLines = new ArrayList<CharArrayBuffer>();
145         return parseHeaders(inbuffer, maxHeaderCount, maxLineLen,
146                 parser != null ? parser : BasicLineParser.INSTANCE,
147                 headerLines);
148     }
149 
150     /**
151      * Parses HTTP headers from the data receiver stream according to the generic
152      * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
153      *
154      * @param inbuffer Session input buffer
155      * @param maxHeaderCount maximum number of headers allowed. If the number
156      *  of headers received from the data stream exceeds maxCount value, an
157      *  IOException will be thrown. Setting this parameter to a negative value
158      *  or zero will disable the check.
159      * @param maxLineLen maximum number of characters for a header line,
160      *  including the continuation lines. Setting this parameter to a negative
161      *  value or zero will disable the check.
162      * @param parser line parser to use.
163      * @param headerLines List of header lines. This list will be used to store
164      *   intermediate results. This makes it possible to resume parsing of
165      *   headers in case of a {@link java.io.InterruptedIOException}.
166      *
167      * @return array of HTTP headers
168      *
169      * @throws IOException in case of an I/O error
170      * @throws HttpException in case of HTTP protocol violation
171      *
172      * @since 4.1
173      */
174     public static Header[] parseHeaders(
175             final SessionInputBuffer inbuffer,
176             final int maxHeaderCount,
177             final int maxLineLen,
178             final LineParser parser,
179             final List<CharArrayBuffer> headerLines) throws HttpException, IOException {
180         Args.notNull(inbuffer, "Session input buffer");
181         Args.notNull(parser, "Line parser");
182         Args.notNull(headerLines, "Header line list");
183 
184         CharArrayBuffer current = null;
185         CharArrayBuffer previous = null;
186         for (;;) {
187             if (current == null) {
188                 current = new CharArrayBuffer(64);
189             } else {
190                 current.clear();
191             }
192             final int l = inbuffer.readLine(current);
193             if (l == -1 || current.length() < 1) {
194                 break;
195             }
196             // Parse the header name and value
197             // Check for folded headers first
198             // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
199             // discussion on folded headers
200             if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) {
201                 // we have continuation folded header
202                 // so append value
203                 int i = 0;
204                 while (i < current.length()) {
205                     final char ch = current.charAt(i);
206                     if (ch != ' ' && ch != '\t') {
207                         break;
208                     }
209                     i++;
210                 }
211                 if (maxLineLen > 0
212                         && previous.length() + 1 + current.length() - i > maxLineLen) {
213                     throw new MessageConstraintException("Maximum line length limit exceeded");
214                 }
215                 previous.append(' ');
216                 previous.append(current, i, current.length() - i);
217             } else {
218                 headerLines.add(current);
219                 previous = current;
220                 current = null;
221             }
222             if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) {
223                 throw new MessageConstraintException("Maximum header count exceeded");
224             }
225         }
226         final Header[] headers = new Header[headerLines.size()];
227         for (int i = 0; i < headerLines.size(); i++) {
228             final CharArrayBuffer buffer = headerLines.get(i);
229             try {
230                 headers[i] = parser.parseHeader(buffer);
231             } catch (final ParseException ex) {
232                 throw new ProtocolException(ex.getMessage());
233             }
234         }
235         return headers;
236     }
237 
238     /**
239      * Subclasses must override this method to generate an instance of
240      * {@link HttpMessage} based on the initial input from the session buffer.
241      * <p>
242      * Usually this method is expected to read just the very first line or
243      * the very first valid from the data stream and based on the input generate
244      * an appropriate instance of {@link HttpMessage}.
245      *
246      * @param sessionBuffer the session input buffer.
247      * @return HTTP message based on the input from the session buffer.
248      * @throws IOException in case of an I/O error.
249      * @throws HttpException in case of HTTP protocol violation.
250      * @throws ParseException in case of a parse error.
251      */
252     protected abstract T parseHead(SessionInputBuffer sessionBuffer)
253         throws IOException, HttpException, ParseException;
254 
255     @Override
256     public T parse() throws IOException, HttpException {
257         final int st = this.state;
258         switch (st) {
259         case HEAD_LINE:
260             try {
261                 this.message = parseHead(this.sessionBuffer);
262             } catch (final ParseException px) {
263                 throw new ProtocolException(px.getMessage(), px);
264             }
265             this.state = HEADERS;
266             //$FALL-THROUGH$
267         case HEADERS:
268             final Header[] headers = AbstractMessageParser.parseHeaders(
269                     this.sessionBuffer,
270                     this.messageConstraints.getMaxHeaderCount(),
271                     this.messageConstraints.getMaxLineLength(),
272                     this.lineParser,
273                     this.headerLines);
274             this.message.setHeaders(headers);
275             final T result = this.message;
276             this.message = null;
277             this.headerLines.clear();
278             this.state = HEAD_LINE;
279             return result;
280         default:
281             throw new IllegalStateException("Inconsistent parser state");
282         }
283     }
284 
285 }