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 }