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
32package org.apache.http.message;
33
34import org.apache.http.HttpVersion;
35import org.apache.http.ProtocolVersion;
36import org.apache.http.ParseException;
37import org.apache.http.RequestLine;
38import org.apache.http.StatusLine;
39import org.apache.http.Header;
40import org.apache.http.protocol.HTTP;
41import 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@apache.org">Jeff Dever</a>
61 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
62 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
63 * @author and others
64 *
65 * @deprecated Please use {@link java.net.URL#openConnection} instead.
66 *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
67 *     for further details.
68 */
69@Deprecated
70public class BasicLineParser implements LineParser {
71
72    /**
73     * A default instance of this class, for use as default or fallback.
74     * Note that {@link BasicLineParser} is not a singleton, there can
75     * be many instances of the class itself and of derived classes.
76     * The instance here provides non-customized, default behavior.
77     */
78    public final static BasicLineParser DEFAULT = new BasicLineParser();
79
80
81    /**
82     * A version of the protocol to parse.
83     * The version is typically not relevant, but the protocol name.
84     */
85    protected final ProtocolVersion protocol;
86
87
88    /**
89     * Creates a new line parser for the given HTTP-like protocol.
90     *
91     * @param proto     a version of the protocol to parse, or
92     *                  <code>null</code> for HTTP. The actual version
93     *                  is not relevant, only the protocol name.
94     */
95    public BasicLineParser(ProtocolVersion proto) {
96        if (proto == null) {
97            proto = HttpVersion.HTTP_1_1;
98        }
99        this.protocol = proto;
100    }
101
102
103    /**
104     * Creates a new line parser for HTTP.
105     */
106    public BasicLineParser() {
107        this(null);
108    }
109
110
111
112    public final static
113        ProtocolVersion parseProtocolVersion(String value,
114                                             LineParser parser)
115        throws ParseException {
116
117        if (value == null) {
118            throw new IllegalArgumentException
119                ("Value to parse may not be null.");
120        }
121
122        if (parser == null)
123            parser = BasicLineParser.DEFAULT;
124
125        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
126        buffer.append(value);
127        ParserCursor cursor = new ParserCursor(0, value.length());
128        return parser.parseProtocolVersion(buffer, cursor);
129    }
130
131
132    // non-javadoc, see interface LineParser
133    public ProtocolVersion parseProtocolVersion(final CharArrayBuffer buffer,
134                                                final ParserCursor cursor)
135        throws ParseException {
136
137        if (buffer == null) {
138            throw new IllegalArgumentException("Char array buffer may not be null");
139        }
140        if (cursor == null) {
141            throw new IllegalArgumentException("Parser cursor may not be null");
142        }
143
144        final String protoname = this.protocol.getProtocol();
145        final int protolength  = protoname.length();
146
147        int indexFrom = cursor.getPos();
148        int indexTo = cursor.getUpperBound();
149
150        skipWhitespace(buffer, cursor);
151
152        int i = cursor.getPos();
153
154        // long enough for "HTTP/1.1"?
155        if (i + protolength + 4 > indexTo) {
156            throw new ParseException
157                ("Not a valid protocol version: " +
158                 buffer.substring(indexFrom, indexTo));
159        }
160
161        // check the protocol name and slash
162        boolean ok = true;
163        for (int j=0; ok && (j<protolength); j++) {
164            ok = (buffer.charAt(i+j) == protoname.charAt(j));
165        }
166        if (ok) {
167            ok = (buffer.charAt(i+protolength) == '/');
168        }
169        if (!ok) {
170            throw new ParseException
171                ("Not a valid protocol version: " +
172                 buffer.substring(indexFrom, indexTo));
173        }
174
175        i += protolength+1;
176
177        int period = buffer.indexOf('.', i, indexTo);
178        if (period == -1) {
179            throw new ParseException
180                ("Invalid protocol version number: " +
181                 buffer.substring(indexFrom, indexTo));
182        }
183        int major;
184        try {
185            major = Integer.parseInt(buffer.substringTrimmed(i, period));
186        } catch (NumberFormatException e) {
187            throw new ParseException
188                ("Invalid protocol major version number: " +
189                 buffer.substring(indexFrom, indexTo));
190        }
191        i = period + 1;
192
193        int blank = buffer.indexOf(' ', i, indexTo);
194        if (blank == -1) {
195            blank = indexTo;
196        }
197        int minor;
198        try {
199            minor = Integer.parseInt(buffer.substringTrimmed(i, blank));
200        } catch (NumberFormatException e) {
201            throw new ParseException(
202                "Invalid protocol minor version number: " +
203                buffer.substring(indexFrom, indexTo));
204        }
205
206        cursor.updatePos(blank);
207
208        return createProtocolVersion(major, minor);
209
210    } // parseProtocolVersion
211
212
213    /**
214     * Creates a protocol version.
215     * Called from {@link #parseProtocolVersion}.
216     *
217     * @param major     the major version number, for example 1 in HTTP/1.0
218     * @param minor     the minor version number, for example 0 in HTTP/1.0
219     *
220     * @return  the protocol version
221     */
222    protected ProtocolVersion createProtocolVersion(int major, int minor) {
223        return protocol.forVersion(major, minor);
224    }
225
226
227
228    // non-javadoc, see interface LineParser
229    public boolean hasProtocolVersion(final CharArrayBuffer buffer,
230                                      final ParserCursor cursor) {
231
232        if (buffer == null) {
233            throw new IllegalArgumentException("Char array buffer may not be null");
234        }
235        if (cursor == null) {
236            throw new IllegalArgumentException("Parser cursor may not be null");
237        }
238        int index = cursor.getPos();
239
240        final String protoname = this.protocol.getProtocol();
241        final int  protolength = protoname.length();
242
243        if (buffer.length() < protolength+4)
244            return false; // not long enough for "HTTP/1.1"
245
246        if (index < 0) {
247            // end of line, no tolerance for trailing whitespace
248            // this works only for single-digit major and minor version
249            index = buffer.length() -4 -protolength;
250        } else if (index == 0) {
251            // beginning of line, tolerate leading whitespace
252            while ((index < buffer.length()) &&
253                    HTTP.isWhitespace(buffer.charAt(index))) {
254                 index++;
255             }
256        } // else within line, don't tolerate whitespace
257
258
259        if (index + protolength + 4 > buffer.length())
260            return false;
261
262
263        // just check protocol name and slash, no need to analyse the version
264        boolean ok = true;
265        for (int j=0; ok && (j<protolength); j++) {
266            ok = (buffer.charAt(index+j) == protoname.charAt(j));
267        }
268        if (ok) {
269            ok = (buffer.charAt(index+protolength) == '/');
270        }
271
272        return ok;
273    }
274
275
276
277    public final static
278        RequestLine parseRequestLine(final String value,
279                                     LineParser parser)
280        throws ParseException {
281
282        if (value == null) {
283            throw new IllegalArgumentException
284                ("Value to parse may not be null.");
285        }
286
287        if (parser == null)
288            parser = BasicLineParser.DEFAULT;
289
290        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
291        buffer.append(value);
292        ParserCursor cursor = new ParserCursor(0, value.length());
293        return parser.parseRequestLine(buffer, cursor);
294    }
295
296
297    /**
298     * Parses a request line.
299     *
300     * @param buffer    a buffer holding the line to parse
301     *
302     * @return  the parsed request line
303     *
304     * @throws ParseException        in case of a parse error
305     */
306    public RequestLine parseRequestLine(final CharArrayBuffer buffer,
307                                        final ParserCursor cursor)
308        throws ParseException {
309
310        if (buffer == null) {
311            throw new IllegalArgumentException("Char array buffer may not be null");
312        }
313        if (cursor == null) {
314            throw new IllegalArgumentException("Parser cursor may not be null");
315        }
316
317        int indexFrom = cursor.getPos();
318        int indexTo = cursor.getUpperBound();
319
320        try {
321            skipWhitespace(buffer, cursor);
322            int i = cursor.getPos();
323
324            int blank = buffer.indexOf(' ', i, indexTo);
325            if (blank < 0) {
326                throw new ParseException("Invalid request line: " +
327                        buffer.substring(indexFrom, indexTo));
328            }
329            String method = buffer.substringTrimmed(i, blank);
330            cursor.updatePos(blank);
331
332            skipWhitespace(buffer, cursor);
333            i = cursor.getPos();
334
335            blank = buffer.indexOf(' ', i, indexTo);
336            if (blank < 0) {
337                throw new ParseException("Invalid request line: " +
338                        buffer.substring(indexFrom, indexTo));
339            }
340            String uri = buffer.substringTrimmed(i, blank);
341            cursor.updatePos(blank);
342
343            ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
344
345            skipWhitespace(buffer, cursor);
346            if (!cursor.atEnd()) {
347                throw new ParseException("Invalid request line: " +
348                        buffer.substring(indexFrom, indexTo));
349            }
350
351            return createRequestLine(method, uri, ver);
352        } catch (IndexOutOfBoundsException e) {
353            throw new ParseException("Invalid request line: " +
354                                     buffer.substring(indexFrom, indexTo));
355        }
356    } // parseRequestLine
357
358
359    /**
360     * Instantiates a new request line.
361     * Called from {@link #parseRequestLine}.
362     *
363     * @param method    the request method
364     * @param uri       the requested URI
365     * @param ver       the protocol version
366     *
367     * @return  a new status line with the given data
368     */
369    protected RequestLine createRequestLine(final String method,
370                                            final String uri,
371                                            final ProtocolVersion ver) {
372        return new BasicRequestLine(method, uri, ver);
373    }
374
375
376
377    public final static
378        StatusLine parseStatusLine(final String value,
379                                   LineParser parser)
380        throws ParseException {
381
382        if (value == null) {
383            throw new IllegalArgumentException
384                ("Value to parse may not be null.");
385        }
386
387        if (parser == null)
388            parser = BasicLineParser.DEFAULT;
389
390        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
391        buffer.append(value);
392        ParserCursor cursor = new ParserCursor(0, value.length());
393        return parser.parseStatusLine(buffer, cursor);
394    }
395
396
397    // non-javadoc, see interface LineParser
398    public StatusLine parseStatusLine(final CharArrayBuffer buffer,
399                                      final ParserCursor cursor)
400        throws ParseException {
401
402        if (buffer == null) {
403            throw new IllegalArgumentException("Char array buffer may not be null");
404        }
405        if (cursor == null) {
406            throw new IllegalArgumentException("Parser cursor may not be null");
407        }
408
409        int indexFrom = cursor.getPos();
410        int indexTo = cursor.getUpperBound();
411
412        try {
413            // handle the HTTP-Version
414            ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
415
416            // handle the Status-Code
417            skipWhitespace(buffer, cursor);
418            int i = cursor.getPos();
419
420            int blank = buffer.indexOf(' ', i, indexTo);
421            if (blank < 0) {
422                blank = indexTo;
423            }
424            int statusCode = 0;
425            try {
426                statusCode =
427                    Integer.parseInt(buffer.substringTrimmed(i, blank));
428            } catch (NumberFormatException e) {
429                throw new ParseException(
430                    "Unable to parse status code from status line: "
431                    + buffer.substring(indexFrom, indexTo));
432            }
433            //handle the Reason-Phrase
434            i = blank;
435            String reasonPhrase = null;
436            if (i < indexTo) {
437                reasonPhrase = buffer.substringTrimmed(i, indexTo);
438            } else {
439                reasonPhrase = "";
440            }
441            return createStatusLine(ver, statusCode, reasonPhrase);
442
443        } catch (IndexOutOfBoundsException e) {
444            throw new ParseException("Invalid status line: " +
445                                     buffer.substring(indexFrom, indexTo));
446        }
447    } // parseStatusLine
448
449
450    /**
451     * Instantiates a new status line.
452     * Called from {@link #parseStatusLine}.
453     *
454     * @param ver       the protocol version
455     * @param status    the status code
456     * @param reason    the reason phrase
457     *
458     * @return  a new status line with the given data
459     */
460    protected StatusLine createStatusLine(final ProtocolVersion ver,
461                                          final int status,
462                                          final String reason) {
463        return new BasicStatusLine(ver, status, reason);
464    }
465
466
467
468    public final static
469        Header parseHeader(final String value,
470                           LineParser parser)
471        throws ParseException {
472
473        if (value == null) {
474            throw new IllegalArgumentException
475                ("Value to parse may not be null");
476        }
477
478        if (parser == null)
479            parser = BasicLineParser.DEFAULT;
480
481        CharArrayBuffer buffer = new CharArrayBuffer(value.length());
482        buffer.append(value);
483        return parser.parseHeader(buffer);
484    }
485
486
487    // non-javadoc, see interface LineParser
488    public Header parseHeader(CharArrayBuffer buffer)
489        throws ParseException {
490
491        // the actual parser code is in the constructor of BufferedHeader
492        return new BufferedHeader(buffer);
493    }
494
495
496    /**
497     * Helper to skip whitespace.
498     */
499    protected void skipWhitespace(final CharArrayBuffer buffer, final ParserCursor cursor) {
500        int pos = cursor.getPos();
501        int indexTo = cursor.getUpperBound();
502        while ((pos < indexTo) &&
503               HTTP.isWhitespace(buffer.charAt(pos))) {
504            pos++;
505        }
506        cursor.updatePos(pos);
507    }
508
509} // class BasicLineParser
510