1 module hunt.http.codec.http.decode.HttpParser; 2 3 import hunt.http.codec.http.model; 4 import hunt.http.codec.http.hpack.HpackEncoder; 5 6 import hunt.http.HttpField; 7 import hunt.http.HttpHeader; 8 import hunt.http.HttpMethod; 9 import hunt.http.HttpRequest; 10 import hunt.http.HttpResponse; 11 import hunt.http.HttpStatus; 12 import hunt.http.HttpVersion; 13 import hunt.http.QuotedCSV; 14 15 import hunt.collection; 16 import hunt.Exceptions; 17 import hunt.logging; 18 import hunt.text.Common; 19 import hunt.util.StringBuilder; 20 import hunt.util.ConverterUtils; 21 import hunt.util.DateTime; 22 23 import core.time; 24 25 import std.algorithm; 26 import std.array; 27 import std.concurrency : initOnce; 28 import std.container.array; 29 import std.conv; 30 import std.string; 31 32 alias HttpParserState = HttpParser.State; 33 34 private bool contains(T)(T[] items, T item) { 35 return items.canFind(item); 36 } 37 38 39 /* ------------------------------------------------------------ */ 40 41 /** 42 * A Parser for 1.0 and 1.1 as defined by RFC7230 43 * <p> 44 * This parser parses HTTP client and server messages from buffers 45 * passed in the {@link #parseNext(ByteBuffer)} method. The parsed 46 * elements of the HTTP message are passed as event calls to the 47 * {@link HttpHandler} instance the parser is constructed with. 48 * If the passed handler is a {@link RequestHandler} then server side 49 * parsing is performed and if it is a {@link ResponseHandler}, then 50 * client side parsing is done. 51 * </p> 52 * <p> 53 * The contract of the {@link HttpHandler} API is that if a call returns 54 * true then the call to {@link #parseNext(ByteBuffer)} will return as 55 * soon as possible also with a true response. Typically this indicates 56 * that the parsing has reached a stage where the caller should process 57 * the events accumulated by the handler. It is the preferred calling 58 * style that handling such as calling a servlet to process a request, 59 * should be done after a true return from {@link #parseNext(ByteBuffer)} 60 * rather than from within the scope of a call like 61 * {@link RequestHandler#messageComplete()} 62 * </p> 63 * <p> 64 * For performance, the parse is heavily dependent on the 65 * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a 66 * single pass for both the structure ( : and CRLF ) and semantic (which 67 * header and value) of a header. Specifically the static {@link HttpHeader#CACHE} 68 * is used to lookup common combinations of headers and values 69 * (eg. "Connection: close"), or just header names (eg. "Connection:" ). 70 * For headers who's value is not known statically (eg. Host, COOKIE) then a 71 * per parser dynamic Trie of {@link HttpFields} from previous parsed messages 72 * is used to help the parsing of subsequent messages. 73 * </p> 74 * <p> 75 * The parser can work in varying compliance modes: 76 * <dl> 77 * <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd> 78 * <dt>RFC2616</dt><dd>Wrapped headers and HTTP/0.9 supported</dd> 79 * <dt>LEGACY</dt><dd>(aka STRICT) Adherence to Servlet Specification requirement for 80 * exact case of header names, bypassing the header caches, which are case insensitive, 81 * otherwise equivalent to RFC2616</dd> 82 * </dl> 83 * 84 * @see <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a> 85 */ 86 class HttpParser { 87 88 enum INITIAL_URI_LENGTH = 256; 89 private MonoTime startTime; 90 91 /** 92 * Cache of common {@link HttpField}s including: <UL> 93 * <LI>Common static combinations such as:<UL> 94 * <li>Connection: close 95 * <li>Accept-Encoding: gzip 96 * <li>Content-Length: 0 97 * </ul> 98 * <li>Combinations of Content-Type header for common mime types by common charsets 99 * <li>Most common headers with null values so that a lookup will at least 100 * determine the header name even if the name:value combination is not cached 101 * </ul> 102 */ 103 // __gshared Trie!HttpField CACHE; 104 static Trie!HttpField CACHE() { 105 __gshared Trie!HttpField inst; 106 return initOnce!inst(initFieldCache()); 107 } 108 109 // States 110 enum FieldState { 111 FIELD, 112 IN_NAME, 113 VALUE, 114 IN_VALUE, 115 WS_AFTER_NAME, 116 } 117 118 // States 119 enum State { 120 START, 121 METHOD, 122 RESPONSE_VERSION, 123 SPACE1, 124 STATUS, 125 URI, 126 SPACE2, 127 REQUEST_VERSION, 128 REASON, 129 PROXY, 130 HEADER, 131 CONTENT, 132 EOF_CONTENT, 133 CHUNKED_CONTENT, 134 CHUNK_SIZE, 135 CHUNK_PARAMS, 136 CHUNK, 137 TRAILER, 138 END, 139 CLOSE, // The associated stream/endpoint should be closed 140 CLOSED // The associated stream/endpoint is at EOF 141 } 142 143 private static State[] __idleStates = [State.START, State.END, State.CLOSE, State.CLOSED]; 144 private static State[] __completeStates = [State.END, State.CLOSE, State.CLOSED]; 145 146 private HttpParsingHandler _handler; 147 private HttpRequestParsingHandler _requestHandler; 148 private HttpResponseParsingHandler _responseHandler; 149 private ComplianceParsingHandler _complianceHandler; 150 private int _maxHeaderBytes; 151 private HttpCompliance _compliance; 152 private HttpComplianceSection[] _compliances; 153 private HttpField _field; 154 private HttpHeader _header; 155 private string _headerString; 156 private string _valueString; 157 private int _responseStatus; 158 private int _headerBytes; 159 private bool _host; 160 private bool _headerComplete; 161 162 /* ------------------------------------------------------------------------------- */ 163 private State _state = State.START; 164 private FieldState _fieldState = FieldState.FIELD; 165 private bool _eof; 166 private HttpMethod _method; 167 private string _methodString; 168 private HttpVersion _version; 169 private StringBuilder _uri; 170 private EndOfContent _endOfContent; 171 private long _contentLength = -1; 172 private long _contentPosition; 173 private int _chunkLength; 174 private int _chunkPosition; 175 private bool _headResponse; 176 private bool _cr; 177 private ByteBuffer _contentChunk; 178 private Trie!HttpField _fieldCache; 179 180 private int _length; 181 private StringBuilder _string; 182 183 /* ------------------------------------------------------------------------------- */ 184 this(HttpRequestParsingHandler handler) { 185 this(handler, -1, getCompliance()); 186 } 187 188 /* ------------------------------------------------------------------------------- */ 189 this(HttpResponseParsingHandler handler) { 190 this(handler, -1, getCompliance()); 191 } 192 193 /* ------------------------------------------------------------------------------- */ 194 this(HttpRequestParsingHandler handler, int maxHeaderBytes) { 195 this(handler, maxHeaderBytes, getCompliance()); 196 } 197 198 /* ------------------------------------------------------------------------------- */ 199 this(HttpResponseParsingHandler handler, int maxHeaderBytes) { 200 this(handler, maxHeaderBytes, getCompliance()); 201 } 202 203 /* ------------------------------------------------------------------------------- */ 204 this(HttpRequestParsingHandler handler, HttpCompliance compliance) { 205 this(handler, -1, compliance); 206 } 207 208 /* ------------------------------------------------------------------------------- */ 209 this(HttpRequestParsingHandler handler, int maxHeaderBytes, HttpCompliance compliance) { 210 this(handler, null, maxHeaderBytes, compliance is null ? getCompliance() : compliance); 211 } 212 213 /* ------------------------------------------------------------------------------- */ 214 this(HttpResponseParsingHandler handler, int maxHeaderBytes, HttpCompliance compliance) { 215 this(null, handler, maxHeaderBytes, compliance is null ? getCompliance() : compliance); 216 } 217 218 /* ------------------------------------------------------------------------------- */ 219 private this(HttpRequestParsingHandler requestHandler, HttpResponseParsingHandler responseHandler, 220 int maxHeaderBytes, HttpCompliance compliance) { 221 version (HUNT_HTTP_DEBUG) { 222 trace("create http parser"); 223 } 224 _string = new StringBuilder(); 225 _uri = new StringBuilder(INITIAL_URI_LENGTH); 226 if(requestHandler !is null) 227 _handler = requestHandler; 228 else 229 _handler = responseHandler; 230 // _handler = requestHandler !is null ? cast()requestHandler : responseHandler; 231 _requestHandler = requestHandler; 232 _responseHandler = responseHandler; 233 _maxHeaderBytes = maxHeaderBytes; 234 _compliance = compliance; 235 _compliances = compliance.sections(); 236 _complianceHandler = cast(ComplianceParsingHandler)_handler; 237 } 238 239 240 private static Trie!HttpField initFieldCache() { 241 Trie!HttpField cache = new ArrayTrie!HttpField(2048); 242 cache.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE)); 243 cache.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE)); 244 cache.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.UPGRADE)); 245 cache.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip")); 246 cache.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")); 247 cache.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate, br")); 248 cache.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip,deflate,sdch")); 249 cache.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,en;q=0.5")); 250 cache.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-US;q=0.8,en;q=0.6")); 251 252 cache.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, 253 "en-AU,en;q=0.9,it-IT;q=0.8,it;q=0.7,en-GB;q=0.6,en-US;q=0.5")); 254 255 cache.put(new HttpField(HttpHeader.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.3")); 256 cache.put(new HttpField(HttpHeader.ACCEPT, "*/*")); 257 cache.put(new HttpField(HttpHeader.ACCEPT, "image/png,image/*;q=0.8,*/*;q=0.5")); 258 cache.put(new HttpField(HttpHeader.ACCEPT, 259 "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); 260 261 cache.put(new HttpField(HttpHeader.ACCEPT, 262 "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")); 263 264 cache.put(new HttpField(HttpHeader.ACCEPT_RANGES, HttpHeaderValue.BYTES)); 265 cache.put(new HttpField(HttpHeader.PRAGMA, "no-cache")); 266 267 cache.put(new HttpField(HttpHeader.CACHE_CONTROL, 268 "private, no-cache, no-cache=Set-Cookie, proxy-revalidate")); 269 270 cache.put(new HttpField(HttpHeader.CACHE_CONTROL, "no-cache")); 271 cache.put(new HttpField(HttpHeader.CACHE_CONTROL, "max-age=0")); 272 cache.put(new HttpField(HttpHeader.CONTENT_LENGTH, "0")); 273 cache.put(new HttpField(HttpHeader.CONTENT_ENCODING, "gzip")); 274 cache.put(new HttpField(HttpHeader.CONTENT_ENCODING, "deflate")); 275 cache.put(new HttpField(HttpHeader.TRANSFER_ENCODING, "chunked")); 276 cache.put(new HttpField(HttpHeader.EXPIRES, "Fri, 01 Jan 1990 00:00:00 GMT")); 277 278 // Add common Content types as fields 279 foreach (string type ; ["text/plain", "text/html", "text/xml", "text/json", 280 "application/json", "application/x-www-form-urlencoded"]) { 281 HttpField field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type); 282 cache.put(field); 283 284 foreach (string charset ; ["utf-8", "iso-8859-1"]) { 285 cache.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type ~ ";charset=" ~ charset)); 286 cache.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type ~ "; charset=" ~ charset)); 287 cache.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type ~ ";charset=" ~ charset.toUpper())); 288 cache.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type ~ "; charset=" ~ charset.toUpper())); 289 } 290 } 291 292 // Add headers with null values so HttpParser can avoid looking up name again for unknown values 293 foreach (HttpHeader h ; HttpHeader.values()) { 294 // trace(h.toString()); 295 if (!cache.put(new HttpField(h, cast(string) null))) { 296 // FIXME: Needing refactor or cleanup -@zxp at 9/25/2018, 8:11:29 PM 297 // 298 // warning(h.toString()); 299 // throw new IllegalStateException("CACHE FULL"); 300 } 301 } 302 303 // Add some more common headers 304 cache.put(new HttpField(HttpHeader.REFERER, cast(string) null)); 305 cache.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE, cast(string) null)); 306 cache.put(new HttpField(HttpHeader.IF_NONE_MATCH, cast(string) null)); 307 cache.put(new HttpField(HttpHeader.AUTHORIZATION, cast(string) null)); 308 cache.put(new HttpField(HttpHeader.COOKIE, cast(string) null)); 309 310 return cache; 311 } 312 313 private static HttpCompliance getCompliance() { 314 return HttpCompliance.RFC7230; 315 } 316 317 318 /* ------------------------------------------------------------------------------- */ 319 HttpParsingHandler getHandler() { 320 return _handler; 321 } 322 323 /* ------------------------------------------------------------------------------- */ 324 325 /** 326 * Check RFC compliance violation 327 * 328 * @param violation The compliance section violation 329 * @param reason The reason for the violation 330 * @return True if the current compliance level is set so as to Not allow this violation 331 */ 332 protected bool complianceViolation(HttpComplianceSection violation, string reason) { 333 if (_compliances.contains(violation)) 334 return true; 335 336 if (_complianceHandler !is null) 337 _complianceHandler.onComplianceViolation(_compliance, violation, reason); 338 339 return false; 340 } 341 342 /* ------------------------------------------------------------------------------- */ 343 protected void handleViolation(HttpComplianceSection section, string reason) { 344 if (_complianceHandler !is null) 345 _complianceHandler.onComplianceViolation(_compliance, section, reason); 346 } 347 348 /* ------------------------------------------------------------------------------- */ 349 protected string caseInsensitiveHeader(string orig, string normative) { 350 if (_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)) 351 return normative; 352 if (!orig.equals(normative)) 353 handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, orig); 354 return orig; 355 } 356 357 /* ------------------------------------------------------------------------------- */ 358 long getContentLength() { 359 return _contentLength; 360 } 361 362 /* ------------------------------------------------------------ */ 363 long getContentRead() { 364 return _contentPosition; 365 } 366 367 /* ------------------------------------------------------------ */ 368 369 /** 370 * Set if a HEAD response is expected 371 * 372 * @param head true if head response is expected 373 */ 374 void setHeadResponse(bool head) { 375 _headResponse = head; 376 } 377 378 /* ------------------------------------------------------------------------------- */ 379 protected void setResponseStatus(int status) { 380 _responseStatus = status; 381 } 382 383 /* ------------------------------------------------------------------------------- */ 384 State getState() { 385 return _state; 386 } 387 388 /* ------------------------------------------------------------------------------- */ 389 bool inContentState() { 390 return _state >= State.CONTENT && _state < State.END; 391 } 392 393 /* ------------------------------------------------------------------------------- */ 394 bool inHeaderState() { 395 return _state < State.CONTENT; 396 } 397 398 /* ------------------------------------------------------------------------------- */ 399 bool isChunking() { 400 return _endOfContent == EndOfContent.CHUNKED_CONTENT; 401 } 402 403 /* ------------------------------------------------------------ */ 404 bool isStart() { 405 return isState(State.START); 406 } 407 408 /* ------------------------------------------------------------ */ 409 bool isClose() { 410 return isState(State.CLOSE); 411 } 412 413 /* ------------------------------------------------------------ */ 414 bool isClosed() { 415 return isState(State.CLOSED); 416 } 417 418 /* ------------------------------------------------------------ */ 419 bool isIdle() { 420 return __idleStates.contains(_state); 421 } 422 423 /* ------------------------------------------------------------ */ 424 bool isComplete() { 425 return __completeStates.contains(_state); 426 } 427 428 /* ------------------------------------------------------------------------------- */ 429 bool isState(State state) { 430 return _state == state; 431 } 432 433 /* ------------------------------------------------------------------------------- */ 434 enum CharState { 435 ILLEGAL, CR, LF, LEGAL 436 } 437 438 private __gshared CharState[] __charState; 439 440 shared static this() { 441 // token = 1*tchar 442 // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" 443 // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" 444 // / DIGIT / ALPHA 445 // ; any VCHAR, except delimiters 446 // quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE 447 // qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text 448 // obs-text = %x80-FF 449 // comment = "(" *( ctext / quoted-pair / comment ) ")" 450 // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text 451 // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) 452 453 __charState = new CharState[256]; 454 __charState[0..$] = CharState.ILLEGAL; 455 // Arrays.fill(__charState, CharState.ILLEGAL); 456 __charState[HttpTokens.LINE_FEED] = CharState.LF; 457 __charState[HttpTokens.CARRIAGE_RETURN] = CharState.CR; 458 __charState[HttpTokens.TAB] = CharState.LEGAL; 459 __charState[HttpTokens.SPACE] = CharState.LEGAL; 460 461 __charState['!'] = CharState.LEGAL; 462 __charState['#'] = CharState.LEGAL; 463 __charState['$'] = CharState.LEGAL; 464 __charState['%'] = CharState.LEGAL; 465 __charState['&'] = CharState.LEGAL; 466 __charState['\''] = CharState.LEGAL; 467 __charState['*'] = CharState.LEGAL; 468 __charState['+'] = CharState.LEGAL; 469 __charState['-'] = CharState.LEGAL; 470 __charState['.'] = CharState.LEGAL; 471 __charState['^'] = CharState.LEGAL; 472 __charState['_'] = CharState.LEGAL; 473 __charState['`'] = CharState.LEGAL; 474 __charState['|'] = CharState.LEGAL; 475 __charState['~'] = CharState.LEGAL; 476 477 __charState['"'] = CharState.LEGAL; 478 479 __charState['\\'] = CharState.LEGAL; 480 __charState['('] = CharState.LEGAL; 481 __charState[')'] = CharState.LEGAL; 482 __charState[0x21 .. 0x27 + 1] = CharState.LEGAL; 483 __charState[0x2A .. 0x5B + 1] = CharState.LEGAL; 484 __charState[0x5D .. 0x7E + 1] = CharState.LEGAL; 485 __charState[0x80 .. 0xFF + 1] = CharState.LEGAL; 486 } 487 488 /* ------------------------------------------------------------------------------- */ 489 private byte next(ByteBuffer buffer) { 490 byte ch = buffer.get(); 491 492 CharState s = __charState[0xff & ch]; 493 switch (s) { 494 case CharState.ILLEGAL: 495 throw new IllegalCharacterException(_state, ch, buffer); 496 497 case CharState.LF: 498 _cr = false; 499 break; 500 501 case CharState.CR: 502 if (_cr) 503 throw new BadMessageException("Bad EOL"); 504 505 _cr = true; 506 if (buffer.hasRemaining()) { 507 // Don't count the CRs and LFs of the chunked encoding. 508 if (_maxHeaderBytes > 0 && (_state == State.HEADER || _state == State.TRAILER)) 509 _headerBytes++; 510 return next(buffer); 511 } 512 513 // Can return 0 here to indicate the need for more characters, 514 // because a real 0 in the buffer would cause a BadMessage below 515 return 0; 516 517 case CharState.LEGAL: 518 if (_cr) 519 throw new BadMessageException("Bad EOL"); 520 break; 521 522 default: 523 break; 524 } 525 526 return ch; 527 } 528 529 /* ------------------------------------------------------------------------------- */ 530 /* Quick lookahead for the start state looking for a request method or a HTTP version, 531 * otherwise skip white space until something else to parse. 532 */ 533 private bool quickStart(ByteBuffer buffer) { 534 if (_requestHandler !is null) { 535 _method = HttpMethod.lookAheadGet(buffer); 536 if (_method != HttpMethod.Null) { 537 _methodString = _method.asString(); 538 buffer.position(cast(int)(buffer.position() + _methodString.length + 1)); 539 540 setState(State.SPACE1); 541 return false; 542 } 543 } else if (_responseHandler !is null) { 544 _version = HttpVersion.lookAheadGet(buffer); 545 if (_version != HttpVersion.Null) { 546 buffer.position(cast(int) (buffer.position() + _version.asString().length + 1)); 547 setState(State.SPACE1); 548 return false; 549 } 550 } 551 552 // Quick start look 553 while (_state == State.START && buffer.hasRemaining()) { 554 int ch = next(buffer); 555 556 if (ch > HttpTokens.SPACE) { 557 _string.setLength(0); 558 _string.append(cast(char) ch); 559 setState(_requestHandler !is null ? State.METHOD : State.RESPONSE_VERSION); 560 return false; 561 } else if (ch == 0) 562 break; 563 else if (ch < 0) 564 throw new BadMessageException(); 565 566 // count this white space as a header byte to avoid DOS 567 if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) { 568 warningf("padding is too large >%d", _maxHeaderBytes); 569 throw new BadMessageException(HttpStatus.BAD_REQUEST_400); 570 } 571 } 572 return false; 573 } 574 575 /* ------------------------------------------------------------------------------- */ 576 private void setString(string s) { 577 _string.setLength(0); 578 _string.append(s); 579 _length = cast(int)s.length; 580 } 581 582 /* ------------------------------------------------------------------------------- */ 583 private string takeString() { 584 _string.setLength(_length); 585 string s = _string.toString(); 586 _string.setLength(0); 587 _length = -1; 588 return s; 589 } 590 591 /* ------------------------------------------------------------------------------- */ 592 private bool handleHeaderContentMessage() { 593 version (HUNT_HTTP_DEBUG_MORE) trace("handling headers ..."); 594 bool handle_header = _handler.headerComplete(); 595 _headerComplete = true; 596 version (HUNT_HTTP_DEBUG_MORE) trace("handling content ..."); 597 bool handle_content = _handler.contentComplete(); 598 version (HUNT_HTTP_DEBUG_MORE) trace("handling message ..."); 599 bool handle_message = _handler.messageComplete(); 600 return handle_header || handle_content || handle_message; 601 } 602 603 /* ------------------------------------------------------------------------------- */ 604 private bool handleContentMessage() { 605 bool handle_content = _handler.contentComplete(); 606 bool handle_message = _handler.messageComplete(); 607 return handle_content || handle_message; 608 } 609 610 /* ------------------------------------------------------------------------------- */ 611 /* Parse a request or response line 612 */ 613 private bool parseLine(ByteBuffer buffer) { 614 bool handle = false; 615 616 // Process headers 617 while (_state < State.HEADER && buffer.hasRemaining() && !handle) { 618 // process each character 619 byte b = next(buffer); 620 if (b == 0) 621 break; 622 623 if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) { 624 if (_state == State.URI) { 625 warningf("URI is too large >%d", _maxHeaderBytes); 626 throw new BadMessageException(HttpStatus.URI_TOO_LONG_414); 627 } else { 628 if (_requestHandler !is null) 629 warningf("request is too large >%d", _maxHeaderBytes); 630 else 631 warningf("response is too large >%d", _maxHeaderBytes); 632 throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431); 633 } 634 } 635 636 switch (_state) { 637 case State.METHOD: 638 if (b == HttpTokens.SPACE) { 639 _length = _string.length; 640 _methodString = takeString(); 641 642 if (_compliances.contains(HttpComplianceSection.METHOD_CASE_SENSITIVE)) { 643 HttpMethod method = HttpMethod.get(_methodString); 644 if (method != HttpMethod.Null) 645 _methodString = method.asString(); 646 } else { 647 HttpMethod method = HttpMethod.getInsensitive(_methodString); 648 649 if (method != HttpMethod.Null) { 650 if (method.asString() != (_methodString)) 651 handleViolation(HttpComplianceSection.METHOD_CASE_SENSITIVE, _methodString); 652 _methodString = method.asString(); 653 } 654 } 655 656 setState(State.SPACE1); 657 } else if (b < HttpTokens.SPACE) { 658 if (b == HttpTokens.LINE_FEED) 659 throw new BadMessageException("No URI"); 660 else 661 throw new IllegalCharacterException(_state, b, buffer); 662 } else 663 _string.append(cast(char) b); 664 break; 665 666 case State.RESPONSE_VERSION: 667 if (b == HttpTokens.SPACE) { 668 _length = _string.length; 669 string ver = takeString(); 670 _version = HttpVersion.fromString(ver); 671 if (_version == HttpVersion.Null) 672 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version"); 673 setState(State.SPACE1); 674 } else if (b < HttpTokens.SPACE) 675 throw new IllegalCharacterException(_state, b, buffer); 676 else 677 _string.append(cast(char) b); 678 break; 679 680 case State.SPACE1: 681 if (b > HttpTokens.SPACE || b < 0) { 682 if (_responseHandler !is null) { 683 setState(State.STATUS); 684 setResponseStatus(b - '0'); 685 } else { 686 _uri.reset(); 687 setState(State.URI); 688 // quick scan for space or EoBuffer 689 if (buffer.hasArray()) { 690 byte[] array = buffer.array(); 691 int p = buffer.arrayOffset() + buffer.position(); 692 int l = buffer.arrayOffset() + buffer.limit(); 693 int i = p; 694 while (i < l && array[i] > HttpTokens.SPACE) 695 i++; 696 697 int len = i - p; 698 _headerBytes += len; 699 700 if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) { 701 warningf("URI is too large >%d", _maxHeaderBytes); 702 throw new BadMessageException(HttpStatus.URI_TOO_LONG_414); 703 } 704 _uri.append(array, p - 1, len + 1); 705 buffer.position(i - buffer.arrayOffset()); 706 } else 707 _uri.append(b); 708 } 709 } else if (b < HttpTokens.SPACE) { 710 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, 711 _requestHandler !is null ? "No URI" : "No Status"); 712 } 713 break; 714 715 case State.STATUS: 716 if (b == HttpTokens.SPACE) { 717 setState(State.SPACE2); 718 } else if (b >= '0' && b <= '9') { 719 _responseStatus = _responseStatus * 10 + (b - '0'); 720 } else if (b < HttpTokens.SPACE && b >= 0) { 721 setState(State.HEADER); 722 handle = _responseHandler.startResponse(_version, _responseStatus, null) || handle; 723 } else { 724 throw new BadMessageException(); 725 } 726 break; 727 728 case State.URI: 729 if (b == HttpTokens.SPACE) { 730 setState(State.SPACE2); 731 } else if (b < HttpTokens.SPACE && b >= 0) { 732 // HTTP/0.9 733 if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9, "No request version")) { 734 throw new BadMessageException("HTTP/0.9 not supported"); 735 } 736 737 if(_requestHandler !is null) { 738 handle = _requestHandler.startRequest(_methodString, _uri.toString(), HttpVersion.HTTP_0_9); 739 } else { 740 warning("no requestHandler defined"); 741 } 742 743 setState(State.END); 744 BufferUtils.clear(buffer); 745 handle = handleHeaderContentMessage() || handle; 746 } else { 747 _uri.append(b); 748 } 749 break; 750 751 case State.SPACE2: 752 if (b > HttpTokens.SPACE) { 753 _string.setLength(0); 754 _string.append(cast(char) b); 755 if (_responseHandler !is null) { 756 _length = 1; 757 setState(State.REASON); 758 } else { 759 setState(State.REQUEST_VERSION); 760 761 // try quick look ahead for HTTP Version 762 HttpVersion ver; 763 if (buffer.position() > 0 && buffer.hasArray()) { 764 ver = HttpVersion.lookAheadGet(buffer.array(), 765 buffer.arrayOffset() + buffer.position() - 1, 766 buffer.arrayOffset() + buffer.limit()); 767 } else { 768 string key = cast(string)buffer.peek(0, buffer.remaining()).idup; 769 ver = HttpVersion.fromString(key); 770 } 771 772 if (ver != HttpVersion.Null) { 773 int pos = cast(int)(buffer.position() + ver.asString().length - 1); 774 if (pos < buffer.limit()) { 775 byte n = buffer.get(pos); 776 if (n == HttpTokens.CARRIAGE_RETURN) { 777 _cr = true; 778 _version = ver; 779 _string.setLength(0); 780 buffer.position(pos + 1); 781 } else if (n == HttpTokens.LINE_FEED) { 782 _version = ver; 783 _string.setLength(0); 784 buffer.position(pos); 785 } 786 } 787 } 788 } 789 } else if (b == HttpTokens.LINE_FEED) { 790 if (_responseHandler !is null) { 791 setState(State.HEADER); 792 handle = _responseHandler.startResponse(_version, _responseStatus, null) || handle; 793 } else { 794 // HTTP/0.9 795 if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9, "No request version")) 796 throw new BadMessageException("HTTP/0.9 not supported"); 797 798 if(_requestHandler is null) { 799 warning("no requestHandler defined"); 800 } else { 801 handle = _requestHandler.startRequest(_methodString, _uri.toString(), HttpVersion.HTTP_0_9); 802 } 803 804 setState(State.END); 805 BufferUtils.clear(buffer); 806 handle = handleHeaderContentMessage() || handle; 807 } 808 } else if (b < 0) 809 throw new BadMessageException(); 810 break; 811 812 case State.REQUEST_VERSION: 813 if (b == HttpTokens.LINE_FEED) { 814 if (_version == HttpVersion.Null) { 815 _length = _string.length; 816 _version = HttpVersion.fromString(takeString()); 817 } 818 if (_version == HttpVersion.Null) 819 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version"); 820 821 // Should we try to cache header fields? 822 if (_fieldCache is null && 823 _version.getVersion() >= HttpVersion.HTTP_1_1.getVersion() && 824 _handler.getHeaderCacheSize() > 0) { 825 int header_cache = _handler.getHeaderCacheSize(); 826 _fieldCache = new ArrayTernaryTrie!HttpField(header_cache); 827 } 828 829 setState(State.HEADER); 830 831 if(_requestHandler is null) { 832 warning("no requestHandler defined"); 833 } else { 834 handle = _requestHandler.startRequest(_methodString, _uri.toString(), _version) || handle; 835 } 836 continue; 837 } else if (b >= HttpTokens.SPACE) 838 _string.append(cast(char) b); 839 else 840 throw new BadMessageException(); 841 842 break; 843 844 case State.REASON: 845 if (b == HttpTokens.LINE_FEED) { 846 string reason = takeString(); 847 setState(State.HEADER); 848 handle = _responseHandler.startResponse(_version, _responseStatus, reason) || handle; 849 continue; 850 } else if (b >= HttpTokens.SPACE || ((b < 0) && (b >= -96))) { 851 _string.append(cast(char) (0xff & b)); 852 if (b != ' ' && b != '\t') 853 _length = _string.length; 854 } else 855 throw new BadMessageException(); 856 break; 857 858 default: 859 throw new IllegalStateException(_state.to!string()); 860 } 861 } 862 863 return handle; 864 } 865 866 private void parsedHeader() { 867 // handler last header if any. Delayed to here just in case there was a continuation line (above) 868 if (!_headerString.empty() || !_valueString.empty()) { 869 // Handle known headers 870 version(HUNT_HTTP_DEBUG_MORE) { 871 tracef("parsing header: %s, original name: %s, value: %s ", 872 _header.toString(), _headerString, _valueString); 873 } 874 875 if (_header != HttpHeader.Null) { 876 bool canAddToConnectionTrie = false; 877 if(_header == HttpHeader.CONTENT_LENGTH) { 878 if (_endOfContent == EndOfContent.CONTENT_LENGTH) { 879 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Duplicate Content-Length"); 880 } else if (_endOfContent != EndOfContent.CHUNKED_CONTENT) { 881 _contentLength = convertContentLength(_valueString); 882 if (_contentLength <= 0) 883 _endOfContent = EndOfContent.NO_CONTENT; 884 else 885 _endOfContent = EndOfContent.CONTENT_LENGTH; 886 } 887 } 888 else if(_header == HttpHeader.TRANSFER_ENCODING){ 889 if (HttpHeaderValue.CHUNKED.isSame(_valueString)) { 890 _endOfContent = EndOfContent.CHUNKED_CONTENT; 891 _contentLength = -1; 892 } else { 893 string[] values = new QuotedCSV(_valueString).getValues(); 894 if (values.length > 0 && HttpHeaderValue.CHUNKED.isSame(values[$ - 1])) { 895 _endOfContent = EndOfContent.CHUNKED_CONTENT; 896 _contentLength = -1; 897 } else { 898 foreach(string v; values) { 899 if(HttpHeaderValue.CHUNKED.isSame(v)) { 900 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad chunking"); 901 } 902 } 903 } 904 } 905 } 906 else if(_header == HttpHeader.HOST) { 907 _host = true; 908 if ((_field is null) && !_valueString.empty()) { 909 string headerStr = _compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE) ? 910 _header.asString() : _headerString; 911 _field = new HostPortHttpField(_header, headerStr, _valueString); 912 canAddToConnectionTrie = _fieldCache !is null; 913 } 914 } 915 else if(_header == HttpHeader.CONNECTION) { 916 // Don't cache headers if not persistent 917 if (HttpHeaderValue.CLOSE.isSame(_valueString)) 918 _fieldCache = null; 919 else { 920 string[] values = new QuotedCSV(_valueString).getValues(); 921 foreach(string v; values) { 922 if(HttpHeaderValue.CLOSE.isSame(v)) { 923 _fieldCache = null; 924 break; 925 } 926 } 927 } 928 } 929 else if(_header == HttpHeader.AUTHORIZATION || _header == HttpHeader.ACCEPT || 930 _header == HttpHeader.ACCEPT_CHARSET || _header == HttpHeader.ACCEPT_ENCODING || 931 _header == HttpHeader.ACCEPT_LANGUAGE || _header == HttpHeader.COOKIE || 932 _header == HttpHeader.CACHE_CONTROL || _header == HttpHeader.USER_AGENT) { 933 canAddToConnectionTrie = _fieldCache !is null && _field is null; 934 } 935 936 if (canAddToConnectionTrie && !_fieldCache.isFull() 937 && _header != HttpHeader.Null && !_valueString.empty()) { 938 if (_field is null) 939 _field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString); 940 _fieldCache.put(_field); 941 } 942 } 943 _handler.parsedHeader(_field !is null ? _field : new HttpField(_header, _headerString, _valueString)); 944 } 945 946 _headerString = _valueString = null; 947 _header = HttpHeader.Null; 948 _field = null; 949 } 950 951 private void parsedTrailer() { 952 // handler last header if any. Delayed to here just in case there was a continuation line (above) 953 if (!_headerString.empty() || !_valueString.empty()) 954 _handler.parsedTrailer(_field !is null ? _field : new HttpField(_header, _headerString, _valueString)); 955 956 _headerString = _valueString = null; 957 _header = HttpHeader.Null; 958 _field = null; 959 } 960 961 private long convertContentLength(string valueString) { 962 try { 963 return to!long(valueString); 964 } catch (Exception e) { 965 warning("parse long exception: ", e); 966 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Invalid Content-Length Value"); 967 } 968 } 969 970 /* ------------------------------------------------------------------------------- */ 971 /* 972 * Parse the message headers and return true if the handler has signaled for a return 973 */ 974 protected bool parseFields(ByteBuffer buffer) { 975 // Process headers 976 while ((_state == State.HEADER || _state == State.TRAILER) && buffer.hasRemaining()) { 977 // process each character 978 byte b = next(buffer); 979 if (b == 0) 980 break; 981 982 if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) { 983 bool header = _state == State.HEADER; 984 warningf("%s is too large %s>%s", header ? "Header" : "Trailer", _headerBytes, _maxHeaderBytes); 985 throw new BadMessageException(header ? 986 HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431 : 987 HttpStatus.PAYLOAD_TOO_LARGE_413); 988 } 989 990 switch (_fieldState) { 991 case FieldState.FIELD: 992 switch (b) { 993 case HttpTokens.COLON: 994 case HttpTokens.SPACE: 995 case HttpTokens.TAB: { 996 if (complianceViolation(HttpComplianceSection.NO_FIELD_FOLDING, _headerString)) 997 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Header Folding"); 998 999 // header value without name - continuation? 1000 if (_valueString == null || _valueString.empty()) { 1001 _string.setLength(0); 1002 _length = 0; 1003 } else { 1004 setString(_valueString); 1005 _string.append(' '); 1006 _length++; 1007 _valueString = null; 1008 } 1009 setState(FieldState.VALUE); 1010 break; 1011 } 1012 1013 case HttpTokens.LINE_FEED: { 1014 // process previous header 1015 if (_state == State.HEADER) 1016 parsedHeader(); 1017 else 1018 parsedTrailer(); 1019 1020 _contentPosition = 0; 1021 1022 // End of headers or trailers? 1023 if (_state == State.TRAILER) { 1024 setState(State.END); 1025 return _handler.messageComplete(); 1026 } 1027 1028 // Was there a required host header? 1029 if (!_host && _version == HttpVersion.HTTP_1_1 && _requestHandler !is null) { 1030 throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "No Host"); 1031 } 1032 1033 // is it a response that cannot have a body? 1034 if (_responseHandler !is null && // response 1035 (_responseStatus == 304 || // not-modified response 1036 _responseStatus == 204 || // no-content response 1037 _responseStatus < 200)) // 1xx response 1038 _endOfContent = EndOfContent.NO_CONTENT; // ignore any other headers set 1039 1040 // else if we don't know framing 1041 else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) { 1042 if (_responseStatus == 0 // request 1043 || _responseStatus == 304 // not-modified response 1044 || _responseStatus == 204 // no-content response 1045 || _responseStatus < 200) // 1xx response 1046 _endOfContent = EndOfContent.NO_CONTENT; 1047 else 1048 _endOfContent = EndOfContent.EOF_CONTENT; 1049 } 1050 1051 // How is the message ended? 1052 switch (_endOfContent) { 1053 case EndOfContent.EOF_CONTENT: { 1054 setState(State.EOF_CONTENT); 1055 bool handle = _handler.headerComplete(); 1056 _headerComplete = true; 1057 return handle; 1058 } 1059 case EndOfContent.CHUNKED_CONTENT: { 1060 setState(State.CHUNKED_CONTENT); 1061 bool handle = _handler.headerComplete(); 1062 _headerComplete = true; 1063 return handle; 1064 } 1065 case EndOfContent.NO_CONTENT: { 1066 version (HUNT_HTTP_DEBUG) trace("parsing done for no content"); 1067 setState(State.END); 1068 return handleHeaderContentMessage(); 1069 } 1070 default: { 1071 setState(State.CONTENT); 1072 bool handle = _handler.headerComplete(); 1073 _headerComplete = true; 1074 return handle; 1075 } 1076 } 1077 } 1078 1079 default: { 1080 // process previous header 1081 if (_state == State.HEADER) 1082 parsedHeader(); 1083 else 1084 parsedTrailer(); 1085 1086 // handle new header 1087 if (buffer.hasRemaining()) { 1088 // Try a look ahead for the known header name and value. 1089 HttpField cached_field = null; 1090 if(_fieldCache !is null) 1091 cached_field = _fieldCache.getBest(buffer, -1, buffer.remaining()); 1092 // TODO: Tasks pending completion -@zxp at 10/23/2018, 8:03:47 PM 1093 // Can't handle Sec-WebSocket-Key 1094 // if (cached_field is null) 1095 // cached_field = CACHE.getBest(buffer, -1, buffer.remaining()); 1096 1097 if (cached_field !is null) { 1098 string n = cached_field.getName(); 1099 string v = cached_field.getValue(); 1100 1101 if (!_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)) { 1102 // Have to get the fields exactly from the buffer to match case 1103 // BufferUtils.toString(buffer, buffer.position() - 1, n.length, StandardCharsets.US_ASCII); 1104 string en = cast(string)buffer.peek(buffer.position() - 1, cast(int)n.length).idup; 1105 if (!n.equals(en)) { 1106 handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, en); 1107 n = en; 1108 cached_field = new HttpField(cached_field.getHeader(), n, v); 1109 } 1110 } 1111 1112 if (v != null && !_compliances.contains(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE)) { 1113 // BufferUtils.toString(buffer, buffer.position() + n.length + 1, v.length, StandardCharsets.ISO_8859_1); 1114 string ev = cast(string)buffer.peek(buffer.position() + n.length + 1, v.length).idup; 1115 if (!v.equals(ev)) { 1116 handleViolation(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE, ev ~ "!=" ~ v); 1117 v = ev; 1118 cached_field = new HttpField(cached_field.getHeader(), n, v); 1119 } 1120 } 1121 1122 _header = cached_field.getHeader(); 1123 _headerString = n; 1124 1125 if (v == null) { 1126 // Header only 1127 setState(FieldState.VALUE); 1128 _string.setLength(0); 1129 _length = 0; 1130 buffer.position(cast(int)(buffer.position() + n.length + 1)); 1131 break; 1132 } else { 1133 // Header and value 1134 int pos = cast(int) (buffer.position() + n.length + v.length + 1); 1135 byte peek = buffer.get(pos); 1136 1137 if (peek == HttpTokens.CARRIAGE_RETURN || peek == HttpTokens.LINE_FEED) { 1138 _field = cached_field; 1139 _valueString = v; 1140 setState(FieldState.IN_VALUE); 1141 1142 if (peek == HttpTokens.CARRIAGE_RETURN) { 1143 _cr = true; 1144 buffer.position(pos + 1); 1145 } else 1146 buffer.position(pos); 1147 break; 1148 } else { 1149 setState(FieldState.IN_VALUE); 1150 setString(v); 1151 buffer.position(pos); 1152 break; 1153 } 1154 } 1155 } 1156 } 1157 1158 // New header 1159 setState(FieldState.IN_NAME); 1160 _string.setLength(0); 1161 _string.append(cast(char) b); 1162 _length = 1; 1163 } 1164 } 1165 break; 1166 1167 case FieldState.IN_NAME: 1168 switch (b) { 1169 case HttpTokens.SPACE: 1170 case HttpTokens.TAB: 1171 //Ignore trailing whitespaces ? 1172 if (!complianceViolation(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME, null)) { 1173 _headerString = takeString(); 1174 _header = HttpHeader.get(_headerString); 1175 _length = -1; 1176 setState(FieldState.WS_AFTER_NAME); 1177 break; 1178 } 1179 throw new IllegalCharacterException(_state, b, buffer); 1180 1181 case HttpTokens.COLON: 1182 _headerString = takeString(); 1183 _header = HttpHeader.get(_headerString); 1184 _length = -1; 1185 setState(FieldState.VALUE); 1186 break; 1187 1188 case HttpTokens.LINE_FEED: 1189 _headerString = takeString(); 1190 _header = HttpHeader.get(_headerString); 1191 _string.setLength(0); 1192 _valueString = ""; 1193 _length = -1; 1194 1195 if (!complianceViolation(HttpComplianceSection.FIELD_COLON, _headerString)) { 1196 setState(FieldState.FIELD); 1197 break; 1198 } 1199 throw new IllegalCharacterException(_state, b, buffer); 1200 1201 default: 1202 if (b < 0) 1203 throw new IllegalCharacterException(_state, b, buffer); 1204 1205 _string.append(cast(char) b); 1206 _length = _string.length; 1207 break; 1208 } 1209 break; 1210 1211 case FieldState.WS_AFTER_NAME: 1212 1213 switch (b) { 1214 case HttpTokens.SPACE: 1215 case HttpTokens.TAB: 1216 break; 1217 1218 case HttpTokens.COLON: 1219 setState(FieldState.VALUE); 1220 break; 1221 1222 case HttpTokens.LINE_FEED: 1223 if (!complianceViolation(HttpComplianceSection.FIELD_COLON, _headerString)) { 1224 setState(FieldState.FIELD); 1225 break; 1226 } 1227 throw new IllegalCharacterException(_state, b, buffer); 1228 1229 default: 1230 throw new IllegalCharacterException(_state, b, buffer); 1231 } 1232 break; 1233 1234 case FieldState.VALUE: 1235 switch (b) { 1236 case HttpTokens.LINE_FEED: 1237 _string.setLength(0); 1238 _valueString = ""; 1239 _length = -1; 1240 1241 setState(FieldState.FIELD); 1242 break; 1243 1244 case HttpTokens.SPACE: 1245 case HttpTokens.TAB: 1246 break; 1247 1248 default: 1249 _string.append(cast(char) (0xff & b)); 1250 _length = _string.length; 1251 setState(FieldState.IN_VALUE); 1252 break; 1253 } 1254 break; 1255 1256 case FieldState.IN_VALUE: 1257 switch (b) { 1258 case HttpTokens.LINE_FEED: 1259 if (_length > 0) { 1260 _valueString = takeString(); 1261 _length = -1; 1262 } 1263 setState(FieldState.FIELD); 1264 break; 1265 1266 case HttpTokens.SPACE: 1267 case HttpTokens.TAB: 1268 _string.append(cast(char) (0xff & b)); 1269 break; 1270 1271 default: 1272 _string.append(cast(char) (0xff & b)); 1273 _length = _string.length; 1274 break; 1275 } 1276 break; 1277 1278 default: 1279 throw new IllegalStateException(_state.to!string()); 1280 1281 } 1282 } 1283 1284 return false; 1285 } 1286 1287 /* ------------------------------------------------------------------------------- */ 1288 1289 /** 1290 * Parse until next Event. 1291 * 1292 * @param buffer the buffer to parse 1293 * @return True if an {@link HttpRequestParsingHandler} method was called and it returned true; 1294 */ 1295 bool parseNext(ByteBuffer buffer) { 1296 1297 version(HUNT_HTTP_DEBUG_MORE) { 1298 tracef("parseNext s=%s %s", _state, BufferUtils.toDetailString(buffer)); 1299 // tracef("buffer: %s", BufferUtils.toHexString(buffer)); 1300 1301 // startTime = MonoTime.currTime; 1302 // _requestHandler.startRequest("GET", "/plaintext", HttpVersion.HTTP_1_1); 1303 // setState(State.END); 1304 // _handler.messageComplete(); 1305 // BufferUtils.clear(buffer); 1306 // return false; 1307 } 1308 try { 1309 // Start a request/response 1310 if (_state == State.START) { 1311 version(HUNT_METRIC) { 1312 startTime = MonoTime.currTime; 1313 info("start a new parsing process..."); 1314 } 1315 _version = HttpVersion.Null; 1316 _method = HttpMethod.Null; 1317 _methodString = null; 1318 _endOfContent = EndOfContent.UNKNOWN_CONTENT; 1319 _header = HttpHeader.Null; 1320 if (quickStart(buffer)) 1321 return true; 1322 } 1323 1324 // Request/response line 1325 if (_state >= State.START && _state < State.HEADER) { 1326 if (parseLine(buffer)) { 1327 version(HUNT_HTTP_DEBUG) tracef("after parseLine =>%s", buffer.toString()); 1328 // return true; 1329 } 1330 } 1331 1332 // parse headers 1333 if (_state == State.HEADER) { 1334 if (parseFields(buffer)) { 1335 version(HUNT_HTTP_DEBUG_MORE) tracef("after parseFields =>%s", buffer.toString()); 1336 return true; 1337 } 1338 } 1339 1340 // parse content 1341 if (_state >= State.CONTENT && _state < State.TRAILER) { 1342 // Handle HEAD response 1343 if (_responseStatus > 0 && _headResponse) { 1344 setState(State.END); 1345 return handleContentMessage(); 1346 } else { 1347 if (parseContent(buffer)) 1348 return true; 1349 } 1350 } 1351 1352 // parse headers 1353 if (_state == State.TRAILER) { 1354 if (parseFields(buffer)) 1355 return true; 1356 } 1357 1358 // handle end states 1359 if (_state == State.END) { 1360 // eat white space 1361 while (buffer.remaining() > 0 && buffer.get(buffer.position()) <= HttpTokens.SPACE) 1362 buffer.get(); 1363 } else if (isClose() || isClosed()) { 1364 BufferUtils.clear(buffer); 1365 } 1366 1367 // Handle EOF 1368 if (_eof && !buffer.hasRemaining()) { 1369 switch (_state) { 1370 case State.CLOSED: 1371 break; 1372 1373 case State.START: 1374 setState(State.CLOSED); 1375 _handler.earlyEOF(); 1376 break; 1377 1378 case State.END: 1379 case State.CLOSE: 1380 setState(State.CLOSED); 1381 break; 1382 1383 case State.EOF_CONTENT: 1384 case State.TRAILER: 1385 if (_fieldState == FieldState.FIELD) { 1386 // Be forgiving of missing last CRLF 1387 setState(State.CLOSED); 1388 return handleContentMessage(); 1389 } 1390 setState(State.CLOSED); 1391 _handler.earlyEOF(); 1392 break; 1393 1394 case State.CONTENT: 1395 case State.CHUNKED_CONTENT: 1396 case State.CHUNK_SIZE: 1397 case State.CHUNK_PARAMS: 1398 case State.CHUNK: 1399 setState(State.CLOSED); 1400 _handler.earlyEOF(); 1401 break; 1402 1403 default: 1404 version(HUNT_HTTP_DEBUG) 1405 tracef("%s EOF in %s", this, _state); 1406 setState(State.CLOSED); 1407 _handler.badMessage(new BadMessageException(HttpStatus.BAD_REQUEST_400)); 1408 break; 1409 } 1410 } 1411 } catch (BadMessageException x) { 1412 BufferUtils.clear(buffer); 1413 badMessage(x); 1414 } catch (Exception x) { 1415 version(HUNT_DEBUG) warning(x.msg); 1416 version(HUNT_HTTP_DEBUG) warning(x); 1417 BufferUtils.clear(buffer); 1418 badMessage(new BadMessageException(HttpStatus.BAD_REQUEST_400, 1419 _requestHandler !is null ? "Bad Request" : "Bad Response", x)); 1420 } 1421 return false; 1422 } 1423 1424 protected void badMessage(BadMessageException x) { 1425 version(HUNT_DEBUG) 1426 warning("Parse exception: " ~ this.toString() ~ " for " ~ _handler.toString() ~ 1427 ", Exception: ", x.msg); 1428 1429 version(HUNT_HTTP_DEBUG) { 1430 Throwable t = x; 1431 while((t = t.next) !is null) { 1432 error(t.msg); 1433 } 1434 } 1435 1436 setState(State.CLOSE); 1437 if (_headerComplete) 1438 _handler.earlyEOF(); 1439 else 1440 _handler.badMessage(x); 1441 } 1442 1443 protected bool parseContent(ByteBuffer buffer) { 1444 int remaining = buffer.remaining(); 1445 if (remaining == 0 && _state == State.CONTENT) { 1446 long content = _contentLength - _contentPosition; 1447 if (content == 0) { 1448 setState(State.END); 1449 return handleContentMessage(); 1450 } 1451 } 1452 1453 // Handle _content 1454 byte ch; 1455 while (_state < State.TRAILER && remaining > 0) { 1456 switch (_state) { 1457 case State.EOF_CONTENT: 1458 _contentChunk = buffer.slice(); // buffer.asReadOnlyBuffer(); 1459 _contentPosition += remaining; 1460 buffer.position(buffer.position() + remaining); 1461 if (_handler.content(_contentChunk)) 1462 return true; 1463 break; 1464 1465 case State.CONTENT: { 1466 long content = _contentLength - _contentPosition; 1467 if (content == 0) { 1468 setState(State.END); 1469 return handleContentMessage(); 1470 } else { 1471 _contentChunk = buffer.slice(); // buffer.asReadOnlyBuffer(); 1472 1473 // limit content by expected size 1474 if (remaining > content) { 1475 // We can cast remaining to an int as we know that it is smaller than 1476 // or equal to length which is already an int. 1477 _contentChunk.limit(_contentChunk.position() + cast(int) content); 1478 } 1479 1480 _contentPosition += _contentChunk.remaining(); 1481 buffer.position(buffer.position() + _contentChunk.remaining()); 1482 version(HUNT_HTTP_DEBUG) trace("setting content..."); 1483 if (_handler.content(_contentChunk)) 1484 return true; 1485 1486 if (_contentPosition == _contentLength) { 1487 setState(State.END); 1488 return handleContentMessage(); 1489 } 1490 } 1491 break; 1492 } 1493 1494 case State.CHUNKED_CONTENT: { 1495 ch = next(buffer); 1496 if (ch > HttpTokens.SPACE) { 1497 _chunkLength = ConverterUtils.convertHexDigit(ch); 1498 _chunkPosition = 0; 1499 setState(State.CHUNK_SIZE); 1500 } 1501 1502 break; 1503 } 1504 1505 case State.CHUNK_SIZE: { 1506 ch = next(buffer); 1507 if (ch == 0) 1508 break; 1509 if (ch == HttpTokens.LINE_FEED) { 1510 if (_chunkLength == 0) { 1511 setState(State.TRAILER); 1512 if (_handler.contentComplete()) 1513 return true; 1514 } else 1515 setState(State.CHUNK); 1516 } else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) 1517 setState(State.CHUNK_PARAMS); 1518 else 1519 _chunkLength = _chunkLength * 16 + ConverterUtils.convertHexDigit(ch); 1520 break; 1521 } 1522 1523 case State.CHUNK_PARAMS: { 1524 ch = next(buffer); 1525 if (ch == HttpTokens.LINE_FEED) { 1526 if (_chunkLength == 0) { 1527 setState(State.TRAILER); 1528 if (_handler.contentComplete()) 1529 return true; 1530 } else 1531 setState(State.CHUNK); 1532 } 1533 break; 1534 } 1535 1536 case State.CHUNK: { 1537 int chunk = _chunkLength - _chunkPosition; 1538 if (chunk == 0) { 1539 setState(State.CHUNKED_CONTENT); 1540 } else { 1541 _contentChunk = buffer.slice(); // buffer.asReadOnlyBuffer(); 1542 1543 if (remaining > chunk) 1544 _contentChunk.limit(_contentChunk.position() + chunk); 1545 chunk = _contentChunk.remaining(); 1546 1547 _contentPosition += chunk; 1548 _chunkPosition += chunk; 1549 buffer.position(buffer.position() + chunk); 1550 if (_handler.content(_contentChunk)) 1551 return true; 1552 } 1553 break; 1554 } 1555 1556 case State.CLOSED: { 1557 // BufferUtils.clear(buffer); 1558 buffer.clear(); 1559 return false; 1560 } 1561 1562 default: 1563 break; 1564 1565 } 1566 1567 remaining = buffer.remaining(); 1568 } 1569 return false; 1570 } 1571 1572 /* ------------------------------------------------------------------------------- */ 1573 bool isAtEOF() 1574 { 1575 return _eof; 1576 } 1577 1578 /* ------------------------------------------------------------------------------- */ 1579 1580 /** 1581 * Signal that the associated data source is at EOF 1582 */ 1583 void atEOF() { 1584 version(HUNT_HTTP_DEBUG) 1585 tracef("atEOF %s", this); 1586 _eof = true; 1587 } 1588 1589 /* ------------------------------------------------------------------------------- */ 1590 1591 /** 1592 * Request that the associated data source be closed 1593 */ 1594 void close() { 1595 version(HUNT_HTTP_DEBUG) 1596 tracef("close %s", this); 1597 setState(State.CLOSE); 1598 } 1599 1600 /* ------------------------------------------------------------------------------- */ 1601 void reset() { 1602 version(HUNT_HTTP_DEBUG_MORE) 1603 tracef("reset %s", this); 1604 1605 // reset state 1606 if (_state == State.CLOSE || _state == State.CLOSED) 1607 return; 1608 1609 setState(State.START); 1610 _endOfContent = EndOfContent.UNKNOWN_CONTENT; 1611 _contentLength = -1; 1612 _contentPosition = 0; 1613 _responseStatus = 0; 1614 _contentChunk = null; 1615 _headerBytes = 0; 1616 _host = false; 1617 _headerComplete = false; 1618 } 1619 1620 /* ------------------------------------------------------------------------------- */ 1621 protected void setState(State state) { 1622 version(HUNT_HTTP_DEBUG) 1623 tracef("%s --> %s", _state, state); 1624 _state = state; 1625 1626 version(HUNT_METRIC) { 1627 if(state == State.END) { 1628 Duration timeElapsed = MonoTime.currTime - startTime; 1629 warningf("parsing ended in: %d microseconds", timeElapsed.total!(TimeUnit.Microsecond)()); 1630 } 1631 } 1632 } 1633 1634 /* ------------------------------------------------------------------------------- */ 1635 protected void setState(FieldState state) { 1636 // version(HUNT_HTTP_DEBUG) 1637 // tracef("%s:%s --> %s", _state, _field, state); 1638 _fieldState = state; 1639 } 1640 1641 /* ------------------------------------------------------------------------------- */ 1642 Trie!HttpField getFieldCache() { 1643 return _fieldCache; 1644 } 1645 1646 HttpField getCachedField(string name) { 1647 return _fieldCache.get(name); 1648 } 1649 1650 1651 /* ------------------------------------------------------------------------------- */ 1652 override 1653 string toString() { 1654 return format("%s{s=%s,%d of %d}", 1655 typeof(this).stringof, 1656 _state, 1657 _contentPosition, 1658 _contentLength); 1659 } 1660 1661 deprecated("Using HttpParsingHandler instead.") 1662 alias HttpHandler = HttpParsingHandler; 1663 1664 deprecated("Using HttpRequestParsingHandler instead.") 1665 alias RequestHandler = HttpRequestParsingHandler; 1666 1667 deprecated("Using HttpResponseParsingHandler instead.") 1668 alias ResponseHandler = HttpResponseParsingHandler; 1669 1670 deprecated("Using ComplianceParsingHandler instead.") 1671 alias ComplianceHandler = ComplianceParsingHandler; 1672 1673 1674 1675 // /* ------------------------------------------------------------------------------- */ 1676 // /* ------------------------------------------------------------------------------- */ 1677 // /* ------------------------------------------------------------------------------- */ 1678 // interface ComplianceHandler : HttpHandler { 1679 // // deprecated("") 1680 // // default void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, string reason) { 1681 // // } 1682 1683 // void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection v, string details); 1684 // // { 1685 // // // onComplianceViolation(compliance, HttpCompliance.requiredCompliance(violation), details); 1686 // // warning(details); 1687 // // } 1688 // } 1689 1690 /* ------------------------------------------------------------------------------- */ 1691 1692 private static class IllegalCharacterException : BadMessageException { 1693 private this(State state, byte ch, ByteBuffer buffer) { 1694 super(400, format("Illegal character 0x%X", ch)); 1695 // Bug #460642 - don't reveal buffers to end user 1696 warningf("Illegal character 0x%X in state=%s for buffer %s", ch, state, BufferUtils.toDetailString(buffer)); 1697 } 1698 } 1699 } 1700 1701 1702 /* ------------------------------------------------------------ */ 1703 /* ------------------------------------------------------------ */ 1704 /* ------------------------------------------------------------ */ 1705 /* Event producing interface while parsing http 1706 * These methods return true if the caller should process the events 1707 * so far received (eg return from parseNext and call HttpChannel.handle). 1708 * If multiple callbacks are called in sequence (eg 1709 * headerComplete then messageComplete) from the same point in the parsing 1710 * then it is sufficient for the caller to process the events only once. 1711 */ 1712 interface HttpParsingHandler { 1713 1714 bool content(ByteBuffer item); 1715 1716 bool headerComplete(); 1717 1718 bool contentComplete(); 1719 1720 bool messageComplete(); 1721 1722 /** 1723 * This is the method called by parser when a HTTP Header name and value is found 1724 * 1725 * @param field The field parsed 1726 */ 1727 void parsedHeader(HttpField field); 1728 1729 /** 1730 * This is the method called by parser when a HTTP Trailer name and value is found 1731 * 1732 * @param field The field parsed 1733 */ 1734 void parsedTrailer(HttpField field); 1735 1736 /* ------------------------------------------------------------ */ 1737 1738 /** 1739 * Called to signal that an EOF was received unexpectedly 1740 * during the parsing of a HTTP message 1741 */ 1742 void earlyEOF(); 1743 1744 /* ------------------------------------------------------------ */ 1745 1746 /** 1747 * Called to signal that a bad HTTP message has been received. 1748 * 1749 * @param failure the failure with the bad message information 1750 */ 1751 void badMessage(BadMessageException failure); 1752 // { 1753 // // badMessage(failure.getCode(), failure.getReason()); 1754 // throw new BadMessageException(failure.getCode(), failure.getReason()); 1755 // } 1756 1757 /** 1758 * @deprecated use {@link #badMessage(BadMessageException)} instead 1759 */ 1760 // deprecated("") 1761 void badMessage(int status, string reason); 1762 1763 /* ------------------------------------------------------------ */ 1764 1765 /** 1766 * @return the size in bytes of the per parser header cache 1767 */ 1768 int getHeaderCacheSize(); 1769 1770 string toString(); 1771 } 1772 1773 /* ------------------------------------------------------------------------------- */ 1774 /* ------------------------------------------------------------------------------- */ 1775 /* ------------------------------------------------------------------------------- */ 1776 interface HttpRequestParsingHandler : HttpParsingHandler { 1777 /** 1778 * This is the method called by parser when the HTTP request line is parsed 1779 * 1780 * @param method The method 1781 * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused. 1782 * @param version the http version in use 1783 * @return true if handling parsing should return. 1784 */ 1785 bool startRequest(string method, string uri, HttpVersion ver); 1786 1787 } 1788 1789 /* ------------------------------------------------------------------------------- */ 1790 /* ------------------------------------------------------------------------------- */ 1791 /* ------------------------------------------------------------------------------- */ 1792 interface HttpResponseParsingHandler : HttpParsingHandler { 1793 /** 1794 * This is the method called by parser when the HTTP request line is parsed 1795 * 1796 * @param version the http version in use 1797 * @param status the response status 1798 * @param reason the response reason phrase 1799 * @return true if handling parsing should return 1800 */ 1801 bool startResponse(HttpVersion ver, int status, string reason); 1802 } 1803 1804 /* ------------------------------------------------------------------------------- */ 1805 /* ------------------------------------------------------------------------------- */ 1806 /* ------------------------------------------------------------------------------- */ 1807 interface ComplianceParsingHandler : HttpParsingHandler { 1808 // deprecated("") 1809 // default void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, string reason) { 1810 // } 1811 1812 void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection v, string details); 1813 // { 1814 // // onComplianceViolation(compliance, HttpCompliance.requiredCompliance(violation), details); 1815 // warning(details); 1816 // } 1817 } 1818 1819 deprecated("Using HttpRequestParsingHandler instead.") 1820 alias HttpRequestHandler = HttpRequestParsingHandler; 1821 1822 deprecated("Using HttpResponseParsingHandler instead.") 1823 alias HttpResponseHandler = HttpResponseParsingHandler;