1 module more.httpparser; 2 3 import std.typecons : BitFlags, Flag, Yes, No; 4 5 enum HttpBadRequestReason 6 { 7 invalidMethodCharacter, 8 methodNameTooLong, 9 badHttpVersion, 10 invalidHeaderNameCharacter, 11 headerNameTooLong, 12 } 13 14 template HttpParser(H) 15 { 16 enum SaveHeaderValueState : ubyte 17 { 18 noNewline, 19 carriageReturn, // '\r' 20 lineFeed, // '\n 21 } 22 static if(H.SupportCallbackStop) 23 { 24 alias OnReturnType = Flag!"stop"; 25 string genCallbackCode(string call) 26 { 27 return `if(` ~ call ~ `) { parser.nextParse = &parseDone; return; }`; 28 } 29 } 30 else 31 { 32 alias OnReturnType = void; 33 string genCallbackCode(string call) 34 { 35 return call ~ ";"; 36 } 37 } 38 39 struct HttpParser 40 { 41 private void function(HttpParser* parser, H.DataType[] buffer) nextParse = &parseMethod; 42 union 43 { 44 uint parserStateUint0; 45 static if(H.SupportPartialData) 46 { 47 SaveHeaderValueState headerValueState; 48 } 49 } 50 mixin H.HttpParserMixinTemplate; 51 void reset() 52 { 53 this = this.init; 54 } 55 @property bool done() const 56 { 57 return nextParse is &parseDone; 58 } 59 void parse(H.DataType[] buffer) 60 { 61 nextParse(&this, buffer); 62 } 63 } 64 private final void parseInvalidState(HttpParser* parser, H.DataType[] buffer) 65 { 66 assert(0, "parse was called in an invalid state!"); 67 } 68 private final void parseDone(HttpParser* parser, H.DataType[] buffer) 69 { 70 assert(0, "the http parser is done"); 71 } 72 private final void parseMethod(HttpParser* parser, H.DataType[] buffer) 73 { 74 for(uint i = 0; i < buffer.length; i++) 75 { 76 if(' ' == buffer[i] ) 77 { 78 mixin(genCallbackCode(`H.onMethod(parser, buffer[0..i])`)); 79 parser.nextParse = &parseUri; 80 parseUri(parser, buffer[i + 1..$]); 81 return; 82 } 83 if(!validTokenChar(buffer[i])) 84 { 85 H.onBadRequest(parser, HttpBadRequestReason.invalidMethodCharacter); 86 parser.nextParse = &parseInvalidState; 87 return; 88 } 89 if(i >= H.MaximumMethodName) 90 { 91 H.onBadRequest(parser, HttpBadRequestReason.methodNameTooLong); 92 parser.nextParse = &parseInvalidState; 93 return; 94 } 95 } 96 97 static if(H.SupportPartialData) 98 { 99 mixin(genCallbackCode(`H.onMethodPartial(parser, buffer)`)); 100 } 101 else 102 { 103 assert(0, "partial data unsupported"); 104 } 105 } 106 107 108 /* 109 grammar here: https://tools.ietf.org/html/rfc3986#section-3 110 URI = [ scheme ":" hier-part ] [ "?" query ] [ "#" fragment ] 111 hier-part = "//" authority path-abempty 112 / path-absolute 113 / path-rootless 114 / path-empty 115 */ 116 private final void parseUri(HttpParser* parser, H.DataType[] buffer) 117 { 118 for(uint i = 0; i < buffer.length; i++) 119 { 120 if(' ' == buffer[i]) 121 { 122 mixin(genCallbackCode(`H.onUri(parser, buffer[0..i])`)); 123 parser.nextParse = &parseVersionAndNewline; 124 parser.parserStateUint0 = 0; 125 parseVersionAndNewline(parser, buffer[i + 1..$]); 126 return; 127 } 128 /* 129 TODO: check if there is a maximum URI and/or if the URI is valid 130 if(!validTokenChar(buffer[i])) 131 { 132 H.onBadRequest(parser, HttpBadRequestReason.invalidMethodCharacter); 133 parser.nextParse = &parseInvalidState; 134 return; 135 } 136 if(i >= H.MaximumMethodName) 137 { 138 H.onBadRequest(parser, HttpBadRequestReason.methodNameTooLong); 139 parser.nextParse = &parseInvalidState; 140 return; 141 } 142 */ 143 } 144 145 static if(H.SupportPartialData) 146 { 147 mixin(genCallbackCode(`H.onUriPartial(parser, buffer)`)); 148 } 149 else 150 { 151 assert(0, "partial data unsupported"); 152 } 153 } 154 155 enum HTTP_VERSION_AND_NEWLINE = "HTTP/1.1\r\n"; 156 private final void parseVersionAndNewline(HttpParser* parser, H.DataType[] buffer) 157 { 158 uint compareLength = cast(uint)HTTP_VERSION_AND_NEWLINE.length - parser.parserStateUint0; 159 if(compareLength > buffer.length) 160 { 161 if(HTTP_VERSION_AND_NEWLINE[parser.parserStateUint0..parser.parserStateUint0 + buffer.length] != buffer[]) 162 { 163 H.onBadRequest(parser, HttpBadRequestReason.badHttpVersion); 164 parser.nextParse = &parseInvalidState; 165 return; 166 } 167 parser.parserStateUint0 += buffer.length; 168 } 169 else 170 { 171 if(HTTP_VERSION_AND_NEWLINE[parser.parserStateUint0..$] != buffer[0..compareLength]) 172 { 173 H.onBadRequest(parser, HttpBadRequestReason.badHttpVersion); 174 parser.nextParse = &parseInvalidState; 175 return; 176 } 177 parser.nextParse = &initialParseHeaderName; 178 initialParseHeaderName(parser, buffer[compareLength..$]); 179 } 180 } 181 182 private final void parseNewline(HttpParser* parser, H.DataType[] buffer) 183 { 184 if(buffer.length > 0) 185 { 186 if(buffer[0] == '\n') 187 { 188 H.onHeadersDone(parser, buffer[1..$]); 189 parser.nextParse = &parseDone; 190 } 191 } 192 } 193 194 private final void initialParseHeaderName(HttpParser* parser, H.DataType[] buffer) 195 { 196 if(buffer.length > 0) 197 { 198 // Check for "\r\n" to end headers 199 if(buffer[0] == '\r') 200 { 201 parser.nextParse = &parseNewline; 202 parseNewline(parser, buffer[1..$]); 203 } 204 else 205 { 206 parser.nextParse = &parseHeaderName; 207 parseHeaderName(parser, buffer); 208 } 209 } 210 } 211 private final void parseHeaderName(HttpParser* parser, H.DataType[] buffer) 212 { 213 for(uint i = 0; i < buffer.length; i++) 214 { 215 if(':' == buffer[i] ) 216 { 217 mixin(genCallbackCode(`H.onHeaderName(parser, buffer[0..i])`)); 218 parser.nextParse = &initialParseHeaderValue; 219 initialParseHeaderValue(parser, buffer[i + 1..$]); 220 return; 221 } 222 if(!validTokenChar(buffer[i])) 223 { 224 H.onBadRequest(parser, HttpBadRequestReason.invalidHeaderNameCharacter); 225 parser.nextParse = &parseInvalidState; 226 return; 227 } 228 if(i >= H.MaximumHeaderName) 229 { 230 H.onBadRequest(parser, HttpBadRequestReason.headerNameTooLong); 231 parser.nextParse = &parseInvalidState; 232 return; 233 } 234 } 235 static if(H.SupportPartialData) 236 { 237 mixin(genCallbackCode(`H.onHeaderNamePartial(parser, buffer)`)); 238 } 239 else 240 { 241 assert(0, "partial data unsupported"); 242 } 243 } 244 private final void initialParseHeaderValue(HttpParser* parser, H.DataType[] buffer) 245 { 246 // skip initial whitespace 247 for(uint i = 0; i < buffer.length; i++) 248 { 249 if(buffer[i] != ' ' && buffer[i] != '\t') 250 { 251 static if(H.SupportPartialData) 252 { 253 parser.headerValueState = SaveHeaderValueState.noNewline; 254 } 255 parser.nextParse = &parseHeaderValue; 256 parseHeaderValue(parser, buffer[i..$]); 257 return; 258 } 259 } 260 } 261 private final void parseHeaderValue(HttpParser* parser, H.DataType[] buffer) 262 { 263 static if(H.SupportPartialData) 264 { 265 final switch(parser.headerValueState) 266 { 267 case SaveHeaderValueState.noNewline: 268 break; 269 case SaveHeaderValueState.carriageReturn: 270 if(buffer.length == 0) 271 { 272 return; 273 } 274 if(buffer[0] == '\n') 275 { 276 parser.headerValueState = SaveHeaderValueState.lineFeed; 277 buffer = buffer[1..$]; 278 goto case SaveHeaderValueState.lineFeed; 279 } 280 // TODO: pass in the original buffer, not a string 281 mixin(genCallbackCode(`H.onHeaderValuePartial(parser, cast(const(H.DataType)[])"\r")`)); 282 parser.headerValueState = SaveHeaderValueState.noNewline; 283 break; 284 case SaveHeaderValueState.lineFeed: 285 if(buffer.length == 0) 286 { 287 return; 288 } 289 if(buffer[0] != ' ' && buffer[0] != '\t') 290 { 291 // header value was already finished 292 mixin(genCallbackCode(`H.onHeaderValue(parser, null)`)); 293 parser.nextParse = &initialParseHeaderName; 294 initialParseHeaderName(parser, buffer); 295 return; 296 } 297 // TODO: pass in the original buffer, not a string 298 mixin(genCallbackCode(`H.onHeaderValuePartial(parser, cast(const(H.DataType)[])"\r\n")`)); 299 parser.headerValueState = SaveHeaderValueState.noNewline; 300 break; 301 } 302 303 assert(parser.headerValueState == SaveHeaderValueState.noNewline); 304 } 305 306 for(uint i = 0; i + 2 < buffer.length; i++) 307 { 308 if('\r' == buffer[i + 0] && 309 '\n' == buffer[i + 1] && 310 (' ' != buffer[i + 2] && '\t' != buffer[i + 2])) 311 { 312 mixin(genCallbackCode(`H.onHeaderValue(parser, buffer[0..i])`)); 313 parser.nextParse = &initialParseHeaderName; 314 initialParseHeaderName(parser, buffer[i + 2..$]); 315 return; 316 } 317 /* 318 TODO: maybe check for valid characters and maybe a maximum header value 319 if(!validTokenChar(buffer[i])) 320 { 321 H.onBadRequest(parser, HttpBadRequestReason.invalidHeaderNameCharacter); 322 parser.nextParse = &parseInvalidState; 323 return; 324 } 325 if(i >= H.MaximumHeaderName) 326 { 327 H.onBadRequest(parser, HttpBadRequestReason.headerNameTooLong); 328 parser.nextParse = &parseInvalidState; 329 return; 330 } 331 */ 332 } 333 334 static if(H.SupportPartialData) 335 { 336 assert(parser.headerValueState == SaveHeaderValueState.noNewline); 337 338 if(buffer.length > 0) 339 { 340 ubyte saved; 341 if(buffer[$-1] == '\r') 342 { 343 saved = 1; 344 parser.headerValueState = SaveHeaderValueState.carriageReturn; 345 } 346 else if(buffer.length >= 2 && buffer[$-1] == '\n' && buffer[$-2] == '\r') 347 { 348 saved = 2; 349 parser.headerValueState = SaveHeaderValueState.lineFeed; 350 } 351 else 352 { 353 saved = 0; 354 } 355 if(buffer.length > saved) 356 { 357 mixin(genCallbackCode(`H.onHeaderValuePartial(parser, buffer[0..$-saved])`)); 358 } 359 } 360 } 361 else 362 { 363 assert(0, "partial data unsupported"); 364 } 365 } 366 } 367 368 enum CharacterFlags : ubyte 369 { 370 none, 371 ctl = 1 << 0, 372 separator = 1 << 1, 373 } 374 __gshared immutable CharacterFlags[128] characterFlagTable = [ 375 '\0' : CharacterFlags.ctl, 376 '\x01' : CharacterFlags.ctl, 377 '\x02' : CharacterFlags.ctl, 378 '\x03' : CharacterFlags.ctl, 379 '\x04' : CharacterFlags.ctl, 380 '\x05' : CharacterFlags.ctl, 381 '\x06' : CharacterFlags.ctl, 382 '\x07' : CharacterFlags.ctl, 383 '\x08' : CharacterFlags.ctl, 384 '\t' : cast(CharacterFlags)(CharacterFlags.ctl | CharacterFlags.separator), 385 '\n' : CharacterFlags.ctl, 386 '\x0B' : CharacterFlags.ctl, 387 '\x0C' : CharacterFlags.ctl, 388 '\r' : CharacterFlags.ctl, 389 '\x0E' : CharacterFlags.ctl, 390 '\x0F' : CharacterFlags.ctl, 391 '\x11' : CharacterFlags.ctl, 392 '\x12' : CharacterFlags.ctl, 393 '\x13' : CharacterFlags.ctl, 394 '\x14' : CharacterFlags.ctl, 395 '\x15' : CharacterFlags.ctl, 396 '\x16' : CharacterFlags.ctl, 397 '\x17' : CharacterFlags.ctl, 398 '\x18' : CharacterFlags.ctl, 399 '\x19' : CharacterFlags.ctl, 400 '\x1A' : CharacterFlags.ctl, 401 '\x1B' : CharacterFlags.ctl, 402 '\x1C' : CharacterFlags.ctl, 403 '\x1D' : CharacterFlags.ctl, 404 '\x1E' : CharacterFlags.ctl, 405 '\x1F' : CharacterFlags.ctl, 406 ' ' : CharacterFlags.separator, 407 '!' : CharacterFlags.none, 408 '"' : CharacterFlags.separator, 409 '#' : CharacterFlags.none, 410 '$' : CharacterFlags.none, 411 '%' : CharacterFlags.none, 412 '&' : CharacterFlags.none, 413 '\'' : CharacterFlags.none, 414 '(' : CharacterFlags.separator, 415 ')' : CharacterFlags.separator, 416 '*' : CharacterFlags.none, 417 '+' : CharacterFlags.none, 418 ',' : CharacterFlags.separator, 419 '-' : CharacterFlags.none, 420 '.' : CharacterFlags.none, 421 '/' : CharacterFlags.separator, 422 '0' : CharacterFlags.none, 423 '1' : CharacterFlags.none, 424 '2' : CharacterFlags.none, 425 '3' : CharacterFlags.none, 426 '4' : CharacterFlags.none, 427 '5' : CharacterFlags.none, 428 '6' : CharacterFlags.none, 429 '7' : CharacterFlags.none, 430 '8' : CharacterFlags.none, 431 '9' : CharacterFlags.none, 432 ':' : CharacterFlags.separator, 433 ';' : CharacterFlags.separator, 434 '<' : CharacterFlags.separator, 435 '=' : CharacterFlags.separator, 436 '>' : CharacterFlags.separator, 437 '?' : CharacterFlags.separator, 438 '@' : CharacterFlags.separator, 439 'A' : CharacterFlags.none, 440 'B' : CharacterFlags.none, 441 'C' : CharacterFlags.none, 442 'D' : CharacterFlags.none, 443 'E' : CharacterFlags.none, 444 'F' : CharacterFlags.none, 445 'G' : CharacterFlags.none, 446 'H' : CharacterFlags.none, 447 'I' : CharacterFlags.none, 448 'J' : CharacterFlags.none, 449 'K' : CharacterFlags.none, 450 'L' : CharacterFlags.none, 451 'M' : CharacterFlags.none, 452 'N' : CharacterFlags.none, 453 'O' : CharacterFlags.none, 454 'P' : CharacterFlags.none, 455 'Q' : CharacterFlags.none, 456 'R' : CharacterFlags.none, 457 'S' : CharacterFlags.none, 458 'T' : CharacterFlags.none, 459 'U' : CharacterFlags.none, 460 'V' : CharacterFlags.none, 461 'W' : CharacterFlags.none, 462 'X' : CharacterFlags.none, 463 'Y' : CharacterFlags.none, 464 'Z' : CharacterFlags.none, 465 '[' : CharacterFlags.separator, 466 '\\' : CharacterFlags.separator, 467 ']' : CharacterFlags.separator, 468 '^' : CharacterFlags.none, 469 '_' : CharacterFlags.none, 470 '`' : CharacterFlags.none, 471 'a' : CharacterFlags.none, 472 'b' : CharacterFlags.none, 473 'c' : CharacterFlags.none, 474 'd' : CharacterFlags.none, 475 'e' : CharacterFlags.none, 476 'f' : CharacterFlags.none, 477 'g' : CharacterFlags.none, 478 'h' : CharacterFlags.none, 479 'i' : CharacterFlags.none, 480 'j' : CharacterFlags.none, 481 'k' : CharacterFlags.none, 482 'l' : CharacterFlags.none, 483 'm' : CharacterFlags.none, 484 'n' : CharacterFlags.none, 485 'o' : CharacterFlags.none, 486 'p' : CharacterFlags.none, 487 'q' : CharacterFlags.none, 488 'r' : CharacterFlags.none, 489 's' : CharacterFlags.none, 490 't' : CharacterFlags.none, 491 'u' : CharacterFlags.none, 492 'v' : CharacterFlags.none, 493 'w' : CharacterFlags.none, 494 'x' : CharacterFlags.none, 495 'y' : CharacterFlags.none, 496 'z' : CharacterFlags.none, 497 '{' : CharacterFlags.separator, 498 '|' : CharacterFlags.none, 499 '}' : CharacterFlags.separator, 500 '~' : CharacterFlags.none, 501 '\x7F' : CharacterFlags.none, 502 ]; 503 504 pragma(inline) 505 auto lookupCharacterFlags(char c) 506 { 507 return (c <= 127) ? characterFlagTable[c] : CharacterFlags.ctl; 508 } 509 pragma(inline) 510 bool validTokenChar(char c) 511 { 512 return 0 == (lookupCharacterFlags(c) & (CharacterFlags.ctl | CharacterFlags.separator)); 513 } 514 version(unittest) 515 { 516 import std.stdio; 517 import std.format; 518 import more.common; 519 enum NextHttpCallback 520 { 521 method, 522 uri, 523 headerName, 524 headerValue, 525 done, 526 } 527 struct Header 528 { 529 string name; 530 string value; 531 } 532 static struct ExpectedData 533 { 534 string expectedMethod; 535 string expectedUri; 536 Header[] expectedHeaders; 537 538 bool expectingBadRequest; 539 HttpBadRequestReason expectedBadRequest; 540 bool gotBadRequest; 541 542 NextHttpCallback nextCallback; 543 uint nextHeaderIndex; 544 uint currentDataOffset; 545 546 this(string expectedMethod, string expectedUri, Header[] expectedHeaders...) 547 { 548 this.expectedMethod = expectedMethod; 549 this.expectedUri = expectedUri; 550 this.expectedHeaders = expectedHeaders; 551 this.expectingBadRequest = false; 552 } 553 this(HttpBadRequestReason expectedBadRequest) 554 { 555 this.expectingBadRequest = true; 556 this.expectedBadRequest = expectedBadRequest; 557 } 558 559 560 void reset() 561 { 562 this.nextCallback = NextHttpCallback.method; 563 this.currentDataOffset = 0; 564 } 565 void allDataHasBeenGiven() 566 { 567 if(expectingBadRequest) 568 { 569 assert(gotBadRequest); 570 } 571 else 572 { 573 assert(nextCallback == NextHttpCallback.done); 574 } 575 } 576 void onBadRequest(HttpBadRequestReason reason) 577 { 578 assert(expectingBadRequest); 579 assert(!gotBadRequest); 580 gotBadRequest = true; 581 assert(expectedBadRequest == reason); 582 } 583 void onMethodPartial(T)(const(T)[] method) 584 { 585 assert(nextCallback == NextHttpCallback.method); 586 if(!expectingBadRequest) 587 { 588 assert(expectedMethod[currentDataOffset..currentDataOffset + method.length] == method[]); 589 currentDataOffset += method.length; 590 } 591 } 592 void onMethod(T)(const(T)[] method) 593 { 594 assert(nextCallback == NextHttpCallback.method); 595 if(!expectingBadRequest) 596 { 597 assert(expectedMethod[currentDataOffset..$] == method[], 598 format("expected \"%s\", got \"%s\"", expectedMethod[currentDataOffset..$], method)); 599 } 600 nextCallback = NextHttpCallback.uri; 601 currentDataOffset = 0; 602 } 603 void onUriPartial(T)(const(T)[] uri) 604 { 605 assert(nextCallback == NextHttpCallback.uri); 606 if(!expectingBadRequest) 607 { 608 assert(expectedUri[currentDataOffset..currentDataOffset + uri.length] == uri[]); 609 currentDataOffset += uri.length; 610 } 611 } 612 void onUri(T)(const(T)[] uri) 613 { 614 assert(nextCallback == NextHttpCallback.uri); 615 if(!expectingBadRequest) 616 { 617 assert(expectedUri[currentDataOffset..$] == uri[]); 618 } 619 if(expectedHeaders.length > 0) 620 { 621 nextCallback = NextHttpCallback.headerName; 622 } 623 else 624 { 625 nextCallback = NextHttpCallback.done; 626 } 627 currentDataOffset = 0; 628 } 629 630 void onHeaderNamePartial(T)(const(T)[] headerName) 631 { 632 assert(nextCallback == NextHttpCallback.headerName); 633 if(!expectingBadRequest) 634 { 635 assert(expectedHeaders[nextHeaderIndex].name[currentDataOffset..currentDataOffset + headerName.length] == headerName[]); 636 currentDataOffset += headerName.length; 637 } 638 } 639 void onHeaderName(T)(const(T)[] headerName) 640 { 641 assert(nextCallback == NextHttpCallback.headerName); 642 if(!expectingBadRequest) 643 { 644 assert(expectedHeaders[nextHeaderIndex].name[currentDataOffset..$] == headerName[]); 645 } 646 nextCallback = NextHttpCallback.headerValue; 647 currentDataOffset = 0; 648 } 649 void onHeaderValuePartial(T)(const(T)[] value) 650 { 651 assert(nextCallback == NextHttpCallback.headerValue); 652 if(!expectingBadRequest) 653 { 654 assert(expectedHeaders[nextHeaderIndex].value[currentDataOffset..currentDataOffset + value.length] == value[], 655 format("expected \"%s\" got \"%s\"", Escaped(expectedHeaders[nextHeaderIndex].value[currentDataOffset..currentDataOffset + value.length]), Escaped(value))); 656 currentDataOffset += value.length; 657 } 658 } 659 void onHeaderValue(T)(const(T)[] value) 660 { 661 assert(nextCallback == NextHttpCallback.headerValue); 662 if(!expectingBadRequest) 663 { 664 assert(expectedHeaders[nextHeaderIndex].value[currentDataOffset..$] == value[], 665 format("expected \"%s\" got \"%s\"", 666 Escaped(expectedHeaders[nextHeaderIndex].value[currentDataOffset..$]), Escaped(value))); 667 } 668 nextHeaderIndex++; 669 if(nextHeaderIndex >= expectedHeaders.length) 670 { 671 nextCallback = NextHttpCallback.done; 672 } 673 else 674 { 675 nextCallback = NextHttpCallback.headerName; 676 } 677 currentDataOffset = 0; 678 } 679 void onHeadersDone(T)(const(T)[] bodyData) 680 { 681 assert(nextCallback == NextHttpCallback.done); 682 assert(nextHeaderIndex == expectedHeaders.length); 683 } 684 } 685 } 686 unittest 687 { 688 import more.test; 689 mixin(scopedTest!"httpparser"); 690 691 static void test(H)(HttpParser!H parser, ExpectedData expected, const(char)[] request) 692 { 693 if(H.SupportPartialData) 694 { 695 foreach(chunkLength; 1..request.length-1) 696 { 697 parser.reset(); 698 parser.expected = expected; 699 size_t offset = 0; 700 for(; offset + chunkLength <= request.length; offset += chunkLength) 701 { 702 //writefln("parsing \"%s\"", Escaped(request[offset..offset + chunkLength])); 703 parser.parse(request[offset..offset + chunkLength]); 704 } 705 if(offset < request.length) 706 { 707 //writefln("parsing \"%s\"", Escaped(request[offset..$])); 708 parser.parse(request[offset..$]); 709 } 710 parser.expected.allDataHasBeenGiven(); 711 } 712 } 713 714 parser.expected.reset(); 715 parser.reset(); 716 parser.expected = expected; 717 //writefln("parsing \"%s\"", Escaped(request)); 718 parser.parse(request); 719 parser.expected.allDataHasBeenGiven(); 720 } 721 722 static struct Hooks1 723 { 724 alias DataType = const(char); 725 726 // Note: maximum are meant to stop bad data earlier on, they do not increase memory usage 727 enum MaximumMethodName = 30; 728 enum MaximumHeaderName = 40; 729 730 enum SupportPartialData = false; 731 enum SupportCallbackStop = false; 732 mixin template HttpParserMixinTemplate() 733 { 734 ExpectedData expected; 735 } 736 static void onBadRequest(HttpParser!Hooks1* parser, HttpBadRequestReason reason) 737 { 738 parser.expected.onBadRequest(reason); 739 } 740 static void onMethod(HttpParser!Hooks1* parser, DataType[] method) 741 { 742 parser.expected.onMethod(method); 743 } 744 static void onUri(HttpParser!Hooks1* parser, DataType[] uri) 745 { 746 parser.expected.onUri(uri); 747 } 748 static void onHeaderName(HttpParser!Hooks1* parser, DataType[] headerName) 749 { 750 parser.expected.onHeaderName(headerName); 751 } 752 static void onHeaderValue(HttpParser!Hooks1* parser, DataType[] value) 753 { 754 parser.expected.onHeaderValue(value); 755 } 756 static void onHeadersDone(HttpParser!Hooks1* parser, DataType[] bodyData) 757 { 758 parser.expected.onHeadersDone(bodyData); 759 } 760 } 761 static struct Hooks2 762 { 763 alias DataType = const(char); 764 765 // Note: maximum are meant to stop bad data earlier on, they do not increase memory usage 766 enum MaximumMethodName = 30; 767 enum MaximumHeaderName = 40; 768 769 enum SupportPartialData = true; 770 enum SupportCallbackStop = false; 771 mixin template HttpParserMixinTemplate() 772 { 773 ExpectedData expected; 774 } 775 static void onBadRequest(HttpParser!Hooks2* parser, HttpBadRequestReason reason) 776 { 777 parser.expected.onBadRequest(reason); 778 } 779 static void onMethodPartial(HttpParser!Hooks2* parser, DataType[] method) 780 { 781 parser.expected.onMethodPartial(method); 782 } 783 static void onMethod(HttpParser!Hooks2* parser, DataType[] method) 784 { 785 parser.expected.onMethod(method); 786 } 787 static void onUriPartial(HttpParser!Hooks2* parser, DataType[] uri) 788 { 789 parser.expected.onUriPartial(uri); 790 } 791 static void onUri(HttpParser!Hooks2* parser, DataType[] uri) 792 { 793 parser.expected.onUri(uri); 794 } 795 static void onHeaderNamePartial(HttpParser!Hooks2* parser, DataType[] headerName) 796 { 797 parser.expected.onHeaderNamePartial(headerName); 798 } 799 static void onHeaderName(HttpParser!Hooks2* parser, DataType[] headerName) 800 { 801 parser.expected.onHeaderName(headerName); 802 } 803 static void onHeaderValuePartial(HttpParser!Hooks2* parser, DataType[] value) 804 { 805 parser.expected.onHeaderValuePartial(value); 806 } 807 static void onHeaderValue(HttpParser!Hooks2* parser, DataType[] value) 808 { 809 parser.expected.onHeaderValue(value); 810 } 811 static void onHeadersDone(HttpParser!Hooks2* parser, DataType[] bodyData) 812 { 813 parser.expected.onHeadersDone(bodyData); 814 } 815 } 816 // TODO: add tests to make sure the SupportCallbackStop works properly 817 static struct Hooks3 818 { 819 alias DataType = char; 820 821 // Note: maximum are meant to stop bad data earlier on, they do not increase memory usage 822 enum MaximumMethodName = 30; 823 enum MaximumHeaderName = 40; 824 825 enum SupportPartialData = true; 826 enum SupportCallbackStop = true; 827 mixin template HttpParserMixinTemplate() 828 { 829 ExpectedData expected; 830 } 831 static void onBadRequest(HttpParser!Hooks2* parser, HttpBadRequestReason reason) 832 { 833 parser.expected.onBadRequest(reason); 834 } 835 static Flag!"stop" onMethodPartial(HttpParser!Hooks2* parser, DataType[] method) 836 { 837 parser.expected.onMethodPartial(method); 838 return No.stop; 839 } 840 static Flag!"stop" onMethod(HttpParser!Hooks2* parser, DataType[] method) 841 { 842 parser.expected.onMethod(method); 843 return No.stop; 844 } 845 static Flag!"stop" onUriPartial(HttpParser!Hooks2* parser, DataType[] uri) 846 { 847 parser.expected.onUriPartial(uri); 848 return No.stop; 849 } 850 static Flag!"stop" onUri(HttpParser!Hooks2* parser, DataType[] uri) 851 { 852 parser.expected.onUri(uri); 853 return No.stop; 854 } 855 static Flag!"stop" onHeaderNamePartial(HttpParser!Hooks2* parser, DataType[] headerName) 856 { 857 parser.expected.onHeaderNamePartial(headerName); 858 return No.stop; 859 } 860 static Flag!"stop" onHeaderName(HttpParser!Hooks2* parser, DataType[] headerName) 861 { 862 parser.expected.onHeaderName(headerName); 863 return No.stop; 864 } 865 static Flag!"stop" onHeaderValuePartial(HttpParser!Hooks2* parser, DataType[] value) 866 { 867 parser.expected.onHeaderValuePartial(value); 868 return No.stop; 869 } 870 static Flag!"stop" onHeaderValue(HttpParser!Hooks2* parser, DataType[] value) 871 { 872 parser.expected.onHeaderValue(value); 873 return No.stop; 874 } 875 static void onHeadersDone(HttpParser!Hooks2* parser, DataType[] bodyData) 876 { 877 parser.expected.onHeadersDone(bodyData); 878 } 879 } 880 881 template tuple(T...) 882 { 883 alias tuple = T; 884 } 885 foreach(hooks; tuple!(Hooks1, Hooks2)) 886 { 887 auto parser = HttpParser!hooks(); 888 889 // invalid method character 890 foreach(c; '\0'..'\x7F') 891 { 892 if(c.validTokenChar || c == ' ') 893 { 894 continue; 895 } 896 { 897 parser.reset(); 898 char[2] method; 899 method[0] = c; 900 method[1] = ' '; 901 parser.expected = ExpectedData(HttpBadRequestReason.invalidMethodCharacter); 902 parser.parse(method); 903 } 904 { 905 parser.reset(); 906 char[5] method; 907 method[0..3] = "GET"; 908 method[3] = c; 909 method[4] = ' '; 910 parser.expected = ExpectedData(HttpBadRequestReason.invalidMethodCharacter); 911 parser.parse(method); 912 } 913 } 914 // method name too long 915 { 916 parser.reset(); 917 char[hooks.MaximumMethodName + 1] method; 918 method[] = 'A'; 919 parser.expected = ExpectedData(HttpBadRequestReason.methodNameTooLong); 920 parser.parse(method); 921 } 922 923 // bad http version 924 foreach(badHttpVersion; [ 925 "GET /index.html HTTP/1.1\r\r", 926 "GET /index.html HTTP/1.0\r\n", 927 "GET /index.html !TTP/1.0\r\n", 928 ]) 929 { 930 parser.reset(); 931 parser.expected = ExpectedData(HttpBadRequestReason.badHttpVersion); 932 writefln("test \"%s\"", Escaped(badHttpVersion)); 933 parser.parse(badHttpVersion); 934 parser.expected.allDataHasBeenGiven(); 935 } 936 937 test(parser, ExpectedData("GET", "/index.html"), "GET /index.html HTTP/1.1\r\n\r\n"); 938 test(parser, ExpectedData("POST", "12345678901234567890__________1234567890fdjadjakljflda"), 939 "POST 12345678901234567890__________1234567890fdjadjakljflda HTTP/1.1\r\n\r\n"); 940 941 test(parser, ExpectedData("GET", "/index.html", Header("Host", "www.google.com")), 942 "GET /index.html HTTP/1.1\r\nHost: www.google.com\r\n\r\n"); 943 test(parser, ExpectedData("GET", "/index.html", Header("Host", "www.google.com\r\n hi")), 944 "GET /index.html HTTP/1.1\r\nHost: www.google.com\r\n hi\r\n\r\n"); 945 test(parser, ExpectedData("GET", "/index.html", Header("Host", "www.google.com\r\r")), 946 "GET /index.html HTTP/1.1\r\nHost: www.google.com\r\r\r\n\r\n"); 947 } 948 }