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