• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicLineParser.java $
3  * $Revision: 591798 $
4  * $Date: 2007-11-04 08:19:29 -0800 (Sun, 04 Nov 2007) $
5  *
6  * ====================================================================
7  * Licensed to the Apache Software Foundation (ASF) under one
8  * or more contributor license agreements.  See the NOTICE file
9  * distributed with this work for additional information
10  * regarding copyright ownership.  The ASF licenses this file
11  * to you under the Apache License, Version 2.0 (the
12  * "License"); you may not use this file except in compliance
13  * with the License.  You may obtain a copy of the License at
14  *
15  *   http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing,
18  * software distributed under the License is distributed on an
19  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20  * KIND, either express or implied.  See the License for the
21  * specific language governing permissions and limitations
22  * under the License.
23  * ====================================================================
24  *
25  * This software consists of voluntary contributions made by many
26  * individuals on behalf of the Apache Software Foundation.  For more
27  * information on the Apache Software Foundation, please see
28  * <http://www.apache.org/>.
29  *
30  */
31 
32 package org.apache.http.message;
33 
34 import org.apache.http.HttpVersion;
35 import org.apache.http.ProtocolVersion;
36 import org.apache.http.ParseException;
37 import org.apache.http.RequestLine;
38 import org.apache.http.StatusLine;
39 import org.apache.http.Header;
40 import org.apache.http.protocol.HTTP;
41 import org.apache.http.util.CharArrayBuffer;
42 
43 
44 /**
45  * Basic parser for lines in the head section of an HTTP message.
46  * There are individual methods for parsing a request line, a
47  * status line, or a header line.
48  * The lines to parse are passed in memory, the parser does not depend
49  * on any specific IO mechanism.
50  * Instances of this class are stateless and thread-safe.
51  * Derived classes MUST maintain these properties.
52  *
53  * <p>
54  * Note: This class was created by refactoring parsing code located in
55  * various other classes. The author tags from those other classes have
56  * been replicated here, although the association with the parsing code
57  * taken from there has not been traced.
58  * </p>
59  *
60  * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
61  * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
62  * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
63  * @author and others
64  */
65 public class BasicLineParser implements LineParser {
66 
67     /**
68      * A default instance of this class, for use as default or fallback.
69      * Note that {@link BasicLineParser} is not a singleton, there can
70      * be many instances of the class itself and of derived classes.
71      * The instance here provides non-customized, default behavior.
72      */
73     public final static BasicLineParser DEFAULT = new BasicLineParser();
74 
75 
76     /**
77      * A version of the protocol to parse.
78      * The version is typically not relevant, but the protocol name.
79      */
80     protected final ProtocolVersion protocol;
81 
82 
83     /**
84      * Creates a new line parser for the given HTTP-like protocol.
85      *
86      * @param proto     a version of the protocol to parse, or
87      *                  <code>null</code> for HTTP. The actual version
88      *                  is not relevant, only the protocol name.
89      */
BasicLineParser(ProtocolVersion proto)90     public BasicLineParser(ProtocolVersion proto) {
91         if (proto == null) {
92             proto = HttpVersion.HTTP_1_1;
93         }
94         this.protocol = proto;
95     }
96 
97 
98     /**
99      * Creates a new line parser for HTTP.
100      */
BasicLineParser()101     public BasicLineParser() {
102         this(null);
103     }
104 
105 
106 
107     public final static
parseProtocolVersion(String value, LineParser parser)108         ProtocolVersion parseProtocolVersion(String value,
109                                              LineParser parser)
110         throws ParseException {
111 
112         if (value == null) {
113             throw new IllegalArgumentException
114                 ("Value to parse may not be null.");
115         }
116 
117         if (parser == null)
118             parser = BasicLineParser.DEFAULT;
119 
120         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
121         buffer.append(value);
122         ParserCursor cursor = new ParserCursor(0, value.length());
123         return parser.parseProtocolVersion(buffer, cursor);
124     }
125 
126 
127     // non-javadoc, see interface LineParser
parseProtocolVersion(final CharArrayBuffer buffer, final ParserCursor cursor)128     public ProtocolVersion parseProtocolVersion(final CharArrayBuffer buffer,
129                                                 final ParserCursor cursor)
130         throws ParseException {
131 
132         if (buffer == null) {
133             throw new IllegalArgumentException("Char array buffer may not be null");
134         }
135         if (cursor == null) {
136             throw new IllegalArgumentException("Parser cursor may not be null");
137         }
138 
139         final String protoname = this.protocol.getProtocol();
140         final int protolength  = protoname.length();
141 
142         int indexFrom = cursor.getPos();
143         int indexTo = cursor.getUpperBound();
144 
145         skipWhitespace(buffer, cursor);
146 
147         int i = cursor.getPos();
148 
149         // long enough for "HTTP/1.1"?
150         if (i + protolength + 4 > indexTo) {
151             throw new ParseException
152                 ("Not a valid protocol version: " +
153                  buffer.substring(indexFrom, indexTo));
154         }
155 
156         // check the protocol name and slash
157         boolean ok = true;
158         for (int j=0; ok && (j<protolength); j++) {
159             ok = (buffer.charAt(i+j) == protoname.charAt(j));
160         }
161         if (ok) {
162             ok = (buffer.charAt(i+protolength) == '/');
163         }
164         if (!ok) {
165             throw new ParseException
166                 ("Not a valid protocol version: " +
167                  buffer.substring(indexFrom, indexTo));
168         }
169 
170         i += protolength+1;
171 
172         int period = buffer.indexOf('.', i, indexTo);
173         if (period == -1) {
174             throw new ParseException
175                 ("Invalid protocol version number: " +
176                  buffer.substring(indexFrom, indexTo));
177         }
178         int major;
179         try {
180             major = Integer.parseInt(buffer.substringTrimmed(i, period));
181         } catch (NumberFormatException e) {
182             throw new ParseException
183                 ("Invalid protocol major version number: " +
184                  buffer.substring(indexFrom, indexTo));
185         }
186         i = period + 1;
187 
188         int blank = buffer.indexOf(' ', i, indexTo);
189         if (blank == -1) {
190             blank = indexTo;
191         }
192         int minor;
193         try {
194             minor = Integer.parseInt(buffer.substringTrimmed(i, blank));
195         } catch (NumberFormatException e) {
196             throw new ParseException(
197                 "Invalid protocol minor version number: " +
198                 buffer.substring(indexFrom, indexTo));
199         }
200 
201         cursor.updatePos(blank);
202 
203         return createProtocolVersion(major, minor);
204 
205     } // parseProtocolVersion
206 
207 
208     /**
209      * Creates a protocol version.
210      * Called from {@link #parseProtocolVersion}.
211      *
212      * @param major     the major version number, for example 1 in HTTP/1.0
213      * @param minor     the minor version number, for example 0 in HTTP/1.0
214      *
215      * @return  the protocol version
216      */
createProtocolVersion(int major, int minor)217     protected ProtocolVersion createProtocolVersion(int major, int minor) {
218         return protocol.forVersion(major, minor);
219     }
220 
221 
222 
223     // non-javadoc, see interface LineParser
hasProtocolVersion(final CharArrayBuffer buffer, final ParserCursor cursor)224     public boolean hasProtocolVersion(final CharArrayBuffer buffer,
225                                       final ParserCursor cursor) {
226 
227         if (buffer == null) {
228             throw new IllegalArgumentException("Char array buffer may not be null");
229         }
230         if (cursor == null) {
231             throw new IllegalArgumentException("Parser cursor may not be null");
232         }
233         int index = cursor.getPos();
234 
235         final String protoname = this.protocol.getProtocol();
236         final int  protolength = protoname.length();
237 
238         if (buffer.length() < protolength+4)
239             return false; // not long enough for "HTTP/1.1"
240 
241         if (index < 0) {
242             // end of line, no tolerance for trailing whitespace
243             // this works only for single-digit major and minor version
244             index = buffer.length() -4 -protolength;
245         } else if (index == 0) {
246             // beginning of line, tolerate leading whitespace
247             while ((index < buffer.length()) &&
248                     HTTP.isWhitespace(buffer.charAt(index))) {
249                  index++;
250              }
251         } // else within line, don't tolerate whitespace
252 
253 
254         if (index + protolength + 4 > buffer.length())
255             return false;
256 
257 
258         // just check protocol name and slash, no need to analyse the version
259         boolean ok = true;
260         for (int j=0; ok && (j<protolength); j++) {
261             ok = (buffer.charAt(index+j) == protoname.charAt(j));
262         }
263         if (ok) {
264             ok = (buffer.charAt(index+protolength) == '/');
265         }
266 
267         return ok;
268     }
269 
270 
271 
272     public final static
parseRequestLine(final String value, LineParser parser)273         RequestLine parseRequestLine(final String value,
274                                      LineParser parser)
275         throws ParseException {
276 
277         if (value == null) {
278             throw new IllegalArgumentException
279                 ("Value to parse may not be null.");
280         }
281 
282         if (parser == null)
283             parser = BasicLineParser.DEFAULT;
284 
285         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
286         buffer.append(value);
287         ParserCursor cursor = new ParserCursor(0, value.length());
288         return parser.parseRequestLine(buffer, cursor);
289     }
290 
291 
292     /**
293      * Parses a request line.
294      *
295      * @param buffer    a buffer holding the line to parse
296      *
297      * @return  the parsed request line
298      *
299      * @throws ParseException        in case of a parse error
300      */
parseRequestLine(final CharArrayBuffer buffer, final ParserCursor cursor)301     public RequestLine parseRequestLine(final CharArrayBuffer buffer,
302                                         final ParserCursor cursor)
303         throws ParseException {
304 
305         if (buffer == null) {
306             throw new IllegalArgumentException("Char array buffer may not be null");
307         }
308         if (cursor == null) {
309             throw new IllegalArgumentException("Parser cursor may not be null");
310         }
311 
312         int indexFrom = cursor.getPos();
313         int indexTo = cursor.getUpperBound();
314 
315         try {
316             skipWhitespace(buffer, cursor);
317             int i = cursor.getPos();
318 
319             int blank = buffer.indexOf(' ', i, indexTo);
320             if (blank < 0) {
321                 throw new ParseException("Invalid request line: " +
322                         buffer.substring(indexFrom, indexTo));
323             }
324             String method = buffer.substringTrimmed(i, blank);
325             cursor.updatePos(blank);
326 
327             skipWhitespace(buffer, cursor);
328             i = cursor.getPos();
329 
330             blank = buffer.indexOf(' ', i, indexTo);
331             if (blank < 0) {
332                 throw new ParseException("Invalid request line: " +
333                         buffer.substring(indexFrom, indexTo));
334             }
335             String uri = buffer.substringTrimmed(i, blank);
336             cursor.updatePos(blank);
337 
338             ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
339 
340             skipWhitespace(buffer, cursor);
341             if (!cursor.atEnd()) {
342                 throw new ParseException("Invalid request line: " +
343                         buffer.substring(indexFrom, indexTo));
344             }
345 
346             return createRequestLine(method, uri, ver);
347         } catch (IndexOutOfBoundsException e) {
348             throw new ParseException("Invalid request line: " +
349                                      buffer.substring(indexFrom, indexTo));
350         }
351     } // parseRequestLine
352 
353 
354     /**
355      * Instantiates a new request line.
356      * Called from {@link #parseRequestLine}.
357      *
358      * @param method    the request method
359      * @param uri       the requested URI
360      * @param ver       the protocol version
361      *
362      * @return  a new status line with the given data
363      */
createRequestLine(final String method, final String uri, final ProtocolVersion ver)364     protected RequestLine createRequestLine(final String method,
365                                             final String uri,
366                                             final ProtocolVersion ver) {
367         return new BasicRequestLine(method, uri, ver);
368     }
369 
370 
371 
372     public final static
parseStatusLine(final String value, LineParser parser)373         StatusLine parseStatusLine(final String value,
374                                    LineParser parser)
375         throws ParseException {
376 
377         if (value == null) {
378             throw new IllegalArgumentException
379                 ("Value to parse may not be null.");
380         }
381 
382         if (parser == null)
383             parser = BasicLineParser.DEFAULT;
384 
385         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
386         buffer.append(value);
387         ParserCursor cursor = new ParserCursor(0, value.length());
388         return parser.parseStatusLine(buffer, cursor);
389     }
390 
391 
392     // non-javadoc, see interface LineParser
parseStatusLine(final CharArrayBuffer buffer, final ParserCursor cursor)393     public StatusLine parseStatusLine(final CharArrayBuffer buffer,
394                                       final ParserCursor cursor)
395         throws ParseException {
396 
397         if (buffer == null) {
398             throw new IllegalArgumentException("Char array buffer may not be null");
399         }
400         if (cursor == null) {
401             throw new IllegalArgumentException("Parser cursor may not be null");
402         }
403 
404         int indexFrom = cursor.getPos();
405         int indexTo = cursor.getUpperBound();
406 
407         try {
408             // handle the HTTP-Version
409             ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
410 
411             // handle the Status-Code
412             skipWhitespace(buffer, cursor);
413             int i = cursor.getPos();
414 
415             int blank = buffer.indexOf(' ', i, indexTo);
416             if (blank < 0) {
417                 blank = indexTo;
418             }
419             int statusCode = 0;
420             try {
421                 statusCode =
422                     Integer.parseInt(buffer.substringTrimmed(i, blank));
423             } catch (NumberFormatException e) {
424                 throw new ParseException(
425                     "Unable to parse status code from status line: "
426                     + buffer.substring(indexFrom, indexTo));
427             }
428             //handle the Reason-Phrase
429             i = blank;
430             String reasonPhrase = null;
431             if (i < indexTo) {
432                 reasonPhrase = buffer.substringTrimmed(i, indexTo);
433             } else {
434                 reasonPhrase = "";
435             }
436             return createStatusLine(ver, statusCode, reasonPhrase);
437 
438         } catch (IndexOutOfBoundsException e) {
439             throw new ParseException("Invalid status line: " +
440                                      buffer.substring(indexFrom, indexTo));
441         }
442     } // parseStatusLine
443 
444 
445     /**
446      * Instantiates a new status line.
447      * Called from {@link #parseStatusLine}.
448      *
449      * @param ver       the protocol version
450      * @param status    the status code
451      * @param reason    the reason phrase
452      *
453      * @return  a new status line with the given data
454      */
createStatusLine(final ProtocolVersion ver, final int status, final String reason)455     protected StatusLine createStatusLine(final ProtocolVersion ver,
456                                           final int status,
457                                           final String reason) {
458         return new BasicStatusLine(ver, status, reason);
459     }
460 
461 
462 
463     public final static
parseHeader(final String value, LineParser parser)464         Header parseHeader(final String value,
465                            LineParser parser)
466         throws ParseException {
467 
468         if (value == null) {
469             throw new IllegalArgumentException
470                 ("Value to parse may not be null");
471         }
472 
473         if (parser == null)
474             parser = BasicLineParser.DEFAULT;
475 
476         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
477         buffer.append(value);
478         return parser.parseHeader(buffer);
479     }
480 
481 
482     // non-javadoc, see interface LineParser
parseHeader(CharArrayBuffer buffer)483     public Header parseHeader(CharArrayBuffer buffer)
484         throws ParseException {
485 
486         // the actual parser code is in the constructor of BufferedHeader
487         return new BufferedHeader(buffer);
488     }
489 
490 
491     /**
492      * Helper to skip whitespace.
493      */
skipWhitespace(final CharArrayBuffer buffer, final ParserCursor cursor)494     protected void skipWhitespace(final CharArrayBuffer buffer, final ParserCursor cursor) {
495         int pos = cursor.getPos();
496         int indexTo = cursor.getUpperBound();
497         while ((pos < indexTo) &&
498                HTTP.isWhitespace(buffer.charAt(pos))) {
499             pos++;
500         }
501         cursor.updatePos(pos);
502     }
503 
504 } // class BasicLineParser
505