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             LineParser parser)
144         throws HttpException, IOException {
145         if (parser == null) {
146             parser = BasicLineParser.INSTANCE;
147         }
148         final List<CharArrayBuffer> headerLines = new ArrayList<CharArrayBuffer>();
149         return parseHeaders(inbuffer, maxHeaderCount, maxLineLen, parser, headerLines);
150     }
151 
152     /**
153      * Parses HTTP headers from the data receiver stream according to the generic
154      * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
155      *
156      * @param inbuffer Session input buffer
157      * @param maxHeaderCount maximum number of headers allowed. If the number
158      *  of headers received from the data stream exceeds maxCount value, an
159      *  IOException will be thrown. Setting this parameter to a negative value
160      *  or zero will disable the check.
161      * @param maxLineLen maximum number of characters for a header line,
162      *  including the continuation lines. Setting this parameter to a negative
163      *  value or zero will disable the check.
164      * @param parser line parser to use.
165      * @param headerLines List of header lines. This list will be used to store
166      *   intermediate results. This makes it possible to resume parsing of
167      *   headers in case of a {@link java.io.InterruptedIOException}.
168      *
169      * @return array of HTTP headers
170      *
171      * @throws IOException in case of an I/O error
172      * @throws HttpException in case of HTTP protocol violation
173      *
174      * @since 4.1
175      */
176     public static Header[] parseHeaders(
177             final SessionInputBuffer inbuffer,
178             final int maxHeaderCount,
179             final int maxLineLen,
180             final LineParser parser,
181             final List<CharArrayBuffer> headerLines) throws HttpException, IOException {
182         Args.notNull(inbuffer, "Session input buffer");
183         Args.notNull(parser, "Line parser");
184         Args.notNull(headerLines, "Header line list");
185 
186         CharArrayBuffer current = null;
187         CharArrayBuffer previous = null;
188         for (;;) {
189             if (current == null) {
190                 current = new CharArrayBuffer(64);
191             } else {
192                 current.clear();
193             }
194             final int l = inbuffer.readLine(current);
195             if (l == -1 || current.length() < 1) {
196                 break;
197             }
198             // Parse the header name and value
199             // Check for folded headers first
200             // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
201             // discussion on folded headers
202             if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) {
203                 // we have continuation folded header
204                 // so append value
205                 int i = 0;
206                 while (i < current.length()) {
207                     final char ch = current.charAt(i);
208                     if (ch != ' ' && ch != '\t') {
209                         break;
210                     }
211                     i++;
212                 }
213                 if (maxLineLen > 0
214                         && previous.length() + 1 + current.length() - i > maxLineLen) {
215                     throw new MessageConstraintException("Maximum line length limit exceeded");
216                 }
217                 previous.append(' ');
218                 previous.append(current, i, current.length() - i);
219             } else {
220                 headerLines.add(current);
221                 previous = current;
222                 current = null;
223             }
224             if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) {
225                 throw new MessageConstraintException("Maximum header count exceeded");
226             }
227         }
228         final Header[] headers = new Header[headerLines.size()];
229         for (int i = 0; i < headerLines.size(); i++) {
230             final CharArrayBuffer buffer = headerLines.get(i);
231             try {
232                 headers[i] = parser.parseHeader(buffer);
233             } catch (final ParseException ex) {
234                 throw new ProtocolException(ex.getMessage());
235             }
236         }
237         return headers;
238     }
239 
240     /**
241      * Subclasses must override this method to generate an instance of
242      * {@link HttpMessage} based on the initial input from the session buffer.
243      * <p>
244      * Usually this method is expected to read just the very first line or
245      * the very first valid from the data stream and based on the input generate
246      * an appropriate instance of {@link HttpMessage}.
247      *
248      * @param sessionBuffer the session input buffer.
249      * @return HTTP message based on the input from the session buffer.
250      * @throws IOException in case of an I/O error.
251      * @throws HttpException in case of HTTP protocol violation.
252      * @throws ParseException in case of a parse error.
253      */
254     protected abstract T parseHead(SessionInputBuffer sessionBuffer)
255         throws IOException, HttpException, ParseException;
256 
257     public T parse() throws IOException, HttpException {
258         final int st = this.state;
259         switch (st) {
260         case HEAD_LINE:
261             try {
262                 this.message = parseHead(this.sessionBuffer);
263             } catch (final ParseException px) {
264                 throw new ProtocolException(px.getMessage(), px);
265             }
266             this.state = HEADERS;
267             //$FALL-THROUGH$
268         case HEADERS:
269             final Header[] headers = AbstractMessageParser.parseHeaders(
270                     this.sessionBuffer,
271                     this.messageConstraints.getMaxHeaderCount(),
272                     this.messageConstraints.getMaxLineLength(),
273                     this.lineParser,
274                     this.headerLines);
275             this.message.setHeaders(headers);
276             final T result = this.message;
277             this.message = null;
278             this.headerLines.clear();
279             this.state = HEAD_LINE;
280             return result;
281         default:
282             throw new IllegalStateException("Inconsistent parser state");
283         }
284     }
285 
286 }