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