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 readLen = inBuffer.readLine(current);
191 if (readLen == -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 }
284