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.ParseException;
38  import org.apache.http.ProtocolException;
39  import org.apache.http.annotation.NotThreadSafe;
40  import org.apache.http.io.HttpMessageParser;
41  import org.apache.http.io.SessionInputBuffer;
42  import org.apache.http.message.LineParser;
43  import org.apache.http.message.BasicLineParser;
44  import org.apache.http.params.CoreConnectionPNames;
45  import org.apache.http.params.HttpParams;
46  import org.apache.http.util.CharArrayBuffer;
47  
48  /**
49   * Abstract base class for HTTP message parsers that obtain input from
50   * an instance of {@link SessionInputBuffer}.
51   * <p>
52   * The following parameters can be used to customize the behavior of this
53   * class:
54   * <ul>
55   *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
56   *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
57   * </ul>
58   *
59   * @since 4.0
60   */
61  @NotThreadSafe
62  public abstract class AbstractMessageParser<T extends HttpMessage> implements HttpMessageParser<T> {
63  
64      private static final int HEAD_LINE    = 0;
65      private static final int HEADERS      = 1;
66  
67      private final SessionInputBuffer sessionBuffer;
68      private final int maxHeaderCount;
69      private final int maxLineLen;
70      private final List<CharArrayBuffer> headerLines;
71      protected final LineParser lineParser;
72  
73      private int state;
74      private T message;
75  
76      /**
77       * Creates an instance of this class.
78       *
79       * @param buffer the session input buffer.
80       * @param parser the line parser.
81       * @param params HTTP parameters.
82       */
83      public AbstractMessageParser(
84              final SessionInputBuffer buffer,
85              final LineParser parser,
86              final HttpParams params) {
87          super();
88          if (buffer == null) {
89              throw new IllegalArgumentException("Session input buffer may not be null");
90          }
91          if (params == null) {
92              throw new IllegalArgumentException("HTTP parameters may not be null");
93          }
94          this.sessionBuffer = buffer;
95          this.maxHeaderCount = params.getIntParameter(
96                  CoreConnectionPNames.MAX_HEADER_COUNT, -1);
97          this.maxLineLen = params.getIntParameter(
98                  CoreConnectionPNames.MAX_LINE_LENGTH, -1);
99          this.lineParser = (parser != null) ? parser : BasicLineParser.DEFAULT;
100         this.headerLines = new ArrayList<CharArrayBuffer>();
101         this.state = HEAD_LINE;
102     }
103 
104     /**
105      * Parses HTTP headers from the data receiver stream according to the generic
106      * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
107      *
108      * @param inbuffer Session input buffer
109      * @param maxHeaderCount maximum number of headers allowed. If the number
110      *  of headers received from the data stream exceeds maxCount value, an
111      *  IOException will be thrown. Setting this parameter to a negative value
112      *  or zero will disable the check.
113      * @param maxLineLen maximum number of characters for a header line,
114      *  including the continuation lines. Setting this parameter to a negative
115      *  value or zero will disable the check.
116      * @return array of HTTP headers
117      * @param parser line parser to use. Can be <code>null</code>, in which case
118      *  the default implementation of this interface will be used.
119      *
120      * @throws IOException in case of an I/O error
121      * @throws HttpException in case of HTTP protocol violation
122      */
123     public static Header[] parseHeaders(
124             final SessionInputBuffer inbuffer,
125             int maxHeaderCount,
126             int maxLineLen,
127             LineParser parser)
128         throws HttpException, IOException {
129         if (parser == null) {
130             parser = BasicLineParser.DEFAULT;
131         }
132         List<CharArrayBuffer> headerLines = new ArrayList<CharArrayBuffer>();
133         return parseHeaders(inbuffer, maxHeaderCount, maxLineLen, parser, headerLines);
134     }
135 
136     /**
137      * Parses HTTP headers from the data receiver stream according to the generic
138      * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3.
139      *
140      * @param inbuffer Session input buffer
141      * @param maxHeaderCount maximum number of headers allowed. If the number
142      *  of headers received from the data stream exceeds maxCount value, an
143      *  IOException will be thrown. Setting this parameter to a negative value
144      *  or zero will disable the check.
145      * @param maxLineLen maximum number of characters for a header line,
146      *  including the continuation lines. Setting this parameter to a negative
147      *  value or zero will disable the check.
148      * @param parser line parser to use.
149      * @param headerLines List of header lines. This list will be used to store
150      *   intermediate results. This makes it possible to resume parsing of
151      *   headers in case of a {@link java.io.InterruptedIOException}.
152      *
153      * @return array of HTTP headers
154      *
155      * @throws IOException in case of an I/O error
156      * @throws HttpException in case of HTTP protocol violation
157      *
158      * @since 4.1
159      */
160     public static Header[] parseHeaders(
161             final SessionInputBuffer inbuffer,
162             int maxHeaderCount,
163             int maxLineLen,
164             final LineParser parser,
165             final List<CharArrayBuffer> headerLines)
166         throws HttpException, IOException {
167 
168         if (inbuffer == null) {
169             throw new IllegalArgumentException("Session input buffer may not be null");
170         }
171         if (parser == null) {
172             throw new IllegalArgumentException("Line parser may not be null");
173         }
174         if (headerLines == null) {
175             throw new IllegalArgumentException("Header line list may not be null");
176         }
177 
178         CharArrayBuffer current = null;
179         CharArrayBuffer previous = null;
180         for (;;) {
181             if (current == null) {
182                 current = new CharArrayBuffer(64);
183             } else {
184                 current.clear();
185             }
186             int l = inbuffer.readLine(current);
187             if (l == -1 || current.length() < 1) {
188                 break;
189             }
190             // Parse the header name and value
191             // Check for folded headers first
192             // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
193             // discussion on folded headers
194             if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) {
195                 // we have continuation folded header
196                 // so append value
197                 int i = 0;
198                 while (i < current.length()) {
199                     char ch = current.charAt(i);
200                     if (ch != ' ' && ch != '\t') {
201                         break;
202                     }
203                     i++;
204                 }
205                 if (maxLineLen > 0
206                         && previous.length() + 1 + current.length() - i > maxLineLen) {
207                     throw new IOException("Maximum line length limit exceeded");
208                 }
209                 previous.append(' ');
210                 previous.append(current, i, current.length() - i);
211             } else {
212                 headerLines.add(current);
213                 previous = current;
214                 current = null;
215             }
216             if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) {
217                 throw new IOException("Maximum header count exceeded");
218             }
219         }
220         Header[] headers = new Header[headerLines.size()];
221         for (int i = 0; i < headerLines.size(); i++) {
222             CharArrayBuffer buffer = headerLines.get(i);
223             try {
224                 headers[i] = parser.parseHeader(buffer);
225             } catch (ParseException ex) {
226                 throw new ProtocolException(ex.getMessage());
227             }
228         }
229         return headers;
230     }
231 
232     /**
233      * Subclasses must override this method to generate an instance of
234      * {@link HttpMessage} based on the initial input from the session buffer.
235      * <p>
236      * Usually this method is expected to read just the very first line or
237      * the very first valid from the data stream and based on the input generate
238      * an appropriate instance of {@link HttpMessage}.
239      *
240      * @param sessionBuffer the session input buffer.
241      * @return HTTP message based on the input from the session buffer.
242      * @throws IOException in case of an I/O error.
243      * @throws HttpException in case of HTTP protocol violation.
244      * @throws ParseException in case of a parse error.
245      */
246     protected abstract T parseHead(SessionInputBuffer sessionBuffer)
247         throws IOException, HttpException, ParseException;
248 
249     public T parse() throws IOException, HttpException {
250         int st = this.state;
251         switch (st) {
252         case HEAD_LINE:
253             try {
254                 this.message = parseHead(this.sessionBuffer);
255             } catch (ParseException px) {
256                 throw new ProtocolException(px.getMessage(), px);
257             }
258             this.state = HEADERS;
259             //$FALL-THROUGH$
260         case HEADERS:
261             Header[] headers = AbstractMessageParser.parseHeaders(
262                     this.sessionBuffer,
263                     this.maxHeaderCount,
264                     this.maxLineLen,
265                     this.lineParser,
266                     this.headerLines);
267             this.message.setHeaders(headers);
268             T result = this.message;
269             this.message = null;
270             this.headerLines.clear();
271             this.state = HEAD_LINE;
272             return result;
273         default:
274             throw new IllegalStateException("Inconsistent parser state");
275         }
276     }
277 
278 }