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