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