1 module more.cgi;
2 
3 static import core.stdc.stdio;
4 import core.stdc..string : strlen, strcpy, memmove;
5 import core.time : MonoTime, Duration;
6 import core.stdc.stdlib : alloca;
7 
8 import std.typecons : Flag, Yes, No;
9 import std.format : format, sformat;
10 import std..string : startsWith, indexOf;
11 import std.algorithm : canFind;
12 import std.conv : text, to;
13 import std.traits : hasMember;
14 import std.typecons : tuple;
15 import std.random : Random;
16 import std.exception : ErrnoException;
17 import std.stdio : File, stdout, stderr, stdin;
18 
19 import more.parse : hexValue, findCharPtr, skipCharSet;
20 import more.format : formatEscapeByPolicy, formatEscapeSet, asciiFormatEscaped;
21 public import more.format : formatHex;
22 public import more.uri : uriDecode, tryUriDecode;
23 
24 version = ToLogFile;
25 version (ToLogFile)
26 {
27     private __gshared bool logFileOpen = false;
28     private __gshared File logFile;
29 }
30 
31 void log(T...)(T args)
32 {
33     stderr.writeln(args);
34     stderr.flush();
35 }
36 void logf(T...)(const(char)[] fmt, T args)
37 {
38     version (ToLogFile)
39     {
40         if (!logFileOpen)
41         {
42             logFile = File("/tmp/log", "a");
43             import std.datetime;
44             logFile.writefln("---------------- %s -----------------------", Clock.currTime);
45             logFileOpen = true;
46         }
47         logFile.writefln(fmt, args);
48         logFile.flush();
49     }
50     stderr.writefln(fmt, args);
51     stderr.flush();
52 }
53 
54 auto fread(T)(ref File file, T[] buffer)
55 {
56     return core.stdc.stdio.fread(buffer.ptr, T.sizeof, buffer.length, file.getFP);
57 }
58 
59 enum ResponseState
60 {
61     headers, content
62 }
63 __gshared ResponseState responseState = ResponseState.headers;
64 void ensureResponseFinished()
65 {
66     if(responseState == ResponseState.headers) {
67         finishHeaders();
68     }
69 }
70 
71 bool inHeaders()
72 {
73     return responseState == ResponseState.headers;
74 }
75 
76 void assertInHeaders(string functionName)
77 {
78     if(responseState != ResponseState.headers)
79     {
80         logf("Error: function \"%s\" was called after headers were finished", functionName);
81         assert(0, "function \"" ~ functionName ~ "\" was called after headers were finished");
82     }
83 }
84 void assertInContent(string functionName)
85 {
86     version(unittest)
87     {
88         return;
89     }
90     if(responseState != ResponseState.content)
91     {
92         logf("Error: function \"%s\" was called before headers were finished", functionName);
93         assert(0, "function \"" ~ functionName ~ "\" was called before headers were finished");
94     }
95 }
96 
97 @property bool inHeaderState() { return responseState == ResponseState.headers; }
98 void addHeader(const(char)[] name, const(char)[] value)
99 {
100     assertInHeaders("addHeader");
101     stdout.writef("%s: %s\r\n", name, value);
102 }
103 void addSetCookieHeader(const(char)[] name, const(char)[] value, Duration expireTimeFromNow = Duration.zero)
104 {
105     assertInHeaders("addSetCookieHeader");
106     if(expireTimeFromNow == Duration.zero) {
107         stdout.writef("Set-Cookie: %s=%s\r\n", name, value);
108     } else {
109         assert(0, "not implemented");
110     }
111 }
112 void addUnsetCookieHeader(const(char)[] name)
113 {
114     assertInHeaders("addUnsetCookieHeader");
115     stdout.writef("Set-Cookie: %s=; expires=Thu, 01 Jan 1970 00:00:00 GMT\r\n", name);
116 }
117 void finishHeaders()
118 {
119     assertInHeaders("finishHeaders");
120     stdout.write("\r\n");
121     responseState = ResponseState.content;
122 }
123 
124 // writes a string or formatted string to stdout
125 void write(T...)(const(char)[] fmt, T args)
126 {
127     assertInContent("write");
128     static if(T.length == 0) {
129         stdout.write(fmt);
130     } else {
131         stdout.writef(fmt, args);
132     }
133 }
134 // with an appended '\n' character, writes a string or formatted
135 // string to stdout
136 void writeln(T...)(const(char)[] fmt, T args)
137 {
138     assertInContent("writeln");
139     static if(T.length == 0) {
140         stdout.writeln(fmt);
141     } else {
142         stdout.writefln(fmt, args);
143     }
144 }
145 // writes a list of values to stdout
146 void listWrite(T...)(T args)
147 {
148     assertInContent("listWrite");
149     stdout.write(args);
150 }
151 // with an appended '\n' character, writes a list of values to stdout
152 void listWriteln(T...)(T args)
153 {
154     assertInContent("listWriteln");
155     stdout.writeln(args);
156 }
157 unittest
158 {
159     import more.test;
160     mixin(scopedTest!"cgi - Hello World");
161 
162     // just instantiate the templates
163     // all of the following should print "Hello, World!\n"
164     if (false)
165     {
166         write("Hello");
167         write(", World!\n");
168         write("Hello%s", ", World!\n");
169         writeln("Hello, World!");
170         writeln("Hello%s", ", World!");
171         listWrite("Hello", ", Wo", "rld!\n");
172         listWriteln("Hel", "lo, Wo", "rld!");
173     }
174 }
175 
176 void writeFile(const(char)[] filename)
177 {
178     assertInContent("writeFile");
179     auto file = File(filename, "rb");
180 
181     enum MAX_BUFFER_SIZE = 8192;
182     auto fileSize = file.size();
183     auto bufferSize = (fileSize > MAX_BUFFER_SIZE) ? MAX_BUFFER_SIZE : cast(size_t) fileSize;
184     auto buffer = new char[bufferSize];
185     auto fileLeft = fileSize;
186     for(;fileLeft > 0;)
187     {
188         auto nextReadSize = (fileLeft > bufferSize) ? bufferSize : cast(size_t)fileLeft;
189         auto readSize = fread(file, buffer);
190         if(readSize == 0)
191         {
192             auto message = format("only read %s bytes out of %s byte file \"%s\"", fileSize - fileLeft, fileSize, filename);
193             logf("Error: %s", message);
194             assert(0, message);
195         }
196         stdout.rawWrite(buffer[0..readSize]);
197         fileLeft -= readSize;
198     }
199 }
200 
201 auto dupzstring(T)(T[] str)
202 {
203     auto result = str.ptr[0..str.length + 1].dup;
204     return result[0..$-1];
205 }
206 
207 version(Windows)
208 {
209     struct EnvPairString
210     {
211         private const(char)[] pair;
212         //@property const(char)* ptr() { return pair.ptr; }
213         auto asDString() { return pair; }
214     }
215     import core.sys.windows.windows : GetLastError, GetEnvironmentStringsA, FreeEnvironmentStringsA;
216     private struct EnvPairStringsRange
217     {
218         const(char)* environmentStrings;
219         EnvPairString current;
220         this(const(char)* environmentStrings)
221         {
222             this.environmentStrings = environmentStrings;
223             this.current.pair = environmentStrings[0..strlen(environmentStrings)];
224         }
225         @property bool empty() { return current.pair.ptr == null; }
226         auto front() { return current; }
227         void popFront()
228         {
229             auto nextPtr = current.pair.ptr + current.pair.length + 1;
230             if(*nextPtr == '\0')
231             {
232                 current.pair = null;
233                 assert(FreeEnvironmentStringsA(cast(char*)environmentStrings));
234             }
235             else
236             {
237                 current.pair = nextPtr[0..strlen(nextPtr)];
238             }
239         }
240     }
241     auto envPairStrings()
242     {
243         auto environmentStrings = GetEnvironmentStringsA();
244         assert(environmentStrings, format("GetEnvironmentStringsA failed (e=%s)", GetLastError()));
245         return EnvPairStringsRange(environmentStrings);
246     }
247     struct EnvPair
248     {
249         private const(char)[] name;
250         private const(char)[] value;
251         @property const(char)[] tempName() { return name; }
252         @property const(char)[] tempValue() { return value; }
253         @property char[] permanentValue() { return value.dupzstring; }
254     }
255     auto envPairs()
256     {
257         struct Range
258         {
259             EnvPairStringsRange range;
260             @property bool empty() { return range.empty(); }
261             auto front()
262             {
263                 auto pairString = range.front();
264                 auto nameLimit = pairString.pair.ptr.findCharPtr('=');
265                 auto nameLimitIndex = nameLimit - pairString.pair.ptr;
266                 auto valueStartIndex = nameLimitIndex + ( (*nameLimit == '=') ? 1 : 0);
267 
268                 return EnvPair(pairString.pair[       0        .. nameLimitIndex],
269                                pairString.pair[valueStartIndex ..      $        ]);
270             }
271             void popFront()
272             {
273                 range.popFront();
274             }
275         }
276         return Range(envPairStrings());
277     }
278 }
279 else
280 {
281     struct EnvPairString
282     {
283         private char* pair;
284         //@property const(char)* ptr() { return pair; }
285         auto asDString() { return pair[0..strlen(pair)]; }
286     }
287 
288     extern(C) extern __gshared char** environ;
289     private struct EnvPairStringsRange
290     {
291         EnvPairString* next;
292         @property bool empty() { return next.pair is null; }
293         auto front() { return *next; }
294         void popFront()
295         {
296             next++;
297         }
298     }
299     auto envPairStrings()
300     {
301         return EnvPairStringsRange(cast(EnvPairString*)environ);
302     }
303     struct EnvPair
304     {
305         private char[] name;
306         private char[] value;
307         @property char[] tempName() { return name; }
308         @property char[] tempValue() { return value; }
309         @property char[] permanentValue() { return value; }
310     }
311     auto envPairs()
312     {
313         struct Range
314         {
315             EnvPairStringsRange range;
316             @property bool empty() { return range.empty(); }
317             auto front()
318             {
319                 auto pairString = range.front();
320                 auto nameLimit = pairString.pair.findCharPtr('=');
321                 auto nameLimitIndex = nameLimit - pairString.pair;
322                 auto valueStart = nameLimit + ( (*nameLimit == '=') ? 1 : 0);
323 
324                 return EnvPair(pairString.pair[       0        .. nameLimitIndex],
325                                valueStart[0..strlen(valueStart)]);
326             }
327             void popFront()
328             {
329                 range.popFront();
330             }
331         }
332         return Range(envPairStrings());
333     }
334 }
335 
336 auto cookieRange(T)(T* cookieString)
337 {
338     struct Cookie
339     {
340         T[] name;
341         T[] value;
342     }
343     struct Range
344     {
345         Cookie cookie;
346         T* next;
347         this(T* next)
348         {
349             this.next = next;
350             popFront();
351         }
352         @property bool empty() { return cookie.name is null; }
353         auto front() { return cookie; }
354         void popFront()
355         {
356             //logf("cookieRange.popFront (\"%s\")", next[0..strlen(next)]);
357             next = skipCharSet!" ;"(next);
358             if(*next == '\0') {
359                 cookie.name = null;
360                 //logf("cookieRange.popFront return null");
361                 return;
362             }
363 
364             auto namePtr = next;
365             auto valuePtr = findCharPtr(next, '=');
366             cookie.name = namePtr[0..valuePtr - namePtr];
367             trimRight(&cookie.name);
368             if(*valuePtr == '=') {
369                 valuePtr++;
370                 next = findCharPtr(next, ';');
371                 cookie.value = valuePtr[0..next - valuePtr];
372                 trimRight(&cookie.value);
373             } else {
374                 cookie.value = valuePtr[0..0];
375                 next = valuePtr;
376             }
377             //logf("cookieRange.popFront name=\"%s\" value=\"%s\"", cookie.name, cookie.value);
378         }
379     }
380     return Range(cookieString);
381 }
382 
383 auto queryVarsRange(T)(T* varZString)
384 {
385     struct Var
386     {
387         T[] name;
388         T[] value;
389     }
390     struct Range
391     {
392         Var var;
393         T* next;
394         this(T* varZString)
395         {
396             this.next = varZString;
397             popFront();
398         }
399         @property bool empty() { return var.name is null; }
400         auto front() { return var; }
401         void popFront()
402         {
403             next = skipCharSet!"&"(next);
404 
405             if(*next == '\0') {
406                 var.name = null;
407             } else {
408                 auto namePtr = next;
409                 auto valuePtr = findCharPtr(next, '=');
410 
411                 var.name = namePtr[0..valuePtr - namePtr];
412                 if(*valuePtr == '=') {
413                   valuePtr++;
414                   next = findCharPtr(valuePtr, '&');
415                   var.value = valuePtr[0..next - valuePtr];
416                 } else {
417                   var.value = valuePtr[0..0];
418                   next = valuePtr;
419                 }
420                 //logf("queryVarsRange name=\"%s\" value=\"%s\"", var.name, var.value);
421             }
422         }
423     }
424     return Range(varZString);
425 }
426 
427 // TODO: make this correct
428 bool isValidHtmlToken(char c)
429 {
430     return
431       (c >= 'a' && c <= 'z') ||
432       (c >= 'A' && c <= 'Z') ||
433       c == '_' ||
434       c == '-';
435 }
436 
437 /**
438 Pulls the content-type "token/token" from the "Content-Type: header value.
439 */
440 inout(char)[] pullContentTypeZString(inout(char)* contentType)
441 {
442     if (contentType == null)
443         return null;
444 
445     auto start = contentType;
446     char c;
447     for(;; contentType++) {
448         c = *contentType;
449         if(!isValidHtmlToken(c)) {
450             break;
451         }
452     }
453     if(c == '/') {
454         for(;;) {
455           contentType++;
456           c = *contentType;
457           if(!isValidHtmlToken(c)) {
458               break;
459           }
460         }
461     }
462     return start[0 .. contentType - start];
463 }
464 
465 enum HeaderContentType = "Content-Type: ";
466 
467 auto contentTypeArgRange(Char)(Char* contentTypeParams)
468 {
469     struct Pair
470     {
471         Char[] name;
472         Char[] value;
473     }
474     struct Range
475     {
476         Pair current;
477         this(Char* next)
478         {
479             current.value = next[0..0];
480             popFront();
481         }
482         bool empty() { return current.name is null; }
483         auto front() { return current; }
484         void popFront()
485         {
486             auto next = (current.value.ptr + current.value.length).skipCharSet!" ;"();
487             if(*next == '\0') {
488                 current.name = null;
489             } else {
490                 auto nameLimit = next.findCharPtr('=');
491                 current.name = next[0..nameLimit - next];
492                 if(*nameLimit == '\0') {
493                     current.value = nameLimit[0..0];
494                 } else {
495                     auto valueStart = nameLimit + 1;
496                     auto valueEnd = valueStart.findCharPtr(';');
497                     current.value = trimRight2(valueStart[0..valueEnd - valueStart]);
498                 }
499             }
500         }
501     }
502     return Range(contentTypeParams);
503 }
504 auto httpHeaderArgRange(Char)(Char[] headerArgs)
505 {
506     struct Pair
507     {
508         Char[] name;
509         Char[] value;
510     }
511     struct Range
512     {
513         Pair current;
514         Char* next;
515         Char* limit;
516         this(Char[] start)
517         {
518             this.next = start.ptr;
519             this.limit = start.ptr + start.length;
520             popFront();
521         }
522         bool empty() { return current.name is null; }
523         auto front() { return current; }
524         void popFront()
525         {
526             next = next.skipCharSet!" ;"(limit);
527             if(next == limit) {
528                 current.name = null;
529             } else {
530                 auto nameLimit = next.findCharPtr(limit, '=');
531                 current.name = next[0..nameLimit - next];
532                 if(nameLimit == limit) {
533                     current.value = nameLimit[0..0];
534                 } else {
535                     auto valueStart = nameLimit + 1;
536                     next = valueStart.findCharPtr(limit, ';');
537                     current.value = trimRight2(valueStart[0..next - valueStart]).escapeQuotesIfThere;
538                 }
539             }
540         }
541     }
542     return Range(headerArgs);
543 }
544 
545 inout(char)[] escapeQuotesIfThere(inout(char)[] str)
546 {
547     if (str.length >= 2 && str[0] == '"' && str[$-1] == '"')
548     {
549         return str[1 ..($-1)]; // TODO: escape the quotes properly
550     }
551     return str;
552 }
553 
554 inout(char)[] trimNewline(inout(char)[] str)
555 {
556     if(str.length >= 1 && str[$-1] == '\n')
557     {
558         if(str.length >= 2 && str[$-2] == '\r')
559         {
560             return str[0..$-2];
561         }
562         return str[0..$-1];
563     }
564     return str;
565 }
566 inout(char)[] trimRight2(inout(char)[] str)
567 {
568     for(; str.length > 0 && str[$-1] == ' '; str.length--) { }
569     return str;
570 }
571 void trimRight(const(char)[]* str)
572 {
573     for(;(*str).length > 0 && (*str)[$-1] == ' '; (*str).length--) { }
574 }
575 
576 auto asUpper(T)(T c)
577 {
578     return (c >= 'a' && c <= 'z') ? (c - ('a'-'A')) : c;
579 }
580 bool strEqualNoCase(const(char)[] left, const(char)[] right)
581 {
582     if(left.length != right.length) {
583         return false;
584     }
585     foreach(i; 0..left.length) {
586         if(left[i].asUpper != right[i].asUpper) {
587             return false;
588         }
589     }
590     return true;
591 }
592 
593 alias noStrings = tuple!();
594 template strings(T...)
595 {
596     alias strings = T;
597 }
598 
599 __gshared string stdinReader = null;
600 
601 template HttpTemplate(Hooks)
602 {
603     static assert(Hooks.CookieVars.length == 0 || hasMember!(Env, "HTTP_COOKIE"),
604                   "if you configure any CookieVars, you must include HTTP_COOKIE in your EnvVars");
605     static assert(Hooks.ReadFormPostData == false || (hasMember!(Env, "CONTENT_TYPE") && hasMember!(Env, "CONTENT_LENGTH")),
606                   "if you set ReadFormPostData to true, you must include CONTENT_TYPE and CONTENT_LENGTH in your EnvVars");
607     enum ParseUrlVars = (Hooks.UrlVars.length > 0 || Hooks.UrlOrFormVars.length > 0);
608     static assert(!ParseUrlVars || hasMember!(Env, "QUERY_STRING"),
609                   "if you have any UrlVars or any UrlOrFormVars, you must include QUERY_STRING in your EnvVars");
610 
611     struct HttpTemplate
612     {
613       Env env;
614       Cookies cookies;
615       UrlVars urlVars;
616       FormVars formVars;
617       UrlOrFormVars urlOrFormVars;
618 
619       void init()
620       {
621         static if(Hooks.ReadFormPostData) {
622           bool postContentIsUrlFormData = false;
623         }
624 
625         // load environment variables
626         {
627           size_t varsLeft = Hooks.EnvVars.length;
628         ENV_LOOP:
629           foreach(envPair; envPairs()) {
630             foreach(varName; Hooks.EnvVars) {
631               if(__traits(getMember, env, varName) is null && envPair.name.strEqualNoCase(varName)) {
632                 __traits(getMember, env, varName) = envPair.permanentValue;
633                 static if(varName == "HTTP_COOKIE" && Hooks.CookieVars.length > 0) {
634                   parseCookies(__traits(getMember, env, varName));
635                 } else static if(varName == "CONTENT_TYPE" && Hooks.ReadFormPostData) {
636                   if(__traits(getMember, env, varName).startsWith("application/x-www-form-urlencoded")) {
637                     postContentIsUrlFormData = true;
638                   }
639                 }
640                 varsLeft--;
641                 if(varsLeft == 0) {
642                   break ENV_LOOP;
643                 }
644                 break;
645               }
646             }
647           }
648         }
649 
650         static if(Hooks.ReadFormPostData) {
651           // read post parameters before url params, url takes precedence
652           if(postContentIsUrlFormData) {
653             if(env.CONTENT_LENGTH is null) {
654               throw new Exception("Content-Type header is set but no Content-Length, this is not implemented");
655             }
656             // TODO: handle errors when this conversion fails
657             auto contentLength = env.CONTENT_LENGTH.to!size_t;
658             auto buffer = new char[contentLength + 1];
659             {
660               auto readLength = fread(stdin, buffer[0..contentLength]);
661               if(readLength != contentLength) {
662                 throw new Exception(format("based on CONTENT_LENGTH, expected %s bytes of data from stdin but only got %s",
663                                           contentLength, readLength));
664               }
665             }
666             stdinReader = "the application/x-www-form-urlencoded parser";
667             buffer[contentLength] = '\0';
668             size_t formVarsLeft = Hooks.FormVars.length;
669             size_t urlOrFormVarsLeft = Hooks.UrlOrFormVars.length;
670           POST_CONTENT_VAR_LOOP:
671             foreach(var; queryVarsRange(buffer.ptr)) {
672 
673               foreach(varName; Hooks.FormVars) {
674                 
675                 if(__traits(getMember, formVars, varName) is null && var.name == varName) {
676                   __traits(getMember, formVars, varName) = var.value;
677                   static if(hasMember!(Hooks, "DisableVarUriDecoding"))
678                       bool doDecode = !Hooks.DisableVarUriDecoding;
679                   else
680                       bool doDecode = true;
681                   if (doDecode)
682                   {
683                       auto result = tryUriDecode(__traits(getMember, formVars, varName));
684                       if (result is null)
685                       {
686                           // TODO: handle this error
687                           logf("WARNING: failed to decode post form data variable '%s' (TODO: detect and handle this error)", varName);
688                       }
689                       else
690                       {
691                           __traits(getMember, formVars, varName) = result;
692                       }
693                   }
694 
695                   formVarsLeft--;
696                   if(formVarsLeft == 0 && urlOrFormVarsLeft == 0) {
697                     break POST_CONTENT_VAR_LOOP;
698                   }
699                   break;
700                 }
701               }
702               foreach(varName; Hooks.UrlOrFormVars) {
703 
704                 if(__traits(getMember, urlOrFormVars, varName) is null && var.name == varName) {
705                   __traits(getMember, urlOrFormVars, varName) = var.value;
706                   static if(hasMember!(Hooks, "DisableVarUriDecoding"))
707                       bool doDecode = !Hooks.DisableVarUriDecoding;
708                   else
709                       bool doDecode = true;
710                   if (doDecode)
711                   {
712                       auto result = tryUriDecode(__traits(getMember, urlOrFormVars, varName));
713                       if (result is null)
714                       {
715                           // TODO: handle this error
716                           logf("WARNING: failed to decode post form data variable '%s' (TODO: detect and handle this error)", varName);
717                       }
718                       else
719                       {
720                           __traits(getMember, urlOrFormVars, varName) = result;
721                       }
722                   }
723                   urlOrFormVarsLeft--;
724                   if(formVarsLeft == 0 && urlOrFormVarsLeft == 0) {
725                     break POST_CONTENT_VAR_LOOP;
726                   }
727                   break;
728                 }
729               }
730             }
731           }
732         }
733 
734         // read url vars
735         static if(ParseUrlVars)
736         {
737             if(env.QUERY_STRING.ptr)
738             {
739                 uriDecode(env.QUERY_STRING.ptr, env.QUERY_STRING.ptr);
740 
741                 size_t urlVarsLeft = Hooks.UrlVars.length;
742                 size_t urlOrFormVarsLeft = Hooks.UrlOrFormVars.length;
743             URL_VAR_LOOP:
744                 foreach(var; queryVarsRange(env.QUERY_STRING.ptr))
745                 {
746                     foreach(varName; Hooks.UrlVars)
747                     {
748                         if(__traits(getMember, urlVars, varName) is null && var.name == varName)
749                         {
750                             __traits(getMember, urlVars, varName) = var.value;
751                             urlVarsLeft--;
752                             if(urlVarsLeft == 0 && urlOrFormVarsLeft == 0)
753                             {
754                                 break URL_VAR_LOOP;
755                             }
756                             break;
757                         }
758                     }
759                     foreach(varName; Hooks.UrlOrFormVars)
760                     {
761                         if(__traits(getMember, urlOrFormVars, varName) is null && var.name == varName)
762                         {
763                             __traits(getMember, urlOrFormVars, varName) = var.value;
764                             urlOrFormVarsLeft--;
765                             if(urlVarsLeft == 0 && urlOrFormVarsLeft == 0)
766                             {
767                                 break URL_VAR_LOOP;
768                             }
769                             break;
770                         }
771                     }
772                 }
773             }
774         }
775       }
776       private void parseCookies(const(char)[] cookieString)
777       {
778         //logf("parseCookies \"%s\"", cookieString);
779         size_t varsLeft = Hooks.CookieVars.length;
780       COOKIE_LOOP:
781         foreach(cookie; cookieRange!(const(char))(cookieString.ptr)) {
782           //logf("   \"%s\" = \"%s\"", cookie.name, cookie.value);
783           foreach(varName; Hooks.CookieVars) {
784             if(__traits(getMember, cookies, varName) is null && cookie.name == varName) {
785               __traits(getMember, cookies, varName) = cookie.value;
786               varsLeft--;
787               if(varsLeft == 0) {
788                 break COOKIE_LOOP;
789               }
790               break;
791             }
792           }
793         }
794       }
795 
796         static if(hasMember!(Env, "CONTENT_TYPE")/* && !hasMember!(Env, "CONTENT_LENGTH")*/)
797         {
798 
799             // returns: an error message on error
800             string getMultipartBoundary(const(char)[]* outBoundary)
801             {
802               auto contentType = pullContentTypeZString(env.CONTENT_TYPE.ptr);
803               if(contentType != "multipart/form-data")
804                 return format("Content-Type is not 'multipart/form-data', it is '%s'", contentType);
805 
806               foreach(arg; contentTypeArgRange(env.CONTENT_TYPE.ptr + contentType.length)) {
807                 if(arg.name == "boundary") {
808                   *outBoundary = arg.value;
809                   return null; // no error
810                 } else {
811                   // ignore
812                   //assert(0, format("unknown Content-Type param \"%s\"=\"%s\"", arg.name, arg.value));
813                 }
814               }
815               return format("Content-Type '%s' is missing the 'boundary' parameter", env.CONTENT_TYPE);
816             }
817 
818             /**
819             Returns: null on success, error message on error
820             */
821             string uploadFile(const(char)[] fileFormName, char[] uploadBuffer,
822                 string delegate(const(char)[] clientFilename, string* serverFilename) onFilename)
823             {
824                 // TODO: might need to support variables as well
825                 const(char)[] boundary;
826                 {
827                     auto error = getMultipartBoundary(&boundary);
828                     if (error !is null)
829                         return error;
830                 }
831                 auto reader = StdinMultipartReader(uploadBuffer, boundary);
832 
833                 for (auto partResult = reader.start(); ;partResult = reader.readHeaders())
834                 {
835                     if (partResult.isDone)
836                         return "missing file from post data";
837                     if (partResult.isError)
838                         return partResult.makeErrorMessage();
839 
840                     if (partResult.formData.name != "file")
841                         return format("got unknown form variable '%s'", partResult.formData.name);
842 
843                     if (partResult.formData.filename.canFind('/'))
844                         return format("filename '%s' contains slashes", partResult.formData.filename);
845 
846                     string serverFilename = null;
847                     {
848                         auto error = onFilename(partResult.formData.filename, &serverFilename);
849                         if (error !is null)
850                             return error;
851                     }
852                     if (serverFilename is null)
853                         serverFilename = partResult.formData.filename;
854 
855                     auto file = File(serverFilename, "wb");
856                     for (;;)
857                     {
858                         auto contentResult = reader.readContent();
859                         if (contentResult.isError)
860                             return contentResult.makeErrorMessage();
861 
862                         //writeln("<pre>got %s bytes of content data</pre>", contentResult.content.length);
863                         file.rawWrite(contentResult.content);
864                         if (contentResult.gotBoundary)
865                             break;
866                     }
867                     return null; // success
868                 }
869             }
870         }
871 
872     }
873 
874     struct Env
875     {
876       mixin(function() {
877           auto result = "";
878           foreach(varName; Hooks.EnvVars) {
879             result ~= "char[] " ~ varName ~ "\n;";
880           }
881           return result;
882         }());
883     }
884     struct Cookies
885     {
886       mixin(function() {
887           auto result = "";
888           foreach(varName; Hooks.CookieVars) {
889             result ~= "const(char)[] " ~ varName ~ "\n;";
890           }
891           return result;
892         }());
893     }
894     struct UrlVars
895     {
896       mixin(function() {
897           auto result = "";
898           foreach(varName; Hooks.UrlVars) {
899             result ~= "const(char)[] " ~ varName ~ "\n;";
900           }
901           return result;
902         }());
903     }
904     struct FormVars
905     {
906       mixin(function() {
907           auto result = "";
908           foreach(varName; Hooks.FormVars) {
909             result ~= "const(char)[] " ~ varName ~ "\n;";
910           }
911           return result;
912         }());
913     }
914     struct UrlOrFormVars
915     {
916       mixin(function() {
917           auto result = "";
918           foreach(varName; Hooks.UrlOrFormVars) {
919             result ~= "const(char)[] " ~ varName ~ "\n;";
920           }
921           return result;
922         }());
923     }
924 }
925 
926 
927 // returns: null on success, ErrnoException on error
928 ErrnoException tryOpenFile(File* file, const(char)[] filename, in char[] openmode)
929 {
930     try {
931         *file = File(filename, openmode);
932         return null;
933     } catch(ErrnoException e) {
934         return e;
935     }
936 }
937 
938 char[] tryRead(File file)
939 {
940     auto filesize = file.size();
941     if(filesize + 1 > size_t.max) {
942         assert(0, text(file.name, ": file is too large ", filesize, " > ", size_t.max));
943     }
944     auto contents = new char[cast(size_t)(filesize + 1)]; // add 1 for '\0'
945     auto readSize = fread(file, contents);
946     assert(filesize == readSize, text("rawRead only read ", readSize, " bytes of ", filesize, " byte file"));
947     contents[cast(size_t)filesize] = '\0';
948     return contents[0..$-1];
949 }
950 
951 char[] tryReadFile(const(char)[] filename)
952 {
953     File file;
954     try {
955         file = File(filename, "rb");
956     } catch(ErrnoException e) {
957         return null;
958     }
959     auto filesize = file.size();
960     if(filesize + 1 > size_t.max) {
961         assert(0, text(filename, ": file is too large ", filesize, " > ", size_t.max));
962     }
963     auto contents = new char[cast(size_t)(filesize + 1)]; // add 1 for '\0'
964     auto readSize = fread(file, contents);
965     assert(filesize == readSize, text("fread only read ", readSize, " bytes of ", filesize, " byte file"));
966     contents[cast(size_t)filesize] = '\0';
967     return contents[0..$-1];
968 }
969 // returns error message
970 string tryWriteFile(T)(const(char)[] filename, const(T)[] content) if(T.sizeof == 1)
971 {
972     File file;
973     try {
974         file = File(filename, "wb");
975     } catch(ErrnoException e) {
976         return (e.msg is null) ? "failed to open file" : e.msg; // fail
977     }
978     scope(exit) file.close();
979     file.rawWrite(content);
980     return null; // success
981 }
982 
983 @property auto defaultGsharedRandom()
984 {
985   static __gshared Random random;
986   static __gshared bool seeded = false;
987 
988   if(!seeded) {
989       random = Random(cast(uint)MonoTime.currTime.ticks);
990       seeded = true;
991   }
992   return &random;
993 }
994 
995 void fillRandom(T)(T random, ubyte[] output)
996 {
997     alias UintType = typeof(random.front);
998 
999     ubyte shift = UintType.sizeof * 8;
1000     for(size_t outIndex = 0; outIndex < output.length; outIndex++) {
1001         if(shift == 0) {
1002             random.popFront();
1003             shift = (UintType.sizeof - 1) * 8;
1004         } else {
1005             shift -= 8;
1006         }
1007         output[outIndex] = cast(ubyte)(random.front >> shift);
1008     }
1009 }
1010 void fillRandomHex(T)(T random, char[] hex)
1011     in { assert(hex.length % 2 == 0); } body
1012 {
1013     // TODO: handle the case where hex is large, then
1014     //       just generate in chunks
1015     auto binLength = hex.length / 2;
1016     auto binPtr = cast(ubyte*)alloca(binLength);
1017     assert(binPtr !is null);
1018     auto bin = binPtr[0..binLength];
1019     fillRandom(random, bin);
1020     auto formatLength = sformat(hex, "%s", formatHex(bin)).length;
1021     assert(formatLength == binLength * 2);
1022 }
1023 
1024 alias formatJsSingleQuote = formatEscapeSet!(`\`, `\'`);
1025 alias formatJsDoubleQuote = formatEscapeSet!(`\`, `\"`);
1026 alias formatJsonString = formatEscapeSet!(`\`, `\"`);
1027 unittest
1028 {
1029     import more.test;
1030     mixin(scopedTest!"cgi - formatJsonString");
1031 
1032     assert(`` == format("%s", formatJsonString(``)));
1033     assert(`a` == format("%s", formatJsonString(`a`)));
1034     assert(`abcd` == format("%s", formatJsonString(`abcd`)));
1035 
1036     assert(`\"` == format("%s", formatJsonString(`"`)));
1037     assert(`\\` == format("%s", formatJsonString(`\`)));
1038     assert(`\"\\` == format("%s", formatJsonString(`"\`)));
1039     assert(`a\"\\` == format("%s", formatJsonString(`a"\`)));
1040     assert(`\"a\\` == format("%s", formatJsonString(`"a\`)));
1041     assert(`\"\\a` == format("%s", formatJsonString(`"\a`)));
1042     assert(`abcd\"\\` == format("%s", formatJsonString(`abcd"\`)));
1043     assert(`\"abcd\\` == format("%s", formatJsonString(`"abcd\`)));
1044     assert(`\"\\abcd` == format("%s", formatJsonString(`"\abcd`)));
1045 }
1046 
1047 
1048 // returns the amount of characters that were parsed
1049 size_t parseHex(const(char)[] hex, ubyte[] bin)
1050 {
1051     size_t hexIndex = 0;
1052     size_t binIndex = 0;
1053     for(;;) {
1054         if(hexIndex + 1 > hex.length || binIndex >= bin.length) {
1055             break;
1056         }
1057         auto high = hexValue(hex[hexIndex + 0]);
1058         auto low  = hexValue(hex[hexIndex + 1]);
1059         if(high == ubyte.max || low == ubyte.max) {
1060             break;
1061         }
1062         bin[binIndex++] = cast(ubyte)(high << 4 | low);
1063         hexIndex += 2;
1064     }
1065     return hexIndex;
1066 }
1067 
1068 /**
1069 Returns:`buffer.length` on success, negative on error, otherwise, the number of bytes read before EOF
1070 */
1071 //
1072 auto readFull(Policy)(char[] buffer)
1073 {
1074     size_t totalRead = 0;
1075     for (;;)
1076     {
1077         if (totalRead == buffer.length)
1078             return totalRead;
1079         auto result = Policy.read(buffer[totalRead .. $]);
1080         if (result <= 0)
1081         {
1082             if (result == 0)
1083                 return totalRead;
1084             return result;
1085         }
1086 
1087         totalRead += result;
1088     }
1089 }
1090 
1091 auto upTo(T)(T array, char toChar)
1092 {
1093     auto charIndex = array.indexOf(toChar);
1094     return (charIndex < 0) ? array : array[0 .. charIndex];
1095 }
1096 
1097 auto indexOfParts(U, T...)(U haystack, T parts)
1098 {
1099     ptrdiff_t offset = 0;
1100     for (;;)
1101     {
1102         auto next = haystack[offset .. $].indexOf(parts[0]);
1103         if (next == -1)
1104             return -1;
1105         offset += next;
1106         next = offset + parts[0].length;
1107         bool mismatch = false;
1108         foreach (part; parts[1 .. $])
1109         {
1110             if (!haystack[next .. $].startsWith(part))
1111             {
1112                 mismatch = true;
1113                 break;
1114             }
1115             next += part.length;
1116         }
1117         if (!mismatch)
1118             return offset;
1119         offset++;
1120     }
1121 }
1122 unittest
1123 {
1124     assert("1234".indexOfParts("2") == 1);
1125     assert("1234".indexOfParts("2", "3") == 1);
1126     assert("1234".indexOfParts("23") == 1);
1127     assert("1234".indexOfParts("23", "4") == 1);
1128     assert("1234".indexOfParts("2", "34") == 1);
1129     assert("123456789".indexOfParts("567", "8") == 4);
1130     assert("123456789".indexOfParts("5", "678") == 4);
1131     assert("123456789".indexOfParts("5", "67", "89") == 4);
1132     assert("1231234123".indexOfParts("1", "2", "34") == 3);
1133 }
1134 
1135 struct MultipartFormData
1136 {
1137     string name;
1138     string filename;
1139     string contentType;
1140     void reset()
1141     {
1142         this.name = null;
1143         this.filename = null;
1144         this.contentType = null;
1145     }
1146 }
1147 
1148 struct MultipartReaderTemplate(Policy)
1149 {
1150     // TODO: mabye create a mixin for this
1151     static struct ReadHeadersResult
1152     {
1153         static ReadHeadersResult atContent(MultipartFormData formData) { return ReadHeadersResult(formData); }
1154         static ReadHeadersResult done() { return ReadHeadersResult(State.done); }
1155         static ReadHeadersResult policyError(string errorMsg)
1156         {
1157             auto r = ReadHeadersResult(State.policyError);
1158             r.str = errorMsg;
1159             return r;
1160         }
1161         static ReadHeadersResult bufferTooSmallForBoundary() { return ReadHeadersResult(State.bufferTooSmallForBoundary); }
1162         static ReadHeadersResult bufferTooSmallForHeaders() { return ReadHeadersResult(State.bufferTooSmallForHeaders); }
1163         static ReadHeadersResult readError(int errorNumber)
1164         {
1165             auto r = ReadHeadersResult(State.readError);
1166             r.int_ = errorNumber;
1167             return r;
1168         }
1169         static ReadHeadersResult invalidFirstBoundary() { return ReadHeadersResult(State.invalidFirstBoundary); }
1170         static ReadHeadersResult invalidBoundaryPostfix() { return ReadHeadersResult(State.invalidBoundaryPostfix); }
1171         static ReadHeadersResult headerTooBig() { return ReadHeadersResult(State.headerTooBig); }
1172         static ReadHeadersResult eofInsideHeaders() { return ReadHeadersResult(State.eofInsideHeaders); }
1173         static ReadHeadersResult unknownHeader(char[] s)
1174         {
1175             auto r = ReadHeadersResult(State.unknownHeader);
1176             r.charArray = s;
1177             return r;
1178         }
1179         static ReadHeadersResult missingContentDisposition() { return ReadHeadersResult(State.missingContentDisposition); }
1180         static ReadHeadersResult unknownContentDispositionArg(char[] s)
1181         {
1182             auto r = ReadHeadersResult(State.unknownContentDispositionArg);
1183             r.charArray = s;
1184             return r;
1185         }
1186         static ReadHeadersResult contentDispositionMissingFormData() { return ReadHeadersResult(State.contentDispositionMissingFormData); }
1187         static ReadHeadersResult contentDispositionMissingName() { return ReadHeadersResult(State.contentDispositionMissingName); }
1188 
1189         private enum State : ubyte
1190         {
1191             atContent,
1192             done,
1193             // error states
1194             errorStates,
1195             policyError = errorStates,
1196             bufferTooSmallForBoundary,
1197             bufferTooSmallForHeaders,
1198             readError,
1199             invalidFirstBoundary,
1200             invalidBoundaryPostfix,
1201             headerTooBig,
1202             eofInsideHeaders,
1203             unknownHeader,
1204             missingContentDisposition,
1205             unknownContentDispositionArg,
1206             contentDispositionMissingFormData,
1207             contentDispositionMissingName,
1208         }
1209         State state;
1210         union
1211         {
1212             MultipartFormData formData;
1213             char[] charArray;
1214             string str;
1215             int int_;
1216         }
1217         private this(State state)
1218         {
1219             this.state = state;
1220         }
1221         private this(MultipartFormData formData)
1222         {
1223             this.state = State.atContent;
1224             this.formData = formData;
1225         }
1226 
1227         bool isError() const { return state >= State.errorStates; }
1228         bool isDone() const { return state == State.done; }
1229 
1230         string makeErrorMessage() const
1231         {
1232             final switch(state)
1233             {
1234                 case State.atContent: return "no error";
1235                 case State.done: return "no error";
1236                 case State.policyError: return str;
1237                 case State.bufferTooSmallForBoundary:
1238                     return "upload buffer too small (cannot hold boundary)";
1239                 case State.bufferTooSmallForHeaders:
1240                     return "upload buffer too small (cannot hold headers)";
1241                 case State.readError:
1242                     return format("read failed (e=%d)", int_);
1243                 case State.invalidFirstBoundary:
1244                     return "invalid upload data (initial boundary is not right)";
1245                 case State.invalidBoundaryPostfix:
1246                     return "invalid upload data (invalid boundary postfix)";
1247                 case State.headerTooBig:
1248                     return format("invalid upload data (header exceeded max size of %s)", Policy.MaxHeader);
1249                 case State.eofInsideHeaders:
1250                     return "invalid upload data (input ended inside headers)";
1251                 case State.unknownHeader:
1252                     return format("invalid upload data (unrecognized header '%s')", charArray);
1253                 case State.missingContentDisposition:
1254                     return "invalid upload data (missing Content-Disposition)";
1255                 case State.unknownContentDispositionArg:
1256                     return format("invalid upload data (unknown Content-Disposition arg '%s')", charArray);
1257                 case State.contentDispositionMissingFormData:
1258                     return "invalid upload data (Content-Disposition is missing form-data argument)";
1259                 case State.contentDispositionMissingName:
1260                     return "invalid upload data (Content-Disposition is missing name argument)";
1261             }
1262         }
1263     }
1264 
1265     char[] buffer;
1266     const(char)[] boundary;
1267     size_t dataOffset;
1268     size_t dataLimit;
1269 
1270     pragma(inline) auto encapsulateBoundaryLength() const
1271     {
1272         return 4 +                // "\r\n--"
1273                boundary.length ;  // Content-Type boundary string
1274     }
1275     ReadHeadersResult start()
1276     {
1277         {
1278             auto error = Policy.checkForErrorBeforeStart();
1279             if (error !is null)
1280                 return ReadHeadersResult.policyError(error);
1281         }
1282         // TODO: the multipart content could start with a prologue instead
1283         //       of starting with the boundary right away
1284         auto firstBoundaryLength = 2 + boundary.length; // "--" ~ boundary
1285         if (buffer.length < firstBoundaryLength)
1286             return ReadHeadersResult.bufferTooSmallForBoundary;
1287 
1288         auto result = readFull!Policy(buffer[0 .. firstBoundaryLength]);
1289         if (result != firstBoundaryLength)
1290         {
1291             if (result == 0)
1292                 return ReadHeadersResult.done;
1293             return ReadHeadersResult.readError(Policy.getError(result));
1294         }
1295         if (buffer[0 .. 2] != "--" ||
1296             buffer[2 .. 2 + boundary.length] != boundary)
1297             return ReadHeadersResult.invalidFirstBoundary;
1298         dataOffset = 0;
1299         dataLimit = 0;
1300         return readHeaders();
1301     }
1302 
1303     /** shift current data to the beginning of the buffer */
1304     private void shiftData()
1305     {
1306         auto saveLength = dataLimit - dataOffset;
1307         if (saveLength > 0)
1308         {
1309             memmove(buffer.ptr, buffer.ptr + dataOffset, saveLength);
1310         }
1311         dataOffset = 0;
1312         dataLimit = saveLength;
1313     }
1314 
1315     // returns: positive on success, 0 on EOF, negative on error
1316     private auto ensureSmallDataSizeAvailable(uint size)
1317     in { /*assert(size <= buffer.length);*/ } do
1318     {
1319         auto dataSize = dataLimit - dataOffset;
1320         if (size > dataSize)
1321         {
1322             shiftData();
1323             auto bufferAvailable = buffer.length - dataLimit;
1324             auto sizeToRead = size - dataSize;
1325             // NOTE: sizeToRead must be <= (buffer.length - dataLimit)
1326             //       because size <= buffer.length
1327             auto result = Policy.read(buffer[dataLimit .. dataLimit + sizeToRead]);
1328             if (result <= 0)
1329                 return result;
1330             dataLimit += result;
1331             if (result != sizeToRead)
1332                 return -1;
1333             return result;
1334         }
1335         return 1; // success
1336     }
1337 
1338     ReadHeadersResult readHeaders()
1339     {
1340         // check whether we start with a "\r\n" or "--"
1341         {
1342             auto result = ensureSmallDataSizeAvailable(2);
1343             if (result <= 0)
1344             {
1345                 if (result == 0)
1346                     return ReadHeadersResult.eofInsideHeaders;
1347                 return ReadHeadersResult.readError(Policy.getError(result));
1348             }
1349         }
1350         auto boundaryPostfix = buffer[dataOffset .. dataOffset + 2];
1351         if (boundaryPostfix == "\r\n")
1352             dataOffset += 2;
1353         else if (boundaryPostfix == "--")
1354             return ReadHeadersResult.done;
1355         else
1356             return ReadHeadersResult.invalidBoundaryPostfix;
1357 
1358         MultipartFormData formData;
1359         auto checkIndex = dataOffset;
1360         //import std.stdio; writefln("+ readHeaders (checkIndex=%s, dataSize=%s)", checkIndex, dataLimit - dataOffset);
1361         for (;;)
1362         {
1363             // find the end of the next header
1364             auto newlinePos = buffer[checkIndex .. dataLimit].indexOf("\n");
1365             //import std.stdio; writefln("newlinePos=%s, (checkIndex=%s)", newlinePos, checkIndex);
1366             if (newlinePos == -1)
1367             {
1368                 shiftData();
1369                 if (dataLimit > Policy.MaxHeader)
1370                     return ReadHeadersResult.headerTooBig();
1371                 auto bufferAvailable = buffer.length - dataLimit;
1372                 if (bufferAvailable == 0)
1373                     return ReadHeadersResult.bufferTooSmallForHeaders;
1374                 // TODO: don't read TOO much, we don't want to
1375                 //       end up "shifting" too much data
1376                 auto result = Policy.read(buffer[dataLimit .. $]);
1377                 //import std.stdio; writefln("read %s bytes", result);
1378                 if (result <= 0)
1379                 {
1380                     if (result == 0)
1381                     {
1382                         if (dataOffset == dataLimit) {
1383                             return ReadHeadersResult.done;
1384                         }
1385                         return ReadHeadersResult.eofInsideHeaders;
1386                     }
1387                     return ReadHeadersResult.readError(Policy.getError(result));
1388                 }
1389                 checkIndex = dataLimit;
1390                 dataLimit += result;
1391             }
1392             else
1393             {
1394                 auto newlineOffset = checkIndex + newlinePos;
1395                 auto header = buffer[dataOffset .. newlineOffset - 1];
1396                 dataOffset = newlineOffset + 1;
1397                 //import std.stdio; writefln("header = '%s'", header.asciiFormatEscaped);
1398 
1399                 if (header.length == 0)
1400                 {
1401                     if (formData.name is null)
1402                         return ReadHeadersResult.missingContentDisposition;
1403                     return ReadHeadersResult.atContent(formData);
1404                 }
1405                 import std.algorithm : skipOver;
1406                 if (header.skipOver("Content-Disposition: "))
1407                 {
1408                     if (!header.skipOver("form-data"))
1409                         return ReadHeadersResult.contentDispositionMissingFormData;
1410                     foreach(arg; httpHeaderArgRange(header))
1411                     {
1412                         if(arg.name == "name")
1413                             formData.name = Policy.allocName(arg.value);
1414                         else if(arg.name == "filename")
1415                             formData.filename = Policy.allocName(arg.value);
1416                         else
1417                             return ReadHeadersResult.unknownContentDispositionArg(arg.name);
1418                     }
1419                     if (formData.name is null)
1420                         return ReadHeadersResult.contentDispositionMissingName;
1421                     //import std.stdio; writefln("formData.name = '%s'", formData.name);
1422                 }
1423                 else if (header.skipOver("Content-Type: "))
1424                 {
1425                     formData.contentType = Policy.allocName(header);
1426                 }
1427                 else
1428                 {
1429                     return ReadHeadersResult.unknownHeader(header.upTo(':'));
1430                 }
1431                 checkIndex = dataOffset;
1432             }
1433         }
1434     }
1435 
1436     static struct ReadContentResult
1437     {
1438         static ReadContentResult contentButNoBoundaryYet(char[] content)
1439         {
1440             ReadContentResult result = void;
1441             result.state = State.contentButNoBoundaryYet;
1442             result._content = content;
1443             return result;
1444         }
1445         static ReadContentResult contentWithBoundary(char[] content)
1446         {
1447             ReadContentResult result = void;
1448             result.state = State.contentWithBoundary;
1449             result._content = content;
1450             return result;
1451         }
1452         static ReadContentResult bufferTooSmallForBoundary()
1453         { return ReadContentResult(State.bufferTooSmallForBoundary); }
1454         static ReadContentResult noEndingBoundary()
1455         { return ReadContentResult(State.noEndingBoundary); }
1456         static ReadContentResult readError(int errorNumber)
1457         {
1458             auto r = ReadContentResult(State.readError);
1459             r.int_ = errorNumber;
1460             return r;
1461         }
1462 
1463         private enum State : ubyte
1464         {
1465             // gotContent states
1466             contentButNoBoundaryYet,
1467             contentWithBoundary,
1468             // error states
1469             errorStates,
1470             bufferTooSmallForBoundary = errorStates,
1471             noEndingBoundary,
1472             readError,
1473         }
1474         State state;
1475         union
1476         {
1477             private char[] _content;
1478             private int int_;
1479         }
1480         bool isError() const { return state >= State.errorStates; }
1481         char[] content() const { return cast(char[])_content; }
1482         bool gotBoundary() const { return state == State.contentWithBoundary; }
1483 
1484         string makeErrorMessage() const
1485         {
1486             final switch(state)
1487             {
1488                 case State.contentButNoBoundaryYet: return "no error";
1489                 case State.contentWithBoundary: return "no error";
1490                 case State.bufferTooSmallForBoundary:
1491                     return "upload buffer too small (cannot hold boundary)";
1492                 case State.noEndingBoundary:
1493                     return "invalid upload data (missing terminating boundary)";
1494                 case State.readError:
1495                     return format("read failed (e=%d)", int_);
1496             }
1497         }
1498     }
1499     ReadContentResult readContent()
1500     {
1501         auto checkIndex = dataOffset;
1502         //import std.stdio; writefln("+ readContent (checkIndex=%s, dataSize=%s)", checkIndex, dataLimit - dataOffset);
1503         for (;;)
1504         {
1505             //import std.stdio; writefln("readContent Loop");
1506             auto nextBoundaryPos = buffer[checkIndex .. dataLimit].indexOfParts("\r\n--", boundary);
1507             if (nextBoundaryPos == -1)
1508             {
1509                 //import std.stdio; writefln("boundary not found!");
1510                 auto dataLength = dataLimit - dataOffset;
1511                 if (dataLength >= encapsulateBoundaryLength) {
1512                     auto returnLimit = dataLimit - (encapsulateBoundaryLength - 1);
1513                     auto content = buffer[dataOffset .. returnLimit];
1514                     //import std.stdio; writefln("returning data %s to %s", dataOffset, returnLimit);
1515                     dataOffset = returnLimit;
1516                     return ReadContentResult.contentButNoBoundaryYet(content);
1517                 }
1518                 // we need to read more data
1519                 shiftData();
1520                 auto bufferAvailable = buffer.length - dataLimit;
1521                 //import std.stdio; writefln("readContent shift (data=%s, available=%s)", dataLimit, bufferAvailable);
1522                 if (bufferAvailable == 0)
1523                     return ReadContentResult.bufferTooSmallForBoundary;
1524                 // TODO: don't read TOO much, we don't want to
1525                 //       end up "shifting" too much data
1526                 auto result = Policy.read(buffer[dataLimit .. $]);
1527                 //import std.stdio; writefln("read %s bytes (in readContent)", result);
1528                 if (result <= 0)
1529                 {
1530                     if (result == 0)
1531                         return ReadContentResult.noEndingBoundary;
1532                     return ReadContentResult.readError(Policy.getError(result));
1533                 }
1534                 if (dataLimit > (encapsulateBoundaryLength - 1))
1535                     checkIndex = dataLimit - (encapsulateBoundaryLength - 1);
1536                 else
1537                     checkIndex = 0;
1538                 dataLimit += result;
1539 //                import std.stdio; writefln("checkIndex = %s, dataLimit=%s, encapsulateBoundaryLength=%s check='%s'",
1540 //                    checkIndex, dataLimit, encapsulateBoundaryLength, buffer[checkIndex .. dataLimit]);
1541 //                import std.stdio; writefln("checkIndex = %s, dataLimit=%s, encapsulateBoundaryLength=%s",
1542 //                    checkIndex, dataLimit, encapsulateBoundaryLength);
1543             }
1544             else
1545             {
1546                 //import std.stdio; writefln("foundBoundary!");
1547                 auto nextBoundaryIndex = checkIndex + nextBoundaryPos;
1548                 auto contentStart = dataOffset;
1549                 dataOffset = nextBoundaryIndex + encapsulateBoundaryLength;
1550                 return ReadContentResult.contentWithBoundary(buffer[contentStart .. nextBoundaryIndex]);
1551             }
1552         }
1553     }
1554 }
1555 
1556 version (linux)
1557 {
1558     extern(C) ptrdiff_t read(int fd, void* ptr, size_t len);
1559 }
1560 else static assert(0, "read function not implemented on this platform");
1561 
1562 private struct StdinMultipartReaderPolicy
1563 {
1564     enum MaxHeader = 200;
1565     // returns error message if we cannot start reading
1566     static string checkForErrorBeforeStart()
1567     {
1568         if (stdinReader)
1569             return format("CodeBug: cannot use StdinMultipartReader because stdin was already read by '%s'", stdinReader);
1570         stdinReader = "multipartReader";
1571         return null; // no errors
1572     }
1573     static auto read(char[] buffer)
1574     {
1575         return .read(0, buffer.ptr, buffer.length);
1576     }
1577     static auto getError(ptrdiff_t result)
1578     {
1579         static import core.stdc.errno;
1580         return core.stdc.errno.errno;
1581     }
1582     static auto allocName(const(char)[] name)
1583     {
1584         return name.idup;
1585     }
1586 }
1587 alias StdinMultipartReader = MultipartReaderTemplate!StdinMultipartReaderPolicy;
1588 
1589 unittest
1590 {
1591     static char[] inBuffer;
1592     static size_t inOffset;
1593     static size_t inLimit;
1594     inBuffer = new char[3000];
1595     static void setInData(string s)
1596     {
1597         //import std.stdio; writefln("inData '%s'", s);
1598         inBuffer[0 .. s.length] = s;
1599         inOffset = 0;
1600         inLimit = s.length;
1601     }
1602     static string generateTestContent(size_t size)
1603     {
1604         static hexmap = "0123456789abcdef";
1605         auto content = new char[size];
1606         foreach (i; 0 .. size) {
1607             content[i] = hexmap[i & 0b1111];
1608         }
1609         return cast(string)content;
1610     }
1611     static struct TestMultipartReaderPolicy
1612     {
1613         enum MaxHeader = 200;
1614         pragma(inline)
1615         static string checkForErrorBeforeStart()
1616         {
1617             return null; // no error
1618         }
1619         pragma(inline)
1620         static auto read(char[] buffer)
1621         {
1622             //import std.stdio; writefln("ReadCall (in %s-%s) (size=%s)", inOffset, inLimit, buffer.length);
1623             auto readSize = inLimit - inOffset;
1624             if (buffer.length < readSize)
1625                 readSize = buffer.length;
1626             buffer[0 .. readSize] = inBuffer[inOffset .. inOffset + readSize];
1627             inOffset += readSize;
1628             return readSize;
1629         }
1630         static auto getError(ptrdiff_t result)
1631         {
1632             assert(0, "not implemented");
1633             return result;
1634         }
1635         static auto allocName(const(char)[] name)
1636         {
1637             return name.idup;
1638         }
1639     }
1640     alias TestMultipartReader = MultipartReaderTemplate!TestMultipartReaderPolicy;
1641     char[  1]   buffer1;
1642     char[  4]   buffer4;
1643     char[100] buffer100;
1644     {
1645         setInData("");
1646         auto reader = TestMultipartReader(buffer100, "MYBOUNDARY");
1647         assert(reader.start().isDone);
1648     }
1649     {
1650         setInData("--MYBOUNDARY\r\n\r\n");
1651         auto reader = TestMultipartReader(buffer100, "MYBOUNDARY");
1652         assert(reader.start().state == TestMultipartReader.ReadHeadersResult.State.missingContentDisposition);
1653     }
1654     {
1655         setInData("--MYBOUNDARY\r\n"
1656             ~ "Content-Disposition: form-data; name=\"TestVariable\"\r\n\r\n");
1657         auto reader = TestMultipartReader(buffer100, "MYBOUNDARY");
1658         auto result = reader.start();
1659         assert(!result.isError);
1660     }
1661     {
1662         setInData("--MYBOUNDARY\r\n"
1663             ~ "Content-Disposition: form-data; name=\"TestVar\"\r\n"
1664             ~ "Content-Type: plain/text\r\n\r\n");
1665         auto reader = TestMultipartReader(buffer100, "MYBOUNDARY");
1666         auto result = reader.start();
1667         assert(!result.isError);
1668 
1669     }
1670     {
1671         setInData("--MYBOUNDARY\r\n"
1672             ~ "Content-Disposition: form-data; name=\"TestVar\"\r\n"
1673             ~ "Content-Type: plain/text\r\n\r\n"
1674             ~ "\r\n--MYBOUNDARY\r\n");
1675         auto reader = TestMultipartReader(buffer100, "MYBOUNDARY");
1676         {
1677             auto result = reader.start();
1678             assert(!result.isError);
1679         }
1680         {
1681             auto result = reader.readContent();
1682             assert(result.gotBoundary);
1683             assert(result.content.length == 0);
1684         }
1685         {
1686             auto result = reader.readHeaders();
1687             assert(result.isDone);
1688         }
1689     }
1690 
1691     static void readAndDropContent(TestMultipartReader* reader)
1692     {
1693         for (;;)
1694         {
1695             auto result = reader.readContent();
1696             assert(!result.isError);
1697             if (result.gotBoundary)
1698                 return;
1699         }
1700     }
1701     static void readAndDropContentSize(TestMultipartReader* reader, size_t contentLeft)
1702     {
1703         for (;;)
1704         {
1705             auto result = reader.readContent();
1706             assert(!result.isError);
1707             if (result.content.length > contentLeft)
1708             {
1709                 import std.stdio;
1710                 writefln("result.content.length %s contentLeft %s", result.content.length, contentLeft);
1711             }
1712             assert(result.content.length <= contentLeft);
1713             contentLeft -= result.content.length;
1714             if (contentLeft == 0)
1715             {
1716                 if (result.gotBoundary)
1717                     return;
1718             }
1719             assert(!result.gotBoundary);
1720         }
1721     }
1722 
1723     foreach (bufferSize; 50 .. 101)
1724     {
1725         foreach (contentSize; 0 .. 301)
1726         {
1727             setInData("--MYBOUNDARY\r\n"
1728                 ~ "Content-Disposition: form-data; name=\"TestVar\"\r\n"
1729                 ~ "Content-Type: plain/text\r\n\r\n"
1730                 ~ generateTestContent(contentSize)
1731                 ~ "\r\n--MYBOUNDARY\r\n");
1732             auto reader = TestMultipartReader(buffer100[0 .. bufferSize], "MYBOUNDARY");
1733             {
1734                 auto result = reader.start();
1735                 assert(!result.isError);
1736             }
1737             readAndDropContentSize(&reader, contentSize);
1738             {
1739                 auto result = reader.readHeaders();
1740                 //import std.stdio; writefln("state = %s", result.state);
1741                 assert(result.isDone);
1742             }
1743         }
1744     }
1745 
1746     foreach (bufferSize; 60 .. 101)
1747     {
1748         setInData(  "------WebKitFormBoundaryDL9nQxvP3BPx3UvL\r\n"
1749                   ~ "Content-Disposition: form-data; name=\"TestVariable1\"\r\n"
1750                   ~ "\r\n"
1751                   ~ "TestValue1\r\n"
1752                   ~ "------WebKitFormBoundaryDL9nQxvP3BPx3UvL\r\n"
1753                   ~ "Content-Disposition: form-data; name=\"TestVariable2\"\r\n"
1754                   ~ "\r\n"
1755                   ~ "TestValue2\r\n"
1756                   ~ "------WebKitFormBoundaryDL9nQxvP3BPx3UvL\r\n"
1757                   ~ "Content-Disposition: form-data; name=\"TestVariable3\"\r\n"
1758                   ~ "\r\n"
1759                   ~ "TestValue3\r\n"
1760                   ~ "------WebKitFormBoundaryDL9nQxvP3BPx3UvL\r\n"
1761                   ~ "Content-Disposition: form-data; name=\"file\"; filename=\"10bytesandnewline.txt\"\r\n"
1762                   ~ "Content-Type: text/plain\r\n"
1763                   ~ "\r\n"
1764                   ~ "0123456789\r\n"
1765                   ~ "\r\n"
1766                   ~ "------WebKitFormBoundaryDL9nQxvP3BPx3UvL--\r\n");
1767         auto reader = TestMultipartReader(buffer100[0 .. bufferSize], "----WebKitFormBoundaryDL9nQxvP3BPx3UvL");
1768         {
1769             auto result = reader.start();
1770             assert(!result.isError);
1771         }
1772         readAndDropContentSize(&reader, 10);
1773     }
1774 
1775 }
1776 
1777 
1778 // Assumption: str is null terminated
1779 alias zStringByLine = delimited!'\n'.sentinalRange!('\0', const(char));
1780 alias stringByLine = delimited!'\n'.range;
1781 unittest
1782 {
1783     import more.test;
1784     mixin(scopedTest!"cgi - strings by line");
1785 
1786     auto expected = ["a", "bcd", "ef"];
1787     {
1788         size_t i = 0;
1789         foreach(line; zStringByLine("a\nbcd\nef\0")) {
1790             assert(line == expected[i]);
1791             i++;
1792         }
1793     }
1794     {
1795         size_t i = 0;
1796         foreach(line; stringByLine("a\nbcd\nef")) {
1797             assert(line == expected[i]);
1798             i++;
1799         }
1800     }
1801 }
1802 
1803 //
1804 // Functions to operate on delimited data
1805 //
1806 // delimited data is of the form
1807 // <data> [<delimiter> <data>] *
1808 //
1809 template delimited(char delimiter)
1810 {
1811     auto sentinalRange(char sentinal = '\0', T)(T* str)
1812     {
1813         struct Range
1814         {
1815             T[] current;
1816             T* next;
1817             this(T* str)
1818             {
1819                 this.next = str;
1820                 popFront();
1821             }
1822             bool empty() { return current.ptr == null; }
1823             auto front() { return current; }
1824             void popFront()
1825             {
1826                 auto start = next;
1827                 if(*start == sentinal) {
1828                     current = null;
1829                     return;
1830                 }
1831 
1832                 for(;;next++) {
1833                     auto c = *next;
1834                     if(c == delimiter) {
1835                         current = start[0..next-start];
1836                         next++;
1837                         return;
1838                     }
1839                     if(c == '\0') {
1840                         current = start[0..next-start];
1841                         return;
1842                     }
1843                 }
1844             }
1845         }
1846         return Range(str);
1847     }
1848     auto range(T)(T[] str)
1849     {
1850         struct Range
1851         {
1852             T[] current;
1853             T[] rest;
1854             this(T[] str)
1855             {
1856                 rest = str;
1857                 popFront();
1858             }
1859             bool empty() { return current.ptr == null; }
1860             auto front() { return current; }
1861             void popFront()
1862             {
1863                 if(rest.length == 0) {
1864                     current = null;
1865                     return;
1866                 }
1867 
1868                 for(size_t i = 0;;) {
1869                     auto c = rest[i];
1870                     if(c == delimiter) {
1871                         current = rest[0..i];
1872                         rest = rest[i + 1..$];
1873                         return;
1874                     }
1875                     i++;
1876                     if(i >= rest.length) {
1877                         current = rest;
1878                         rest = rest[$..$];
1879                         return;
1880                     }
1881                 }
1882             }
1883         }
1884         return Range(str);
1885     }
1886 
1887     enum findFormatCode = q{
1888     {
1889         if (haystack == null)
1890             return null;
1891         for(;;) {
1892             auto nextLimit = %s;
1893             auto value = haystack[0..nextLimit - haystack];
1894             if(value == needle) {
1895                 return value;
1896             }
1897             if(%s) {
1898                 return null;
1899             }
1900             haystack = nextLimit + 1;
1901         }
1902     }
1903     };
1904 
1905     mixin(q{
1906         inout(char)[] find(char sentinal = '\0')(inout(char)* haystack, const(char)[] needle)
1907     } ~ format(findFormatCode, q{haystack.findCharPtr!sentinal(delimiter)}, q{*nextLimit == sentinal}));
1908     mixin(q{
1909       inout(char)[] find(inout(char)* haystack, const(char)* limit,  const(char)[] needle)
1910     } ~ format(findFormatCode, q{haystack.findCharPtr(limit, delimiter)}, q{nextLimit == limit}));
1911     pragma(inline)
1912     inout(char)[] find(inout(char)[] haystack, const(char)[] needle)
1913     {
1914         return find(haystack.ptr, haystack.ptr + haystack.length, needle);
1915     }
1916 
1917     // Appends a string onto an existing string with a delimiter. It checks whether
1918     // or not the previous string already has a delimiter and won't include the delimiter
1919     // in that case
1920     inout(char)[] append(const(char)[] current, inout(char)[] new_)
1921     {
1922         if(current.length == 0) {
1923             return new_;
1924         }
1925         if(current[$-1] == delimiter) {
1926             return cast(inout(char)[])current ~ new_;
1927         }
1928 
1929         return cast(inout(char)[])current ~ delimiter ~ new_;
1930     }
1931     auto removeItem(inout(char)[] current, const(char)[] item)
1932     {
1933         return removeItem(current, item.ptr - current.ptr, item.ptr + item.length - current.ptr);
1934     }
1935     auto removeItemWithLength(inout(char)[] current, size_t itemStart, size_t itemLength)
1936     {
1937         return removeItem(current, itemStart, itemStart + itemLength);
1938     }
1939     // doesn't allocate memory if the item is at the beginning or end
1940     auto removeItem(inout(char)[] current, size_t itemStart, size_t itemLimit) in {
1941         assert(itemStart <= itemLimit);
1942         assert(itemLimit <= current.length);
1943         assert(itemStart == 0 || current[itemStart - 1] == delimiter);
1944         assert(itemLimit == current.length || current[itemLimit] == delimiter); } body
1945     {
1946         if(itemStart == 0) {
1947             if(itemLimit == current.length) {
1948                 return current[0..0];
1949             }
1950             assert(current[itemLimit] == delimiter);
1951             return current[itemLimit + 1..$];
1952         }
1953         assert(current[itemStart - 1] == delimiter);
1954         if(itemLimit == current.length) {
1955             return current[0..itemStart - 1];
1956         }
1957         return current[0..itemStart - 1] ~ current[itemLimit .. $];
1958     }
1959 }
1960 unittest
1961 {
1962     import more.test;
1963     mixin(scopedTest!"cgi - delimited 1");
1964 
1965     assert("" == delimited!':'.find(cast(char*)null, ""));
1966     assert("" == delimited!':'.find("\0".ptr, ""));
1967 
1968     assert(null == delimited!':'.find("\0".ptr, "a"));
1969     assert("a" == delimited!':'.find("a\0".ptr, "a"));
1970     assert(null == delimited!':'.find("ab\0".ptr, "a"));
1971 
1972     assert(null == delimited!':'.find("\0".ptr, "abcd"));
1973     assert(null == delimited!':'.find("abc\0".ptr, "abcd"));
1974     assert("abcd" == delimited!':'.find("abcd\0".ptr, "abcd"));
1975     assert(null == delimited!':'.find("abcde\0".ptr, "abcd"));
1976 
1977     assert(null == delimited!':'.find("b:c\0".ptr, "a"));
1978     assert("a" == delimited!':'.find("b:a\0".ptr, "a"));
1979     assert(null == delimited!':'.find("b:ab\0".ptr, "a"));
1980     assert("a" == delimited!':'.find("a:b\0".ptr, "a"));
1981     assert(null == delimited!':'.find("ab:b\0".ptr, "a"));
1982     assert(null == delimited!':'.find("d:b:e\0".ptr, "a"));
1983     assert("a" == delimited!':'.find("d:b:e:a\0".ptr, "a"));
1984     assert(null == delimited!':'.find("d:b:e:ab\0".ptr, "a"));
1985 
1986     assert(null == delimited!':'.find("bbbb:cccc\0".ptr, "aaaa"));
1987     assert("aaaa" == delimited!':'.find("aaaa:bbbb\0".ptr, "aaaa"));
1988     assert("aaaa" == delimited!':'.find("bbbb:aaaa\0".ptr, "aaaa"));
1989     assert(null == delimited!':'.find("bbbb:aaaae\0".ptr, "aaaa"));
1990     assert(null == delimited!':'.find("dddd:bbbb:eeee\0".ptr, "aaaa"));
1991     assert("aaaa" == delimited!':'.find("aaaa:bbbb:eeee\0".ptr, "aaaa"));
1992     assert("aaaa" == delimited!':'.find("bbbb:aaaa:eeee\0".ptr, "aaaa"));
1993     assert("aaaa" == delimited!':'.find("bbbb:eeee:aaaa\0".ptr, "aaaa"));
1994     assert("aaaa" == delimited!':'.find("dddd:bbbb:eeee:aaaa\0".ptr, "aaaa"));
1995     assert(null == delimited!':'.find("ddddd:bbbb:e:aaaaa\0".ptr, "aaaa"));
1996 
1997     // test the other find variations
1998     assert("a" == delimited!':'.find("a", "a"));
1999 }
2000 
2001 unittest
2002 {
2003     import more.test;
2004     mixin(scopedTest!"cgi - delimited 2");
2005 
2006     assert("" == delimited!':'.removeItem(null, 0, 0));
2007     assert("" == delimited!':'.removeItem("", 0, 0));
2008     assert("" == delimited!':'.removeItem("a", 0, 1));
2009     assert("b" == delimited!':'.removeItem("a:b", 0, 1));
2010     assert("a" == delimited!':'.removeItem("a:b", 2, 3));
2011 
2012     assert("b:c" == delimited!':'.removeItem("a:b:c", 0, 1));
2013     assert("a:c" == delimited!':'.removeItem("a:b:c", 2, 3));
2014     assert("a:b" == delimited!':'.removeItem("a:b:c", 4, 5));
2015 
2016     assert("efgh:ijkl" == delimited!':'.removeItem("abcd:efgh:ijkl", 0, 4));
2017     assert("abcd:ijkl" == delimited!':'.removeItem("abcd:efgh:ijkl", 5, 9));
2018     assert("abcd:efgh" == delimited!':'.removeItem("abcd:efgh:ijkl", 10, 14));
2019 }