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.config.MessageConstraints;
41  import org.apache.http.io.HttpMessageParser;
42  import org.apache.http.io.SessionInputBuffer;
43  import org.apache.http.message.BasicLineParser;
44  import org.apache.http.message.LineParser;
45  import org.apache.http.params.HttpParamConfig;
46  import org.apache.http.params.HttpParams;
47  import org.apache.http.util.Args;
48  import org.apache.http.util.CharArrayBuffer;
49  
50  /**
51   * Abstract base class for HTTP message parsers that obtain input from
52   * an instance of {@link SessionInputBuffer}.
53   *
54   * @since 4.0
55   */
56  @SuppressWarnings("deprecation")
57  public abstract class AbstractMessageParser<T extends HttpMessage> implements HttpMessageParser<T> {
58  
59      private static final int HEAD_LINE    = 0;
60      private static final int HEADERS      = 1;
61  
62      private final SessionInputBuffer sessionBuffer;
63      private final MessageConstraints messageConstraints;
64      private final List<CharArrayBuffer> headerLines;
65      protected final LineParser lineParser;
66  
67      private int state;
68      private T message;
69  
70      /**
71       * Creates an instance of AbstractMessageParser.
72       *
73       * @param buffer the session input buffer.
74       * @param parser the line parser.
75       * @param params HTTP parameters.
76       *
77       * @deprecated (4.3) use {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer,
78       *   LineParser, MessageConstraints)}
79       */
80      @Deprecated
81      public AbstractMessageParser(
82              final SessionInputBuffer buffer,
83              final LineParser parser,
84              final HttpParams params) {
85          super();
86          Args.notNull(buffer, "Session input buffer");
87          Args.notNull(params, "HTTP parameters");
88          this.sessionBuffer = buffer;
89          this.messageConstraints = HttpParamConfig.getMessageConstraints(params);
90          this.lineParser = (parser != null) ? parser : BasicLineParser.INSTANCE;
91          this.headerLines = new ArrayList<CharArrayBuffer>();
92          this.state = HEAD_LINE;
93      }
94  
95      /**
96       * Creates new instance of AbstractMessageParser.
97       *
98       * @param buffer the session input buffer.
99       * @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 }