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