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 }