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