001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-main/src/main/java/org/apache/http/message/BasicLineParser.java $
003: * $Revision: 591798 $
004: * $Date: 2007-11-04 17:19:29 +0100 (Sun, 04 Nov 2007) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031:
032: package org.apache.http.message;
033:
034: import org.apache.http.HttpVersion;
035: import org.apache.http.ProtocolVersion;
036: import org.apache.http.ParseException;
037: import org.apache.http.RequestLine;
038: import org.apache.http.StatusLine;
039: import org.apache.http.Header;
040: import org.apache.http.protocol.HTTP;
041: import org.apache.http.util.CharArrayBuffer;
042:
043: /**
044: * Basic parser for lines in the head section of an HTTP message.
045: * There are individual methods for parsing a request line, a
046: * status line, or a header line.
047: * The lines to parse are passed in memory, the parser does not depend
048: * on any specific IO mechanism.
049: * Instances of this class are stateless and thread-safe.
050: * Derived classes MUST maintain these properties.
051: *
052: * <p>
053: * Note: This class was created by refactoring parsing code located in
054: * various other classes. The author tags from those other classes have
055: * been replicated here, although the association with the parsing code
056: * taken from there has not been traced.
057: * </p>
058: *
059: * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
060: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
061: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
062: * @author and others
063: */
064: public class BasicLineParser implements LineParser {
065:
066: /**
067: * A default instance of this class, for use as default or fallback.
068: * Note that {@link BasicLineParser} is not a singleton, there can
069: * be many instances of the class itself and of derived classes.
070: * The instance here provides non-customized, default behavior.
071: */
072: public final static BasicLineParser DEFAULT = new BasicLineParser();
073:
074: /**
075: * A version of the protocol to parse.
076: * The version is typically not relevant, but the protocol name.
077: */
078: protected final ProtocolVersion protocol;
079:
080: /**
081: * Creates a new line parser for the given HTTP-like protocol.
082: *
083: * @param proto a version of the protocol to parse, or
084: * <code>null</code> for HTTP. The actual version
085: * is not relevant, only the protocol name.
086: */
087: public BasicLineParser(ProtocolVersion proto) {
088: if (proto == null) {
089: proto = HttpVersion.HTTP_1_1;
090: }
091: this .protocol = proto;
092: }
093:
094: /**
095: * Creates a new line parser for HTTP.
096: */
097: public BasicLineParser() {
098: this (null);
099: }
100:
101: public final static ProtocolVersion parseProtocolVersion(
102: String value, LineParser parser) throws ParseException {
103:
104: if (value == null) {
105: throw new IllegalArgumentException(
106: "Value to parse may not be null.");
107: }
108:
109: if (parser == null)
110: parser = BasicLineParser.DEFAULT;
111:
112: CharArrayBuffer buffer = new CharArrayBuffer(value.length());
113: buffer.append(value);
114: ParserCursor cursor = new ParserCursor(0, value.length());
115: return parser.parseProtocolVersion(buffer, cursor);
116: }
117:
118: // non-javadoc, see interface LineParser
119: public ProtocolVersion parseProtocolVersion(
120: final CharArrayBuffer buffer, final ParserCursor cursor)
121: throws ParseException {
122:
123: if (buffer == null) {
124: throw new IllegalArgumentException(
125: "Char array buffer may not be null");
126: }
127: if (cursor == null) {
128: throw new IllegalArgumentException(
129: "Parser cursor may not be null");
130: }
131:
132: final String protoname = this .protocol.getProtocol();
133: final int protolength = protoname.length();
134:
135: int indexFrom = cursor.getPos();
136: int indexTo = cursor.getUpperBound();
137:
138: skipWhitespace(buffer, cursor);
139:
140: int i = cursor.getPos();
141:
142: // long enough for "HTTP/1.1"?
143: if (i + protolength + 4 > indexTo) {
144: throw new ParseException("Not a valid protocol version: "
145: + buffer.substring(indexFrom, indexTo));
146: }
147:
148: // check the protocol name and slash
149: boolean ok = true;
150: for (int j = 0; ok && (j < protolength); j++) {
151: ok = (buffer.charAt(i + j) == protoname.charAt(j));
152: }
153: if (ok) {
154: ok = (buffer.charAt(i + protolength) == '/');
155: }
156: if (!ok) {
157: throw new ParseException("Not a valid protocol version: "
158: + buffer.substring(indexFrom, indexTo));
159: }
160:
161: i += protolength + 1;
162:
163: int period = buffer.indexOf('.', i, indexTo);
164: if (period == -1) {
165: throw new ParseException(
166: "Invalid protocol version number: "
167: + buffer.substring(indexFrom, indexTo));
168: }
169: int major;
170: try {
171: major = Integer
172: .parseInt(buffer.substringTrimmed(i, period));
173: } catch (NumberFormatException e) {
174: throw new ParseException(
175: "Invalid protocol major version number: "
176: + buffer.substring(indexFrom, indexTo));
177: }
178: i = period + 1;
179:
180: int blank = buffer.indexOf(' ', i, indexTo);
181: if (blank == -1) {
182: blank = indexTo;
183: }
184: int minor;
185: try {
186: minor = Integer.parseInt(buffer.substringTrimmed(i, blank));
187: } catch (NumberFormatException e) {
188: throw new ParseException(
189: "Invalid protocol minor version number: "
190: + buffer.substring(indexFrom, indexTo));
191: }
192:
193: cursor.updatePos(blank);
194:
195: return createProtocolVersion(major, minor);
196:
197: } // parseProtocolVersion
198:
199: /**
200: * Creates a protocol version.
201: * Called from {@link #parseProtocolVersion}.
202: *
203: * @param major the major version number, for example 1 in HTTP/1.0
204: * @param minor the minor version number, for example 0 in HTTP/1.0
205: *
206: * @return the protocol version
207: */
208: protected ProtocolVersion createProtocolVersion(int major, int minor) {
209: return protocol.forVersion(major, minor);
210: }
211:
212: // non-javadoc, see interface LineParser
213: public boolean hasProtocolVersion(final CharArrayBuffer buffer,
214: final ParserCursor cursor) {
215:
216: if (buffer == null) {
217: throw new IllegalArgumentException(
218: "Char array buffer may not be null");
219: }
220: if (cursor == null) {
221: throw new IllegalArgumentException(
222: "Parser cursor may not be null");
223: }
224: int index = cursor.getPos();
225:
226: final String protoname = this .protocol.getProtocol();
227: final int protolength = protoname.length();
228:
229: if (buffer.length() < protolength + 4)
230: return false; // not long enough for "HTTP/1.1"
231:
232: if (index < 0) {
233: // end of line, no tolerance for trailing whitespace
234: // this works only for single-digit major and minor version
235: index = buffer.length() - 4 - protolength;
236: } else if (index == 0) {
237: // beginning of line, tolerate leading whitespace
238: while ((index < buffer.length())
239: && HTTP.isWhitespace(buffer.charAt(index))) {
240: index++;
241: }
242: } // else within line, don't tolerate whitespace
243:
244: if (index + protolength + 4 > buffer.length())
245: return false;
246:
247: // just check protocol name and slash, no need to analyse the version
248: boolean ok = true;
249: for (int j = 0; ok && (j < protolength); j++) {
250: ok = (buffer.charAt(index + j) == protoname.charAt(j));
251: }
252: if (ok) {
253: ok = (buffer.charAt(index + protolength) == '/');
254: }
255:
256: return ok;
257: }
258:
259: public final static RequestLine parseRequestLine(
260: final String value, LineParser parser)
261: throws ParseException {
262:
263: if (value == null) {
264: throw new IllegalArgumentException(
265: "Value to parse may not be null.");
266: }
267:
268: if (parser == null)
269: parser = BasicLineParser.DEFAULT;
270:
271: CharArrayBuffer buffer = new CharArrayBuffer(value.length());
272: buffer.append(value);
273: ParserCursor cursor = new ParserCursor(0, value.length());
274: return parser.parseRequestLine(buffer, cursor);
275: }
276:
277: /**
278: * Parses a request line.
279: *
280: * @param buffer a buffer holding the line to parse
281: *
282: * @return the parsed request line
283: *
284: * @throws ParseException in case of a parse error
285: */
286: public RequestLine parseRequestLine(final CharArrayBuffer buffer,
287: final ParserCursor cursor) throws ParseException {
288:
289: if (buffer == null) {
290: throw new IllegalArgumentException(
291: "Char array buffer may not be null");
292: }
293: if (cursor == null) {
294: throw new IllegalArgumentException(
295: "Parser cursor may not be null");
296: }
297:
298: int indexFrom = cursor.getPos();
299: int indexTo = cursor.getUpperBound();
300:
301: try {
302: skipWhitespace(buffer, cursor);
303: int i = cursor.getPos();
304:
305: int blank = buffer.indexOf(' ', i, indexTo);
306: if (blank < 0) {
307: throw new ParseException("Invalid request line: "
308: + buffer.substring(indexFrom, indexTo));
309: }
310: String method = buffer.substringTrimmed(i, blank);
311: cursor.updatePos(blank);
312:
313: skipWhitespace(buffer, cursor);
314: i = cursor.getPos();
315:
316: blank = buffer.indexOf(' ', i, indexTo);
317: if (blank < 0) {
318: throw new ParseException("Invalid request line: "
319: + buffer.substring(indexFrom, indexTo));
320: }
321: String uri = buffer.substringTrimmed(i, blank);
322: cursor.updatePos(blank);
323:
324: ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
325:
326: skipWhitespace(buffer, cursor);
327: if (!cursor.atEnd()) {
328: throw new ParseException("Invalid request line: "
329: + buffer.substring(indexFrom, indexTo));
330: }
331:
332: return createRequestLine(method, uri, ver);
333: } catch (IndexOutOfBoundsException e) {
334: throw new ParseException("Invalid request line: "
335: + buffer.substring(indexFrom, indexTo));
336: }
337: } // parseRequestLine
338:
339: /**
340: * Instantiates a new request line.
341: * Called from {@link #parseRequestLine}.
342: *
343: * @param method the request method
344: * @param uri the requested URI
345: * @param ver the protocol version
346: *
347: * @return a new status line with the given data
348: */
349: protected RequestLine createRequestLine(final String method,
350: final String uri, final ProtocolVersion ver) {
351: return new BasicRequestLine(method, uri, ver);
352: }
353:
354: public final static StatusLine parseStatusLine(final String value,
355: LineParser parser) throws ParseException {
356:
357: if (value == null) {
358: throw new IllegalArgumentException(
359: "Value to parse may not be null.");
360: }
361:
362: if (parser == null)
363: parser = BasicLineParser.DEFAULT;
364:
365: CharArrayBuffer buffer = new CharArrayBuffer(value.length());
366: buffer.append(value);
367: ParserCursor cursor = new ParserCursor(0, value.length());
368: return parser.parseStatusLine(buffer, cursor);
369: }
370:
371: // non-javadoc, see interface LineParser
372: public StatusLine parseStatusLine(final CharArrayBuffer buffer,
373: final ParserCursor cursor) throws ParseException {
374:
375: if (buffer == null) {
376: throw new IllegalArgumentException(
377: "Char array buffer may not be null");
378: }
379: if (cursor == null) {
380: throw new IllegalArgumentException(
381: "Parser cursor may not be null");
382: }
383:
384: int indexFrom = cursor.getPos();
385: int indexTo = cursor.getUpperBound();
386:
387: try {
388: // handle the HTTP-Version
389: ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
390:
391: // handle the Status-Code
392: skipWhitespace(buffer, cursor);
393: int i = cursor.getPos();
394:
395: int blank = buffer.indexOf(' ', i, indexTo);
396: if (blank < 0) {
397: blank = indexTo;
398: }
399: int statusCode = 0;
400: try {
401: statusCode = Integer.parseInt(buffer.substringTrimmed(
402: i, blank));
403: } catch (NumberFormatException e) {
404: throw new ParseException(
405: "Unable to parse status code from status line: "
406: + buffer.substring(indexFrom, indexTo));
407: }
408: //handle the Reason-Phrase
409: i = blank;
410: String reasonPhrase = null;
411: if (i < indexTo) {
412: reasonPhrase = buffer.substringTrimmed(i, indexTo);
413: } else {
414: reasonPhrase = "";
415: }
416: return createStatusLine(ver, statusCode, reasonPhrase);
417:
418: } catch (IndexOutOfBoundsException e) {
419: throw new ParseException("Invalid status line: "
420: + buffer.substring(indexFrom, indexTo));
421: }
422: } // parseStatusLine
423:
424: /**
425: * Instantiates a new status line.
426: * Called from {@link #parseStatusLine}.
427: *
428: * @param ver the protocol version
429: * @param status the status code
430: * @param reason the reason phrase
431: *
432: * @return a new status line with the given data
433: */
434: protected StatusLine createStatusLine(final ProtocolVersion ver,
435: final int status, final String reason) {
436: return new BasicStatusLine(ver, status, reason);
437: }
438:
439: public final static Header parseHeader(final String value,
440: LineParser parser) throws ParseException {
441:
442: if (value == null) {
443: throw new IllegalArgumentException(
444: "Value to parse may not be null");
445: }
446:
447: if (parser == null)
448: parser = BasicLineParser.DEFAULT;
449:
450: CharArrayBuffer buffer = new CharArrayBuffer(value.length());
451: buffer.append(value);
452: return parser.parseHeader(buffer);
453: }
454:
455: // non-javadoc, see interface LineParser
456: public Header parseHeader(CharArrayBuffer buffer)
457: throws ParseException {
458:
459: // the actual parser code is in the constructor of BufferedHeader
460: return new BufferedHeader(buffer);
461: }
462:
463: /**
464: * Helper to skip whitespace.
465: */
466: protected void skipWhitespace(final CharArrayBuffer buffer,
467: final ParserCursor cursor) {
468: int pos = cursor.getPos();
469: int indexTo = cursor.getUpperBound();
470: while ((pos < indexTo) && HTTP.isWhitespace(buffer.charAt(pos))) {
471: pos++;
472: }
473: cursor.updatePos(pos);
474: }
475:
476: } // class BasicLineParser
|