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