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