1 /****************************************************************************** 2 3 HTTP message header parser 4 5 Link with 6 7 -L-lglib-2.0 8 9 . 10 11 Copyright: 12 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 13 All rights reserved. 14 15 License: 16 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 17 Alternatively, this file may be distributed under the terms of the Tango 18 3-Clause BSD License (see LICENSE_BSD.txt for details). 19 20 ******************************************************************************/ 21 22 module ocean.net.http.message.HttpHeaderParser; 23 24 25 import ocean.meta.types.Qualifiers; 26 import ocean.core.Enforce; 27 import ocean.core.Verify; 28 import ocean.text.util.SplitIterator: ChrSplitIterator, ISplitIterator; 29 30 import ocean.net.http.HttpException: HttpParseException; 31 32 version (unittest) import ocean.core.Test; 33 34 public alias long ssize_t; 35 36 /****************************************************************************** 37 38 Searches the haystack[0 .. haystack_len], if haystack_len is >= 0, for the 39 first occurrence of the string needle. If haystack[0 .. haystack_len] 40 contains a NUL byte, the search will stop there. 41 If haystack_len is -1, haystack is assumed to be a NUL-terminated string and 42 needle is searched in the whole haystack string. 43 44 This is a GLib function. 45 46 @see http://developer.gnome.org/glib/stable/glib-String-Utility-Functions.html#g-strstr-len 47 48 Params: 49 haystack = haystack.ptr for haystack_len >= 0 or a pointer to a 50 NUL-terminated string if haystack_len = -1 51 haystack_len = haystack.length or -1 if haystack is NUL-terminated 52 needle = the string to search for (NUL-terminated) 53 54 Returns: 55 a pointer to the found occurrence, or null if not found. 56 57 ******************************************************************************/ 58 59 extern (C) private char* g_strstr_len(const(char)* haystack, ssize_t haystack_len, const(char)* needle); 60 61 /****************************************************************************** 62 63 Interface for the header parser to get the parse results and set limits 64 65 ******************************************************************************/ 66 67 interface IHttpHeaderParser 68 { 69 /************************************************************************** 70 71 Header element 72 73 **************************************************************************/ 74 75 struct HeaderElement 76 { 77 cstring key, val; 78 } 79 80 /************************************************************************** 81 82 Obtains a list of HeaderElement instances referring to the header lines 83 parsed so far. The key member of each element references the slice of 84 the corresponding header line before the first ':', the val member the 85 slice after the first ':'. Leading and tailing white space is trimmed 86 off both key and val. 87 88 Returns: 89 list of HeaderElement instances referring to the header lines parsed 90 so far 91 92 **************************************************************************/ 93 94 HeaderElement[] header_elements ( ); 95 96 /************************************************************************** 97 98 Returns: 99 list of the the header lines parsed so far 100 101 **************************************************************************/ 102 103 cstring[] header_lines ( ); 104 105 /************************************************************************** 106 107 Returns: 108 limit for the number of HTTP message header lines 109 110 **************************************************************************/ 111 112 size_t header_lines_limit ( ); 113 114 /************************************************************************** 115 116 Sets the limit for the number of HTTP message header lines. 117 118 Note: A buffer size is set to n elements so use a realistic value (not 119 uint.max for example). 120 121 Params: 122 n = limit for the number of HTTP message header lines 123 124 Returns: 125 limit for the number of HTTP message header lines 126 127 **************************************************************************/ 128 129 size_t header_lines_limit ( size_t n ); 130 131 /************************************************************************** 132 133 Returns: 134 limit for the number of HTTP message header lines 135 136 **************************************************************************/ 137 138 size_t header_length_limit ( ); 139 140 /************************************************************************** 141 142 Sets the HTTP message header size limit. This will reset the parse 143 state and clear the content. 144 145 Note: A buffer size is set to n elements so use a realistic value (not 146 uint.max for example). 147 148 Params: 149 n = HTTP message header size limit 150 151 Returns: 152 HTTP message header size limit 153 154 **************************************************************************/ 155 156 size_t header_length_limit ( size_t n ); 157 } 158 159 /****************************************************************************** 160 161 HttpHeaderParser class 162 163 ******************************************************************************/ 164 165 class HttpHeaderParser : IHttpHeaderParser 166 { 167 /*************************************************************************** 168 169 Object pool index -- allows the construction of a pool of objects of 170 this type. 171 172 ***************************************************************************/ 173 174 public size_t object_pool_index; 175 176 /************************************************************************** 177 178 Default values for header size limitation 179 180 **************************************************************************/ 181 182 static immutable size_t DefaultSizeLimit = 16 * 1024, 183 DefaultLinesLimit = 64; 184 185 /************************************************************************** 186 187 Header lines split iterator 188 189 **************************************************************************/ 190 191 private static class SplitHeaderLines : ISplitIterator 192 { 193 /************************************************************************** 194 195 End-of-header-line token 196 197 **************************************************************************/ 198 199 static immutable EndOfHeaderLine = "\r\n"; 200 201 /************************************************************************** 202 203 Locates the first occurrence of the current delimiter string in str, 204 starting from str[start]. 205 206 Params: 207 str = string to scan for delimiter 208 start = search start index 209 210 Returns: 211 index of first occurrence of the current delimiter string in str or 212 str.length if not found 213 214 **************************************************************************/ 215 216 public override size_t locateDelim ( cstring str, size_t start = 0 ) 217 { 218 verify( 219 start < str.length, 220 typeof (this).stringof ~ 221 ".locateDelim: start index out of range" 222 ); 223 char* item = g_strstr_len(str.ptr + start, str.length - start, this.EndOfHeaderLine.ptr); 224 225 return item? item - str.ptr : str.length; 226 } 227 228 /************************************************************************** 229 230 Skips the delimiter which str starts with. 231 Note that the result is correct only if str really starts with a 232 delimiter. 233 234 Params: 235 str = string starting with delimiter 236 237 Returns: 238 index of the first character after the starting delimiter in str 239 240 **************************************************************************/ 241 242 protected override size_t skipDelim ( cstring str ) 243 { 244 verify(str.length >= this.EndOfHeaderLine.length); 245 return this.EndOfHeaderLine.length; 246 } 247 } 248 249 /************************************************************************** 250 251 HTTP message header content buffer. 252 content.length determines the header length limit. 253 254 **************************************************************************/ 255 256 private mstring content; 257 258 /************************************************************************** 259 260 Length of actual data in content. 261 262 **************************************************************************/ 263 264 private size_t content_length; 265 266 /************************************************************************** 267 268 Position (index) in the content up to which the content has already been 269 parsed 270 271 **************************************************************************/ 272 273 private size_t pos = 0; 274 275 /************************************************************************** 276 277 false after reset() and before the start line is complete 278 279 **************************************************************************/ 280 281 private bool have_start_line = false; 282 283 /************************************************************************** 284 285 Number of header lines parsed so far, excluding the start line 286 287 **************************************************************************/ 288 289 private size_t n_header_lines = 0; 290 291 /************************************************************************** 292 293 Header lines, excluding the start line; elements slice this.content. 294 295 **************************************************************************/ 296 297 private cstring[] header_lines_; 298 299 /************************************************************************** 300 301 Header elements 302 303 "key" references the slice of the corresponding header line before the 304 first ':' and "val" after the first ':'. Leading and tailing white space 305 is trimmed off both key and val. 306 307 **************************************************************************/ 308 309 private HeaderElement[] header_elements_; 310 311 /************************************************************************** 312 313 Reusable exception instance 314 315 **************************************************************************/ 316 317 private HttpParseException exception; 318 319 /************************************************************************** 320 321 Indicates that the header is complete 322 323 **************************************************************************/ 324 325 private bool finished = false; 326 327 /************************************************************************** 328 329 Counter consistency check 330 331 **************************************************************************/ 332 333 invariant ( ) 334 { 335 assert (this.pos <= this.content_length); 336 assert (this.header_elements_.length == this.header_lines_.length); 337 assert (this.n_header_lines <= this.header_lines_.length); 338 339 assert (this.content_length <= this.content.length); 340 } 341 342 /************************************************************************** 343 344 Constructor 345 346 **************************************************************************/ 347 348 public this ( ) 349 { 350 this(this.DefaultSizeLimit, this.DefaultLinesLimit); 351 } 352 353 /************************************************************************** 354 355 Constructor 356 357 Note: Each a buffer with size_limit and lines_limit elements is 358 allocated so use realistic values (not uint.max for example). 359 360 Params: 361 size_limit = HTTP message header size limit 362 lines_limit = limit for the number of HTTP message header lines 363 364 **************************************************************************/ 365 366 public this ( size_t size_limit, size_t lines_limit ) 367 { 368 this.exception = new HttpParseException; 369 this.content = new char[size_limit]; 370 this.header_lines_ = new cstring[lines_limit]; 371 this.header_elements_ = new HeaderElement[lines_limit]; 372 } 373 374 /************************************************************************** 375 376 Start line tokens; slice the internal content buffer 377 378 **************************************************************************/ 379 380 public cstring[3] start_line_tokens; 381 382 /************************************************************************** 383 384 Obtains a list of HeaderElement instances referring to the header lines 385 parsed so far. The key member of each element references the slice of 386 the corresponding header line before the first ':', the val member the 387 slice after the first ':'. Leading and tailing white space is trimmed 388 off both key and val. 389 390 Returns: 391 list of HeaderElement instances referring to the header lines parsed 392 so far 393 394 **************************************************************************/ 395 396 public override HeaderElement[] header_elements ( ) 397 { 398 return this.header_elements_[0 .. this.n_header_lines]; 399 } 400 401 /************************************************************************** 402 403 Returns: 404 list of the the header lines parsed so far 405 406 **************************************************************************/ 407 408 public cstring[] header_lines ( ) 409 { 410 return this.header_lines_[0 .. this.n_header_lines]; 411 } 412 413 /************************************************************************** 414 415 Returns: 416 limit for the number of HTTP message header lines 417 418 **************************************************************************/ 419 420 public override size_t header_lines_limit ( ) 421 { 422 return this.header_lines_.length; 423 } 424 425 /************************************************************************** 426 427 Sets the limit for the number of HTTP message header lines. 428 429 Note: A buffer size is set to n elements so use a realistic value (not 430 uint.max for example). 431 432 Params: 433 n = limit for the number of HTTP message header lines 434 435 Returns: 436 limit for the number of HTTP message header lines 437 438 **************************************************************************/ 439 440 public override size_t header_lines_limit ( size_t n ) 441 { 442 if (this.n_header_lines > n) 443 { 444 this.n_header_lines = n; 445 } 446 447 return this.header_lines_.length = n; 448 } 449 450 /************************************************************************** 451 452 Returns: 453 HTTP message header size limit 454 455 **************************************************************************/ 456 457 public override size_t header_length_limit ( ) 458 { 459 return this.content.length; 460 } 461 462 /************************************************************************** 463 464 Sets the HTTP message header size limit. This will reset the parse 465 state and clear the content. 466 467 Note: A buffer size is set to n elements so use a realistic value (not 468 uint.max for example). 469 470 Params: 471 n = HTTP message header size limit 472 473 Returns: 474 HTTP message header size limit 475 476 **************************************************************************/ 477 478 public override size_t header_length_limit ( size_t n ) 479 { 480 this.reset(); 481 482 return this.content.length = n; 483 } 484 485 /************************************************************************** 486 487 Resets the parse state and clears the content. 488 489 Returns: 490 this instance 491 492 **************************************************************************/ 493 494 typeof (this) reset ( ) 495 { 496 this.start_line_tokens[] = null; 497 498 this.pos = 0; 499 this.content_length = 0; 500 501 this.n_header_lines = 0; 502 503 this.have_start_line = false; 504 this.finished = false; 505 506 return this; 507 } 508 509 /************************************************************************** 510 511 Parses content which is expected to be either the start of a HTTP 512 message or a HTTP message fragment that continues the content passed on 513 the last call to this method. Appends the slice of content which is part 514 of the HTTP message header (that is, everything before the end-of-header 515 token "\r\n\r\n" or content itself if it does not contain that token). 516 After the end of the message header has been reached, which is indicated 517 by a non-null return value, reset() must be called before calling this 518 method again. 519 Leading empty header lines are tolerated and ignored: 520 521 "In the interest of robustness, servers SHOULD ignore any empty 522 line(s) received where a Request-Line is expected." 523 524 @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.1 525 526 Returns: 527 A slice of content after the end-of-header token (which may be an 528 empty string) or null if content does not contain the end-of-header 529 token. 530 531 Throws: 532 HttpParseException 533 - on parse error: if 534 * the number of start line tokens is different from 3 or 535 * a regular header_line does not contain a ':'; 536 - on limit excess: if 537 * the header size in bytes exceeds the requested limit or 538 * the number of header lines in exceeds the requested limit. 539 540 Assert()s that this method is not called after the end of header had 541 been reacched. 542 543 **************************************************************************/ 544 545 public cstring parse ( cstring content ) 546 { 547 verify(!this.finished, "parse() called after finished"); 548 549 cstring msg_body_start = null; 550 551 scope split_header = new SplitHeaderLines; 552 553 split_header.include_remaining = false; 554 555 cstring content_remaining = this.appendContent(content); 556 557 foreach (header_line; split_header.reset(this.content[this.pos .. this.content_length])) 558 { 559 cstring remaining = split_header.remaining; 560 561 if (header_line.length) 562 { 563 if (this.have_start_line) 564 { 565 this.parseRegularHeaderLine(header_line); 566 } 567 else 568 { 569 this.parseStartLine(header_line); 570 571 this.have_start_line = true; 572 } 573 574 this.pos = this.content_length - remaining.length; 575 } 576 else 577 { 578 this.finished = this.have_start_line; // Ignore empty leading header lines 579 580 if (this.finished) 581 { 582 msg_body_start = remaining.length? remaining : content_remaining; 583 break; 584 } 585 } 586 } 587 588 return msg_body_start; 589 } 590 591 alias parse opCall; 592 593 /************************************************************************** 594 595 Appends content to this.content. 596 597 Params: 598 content = content fragment to append 599 600 Returns: 601 current content from the current parse position to the end of the 602 newly appended fragment 603 604 Throws: 605 HttpParseException if the header size in bytes exceeds the requested 606 limit. 607 608 **************************************************************************/ 609 610 private cstring appendContent ( cstring chunk ) 611 out (remaining) 612 { 613 assert (remaining.length <= chunk.length); 614 } 615 do 616 { 617 verify(this.content_length <= this.content.length); 618 619 size_t max_len = this.content.length - this.content_length, 620 consumed = chunk.length; 621 622 if (consumed > max_len) 623 { 624 /* 625 * If the chunk exceeds the header length limit, it may contain the 626 * start of the message body: Look for the end-of-header token in 627 * chunk[0 .. max_len]. If not found, the header is really too long. 628 */ 629 630 static immutable end_of_header = "\r\n\r\n"; 631 632 char* header_end = g_strstr_len(chunk.ptr, max_len, end_of_header.ptr); 633 634 enforce(this.exception.set("request header too long: ") 635 .append(this.start_line_tokens[1]), 636 header_end !is null); 637 638 consumed = (header_end - chunk.ptr) + end_of_header.length; 639 640 verify(chunk[consumed - end_of_header.length .. consumed] == end_of_header); 641 } 642 643 // Append chunk to this.content. 644 645 size_t end = this.content_length + consumed; 646 647 this.content[this.content_length .. end] = chunk[0 .. consumed]; 648 649 this.content_length = end; 650 651 /* 652 * Return the tail of chunk that was not appended. This tail is empty 653 * unless chunk exceeded the header length limit and the end-of-header 654 * token was found in chunk. 655 */ 656 657 return chunk[consumed .. $]; 658 } 659 660 /************************************************************************** 661 662 Parses header_line which is expected to be a regular HTTP message header 663 line (not the start line or the empty message header termination line). 664 665 Params: 666 header_line = regular message header line 667 668 Returns: 669 HeaderElement instance referring to the parsed line 670 671 Throws: 672 HttpParseException 673 - if the number of header lines exceeds the requested limit or 674 - on parse error: if the header_line does not contain a ':'. 675 676 **************************************************************************/ 677 678 private void parseRegularHeaderLine ( cstring header_line ) 679 { 680 681 enforce(this.exception.set("too many request header lines"), 682 this.n_header_lines <= this.header_lines_.length); 683 684 scope split_tokens = new ChrSplitIterator(':'); 685 686 split_tokens.collapse = true; 687 split_tokens.include_remaining = false; 688 689 690 foreach (field_name; split_tokens.reset(header_line)) 691 { 692 this.header_elements_[this.n_header_lines] = HeaderElement(ChrSplitIterator.trim(field_name), 693 ChrSplitIterator.trim(split_tokens.remaining)); 694 695 break; 696 } 697 698 enforce(this.exception.set("invalid header line (no ':')"), 699 split_tokens.n); 700 701 this.header_lines_[this.n_header_lines++] = header_line; 702 } 703 704 /************************************************************************** 705 706 Parses start_line which is expected to be the HTTP message header start 707 line (not a regular header line or the empty message header termination 708 line). 709 710 Params: 711 header_line = regular message header line 712 713 Throws: 714 HttpParseException on parse error: if the number of start line 715 tokens is different from 3. 716 717 **************************************************************************/ 718 719 private void parseStartLine ( cstring start_line ) 720 { 721 scope split_tokens = new ChrSplitIterator(' '); 722 723 split_tokens.collapse = true; 724 split_tokens.include_remaining = true; 725 726 uint i = 0; 727 728 foreach (token; split_tokens.reset(start_line)) 729 { 730 i = split_tokens.n; 731 732 this.start_line_tokens[i - 1] = token; 733 734 /* 735 * For http responses, the third token is the error description, 736 * which may contain spaces. eg, 737 * "HTTP/1.1 301 Moved Permanently" 738 * 739 * TODO: Replace this foreach with calls to split_tokens.next 740 */ 741 742 if (i >= this.start_line_tokens.length - 1) 743 { 744 this.start_line_tokens[i] = split_tokens.remaining; 745 ++i; 746 break; 747 } 748 } 749 750 enforce(this.exception.set("invalid start line (too few tokens)"), 751 i == this.start_line_tokens.length); 752 } 753 } 754 755 //version = OceanPerformanceTest; 756 757 import core.stdc.time: time; 758 import core.sys.posix.stdlib: srand48, drand48; 759 760 version (OceanPerformanceTest) 761 { 762 import ocean.io.Stdout; 763 import ocean.core.internal.gcInterface: gc_disable, gc_enable; 764 } 765 766 unittest 767 { 768 769 { 770 scope parser = new HttpHeaderParser; 771 772 static immutable content1 = "POST / HTTP/1.1\r\n" // 17 773 ~ "Content-Length: 12\r\n" // 37 774 ~ "\r\n" // 39 775 ~ "Hello World!"; 776 777 778 parser.header_length_limit = 39; 779 780 try 781 { 782 parser.parse(content1); 783 } 784 catch (HttpParseException e) 785 { 786 test(false); 787 } 788 789 parser.reset(); 790 791 parser.header_length_limit = 38; 792 793 try 794 { 795 parser.parse(content1); 796 } 797 catch (HttpParseException e) { } 798 } 799 800 static immutable istring lorem_ipsum = 801 "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod " 802 ~ "tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim " 803 ~ "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex " 804 ~ "ea commodi consequat. Quis aute iure reprehenderit in voluptate velit " 805 ~ "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat " 806 ~ "cupiditat non proident, sunt in culpa qui officia deserunt mollit " 807 ~ "anim id est laborum. Duis autem vel eum iriure dolor in hendrerit in " 808 ~ "vulputate velit esse molestie consequat, vel illum dolore eu feugiat " 809 ~ "nulla facilisis at vero eros et accumsan et iusto odio dignissim qui " 810 ~ "blandit praesent luptatum zzril delenit augue duis dolore te feugait " 811 ~ "nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing " 812 ~ "elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna " 813 ~ "aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud " 814 ~ "exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea " 815 ~ "commodo consequat. Duis autem vel eum iriure dolor in hendrerit in " 816 ~ "vulputate velit esse molestie consequat, vel illum dolore eu feugiat " 817 ~ "nulla facilisis at vero eros et accumsan et iusto odio dignissim qui " 818 ~ "blandit praesent luptatum zzril delenit augue duis dolore te feugait " 819 ~ "nulla facilisi. Nam liber tempor cum soluta nobis eleifend option " 820 ~ "congue nihil imperdiet doming id quod mazim placerat facer possim " 821 ~ "assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed " 822 ~ "diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam " 823 ~ "erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci " 824 ~ "tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo " 825 ~ "consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate " 826 ~ "velit esse molestie consequat, vel illum dolore eu feugiat nulla " 827 ~ "facilisis. At vero eos et accusam et justo duo dolores et ea rebum. " 828 ~ "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum " 829 ~ "dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing " 830 ~ "elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore " 831 ~ "magna aliquyam erat, sed diam voluptua. At vero eos et accusam et " 832 ~ "justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " 833 ~ "takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor " 834 ~ "sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam " 835 ~ "dolore dolores duo eirmod eos erat, et nonumy sed tempor et et " 836 ~ "invidunt justo labore Stet clita ea et gubergren, kasd magna no " 837 ~ "rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum " 838 ~ "dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing " 839 ~ "elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore " 840 ~ "magna aliquyam erat. Consetetur sadipscing elitr, sed diam nonumy " 841 ~ "eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed " 842 ~ "diam voluptua. At vero eos et accusam et justo duo dolores et ea " 843 ~ "rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem " 844 ~ "ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur " 845 ~ "sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et " 846 ~ "dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam " 847 ~ "et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " 848 ~ "takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor " 849 ~ "sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor " 850 ~ "invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. " 851 ~ "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita " 852 ~ "kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit " 853 ~ "amet."; 854 855 static immutable istring content2 = 856 "POST /dir?query=Hello%20World!&abc=def&ghi HTTP/1.1\r\n" 857 ~ "Host: www.example.org:12345\r\n" 858 ~ "User-Agent: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.17) Gecko/20110422 Ubuntu/9.10 (karmic) Firefox/3.6.17\r\n" 859 ~ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" 860 ~ "Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3\r\n" 861 ~ "Accept-Encoding: gzip,deflate\r\n" 862 ~ "Accept-Charset: UTF-8,*\r\n" 863 ~ "Keep-Alive: 115\r\n" 864 ~ "Connection: keep-alive\r\n" 865 ~ "Cache-Control: max-age=0\r\n" 866 ~ "\r\n" ~ 867 lorem_ipsum; 868 869 static immutable parts = 10; 870 871 /* 872 * content will be split into parts parts where the length of each part is 873 * content.length / parts + d with d a random number in the range 874 * [-(content.length / parts) / 3, +(content.length / parts) / 3]. 875 */ 876 877 static size_t random_chunk_length ( ) 878 { 879 static immutable c = content2.length * (2.0f / (parts * 3)); 880 881 static assert (c >= 3, "too many parts"); 882 883 return cast (size_t) (c + cast (float) drand48() * c); 884 } 885 886 srand48(time(null)); 887 888 scope parser = new HttpHeaderParser; 889 890 version (OceanPerformanceTest) 891 { 892 static immutable n = 1000_000; 893 } 894 else 895 { 896 static immutable n = 10; 897 } 898 899 version (OceanPerformanceTest) 900 { 901 gc_disable(); 902 903 scope (exit) gc_enable(); 904 } 905 906 for (uint i = 0; i < n; i++) 907 { 908 parser.reset(); 909 910 { 911 size_t next = random_chunk_length(); 912 913 cstring msg_body_start = parser.parse(content2[0 .. next]); 914 915 while (msg_body_start is null) 916 { 917 size_t pos = next; 918 919 next = pos + random_chunk_length(); 920 921 if (next < content2.length) 922 { 923 msg_body_start = parser.parse(content2[pos .. next]); 924 } 925 else 926 { 927 msg_body_start = parser.parse(content2[pos .. content2.length]); 928 929 test (msg_body_start !is null); 930 test (msg_body_start.length <= content2.length); 931 test (msg_body_start == content2[content2.length - msg_body_start.length .. content2.length]); 932 } 933 } 934 } 935 936 test (parser.start_line_tokens[0] == "POST"); 937 test (parser.start_line_tokens[1] == "/dir?query=Hello%20World!&abc=def&ghi"); 938 test (parser.start_line_tokens[2] == "HTTP/1.1"); 939 940 { 941 auto elements = parser.header_elements; 942 943 with (elements[0]) test (key == "Host" && val == "www.example.org:12345"); 944 with (elements[1]) test (key == "User-Agent" && val == "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.17) Gecko/20110422 Ubuntu/9.10 (karmic) Firefox/3.6.17"); 945 with (elements[2]) test (key == "Accept" && val == "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); 946 with (elements[3]) test (key == "Accept-Language" && val == "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3"); 947 with (elements[4]) test (key == "Accept-Encoding" && val == "gzip,deflate"); 948 with (elements[5]) test (key == "Accept-Charset" && val == "UTF-8,*"); 949 with (elements[6]) test (key == "Keep-Alive" && val == "115"); 950 with (elements[7]) test (key == "Connection" && val == "keep-alive"); 951 with (elements[8]) test (key == "Cache-Control" && val == "max-age=0"); 952 953 test (elements.length == 9); 954 } 955 956 { 957 auto lines = parser.header_lines; 958 959 test (lines[0] == "Host: www.example.org:12345"); 960 test (lines[1] == "User-Agent: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.17) Gecko/20110422 Ubuntu/9.10 (karmic) Firefox/3.6.17"); 961 test (lines[2] == "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); 962 test (lines[3] == "Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3"); 963 test (lines[4] == "Accept-Encoding: gzip,deflate"); 964 test (lines[5] == "Accept-Charset: UTF-8,*"); 965 test (lines[6] == "Keep-Alive: 115"); 966 test (lines[7] == "Connection: keep-alive"); 967 test (lines[8] == "Cache-Control: max-age=0"); 968 969 test (lines.length == 9); 970 } 971 972 version (OceanPerformanceTest) 973 { 974 uint j = i + 1; 975 976 if (!(j % 10_000)) 977 { 978 Stderr(HttpHeaderParser.stringof)(' ')(j)("\n").flush(); 979 } 980 } 981 } 982 }