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 }