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