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