View Javadoc

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.message;
29  
30  import org.apache.http.HttpVersion;
31  import org.apache.http.ProtocolVersion;
32  import org.apache.http.ParseException;
33  import org.apache.http.RequestLine;
34  import org.apache.http.StatusLine;
35  import org.apache.http.Header;
36  import org.apache.http.annotation.Immutable;
37  import org.apache.http.protocol.HTTP;
38  import org.apache.http.util.CharArrayBuffer;
39  
40  /**
41   * Basic parser for lines in the head section of an HTTP message.
42   * There are individual methods for parsing a request line, a
43   * status line, or a header line.
44   * The lines to parse are passed in memory, the parser does not depend
45   * on any specific IO mechanism.
46   * Instances of this class are stateless and thread-safe.
47   * Derived classes MUST maintain these properties.
48   *
49   * <p>
50   * Note: This class was created by refactoring parsing code located in
51   * various other classes. The author tags from those other classes have
52   * been replicated here, although the association with the parsing code
53   * taken from there has not been traced.
54   * </p>
55   *
56   * @since 4.0
57   */
58  @Immutable
59  public class BasicLineParser implements LineParser {
60  
61      /**
62       * A default instance of this class, for use as default or fallback.
63       * Note that {@link BasicLineParser} is not a singleton, there can
64       * be many instances of the class itself and of derived classes.
65       * The instance here provides non-customized, default behavior.
66       */
67      public final static BasicLineParser DEFAULT = new BasicLineParser();
68  
69  
70      /**
71       * A version of the protocol to parse.
72       * The version is typically not relevant, but the protocol name.
73       */
74      protected final ProtocolVersion protocol;
75  
76  
77      /**
78       * Creates a new line parser for the given HTTP-like protocol.
79       *
80       * @param proto     a version of the protocol to parse, or
81       *                  <code>null</code> for HTTP. The actual version
82       *                  is not relevant, only the protocol name.
83       */
84      public BasicLineParser(ProtocolVersion proto) {
85          if (proto == null) {
86              proto = HttpVersion.HTTP_1_1;
87          }
88          this.protocol = proto;
89      }
90  
91  
92      /**
93       * Creates a new line parser for HTTP.
94       */
95      public BasicLineParser() {
96          this(null);
97      }
98  
99  
100     public final static
101         ProtocolVersion parseProtocolVersion(String value,
102                                              LineParser parser)
103         throws ParseException {
104 
105         if (value == null) {
106             throw new IllegalArgumentException
107                 ("Value to parse may not be null.");
108         }
109 
110         if (parser == null)
111             parser = BasicLineParser.DEFAULT;
112 
113         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
114         buffer.append(value);
115         ParserCursor cursor = new ParserCursor(0, value.length());
116         return parser.parseProtocolVersion(buffer, cursor);
117     }
118 
119 
120     // non-javadoc, see interface LineParser
121     public ProtocolVersion parseProtocolVersion(final CharArrayBuffer buffer,
122                                                 final ParserCursor cursor)
123         throws ParseException {
124 
125         if (buffer == null) {
126             throw new IllegalArgumentException("Char array buffer may not be null");
127         }
128         if (cursor == null) {
129             throw new IllegalArgumentException("Parser cursor may not be null");
130         }
131 
132         final String protoname = this.protocol.getProtocol();
133         final int protolength  = protoname.length();
134 
135         int indexFrom = cursor.getPos();
136         int indexTo = cursor.getUpperBound();
137 
138         skipWhitespace(buffer, cursor);
139 
140         int i = cursor.getPos();
141 
142         // long enough for "HTTP/1.1"?
143         if (i + protolength + 4 > indexTo) {
144             throw new ParseException
145                 ("Not a valid protocol version: " +
146                  buffer.substring(indexFrom, indexTo));
147         }
148 
149         // check the protocol name and slash
150         boolean ok = true;
151         for (int j=0; ok && (j<protolength); j++) {
152             ok = (buffer.charAt(i+j) == protoname.charAt(j));
153         }
154         if (ok) {
155             ok = (buffer.charAt(i+protolength) == '/');
156         }
157         if (!ok) {
158             throw new ParseException
159                 ("Not a valid protocol version: " +
160                  buffer.substring(indexFrom, indexTo));
161         }
162 
163         i += protolength+1;
164 
165         int period = buffer.indexOf('.', i, indexTo);
166         if (period == -1) {
167             throw new ParseException
168                 ("Invalid protocol version number: " +
169                  buffer.substring(indexFrom, indexTo));
170         }
171         int major;
172         try {
173             major = Integer.parseInt(buffer.substringTrimmed(i, period));
174         } catch (NumberFormatException e) {
175             throw new ParseException
176                 ("Invalid protocol major version number: " +
177                  buffer.substring(indexFrom, indexTo));
178         }
179         i = period + 1;
180 
181         int blank = buffer.indexOf(' ', i, indexTo);
182         if (blank == -1) {
183             blank = indexTo;
184         }
185         int minor;
186         try {
187             minor = Integer.parseInt(buffer.substringTrimmed(i, blank));
188         } catch (NumberFormatException e) {
189             throw new ParseException(
190                 "Invalid protocol minor version number: " +
191                 buffer.substring(indexFrom, indexTo));
192         }
193 
194         cursor.updatePos(blank);
195 
196         return createProtocolVersion(major, minor);
197 
198     } // parseProtocolVersion
199 
200 
201     /**
202      * Creates a protocol version.
203      * Called from {@link #parseProtocolVersion}.
204      *
205      * @param major     the major version number, for example 1 in HTTP/1.0
206      * @param minor     the minor version number, for example 0 in HTTP/1.0
207      *
208      * @return  the protocol version
209      */
210     protected ProtocolVersion createProtocolVersion(int major, int minor) {
211         return protocol.forVersion(major, minor);
212     }
213 
214 
215 
216     // non-javadoc, see interface LineParser
217     public boolean hasProtocolVersion(final CharArrayBuffer buffer,
218                                       final ParserCursor cursor) {
219 
220         if (buffer == null) {
221             throw new IllegalArgumentException("Char array buffer may not be null");
222         }
223         if (cursor == null) {
224             throw new IllegalArgumentException("Parser cursor may not be null");
225         }
226         int index = cursor.getPos();
227 
228         final String protoname = this.protocol.getProtocol();
229         final int  protolength = protoname.length();
230 
231         if (buffer.length() < protolength+4)
232             return false; // not long enough for "HTTP/1.1"
233 
234         if (index < 0) {
235             // end of line, no tolerance for trailing whitespace
236             // this works only for single-digit major and minor version
237             index = buffer.length() -4 -protolength;
238         } else if (index == 0) {
239             // beginning of line, tolerate leading whitespace
240             while ((index < buffer.length()) &&
241                     HTTP.isWhitespace(buffer.charAt(index))) {
242                  index++;
243              }
244         } // else within line, don't tolerate whitespace
245 
246 
247         if (index + protolength + 4 > buffer.length())
248             return false;
249 
250 
251         // just check protocol name and slash, no need to analyse the version
252         boolean ok = true;
253         for (int j=0; ok && (j<protolength); j++) {
254             ok = (buffer.charAt(index+j) == protoname.charAt(j));
255         }
256         if (ok) {
257             ok = (buffer.charAt(index+protolength) == '/');
258         }
259 
260         return ok;
261     }
262 
263 
264 
265     public final static
266         RequestLine parseRequestLine(final String value,
267                                      LineParser parser)
268         throws ParseException {
269 
270         if (value == null) {
271             throw new IllegalArgumentException
272                 ("Value to parse may not be null.");
273         }
274 
275         if (parser == null)
276             parser = BasicLineParser.DEFAULT;
277 
278         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
279         buffer.append(value);
280         ParserCursor cursor = new ParserCursor(0, value.length());
281         return parser.parseRequestLine(buffer, cursor);
282     }
283 
284 
285     /**
286      * Parses a request line.
287      *
288      * @param buffer    a buffer holding the line to parse
289      *
290      * @return  the parsed request line
291      *
292      * @throws ParseException        in case of a parse error
293      */
294     public RequestLine parseRequestLine(final CharArrayBuffer buffer,
295                                         final ParserCursor cursor)
296         throws ParseException {
297 
298         if (buffer == null) {
299             throw new IllegalArgumentException("Char array buffer may not be null");
300         }
301         if (cursor == null) {
302             throw new IllegalArgumentException("Parser cursor may not be null");
303         }
304 
305         int indexFrom = cursor.getPos();
306         int indexTo = cursor.getUpperBound();
307 
308         try {
309             skipWhitespace(buffer, cursor);
310             int i = cursor.getPos();
311 
312             int blank = buffer.indexOf(' ', i, indexTo);
313             if (blank < 0) {
314                 throw new ParseException("Invalid request line: " +
315                         buffer.substring(indexFrom, indexTo));
316             }
317             String method = buffer.substringTrimmed(i, blank);
318             cursor.updatePos(blank);
319 
320             skipWhitespace(buffer, cursor);
321             i = cursor.getPos();
322 
323             blank = buffer.indexOf(' ', i, indexTo);
324             if (blank < 0) {
325                 throw new ParseException("Invalid request line: " +
326                         buffer.substring(indexFrom, indexTo));
327             }
328             String uri = buffer.substringTrimmed(i, blank);
329             cursor.updatePos(blank);
330 
331             ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
332 
333             skipWhitespace(buffer, cursor);
334             if (!cursor.atEnd()) {
335                 throw new ParseException("Invalid request line: " +
336                         buffer.substring(indexFrom, indexTo));
337             }
338 
339             return createRequestLine(method, uri, ver);
340         } catch (IndexOutOfBoundsException e) {
341             throw new ParseException("Invalid request line: " +
342                                      buffer.substring(indexFrom, indexTo));
343         }
344     } // parseRequestLine
345 
346 
347     /**
348      * Instantiates a new request line.
349      * Called from {@link #parseRequestLine}.
350      *
351      * @param method    the request method
352      * @param uri       the requested URI
353      * @param ver       the protocol version
354      *
355      * @return  a new status line with the given data
356      */
357     protected RequestLine createRequestLine(final String method,
358                                             final String uri,
359                                             final ProtocolVersion ver) {
360         return new BasicRequestLine(method, uri, ver);
361     }
362 
363 
364 
365     public final static
366         StatusLine parseStatusLine(final String value,
367                                    LineParser parser)
368         throws ParseException {
369 
370         if (value == null) {
371             throw new IllegalArgumentException
372                 ("Value to parse may not be null.");
373         }
374 
375         if (parser == null)
376             parser = BasicLineParser.DEFAULT;
377 
378         CharArrayBuffer buffer = new CharArrayBuffer(value.length());
379         buffer.append(value);
380         ParserCursor cursor = new ParserCursor(0, value.length());
381         return parser.parseStatusLine(buffer, cursor);
382     }
383 
384 
385     // non-javadoc, see interface LineParser
386     public StatusLine parseStatusLine(final CharArrayBuffer buffer,
387                                       final ParserCursor cursor)
388         throws ParseException {
389 
390         if (buffer == null) {
391             throw new IllegalArgumentException("Char array buffer may not be null");
392         }
393         if (cursor == null) {
394             throw new IllegalArgumentException("Parser cursor may not be null");
395         }
396 
397         int indexFrom = cursor.getPos();
398         int indexTo = cursor.getUpperBound();
399 
400         try {
401             // handle the HTTP-Version
402             ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
403 
404             // handle the Status-Code
405             skipWhitespace(buffer, cursor);
406             int i = cursor.getPos();
407 
408             int blank = buffer.indexOf(' ', i, indexTo);
409             if (blank < 0) {
410                 blank = indexTo;
411             }
412             int statusCode = 0;
413             String s = buffer.substringTrimmed(i, blank);
414             for (int j = 0; j < s.length(); j++) {
415                 if (!Character.isDigit(s.charAt(j))) {
416                     throw new ParseException(
417                             "Status line contains invalid status code: "
418                             + buffer.substring(indexFrom, indexTo));
419                 }
420             }
421             try {
422                 statusCode = Integer.parseInt(s);
423             } catch (NumberFormatException e) {
424                 throw new ParseException(
425                         "Status line contains invalid status code: "
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      */
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
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
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      */
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