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