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 moduleocean.net.http.message.HttpHeaderParser;
23 24 25 importocean.transition;
26 importocean.core.Enforce;
27 importocean.core.Verify;
28 importocean.text.util.SplitIterator: ChrSplitIterator, ISplitIterator;
29 30 importocean.net.http.HttpException: HttpParseException;
31 32 version(UnitTest) importocean.core.Test;
33 34 publicaliaslongssize_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) privatechar* g_strstr_len(Const!(char)* haystack, ssize_thaystack_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 interfaceIHttpHeaderParser68 {
69 /**************************************************************************
70 71 Header element
72 73 **************************************************************************/74 75 structHeaderElement76 {
77 cstringkey, 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_theader_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_theader_lines_limit ( size_tn );
130 131 /**************************************************************************
132 133 Returns:
134 limit for the number of HTTP message header lines
135 136 **************************************************************************/137 138 size_theader_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_theader_length_limit ( size_tn );
157 }
158 159 /******************************************************************************
160 161 HttpHeaderParser class
162 163 ******************************************************************************/164 165 classHttpHeaderParser : IHttpHeaderParser166 {
167 /***************************************************************************
168 169 Object pool index -- allows the construction of a pool of objects of
170 this type.
171 172 ***************************************************************************/173 174 publicsize_tobject_pool_index;
175 176 /**************************************************************************
177 178 Default values for header size limitation
179 180 **************************************************************************/181 182 staticimmutablesize_tDefaultSizeLimit = 16 * 1024,
183 DefaultLinesLimit = 64;
184 185 /**************************************************************************
186 187 Header lines split iterator
188 189 **************************************************************************/190 191 privatestaticscopeclassSplitHeaderLines : ISplitIterator192 {
193 /**************************************************************************
194 195 End-of-header-line token
196 197 **************************************************************************/198 199 staticimmutableEndOfHeaderLine = "\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 publicoverridesize_tlocateDelim ( cstringstr, size_tstart = 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 returnitem? 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 protectedoverridesize_tskipDelim ( cstringstr )
243 {
244 verify(str.length >= this.EndOfHeaderLine.length);
245 returnthis.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 privatemstringcontent;
257 258 /**************************************************************************
259 260 Length of actual data in content.
261 262 **************************************************************************/263 264 privatesize_tcontent_length;
265 266 /**************************************************************************
267 268 Position (index) in the content up to which the content has already been
269 parsed
270 271 **************************************************************************/272 273 privatesize_tpos = 0;
274 275 /**************************************************************************
276 277 false after reset() and before the start line is complete
278 279 **************************************************************************/280 281 privateboolhave_start_line = false;
282 283 /**************************************************************************
284 285 Number of header lines parsed so far, excluding the start line
286 287 **************************************************************************/288 289 privatesize_tn_header_lines = 0;
290 291 /**************************************************************************
292 293 Header lines, excluding the start line; elements slice this.content.
294 295 **************************************************************************/296 297 privatecstring[] 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 privateHeaderElement[] header_elements_;
310 311 /**************************************************************************
312 313 Reusable exception instance
314 315 **************************************************************************/316 317 privateHttpParseExceptionexception;
318 319 /**************************************************************************
320 321 Indicates that the header is complete
322 323 **************************************************************************/324 325 privateboolfinished = 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 publicthis ( )
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 publicthis ( size_tsize_limit, size_tlines_limit )
367 {
368 this.exception = newHttpParseException;
369 this.content = newchar[size_limit];
370 this.header_lines_ = newcstring[lines_limit];
371 this.header_elements_ = newHeaderElement[lines_limit];
372 }
373 374 /**************************************************************************
375 376 Start line tokens; slice the internal content buffer
377 378 **************************************************************************/379 380 publiccstring[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 publicoverrideHeaderElement[] header_elements ( )
397 {
398 returnthis.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 publiccstring[] header_lines ( )
409 {
410 returnthis.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 publicoverridesize_theader_lines_limit ( )
421 {
422 returnthis.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 publicoverridesize_theader_lines_limit ( size_tn )
441 {
442 if (this.n_header_lines > n)
443 {
444 this.n_header_lines = n;
445 }
446 447 returnthis.header_lines_.length = n;
448 }
449 450 /**************************************************************************
451 452 Returns:
453 HTTP message header size limit
454 455 **************************************************************************/456 457 publicoverridesize_theader_length_limit ( )
458 {
459 returnthis.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 publicoverridesize_theader_length_limit ( size_tn )
479 {
480 this.reset();
481 482 returnthis.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 returnthis;
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 publiccstringparse ( cstringcontent )
546 {
547 verify(!this.finished, "parse() called after finished");
548 549 cstringmsg_body_start = null;
550 551 scopesplit_header = newSplitHeaderLines;
552 553 split_header.include_remaining = false;
554 555 cstringcontent_remaining = this.appendContent(content);
556 557 foreach (header_line; split_header.reset(this.content[this.pos .. this.content_length]))
558 {
559 cstringremaining = split_header.remaining;
560 561 if (header_line.length)
562 {
563 if (this.have_start_line)
564 {
565 this.parseRegularHeaderLine(header_line);
566 }
567 else568 {
569 this.parseStartLine(header_line);
570 571 this.have_start_line = true;
572 }
573 574 this.pos = this.content_length - remaining.length;
575 }
576 else577 {
578 this.finished = this.have_start_line; // Ignore empty leading header lines579 580 if (this.finished)
581 {
582 msg_body_start = remaining.length? remaining : content_remaining;
583 break;
584 }
585 }
586 }
587 588 returnmsg_body_start;
589 }
590 591 aliasparseopCall;
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 privatecstringappendContent ( cstringchunk )
611 out (remaining)
612 {
613 assert (remaining.length <= chunk.length);
614 }
615 body616 {
617 verify(this.content_length <= this.content.length);
618 619 size_tmax_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 staticimmutableend_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 !isnull);
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_tend = 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 returnchunk[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 privatevoidparseRegularHeaderLine ( cstringheader_line )
679 {
680 681 enforce(this.exception.set("too many request header lines"),
682 this.n_header_lines <= this.header_lines_.length);
683 684 scopesplit_tokens = newChrSplitIterator(':');
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 privatevoidparseStartLine ( cstringstart_line )
720 {
721 scopesplit_tokens = newChrSplitIterator(' ');
722 723 split_tokens.collapse = true;
724 split_tokens.include_remaining = true;
725 726 uinti = 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 importcore.stdc.time: time;
758 importcore.sys.posix.stdlib: srand48, drand48;
759 760 version (OceanPerformanceTest)
761 {
762 importocean.io.Stdout;
763 importocean.core.internal.gcInterface: gc_disable, gc_enable;
764 }
765 766 unittest767 {
768 769 {
770 scopeparser = newHttpHeaderParser;
771 772 staticimmutablecontent1 = "POST / HTTP/1.1\r\n"// 17773 ~ "Content-Length: 12\r\n"// 37774 ~ "\r\n"// 39775 ~ "Hello World!";
776 777 778 parser.header_length_limit = 39;
779 780 try781 {
782 parser.parse(content1);
783 }
784 catch (HttpParseExceptione)
785 {
786 test(false);
787 }
788 789 parser.reset();
790 791 parser.header_length_limit = 38;
792 793 try794 {
795 parser.parse(content1);
796 }
797 catch (HttpParseExceptione) { }
798 }
799 800 staticimmutableistringlorem_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 staticimmutableistringcontent2 =
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 staticimmutableparts = 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 staticsize_trandom_chunk_length ( )
878 {
879 staticimmutablec = content2.length * (2.0f / (parts * 3));
880 881 staticassert (c >= 3, "too many parts");
882 883 returncast (size_t) (c + cast (float) drand48() * c);
884 }
885 886 srand48(time(null));
887 888 scopeparser = newHttpHeaderParser;
889 890 version (OceanPerformanceTest)
891 {
892 staticimmutablen = 1000_000;
893 }
894 else895 {
896 staticimmutablen = 10;
897 }
898 899 version (OceanPerformanceTest)
900 {
901 gc_disable();
902 903 scope (exit) gc_enable();
904 }
905 906 for (uinti = 0; i < n; i++)
907 {
908 parser.reset();
909 910 {
911 size_tnext = random_chunk_length();
912 913 cstringmsg_body_start = parser.parse(content2[0 .. next]);
914 915 while (msg_body_startisnull)
916 {
917 size_tpos = 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 else926 {
927 msg_body_start = parser.parse(content2[pos .. content2.length]);
928 929 test (msg_body_start !isnull);
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 autoelements = 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 autolines = 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 uintj = i + 1;
975 976 if (!(j % 10_000))
977 {
978 Stderr(HttpHeaderParser.stringof)(' ')(j)("\n").flush();
979 }
980 }
981 }
982 }