1 // FIXME: if an exception is thrown, we shouldn't necessarily cache...
2 // FIXME: there's some annoying duplication of code in the various versioned mains
3 
4 // add the Range header in there too. should return 206
5 
6 // FIXME: cgi per-request arena allocator
7 
8 // i need to add a bunch of type templates for validations... mayne @NotNull or NotNull!
9 
10 // FIXME: I might make a cgi proxy class which can change things; the underlying one is still immutable
11 // but the later one can edit and simplify the api. You'd have to use the subclass tho!
12 
13 /*
14 void foo(int f, @("test") string s) {}
15 
16 void main() {
17 	static if(is(typeof(foo) Params == __parameters))
18 		//pragma(msg, __traits(getAttributes, Params[0]));
19 		pragma(msg, __traits(getAttributes, Params[1..2]));
20 	else
21 		pragma(msg, "fail");
22 }
23 */
24 
25 // Note: spawn-fcgi can help with fastcgi on nginx
26 
27 // FIXME: to do: add openssl optionally
28 // make sure embedded_httpd doesn't send two answers if one writes() then dies
29 
30 // future direction: websocket as a separate process that you can sendfile to for an async passoff of those long-lived connections
31 
32 /*
33 	Session manager process: it spawns a new process, passing a
34 	command line argument, to just be a little key/value store
35 	of some serializable struct. On Windows, it CreateProcess.
36 	On Linux, it can just fork or maybe fork/exec. The session
37 	key is in a cookie.
38 
39 	Server-side event process: spawns an async manager. You can
40 	push stuff out to channel ids and the clients listen to it.
41 
42 	websocket process: spawns an async handler. They can talk to
43 	each other or get info from a cgi request.
44 
45 	Tempting to put web.d 2.0 in here. It would:
46 		* map urls and form generation to functions
47 		* have data presentation magic
48 		* do the skeleton stuff like 1.0
49 		* auto-cache generated stuff in files (at least if pure?)
50 		* introspect functions in json for consumers
51 
52 
53 	https://linux.die.net/man/3/posix_spawn
54 */
55 
56 /++
57 	Provides a uniform server-side API for CGI, FastCGI, SCGI, and HTTP web applications.
58 
59 	---
60 	import arsd.cgi;
61 
62 	// Instead of writing your own main(), you should write a function
63 	// that takes a Cgi param, and use mixin GenericMain
64 	// for maximum compatibility with different web servers.
65 	void hello(Cgi cgi) {
66 		cgi.setResponseContentType("text/plain");
67 
68 		if("name" in cgi.get)
69 			cgi.write("Hello, " ~ cgi.get["name"]);
70 		else
71 			cgi.write("Hello, world!");
72 	}
73 
74 	mixin GenericMain!hello;
75 	---
76 
77 	Test on console (works in any interface mode):
78 	$(CONSOLE
79 		$ ./cgi_hello GET / name=whatever
80 	)
81 
82 	If using http version (default on `dub` builds, or on custom builds when passing `-version=embedded_httpd` to dmd):
83 	$(CONSOLE
84 		$ ./cgi_hello --port 8080
85 		# now you can go to http://localhost:8080/?name=whatever
86 	)
87 
88 	Please note: the default port for http is 8085 and for cgi is 4000. I recommend you set your own by the command line argument in a startup script instead of relying on any hard coded defaults. It is possible though to hard code your own with [RequestServer].
89 
90 
91 	Compile_versions:
92 
93 	If you are using `dub`, use:
94 
95 	```sdlang
96 	subConfiguration "arsd-official:cgi" "VALUE_HERE"
97 	```
98 
99 	or to dub.json:
100 
101 	```json
102         	"subConfigurations": {"arsd-official:cgi": "VALUE_HERE"}
103 	```
104 
105 	to change versions. The possible options for `VALUE_HERE` are:
106 
107 	$(LIST
108 		* `embedded_httpd` for the embedded httpd version (built-in web server). This is the default.
109 		* `cgi` for traditional cgi binaries.
110 		* `fastcgi` for FastCGI builds.
111 		* `scgi` for SCGI builds.
112 		* `stdio_http` for speaking raw http over stdin and stdout. See [RequestServer.serveSingleHttpConnectionOnStdio] for more information.
113 	)
114 
115 	With dmd, use:
116 
117 	$(TABLE_ROWS
118 
119 		* + Interfaces
120 		  + (mutually exclusive)
121 
122 		* - `-version=plain_cgi`
123 			- The default building the module alone without dub - a traditional, plain CGI executable will be generated.
124 		* - `-version=embedded_httpd`
125 			- A HTTP server will be embedded in the generated executable. This is default when building with dub.
126 		* - `-version=fastcgi`
127 			- A FastCGI executable will be generated.
128 		* - `-version=scgi`
129 			- A SCGI (SimpleCGI) executable will be generated.
130 
131 		* - `-version=embedded_httpd_threads`
132 			- The embedded HTTP server will use a single process with a thread pool. (use instead of plain `embedded_httpd` if you want this specific implementation)
133 		* - `-version=embedded_httpd_processes`
134 			- The embedded HTTP server will use a prefork style process pool. (use instead of plain `embedded_httpd` if you want this specific implementation)
135 		* - `-version=embedded_httpd_processes_accept_after_fork`
136 			- It will call accept() in each child process, after forking. This is currently the only option, though I am experimenting with other ideas. You probably should NOT specify this right now.
137 		* - `-version=stdio_http`
138 			- The embedded HTTP server will be spoken over stdin and stdout.
139 
140 		* + Tweaks
141 		  + (can be used together with others)
142 
143 		* - `-version=cgi_with_websocket`
144 			- The CGI class has websocket server support.
145 
146 		* - `-version=with_openssl`
147 			- not currently used
148 		* - `-version=cgi_embedded_sessions`
149 			- The session server will be embedded in the cgi.d server process
150 		* - `-version=cgi_session_server_process`
151 			- The session will be provided in a separate process, provided by cgi.d.
152 	)
153 
154 	Compile_and_run:
155 
156 	For CGI, `dmd yourfile.d cgi.d` then put the executable in your cgi-bin directory.
157 
158 	For FastCGI: `dmd yourfile.d cgi.d -version=fastcgi` and run it. spawn-fcgi helps on nginx. You can put the file in the directory for Apache. On IIS, run it with a port on the command line (this causes it to call FCGX_OpenSocket, which can work on nginx too).
159 
160 	For SCGI: `dmd yourfile.d cgi.d -version=scgi` and run the executable, providing a port number on the command line.
161 
162 	For an embedded HTTP server, run `dmd yourfile.d cgi.d -version=embedded_httpd` and run the generated program. It listens on port 8085 by default. You can change this on the command line with the --port option when running your program.
163 
164 	You can also simulate a request by passing parameters on the command line, like:
165 
166 	$(CONSOLE
167 	./yourprogram GET / name=adr
168 	)
169 
170 	And it will print the result to stdout.
171 
172 	CGI_Setup_tips:
173 
174 	On Apache, you may do `SetHandler cgi-script` in your `.htaccess` file.
175 
176 	Integration_tips:
177 
178 	cgi.d works well with dom.d for generating html. You may also use web.d for other utilities and automatic api wrapping.
179 
180 	dom.d usage:
181 
182 	---
183 		import arsd.cgi;
184 		import arsd.dom;
185 
186 		void hello_dom(Cgi cgi) {
187 			auto document = new Document();
188 
189 			static import std.file;
190 			// parse the file in strict mode, requiring it to be well-formed UTF-8 XHTML
191 			// (You'll appreciate this if you've ever had to deal with a missing </div>
192 			// or something in a php or erb template before that would randomly mess up
193 			// the output in your browser. Just check it and throw an exception early!)
194 			//
195 			// You could also hard-code a template or load one at compile time with an
196 			// import expression, but you might appreciate making it a regular file
197 			// because that means it can be more easily edited by the frontend team and
198 			// they can see their changes without needing to recompile the program.
199 			//
200 			// Note on CTFE: if you do choose to load a static file at compile time,
201 			// you *can* parse it in CTFE using enum, which will cause it to throw at
202 			// compile time, which is kinda cool too. Be careful in modifying that document,
203 			// though, as it will be a static instance. You might want to clone on on demand,
204 			// or perhaps modify it lazily as you print it out. (Try element.tree, it returns
205 			// a range of elements which you could send through std.algorithm functions. But
206 			// since my selector implementation doesn't work on that level yet, you'll find that
207 			// harder to use. Of course, you could make a static list of matching elements and
208 			// then use a simple e is e2 predicate... :) )
209 			document.parseUtf8(std.file.read("your_template.html"), true, true);
210 
211 			// fill in data using DOM functions, so placing it is in the hands of HTML
212 			// and it will be properly encoded as text too.
213 			//
214 			// Plain html templates can't run server side logic, but I think that's a
215 			// good thing - it keeps them simple. You may choose to extend the html,
216 			// but I think it is best to try to stick to standard elements and fill them
217 			// in with requested data with IDs or class names. A further benefit of
218 			// this is the designer can also highlight data based on sources in the CSS.
219 			//
220 			// However, all of dom.d is available, so you can format your data however
221 			// you like. You can do partial templates with innerHTML too, or perhaps better,
222 			// injecting cloned nodes from a partial document.
223 			//
224 			// There's a lot of possibilities.
225 			document["#name"].innerText = cgi.request("name", "default name");
226 
227 			// send the document to the browser. The second argument to `cgi.write`
228 			// indicates that this is all the data at once, enabling a few small
229 			// optimizations.
230 			cgi.write(document.toString(), true);
231 		}
232 	---
233 
234 	Concepts:
235 		Input: [Cgi.get], [Cgi.post], [Cgi.request], [Cgi.files], [Cgi.cookies], [Cgi.pathInfo], [Cgi.requestMethod],
236 		       and HTTP headers ([Cgi.headers], [Cgi.userAgent], [Cgi.referrer], [Cgi.accept], [Cgi.authorization], [Cgi.lastEventId])
237 
238 		Output: [Cgi.write], [Cgi.header], [Cgi.setResponseStatus], [Cgi.setResponseContentType], [Cgi.gzipResponse]
239 
240 		Cookies: [Cgi.setCookie], [Cgi.clearCookie], [Cgi.cookie], [Cgi.cookies]
241 
242 		Caching: [Cgi.setResponseExpires], [Cgi.updateResponseExpires], [Cgi.setCache]
243 
244 		Redirections: [Cgi.setResponseLocation]
245 
246 		Other Information: [Cgi.remoteAddress], [Cgi.https], [Cgi.port], [Cgi.scriptName], [Cgi.requestUri], [Cgi.getCurrentCompleteUri], [Cgi.onRequestBodyDataReceived]
247 
248 		Overriding behavior: [Cgi.handleIncomingDataChunk], [Cgi.prepareForIncomingDataChunks], [Cgi.cleanUpPostDataState]
249 
250 		Installing: Apache, IIS, CGI, FastCGI, SCGI, embedded HTTPD (not recommended for production use)
251 
252 	Guide_for_PHP_users:
253 		If you are coming from PHP, here's a quick guide to help you get started:
254 
255 		$(SIDE_BY_SIDE
256 			$(COLUMN
257 				```php
258 				<?php
259 					$foo = $_POST["foo"];
260 					$bar = $_GET["bar"];
261 					$baz = $_COOKIE["baz"];
262 
263 					$user_ip = $_SERVER["REMOTE_ADDR"];
264 					$host = $_SERVER["HTTP_HOST"];
265 					$path = $_SERVER["PATH_INFO"];
266 
267 					setcookie("baz", "some value");
268 
269 					echo "hello!";
270 				?>
271 				```
272 			)
273 			$(COLUMN
274 				---
275 				import arsd.cgi;
276 				void app(Cgi cgi) {
277 					string foo = cgi.post["foo"];
278 					string bar = cgi.get["bar"];
279 					string baz = cgi.cookies["baz"];
280 
281 					string user_ip = cgi.remoteAddress;
282 					string host = cgi.host;
283 					string path = cgi.pathInfo;
284 
285 					cgi.setCookie("baz", "some value");
286 
287 					cgi.write("hello!");
288 				}
289 
290 				mixin GenericMain!app
291 				---
292 			)
293 		)
294 
295 		$(H3 Array elements)
296 
297 
298 		In PHP, you can give a form element a name like `"something[]"`, and then
299 		`$_POST["something"]` gives an array. In D, you can use whatever name
300 		you want, and access an array of values with the `cgi.getArray["name"]` and
301 		`cgi.postArray["name"]` members.
302 
303 		$(H3 Databases)
304 
305 		PHP has a lot of stuff in its standard library. cgi.d doesn't include most
306 		of these, but the rest of my arsd repository has much of it. For example,
307 		to access a MySQL database, download `database.d` and `mysql.d` from my
308 		github repo, and try this code (assuming, of course, your database is
309 		set up):
310 
311 		---
312 		import arsd.cgi;
313 		import arsd.mysql;
314 
315 		void app(Cgi cgi) {
316 			auto database = new MySql("localhost", "username", "password", "database_name");
317 			foreach(row; mysql.query("SELECT count(id) FROM people"))
318 				cgi.write(row[0] ~ " people in database");
319 		}
320 
321 		mixin GenericMain!app;
322 		---
323 
324 		Similar modules are available for PostgreSQL, Microsoft SQL Server, and SQLite databases,
325 		implementing the same basic interface.
326 
327 	See_Also:
328 
329 	You may also want to see [arsd.dom], [arsd.web], and [arsd.html] for more code for making
330 	web applications.
331 
332 	For working with json, try [arsd.jsvar].
333 
334 	[arsd.database], [arsd.mysql], [arsd.postgres], [arsd.mssql], and [arsd.sqlite] can help in
335 	accessing databases.
336 
337 	If you are looking to access a web application via HTTP, try [std.net.curl], [arsd.curl], or [arsd.http2].
338 
339 	Copyright:
340 
341 	cgi.d copyright 2008-2022, Adam D. Ruppe. Provided under the Boost Software License.
342 
343 	Yes, this file is old, and yes, it is still actively maintained and used.
344 +/
345 module arsd.cgi;
346 
347 version(Demo)
348 unittest {
349 
350 }
351 
352 static import std.file;
353 
354 // for a single thread, linear request thing, use:
355 // -version=embedded_httpd_threads -version=cgi_no_threads
356 
357 version(Posix) {
358 	version(CRuntime_Musl) {
359 
360 	} else version(minimal) {
361 
362 	} else {
363 		version(GNU) {
364 			// GDC doesn't support static foreach so I had to cheat on it :(
365 		} else version(FreeBSD) {
366 			// I never implemented the fancy stuff there either
367 		} else {
368 			version=with_breaking_cgi_features;
369 			version=with_sendfd;
370 			version=with_addon_servers;
371 		}
372 	}
373 }
374 
375 version(Windows) {
376 	version(minimal) {
377 
378 	} else {
379 		// not too concerned about gdc here since the mingw version is fairly new as well
380 		version=with_breaking_cgi_features;
381 	}
382 }
383 
384 void cloexec(int fd) {
385 	version(Posix) {
386 		import core.sys.posix.fcntl;
387 		fcntl(fd, F_SETFD, FD_CLOEXEC);
388 	}
389 }
390 
391 void cloexec(Socket s) {
392 	version(Posix) {
393 		import core.sys.posix.fcntl;
394 		fcntl(s.handle, F_SETFD, FD_CLOEXEC);
395 	}
396 }
397 
398 version(embedded_httpd_hybrid) {
399 	version=embedded_httpd_threads;
400 	version(cgi_no_fork) {} else version(Posix)
401 		version=cgi_use_fork;
402 	version=cgi_use_fiber;
403 }
404 
405 version(cgi_use_fork)
406 	enum cgi_use_fork_default = true;
407 else
408 	enum cgi_use_fork_default = false;
409 
410 // the servers must know about the connections to talk to them; the interfaces are vital
411 version(with_addon_servers)
412 	version=with_addon_servers_connections;
413 
414 version(embedded_httpd) {
415 	version(linux)
416 		version=embedded_httpd_processes;
417 	else {
418 		version=embedded_httpd_threads;
419 	}
420 
421 	/*
422 	version(with_openssl) {
423 		pragma(lib, "crypto");
424 		pragma(lib, "ssl");
425 	}
426 	*/
427 }
428 
429 version(embedded_httpd_processes)
430 	version=embedded_httpd_processes_accept_after_fork; // I am getting much better average performance on this, so just keeping it. But the other way MIGHT help keep the variation down so i wanna keep the code to play with later
431 
432 version(embedded_httpd_threads) {
433 	//  unless the user overrides the default..
434 	version(cgi_session_server_process)
435 		{}
436 	else
437 		version=cgi_embedded_sessions;
438 }
439 version(scgi) {
440 	//  unless the user overrides the default..
441 	version(cgi_session_server_process)
442 		{}
443 	else
444 		version=cgi_embedded_sessions;
445 }
446 
447 // fall back if the other is not defined so we can cleanly version it below
448 version(cgi_embedded_sessions) {}
449 else version=cgi_session_server_process;
450 
451 
452 version=cgi_with_websocket;
453 
454 enum long defaultMaxContentLength = 5_000_000;
455 
456 /*
457 
458 	To do a file download offer in the browser:
459 
460     cgi.setResponseContentType("text/csv");
461     cgi.header("Content-Disposition: attachment; filename=\"customers.csv\"");
462 */
463 
464 // FIXME: the location header is supposed to be an absolute url I guess.
465 
466 // FIXME: would be cool to flush part of a dom document before complete
467 // somehow in here and dom.d.
468 
469 
470 // these are public so you can mixin GenericMain.
471 // FIXME: use a function level import instead!
472 public import std.string;
473 public import std.stdio;
474 public import std.conv;
475 import std.uri;
476 import std.uni;
477 import std.algorithm.comparison;
478 import std.algorithm.searching;
479 import std.exception;
480 import std.base64;
481 static import std.algorithm;
482 import std.datetime;
483 import std.range;
484 
485 import std.process;
486 
487 import std.zlib;
488 
489 
490 T[] consume(T)(T[] range, int count) {
491 	if(count > range.length)
492 		count = range.length;
493 	return range[count..$];
494 }
495 
496 int locationOf(T)(T[] data, string item) {
497 	const(ubyte[]) d = cast(const(ubyte[])) data;
498 	const(ubyte[]) i = cast(const(ubyte[])) item;
499 
500 	// this is a vague sanity check to ensure we aren't getting insanely
501 	// sized input that will infinite loop below. it should never happen;
502 	// even huge file uploads ought to come in smaller individual pieces.
503 	if(d.length > (int.max/2))
504 		throw new Exception("excessive block of input");
505 
506 	for(int a = 0; a < d.length; a++) {
507 		if(a + i.length > d.length)
508 			return -1;
509 		if(d[a..a+i.length] == i)
510 			return a;
511 	}
512 
513 	return -1;
514 }
515 
516 /// If you are doing a custom cgi class, mixing this in can take care of
517 /// the required constructors for you
518 mixin template ForwardCgiConstructors() {
519 	this(long maxContentLength = defaultMaxContentLength,
520 		string[string] env = null,
521 		const(ubyte)[] delegate() readdata = null,
522 		void delegate(const(ubyte)[]) _rawDataOutput = null,
523 		void delegate() _flush = null
524 		) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
525 
526 	this(string[] args) { super(args); }
527 
528 	this(
529 		BufferedInputRange inputData,
530 		string address, ushort _port,
531 		int pathInfoStarts = 0,
532 		bool _https = false,
533 		void delegate(const(ubyte)[]) _rawDataOutput = null,
534 		void delegate() _flush = null,
535 		// this pointer tells if the connection is supposed to be closed after we handle this
536 		bool* closeConnection = null)
537 	{
538 		super(inputData, address, _port, pathInfoStarts, _https, _rawDataOutput, _flush, closeConnection);
539 	}
540 
541 	this(BufferedInputRange ir, bool* closeConnection) { super(ir, closeConnection); }
542 }
543 
544 /// thrown when a connection is closed remotely while we waiting on data from it
545 class ConnectionClosedException : Exception {
546 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
547 		super(message, file, line, next);
548 	}
549 }
550 
551 
552 version(Windows) {
553 // FIXME: ugly hack to solve stdin exception problems on Windows:
554 // reading stdin results in StdioException (Bad file descriptor)
555 // this is probably due to http://d.puremagic.com/issues/show_bug.cgi?id=3425
556 private struct stdin {
557 	struct ByChunk { // Replicates std.stdio.ByChunk
558 	private:
559 		ubyte[] chunk_;
560 	public:
561 		this(size_t size)
562 		in {
563 			assert(size, "size must be larger than 0");
564 		}
565 		do {
566 			chunk_ = new ubyte[](size);
567 			popFront();
568 		}
569 
570 		@property bool empty() const {
571 			return !std.stdio.stdin.isOpen || std.stdio.stdin.eof; // Ugly, but seems to do the job
572 		}
573 		@property nothrow ubyte[] front() {	return chunk_; }
574 		void popFront()	{
575 			enforce(!empty, "Cannot call popFront on empty range");
576 			chunk_ = stdin.rawRead(chunk_);
577 		}
578 	}
579 
580 	import core.sys.windows.windows;
581 static:
582 
583 	T[] rawRead(T)(T[] buf) {
584 		uint bytesRead;
585 		auto result = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buf.ptr, cast(int) (buf.length * T.sizeof), &bytesRead, null);
586 
587 		if (!result) {
588 			auto err = GetLastError();
589 			if (err == 38/*ERROR_HANDLE_EOF*/ || err == 109/*ERROR_BROKEN_PIPE*/) // 'good' errors meaning end of input
590 				return buf[0..0];
591 			// Some other error, throw it
592 
593 			char* buffer;
594 			scope(exit) LocalFree(buffer);
595 
596 			// FORMAT_MESSAGE_ALLOCATE_BUFFER	= 0x00000100
597 			// FORMAT_MESSAGE_FROM_SYSTEM		= 0x00001000
598 			FormatMessageA(0x1100, null, err, 0, cast(char*)&buffer, 256, null);
599 			throw new Exception(to!string(buffer));
600 		}
601 		enforce(!(bytesRead % T.sizeof), "I/O error");
602 		return buf[0..bytesRead / T.sizeof];
603 	}
604 
605 	auto byChunk(size_t sz) { return ByChunk(sz); }
606 
607 	void close() {
608 		std.stdio.stdin.close;
609 	}
610 }
611 }
612 
613 /// The main interface with the web request
614 class Cgi {
615   public:
616 	/// the methods a request can be
617 	enum RequestMethod { GET, HEAD, POST, PUT, DELETE, // GET and POST are the ones that really work
618 		// these are defined in the standard, but idk if they are useful for anything
619 		OPTIONS, TRACE, CONNECT,
620 		// These seem new, I have only recently seen them
621 		PATCH, MERGE,
622 		// this is an extension for when the method is not specified and you want to assume
623 		CommandLine }
624 
625 
626 	/+
627 	/++
628 		Cgi provides a per-request memory pool
629 
630 	+/
631 	void[] allocateMemory(size_t nBytes) {
632 
633 	}
634 
635 	/// ditto
636 	void[] reallocateMemory(void[] old, size_t nBytes) {
637 
638 	}
639 
640 	/// ditto
641 	void freeMemory(void[] memory) {
642 
643 	}
644 	+/
645 
646 
647 /*
648 	import core.runtime;
649 	auto args = Runtime.args();
650 
651 	we can call the app a few ways:
652 
653 	1) set up the environment variables and call the app (manually simulating CGI)
654 	2) simulate a call automatically:
655 		./app method 'uri'
656 
657 		for example:
658 			./app get /path?arg arg2=something
659 
660 	  Anything on the uri is treated as query string etc
661 
662 	  on get method, further args are appended to the query string (encoded automatically)
663 	  on post method, further args are done as post
664 
665 
666 	  @name means import from file "name". if name == -, it uses stdin
667 	  (so info=@- means set info to the value of stdin)
668 
669 
670 	  Other arguments include:
671 	  	--cookie name=value (these are all concated together)
672 		--header 'X-Something: cool'
673 		--referrer 'something'
674 		--port 80
675 		--remote-address some.ip.address.here
676 		--https yes
677 		--user-agent 'something'
678 		--userpass 'user:pass'
679 		--authorization 'Basic base64encoded_user:pass'
680 		--accept 'content' // FIXME: better example
681 		--last-event-id 'something'
682 		--host 'something.com'
683 
684 	  Non-simulation arguments:
685 	  	--port xxx listening port for non-cgi things (valid for the cgi interfaces)
686 		--listening-host  the ip address the application should listen on, or if you want to use unix domain sockets, it is here you can set them: `--listening-host unix:filename` or, on Linux, `--listening-host abstract:name`.
687 
688 */
689 
690 	/** Initializes it with command line arguments (for easy testing) */
691 	this(string[] args, void delegate(const(ubyte)[]) _rawDataOutput = null) {
692 		rawDataOutput = _rawDataOutput;
693 		// these are all set locally so the loop works
694 		// without triggering errors in dmd 2.064
695 		// we go ahead and set them at the end of it to the this version
696 		int port;
697 		string referrer;
698 		string remoteAddress;
699 		string userAgent;
700 		string authorization;
701 		string origin;
702 		string accept;
703 		string lastEventId;
704 		bool https;
705 		string host;
706 		RequestMethod requestMethod;
707 		string requestUri;
708 		string pathInfo;
709 		string queryString;
710 
711 		bool lookingForMethod;
712 		bool lookingForUri;
713 		string nextArgIs;
714 
715 		string _cookie;
716 		string _queryString;
717 		string[][string] _post;
718 		string[string] _headers;
719 
720 		string[] breakUp(string s) {
721 			string k, v;
722 			auto idx = s.indexOf("=");
723 			if(idx == -1) {
724 				k = s;
725 			} else {
726 				k = s[0 .. idx];
727 				v = s[idx + 1 .. $];
728 			}
729 
730 			return [k, v];
731 		}
732 
733 		lookingForMethod = true;
734 
735 		scriptName = args[0];
736 		scriptFileName = args[0];
737 
738 		environmentVariables = cast(const) environment.toAA;
739 
740 		foreach(arg; args[1 .. $]) {
741 			if(arg.startsWith("--")) {
742 				nextArgIs = arg[2 .. $];
743 			} else if(nextArgIs.length) {
744 				if (nextArgIs == "cookie") {
745 					auto info = breakUp(arg);
746 					if(_cookie.length)
747 						_cookie ~= "; ";
748 					_cookie ~= std.uri.encodeComponent(info[0]) ~ "=" ~ std.uri.encodeComponent(info[1]);
749 				}
750 				else if (nextArgIs == "port") {
751 					port = to!int(arg);
752 				}
753 				else if (nextArgIs == "referrer") {
754 					referrer = arg;
755 				}
756 				else if (nextArgIs == "remote-address") {
757 					remoteAddress = arg;
758 				}
759 				else if (nextArgIs == "user-agent") {
760 					userAgent = arg;
761 				}
762 				else if (nextArgIs == "authorization") {
763 					authorization = arg;
764 				}
765 				else if (nextArgIs == "userpass") {
766 					authorization = "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (arg)).idup;
767 				}
768 				else if (nextArgIs == "origin") {
769 					origin = arg;
770 				}
771 				else if (nextArgIs == "accept") {
772 					accept = arg;
773 				}
774 				else if (nextArgIs == "last-event-id") {
775 					lastEventId = arg;
776 				}
777 				else if (nextArgIs == "https") {
778 					if(arg == "yes")
779 						https = true;
780 				}
781 				else if (nextArgIs == "header") {
782 					string thing, other;
783 					auto idx = arg.indexOf(":");
784 					if(idx == -1)
785 						throw new Exception("need a colon in a http header");
786 					thing = arg[0 .. idx];
787 					other = arg[idx + 1.. $];
788 					_headers[thing.strip.toLower()] = other.strip;
789 				}
790 				else if (nextArgIs == "host") {
791 					host = arg;
792 				}
793 				// else
794 				// skip, we don't know it but that's ok, it might be used elsewhere so no error
795 
796 				nextArgIs = null;
797 			} else if(lookingForMethod) {
798 				lookingForMethod = false;
799 				lookingForUri = true;
800 
801 				if(arg.asLowerCase().equal("commandline"))
802 					requestMethod = RequestMethod.CommandLine;
803 				else
804 					requestMethod = to!RequestMethod(arg.toUpper());
805 			} else if(lookingForUri) {
806 				lookingForUri = false;
807 
808 				requestUri = arg;
809 
810 				auto idx = arg.indexOf("?");
811 				if(idx == -1)
812 					pathInfo = arg;
813 				else {
814 					pathInfo = arg[0 .. idx];
815 					_queryString = arg[idx + 1 .. $];
816 				}
817 			} else {
818 				// it is an argument of some sort
819 				if(requestMethod == Cgi.RequestMethod.POST || requestMethod == Cgi.RequestMethod.PATCH || requestMethod == Cgi.RequestMethod.PUT || requestMethod == Cgi.RequestMethod.CommandLine) {
820 					auto parts = breakUp(arg);
821 					_post[parts[0]] ~= parts[1];
822 					allPostNamesInOrder ~= parts[0];
823 					allPostValuesInOrder ~= parts[1];
824 				} else {
825 					if(_queryString.length)
826 						_queryString ~= "&";
827 					auto parts = breakUp(arg);
828 					_queryString ~= std.uri.encodeComponent(parts[0]) ~ "=" ~ std.uri.encodeComponent(parts[1]);
829 				}
830 			}
831 		}
832 
833 		acceptsGzip = false;
834 		keepAliveRequested = false;
835 		requestHeaders = cast(immutable) _headers;
836 
837 		cookie = _cookie;
838 		cookiesArray =  getCookieArray();
839 		cookies = keepLastOf(cookiesArray);
840 
841 		queryString = _queryString;
842 		getArray = cast(immutable) decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
843 		get = keepLastOf(getArray);
844 
845 		postArray = cast(immutable) _post;
846 		post = keepLastOf(_post);
847 
848 		// FIXME
849 		filesArray = null;
850 		files = null;
851 
852 		isCalledWithCommandLineArguments = true;
853 
854 		this.port = port;
855 		this.referrer = referrer;
856 		this.remoteAddress = remoteAddress;
857 		this.userAgent = userAgent;
858 		this.authorization = authorization;
859 		this.origin = origin;
860 		this.accept = accept;
861 		this.lastEventId = lastEventId;
862 		this.https = https;
863 		this.host = host;
864 		this.requestMethod = requestMethod;
865 		this.requestUri = requestUri;
866 		this.pathInfo = pathInfo;
867 		this.queryString = queryString;
868 		this.postBody = null;
869 	}
870 
871 	private {
872 		string[] allPostNamesInOrder;
873 		string[] allPostValuesInOrder;
874 		string[] allGetNamesInOrder;
875 		string[] allGetValuesInOrder;
876 	}
877 
878 	CgiConnectionHandle getOutputFileHandle() {
879 		return _outputFileHandle;
880 	}
881 
882 	CgiConnectionHandle _outputFileHandle = INVALID_CGI_CONNECTION_HANDLE;
883 
884 	/** Initializes it using a CGI or CGI-like interface */
885 	this(long maxContentLength = defaultMaxContentLength,
886 		// use this to override the environment variable listing
887 		in string[string] env = null,
888 		// and this should return a chunk of data. return empty when done
889 		const(ubyte)[] delegate() readdata = null,
890 		// finally, use this to do custom output if needed
891 		void delegate(const(ubyte)[]) _rawDataOutput = null,
892 		// to flush teh custom output
893 		void delegate() _flush = null
894 		)
895 	{
896 
897 		// these are all set locally so the loop works
898 		// without triggering errors in dmd 2.064
899 		// we go ahead and set them at the end of it to the this version
900 		int port;
901 		string referrer;
902 		string remoteAddress;
903 		string userAgent;
904 		string authorization;
905 		string origin;
906 		string accept;
907 		string lastEventId;
908 		bool https;
909 		string host;
910 		RequestMethod requestMethod;
911 		string requestUri;
912 		string pathInfo;
913 		string queryString;
914 
915 
916 
917 		isCalledWithCommandLineArguments = false;
918 		rawDataOutput = _rawDataOutput;
919 		flushDelegate = _flush;
920 		auto getenv = delegate string(string var) {
921 			if(env is null)
922 				return std.process.environment.get(var);
923 			auto e = var in env;
924 			if(e is null)
925 				return null;
926 			return *e;
927 		};
928 
929 		environmentVariables = env is null ?
930 			cast(const) environment.toAA :
931 			env;
932 
933 		// fetching all the request headers
934 		string[string] requestHeadersHere;
935 		foreach(k, v; env is null ? cast(const) environment.toAA() : env) {
936 			if(k.startsWith("HTTP_")) {
937 				requestHeadersHere[replace(k["HTTP_".length .. $].toLower(), "_", "-")] = v;
938 			}
939 		}
940 
941 		this.requestHeaders = assumeUnique(requestHeadersHere);
942 
943 		requestUri = getenv("REQUEST_URI");
944 
945 		cookie = getenv("HTTP_COOKIE");
946 		cookiesArray = getCookieArray();
947 		cookies = keepLastOf(cookiesArray);
948 
949 		referrer = getenv("HTTP_REFERER");
950 		userAgent = getenv("HTTP_USER_AGENT");
951 		remoteAddress = getenv("REMOTE_ADDR");
952 		host = getenv("HTTP_HOST");
953 		pathInfo = getenv("PATH_INFO");
954 
955 		queryString = getenv("QUERY_STRING");
956 		scriptName = getenv("SCRIPT_NAME");
957 		{
958 			import core.runtime;
959 			auto sfn = getenv("SCRIPT_FILENAME");
960 			scriptFileName = sfn.length ? sfn : (Runtime.args.length ? Runtime.args[0] : null);
961 		}
962 
963 		bool iis = false;
964 
965 		// Because IIS doesn't pass requestUri, we simulate it here if it's empty.
966 		if(requestUri.length == 0) {
967 			// IIS sometimes includes the script name as part of the path info - we don't want that
968 			if(pathInfo.length >= scriptName.length && (pathInfo[0 .. scriptName.length] == scriptName))
969 				pathInfo = pathInfo[scriptName.length .. $];
970 
971 			requestUri = scriptName ~ pathInfo ~ (queryString.length ? ("?" ~ queryString) : "");
972 
973 			iis = true; // FIXME HACK - used in byChunk below - see bugzilla 6339
974 
975 			// FIXME: this works for apache and iis... but what about others?
976 		}
977 
978 
979 		auto ugh = decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
980 		getArray = assumeUnique(ugh);
981 		get = keepLastOf(getArray);
982 
983 
984 		// NOTE: on shitpache, you need to specifically forward this
985 		authorization = getenv("HTTP_AUTHORIZATION");
986 		// this is a hack because Apache is a shitload of fuck and
987 		// refuses to send the real header to us. Compatible
988 		// programs should send both the standard and X- versions
989 
990 		// NOTE: if you have access to .htaccess or httpd.conf, you can make this
991 		// unnecessary with mod_rewrite, so it is commented
992 
993 		//if(authorization.length == 0) // if the std is there, use it
994 		//	authorization = getenv("HTTP_X_AUTHORIZATION");
995 
996 		// the REDIRECT_HTTPS check is here because with an Apache hack, the port can become wrong
997 		if(getenv("SERVER_PORT").length && getenv("REDIRECT_HTTPS") != "on")
998 			port = to!int(getenv("SERVER_PORT"));
999 		else
1000 			port = 0; // this was probably called from the command line
1001 
1002 		auto ae = getenv("HTTP_ACCEPT_ENCODING");
1003 		if(ae.length && ae.indexOf("gzip") != -1)
1004 			acceptsGzip = true;
1005 
1006 		accept = getenv("HTTP_ACCEPT");
1007 		lastEventId = getenv("HTTP_LAST_EVENT_ID");
1008 
1009 		auto ka = getenv("HTTP_CONNECTION");
1010 		if(ka.length && ka.asLowerCase().canFind("keep-alive"))
1011 			keepAliveRequested = true;
1012 
1013 		auto or = getenv("HTTP_ORIGIN");
1014 			origin = or;
1015 
1016 		auto rm = getenv("REQUEST_METHOD");
1017 		if(rm.length)
1018 			requestMethod = to!RequestMethod(getenv("REQUEST_METHOD"));
1019 		else
1020 			requestMethod = RequestMethod.CommandLine;
1021 
1022 						// FIXME: hack on REDIRECT_HTTPS; this is there because the work app uses mod_rewrite which loses the https flag! So I set it with [E=HTTPS=%HTTPS] or whatever but then it gets translated to here so i want it to still work. This is arguably wrong but meh.
1023 		https = (getenv("HTTPS") == "on" || getenv("REDIRECT_HTTPS") == "on");
1024 
1025 		// FIXME: DOCUMENT_ROOT?
1026 
1027 		// FIXME: what about PUT?
1028 		if(requestMethod == RequestMethod.POST || requestMethod == Cgi.RequestMethod.PATCH || requestMethod == Cgi.RequestMethod.PUT || requestMethod == Cgi.RequestMethod.CommandLine) {
1029 			version(preserveData) // a hack to make forwarding simpler
1030 				immutable(ubyte)[] data;
1031 			size_t amountReceived = 0;
1032 			auto contentType = getenv("CONTENT_TYPE");
1033 
1034 			// FIXME: is this ever not going to be set? I guess it depends
1035 			// on if the server de-chunks and buffers... seems like it has potential
1036 			// to be slow if they did that. The spec says it is always there though.
1037 			// And it has worked reliably for me all year in the live environment,
1038 			// but some servers might be different.
1039 			auto cls = getenv("CONTENT_LENGTH");
1040 			auto contentLength = to!size_t(cls.length ? cls : "0");
1041 
1042 			immutable originalContentLength = contentLength;
1043 			if(contentLength) {
1044 				if(maxContentLength > 0 && contentLength > maxContentLength) {
1045 					setResponseStatus("413 Request entity too large");
1046 					write("You tried to upload a file that is too large.");
1047 					close();
1048 					throw new Exception("POST too large");
1049 				}
1050 				prepareForIncomingDataChunks(contentType, contentLength);
1051 
1052 
1053 				int processChunk(in ubyte[] chunk) {
1054 					if(chunk.length > contentLength) {
1055 						handleIncomingDataChunk(chunk[0..contentLength]);
1056 						amountReceived += contentLength;
1057 						contentLength = 0;
1058 						return 1;
1059 					} else {
1060 						handleIncomingDataChunk(chunk);
1061 						contentLength -= chunk.length;
1062 						amountReceived += chunk.length;
1063 					}
1064 					if(contentLength == 0)
1065 						return 1;
1066 
1067 					onRequestBodyDataReceived(amountReceived, originalContentLength);
1068 					return 0;
1069 				}
1070 
1071 
1072 				if(readdata is null) {
1073 					foreach(ubyte[] chunk; stdin.byChunk(iis ? contentLength : 4096))
1074 						if(processChunk(chunk))
1075 							break;
1076 				} else {
1077 					// we have a custom data source..
1078 					auto chunk = readdata();
1079 					while(chunk.length) {
1080 						if(processChunk(chunk))
1081 							break;
1082 						chunk = readdata();
1083 					}
1084 				}
1085 
1086 				onRequestBodyDataReceived(amountReceived, originalContentLength);
1087 				postArray = assumeUnique(pps._post);
1088 				filesArray = assumeUnique(pps._files);
1089 				files = keepLastOf(filesArray);
1090 				post = keepLastOf(postArray);
1091 				this.postBody = pps.postBody;
1092 				cleanUpPostDataState();
1093 			}
1094 
1095 			version(preserveData)
1096 				originalPostData = data;
1097 		}
1098 		// fixme: remote_user script name
1099 
1100 
1101 		this.port = port;
1102 		this.referrer = referrer;
1103 		this.remoteAddress = remoteAddress;
1104 		this.userAgent = userAgent;
1105 		this.authorization = authorization;
1106 		this.origin = origin;
1107 		this.accept = accept;
1108 		this.lastEventId = lastEventId;
1109 		this.https = https;
1110 		this.host = host;
1111 		this.requestMethod = requestMethod;
1112 		this.requestUri = requestUri;
1113 		this.pathInfo = pathInfo;
1114 		this.queryString = queryString;
1115 	}
1116 
1117 	/// Cleans up any temporary files. Do not use the object
1118 	/// after calling this.
1119 	///
1120 	/// NOTE: it is called automatically by GenericMain
1121 	// FIXME: this should be called if the constructor fails too, if it has created some garbage...
1122 	void dispose() {
1123 		foreach(file; files) {
1124 			if(!file.contentInMemory)
1125 				if(std.file.exists(file.contentFilename))
1126 					std.file.remove(file.contentFilename);
1127 		}
1128 	}
1129 
1130 	private {
1131 		struct PostParserState {
1132 			string contentType;
1133 			string boundary;
1134 			string localBoundary; // the ones used at the end or something lol
1135 			bool isMultipart;
1136 			bool needsSavedBody;
1137 
1138 			ulong expectedLength;
1139 			ulong contentConsumed;
1140 			immutable(ubyte)[] buffer;
1141 
1142 			// multipart parsing state
1143 			int whatDoWeWant;
1144 			bool weHaveAPart;
1145 			string[] thisOnesHeaders;
1146 			immutable(ubyte)[] thisOnesData;
1147 
1148 			string postBody;
1149 
1150 			UploadedFile piece;
1151 			bool isFile = false;
1152 
1153 			size_t memoryCommitted;
1154 
1155 			// do NOT keep mutable references to these anywhere!
1156 			// I assume they are unique in the constructor once we're all done getting data.
1157 			string[][string] _post;
1158 			UploadedFile[][string] _files;
1159 		}
1160 
1161 		PostParserState pps;
1162 	}
1163 
1164 	/// This represents a file the user uploaded via a POST request.
1165 	static struct UploadedFile {
1166 		/// If you want to create one of these structs for yourself from some data,
1167 		/// use this function.
1168 		static UploadedFile fromData(immutable(void)[] data, string name = null) {
1169 			Cgi.UploadedFile f;
1170 			f.filename = name;
1171 			f.content = cast(immutable(ubyte)[]) data;
1172 			f.contentInMemory = true;
1173 			return f;
1174 		}
1175 
1176 		string name; 		/// The name of the form element.
1177 		string filename; 	/// The filename the user set.
1178 		string contentType; 	/// The MIME type the user's browser reported. (Not reliable.)
1179 
1180 		/**
1181 			For small files, cgi.d will buffer the uploaded file in memory, and make it
1182 			directly accessible to you through the content member. I find this very convenient
1183 			and somewhat efficient, since it can avoid hitting the disk entirely. (I
1184 			often want to inspect and modify the file anyway!)
1185 
1186 			I find the file is very large, it is undesirable to eat that much memory just
1187 			for a file buffer. In those cases, if you pass a large enough value for maxContentLength
1188 			to the constructor so they are accepted, cgi.d will write the content to a temporary
1189 			file that you can re-read later.
1190 
1191 			You can override this behavior by subclassing Cgi and overriding the protected
1192 			handlePostChunk method. Note that the object is not initialized when you
1193 			write that method - the http headers are available, but the cgi.post method
1194 			is not. You may parse the file as it streams in using this method.
1195 
1196 
1197 			Anyway, if the file is small enough to be in memory, contentInMemory will be
1198 			set to true, and the content is available in the content member.
1199 
1200 			If not, contentInMemory will be set to false, and the content saved in a file,
1201 			whose name will be available in the contentFilename member.
1202 
1203 
1204 			Tip: if you know you are always dealing with small files, and want the convenience
1205 			of ignoring this member, construct Cgi with a small maxContentLength. Then, if
1206 			a large file comes in, it simply throws an exception (and HTTP error response)
1207 			instead of trying to handle it.
1208 
1209 			The default value of maxContentLength in the constructor is for small files.
1210 		*/
1211 		bool contentInMemory = true; // the default ought to always be true
1212 		immutable(ubyte)[] content; /// The actual content of the file, if contentInMemory == true
1213 		string contentFilename; /// the file where we dumped the content, if contentInMemory == false. Note that if you want to keep it, you MUST move the file, since otherwise it is considered garbage when cgi is disposed.
1214 
1215 		///
1216 		ulong fileSize() {
1217 			if(contentInMemory)
1218 				return content.length;
1219 			import std.file;
1220 			return std.file.getSize(contentFilename);
1221 
1222 		}
1223 
1224 		///
1225 		void writeToFile(string filenameToSaveTo) const {
1226 			import std.file;
1227 			if(contentInMemory)
1228 				std.file.write(filenameToSaveTo, content);
1229 			else
1230 				std.file.rename(contentFilename, filenameToSaveTo);
1231 		}
1232 	}
1233 
1234 	// given a content type and length, decide what we're going to do with the data..
1235 	protected void prepareForIncomingDataChunks(string contentType, ulong contentLength) {
1236 		pps.expectedLength = contentLength;
1237 
1238 		auto terminator = contentType.indexOf(";");
1239 		if(terminator == -1)
1240 			terminator = contentType.length;
1241 
1242 		pps.contentType = contentType[0 .. terminator];
1243 		auto b = contentType[terminator .. $];
1244 		if(b.length) {
1245 			auto idx = b.indexOf("boundary=");
1246 			if(idx != -1) {
1247 				pps.boundary = b[idx + "boundary=".length .. $];
1248 				pps.localBoundary = "\r\n--" ~ pps.boundary;
1249 			}
1250 		}
1251 
1252 		// while a content type SHOULD be sent according to the RFC, it is
1253 		// not required. We're told we SHOULD guess by looking at the content
1254 		// but it seems to me that this only happens when it is urlencoded.
1255 		if(pps.contentType == "application/x-www-form-urlencoded" || pps.contentType == "") {
1256 			pps.isMultipart = false;
1257 			pps.needsSavedBody = false;
1258 		} else if(pps.contentType == "multipart/form-data") {
1259 			pps.isMultipart = true;
1260 			enforce(pps.boundary.length, "no boundary");
1261 		} else if(pps.contentType == "text/xml") { // FIXME: could this be special and load the post params
1262 			// save the body so the application can handle it
1263 			pps.isMultipart = false;
1264 			pps.needsSavedBody = true;
1265 		} else if(pps.contentType == "application/json") { // FIXME: this could prolly try to load post params too
1266 			// save the body so the application can handle it
1267 			pps.needsSavedBody = true;
1268 			pps.isMultipart = false;
1269 		} else {
1270 			// the rest is 100% handled by the application. just save the body and send it to them
1271 			pps.needsSavedBody = true;
1272 			pps.isMultipart = false;
1273 		}
1274 	}
1275 
1276 	// handles streaming POST data. If you handle some other content type, you should
1277 	// override this. If the data isn't the content type you want, you ought to call
1278 	// super.handleIncomingDataChunk so regular forms and files still work.
1279 
1280 	// FIXME: I do some copying in here that I'm pretty sure is unnecessary, and the
1281 	// file stuff I'm sure is inefficient. But, my guess is the real bottleneck is network
1282 	// input anyway, so I'm not going to get too worked up about it right now.
1283 	protected void handleIncomingDataChunk(const(ubyte)[] chunk) {
1284 		if(chunk.length == 0)
1285 			return;
1286 		assert(chunk.length <= 32 * 1024 * 1024); // we use chunk size as a memory constraint thing, so
1287 							// if we're passed big chunks, it might throw unnecessarily.
1288 							// just pass it smaller chunks at a time.
1289 		if(pps.isMultipart) {
1290 			// multipart/form-data
1291 
1292 
1293 			// FIXME: this might want to be factored out and factorized
1294 			// need to make sure the stream hooks actually work.
1295 			void pieceHasNewContent() {
1296 				// we just grew the piece's buffer. Do we have to switch to file backing?
1297 				if(pps.piece.contentInMemory) {
1298 					if(pps.piece.content.length <= 10 * 1024 * 1024)
1299 						// meh, I'm ok with it.
1300 						return;
1301 					else {
1302 						// this is too big.
1303 						if(!pps.isFile)
1304 							throw new Exception("Request entity too large"); // a variable this big is kinda ridiculous, just reject it.
1305 						else {
1306 							// a file this large is probably acceptable though... let's use a backing file.
1307 							pps.piece.contentInMemory = false;
1308 							// FIXME: say... how do we intend to delete these things? cgi.dispose perhaps.
1309 
1310 							int count = 0;
1311 							pps.piece.contentFilename = getTempDirectory() ~ "arsd_cgi_uploaded_file_" ~ to!string(getUtcTime()) ~ "-" ~ to!string(count);
1312 							// odds are this loop will never be entered, but we want it just in case.
1313 							while(std.file.exists(pps.piece.contentFilename)) {
1314 								count++;
1315 								pps.piece.contentFilename = getTempDirectory() ~ "arsd_cgi_uploaded_file_" ~ to!string(getUtcTime()) ~ "-" ~ to!string(count);
1316 							}
1317 							// I hope this creates the file pretty quickly, or the loop might be useless...
1318 							// FIXME: maybe I should write some kind of custom transaction here.
1319 							std.file.write(pps.piece.contentFilename, pps.piece.content);
1320 
1321 							pps.piece.content = null;
1322 						}
1323 					}
1324 				} else {
1325 					// it's already in a file, so just append it to what we have
1326 					if(pps.piece.content.length) {
1327 						// FIXME: this is surely very inefficient... we'll be calling this by 4kb chunk...
1328 						std.file.append(pps.piece.contentFilename, pps.piece.content);
1329 						pps.piece.content = null;
1330 					}
1331 				}
1332 			}
1333 
1334 
1335 			void commitPart() {
1336 				if(!pps.weHaveAPart)
1337 					return;
1338 
1339 				pieceHasNewContent(); // be sure the new content is handled every time
1340 
1341 				if(pps.isFile) {
1342 					// I'm not sure if other environments put files in post or not...
1343 					// I used to not do it, but I think I should, since it is there...
1344 					pps._post[pps.piece.name] ~= pps.piece.filename;
1345 					pps._files[pps.piece.name] ~= pps.piece;
1346 
1347 					allPostNamesInOrder ~= pps.piece.name;
1348 					allPostValuesInOrder ~= pps.piece.filename;
1349 				} else {
1350 					pps._post[pps.piece.name] ~= cast(string) pps.piece.content;
1351 
1352 					allPostNamesInOrder ~= pps.piece.name;
1353 					allPostValuesInOrder ~= cast(string) pps.piece.content;
1354 				}
1355 
1356 				/*
1357 				stderr.writeln("RECEIVED: ", pps.piece.name, "=",
1358 					pps.piece.content.length < 1000
1359 					?
1360 					to!string(pps.piece.content)
1361 					:
1362 					"too long");
1363 				*/
1364 
1365 				// FIXME: the limit here
1366 				pps.memoryCommitted += pps.piece.content.length;
1367 
1368 				pps.weHaveAPart = false;
1369 				pps.whatDoWeWant = 1;
1370 				pps.thisOnesHeaders = null;
1371 				pps.thisOnesData = null;
1372 
1373 				pps.piece = UploadedFile.init;
1374 				pps.isFile = false;
1375 			}
1376 
1377 			void acceptChunk() {
1378 				pps.buffer ~= chunk;
1379 				chunk = null; // we've consumed it into the buffer, so keeping it just brings confusion
1380 			}
1381 
1382 			immutable(ubyte)[] consume(size_t howMuch) {
1383 				pps.contentConsumed += howMuch;
1384 				auto ret = pps.buffer[0 .. howMuch];
1385 				pps.buffer = pps.buffer[howMuch .. $];
1386 				return ret;
1387 			}
1388 
1389 			dataConsumptionLoop: do {
1390 			switch(pps.whatDoWeWant) {
1391 				default: assert(0);
1392 				case 0:
1393 					acceptChunk();
1394 					// the format begins with two extra leading dashes, then we should be at the boundary
1395 					if(pps.buffer.length < 2)
1396 						return;
1397 					assert(pps.buffer[0] == '-', "no leading dash");
1398 					consume(1);
1399 					assert(pps.buffer[0] == '-', "no second leading dash");
1400 					consume(1);
1401 
1402 					pps.whatDoWeWant = 1;
1403 					goto case 1;
1404 				/* fallthrough */
1405 				case 1: // looking for headers
1406 					// here, we should be lined up right at the boundary, which is followed by a \r\n
1407 
1408 					// want to keep the buffer under control in case we're under attack
1409 					//stderr.writeln("here once");
1410 					//if(pps.buffer.length + chunk.length > 70 * 1024) // they should be < 1 kb really....
1411 					//	throw new Exception("wtf is up with the huge mime part headers");
1412 
1413 					acceptChunk();
1414 
1415 					if(pps.buffer.length < pps.boundary.length)
1416 						return; // not enough data, since there should always be a boundary here at least
1417 
1418 					if(pps.contentConsumed + pps.boundary.length + 6 == pps.expectedLength) {
1419 						assert(pps.buffer.length == pps.boundary.length + 4 + 2); // --, --, and \r\n
1420 						// we *should* be at the end here!
1421 						assert(pps.buffer[0] == '-');
1422 						consume(1);
1423 						assert(pps.buffer[0] == '-');
1424 						consume(1);
1425 
1426 						// the message is terminated by --BOUNDARY--\r\n (after a \r\n leading to the boundary)
1427 						assert(pps.buffer[0 .. pps.boundary.length] == cast(const(ubyte[])) pps.boundary,
1428 							"not lined up on boundary " ~ pps.boundary);
1429 						consume(pps.boundary.length);
1430 
1431 						assert(pps.buffer[0] == '-');
1432 						consume(1);
1433 						assert(pps.buffer[0] == '-');
1434 						consume(1);
1435 
1436 						assert(pps.buffer[0] == '\r');
1437 						consume(1);
1438 						assert(pps.buffer[0] == '\n');
1439 						consume(1);
1440 
1441 						assert(pps.buffer.length == 0);
1442 						assert(pps.contentConsumed == pps.expectedLength);
1443 						break dataConsumptionLoop; // we're done!
1444 					} else {
1445 						// we're not done yet. We should be lined up on a boundary.
1446 
1447 						// But, we want to ensure the headers are here before we consume anything!
1448 						auto headerEndLocation = locationOf(pps.buffer, "\r\n\r\n");
1449 						if(headerEndLocation == -1)
1450 							return; // they *should* all be here, so we can handle them all at once.
1451 
1452 						assert(pps.buffer[0 .. pps.boundary.length] == cast(const(ubyte[])) pps.boundary,
1453 							"not lined up on boundary " ~ pps.boundary);
1454 
1455 						consume(pps.boundary.length);
1456 						// the boundary is always followed by a \r\n
1457 						assert(pps.buffer[0] == '\r');
1458 						consume(1);
1459 						assert(pps.buffer[0] == '\n');
1460 						consume(1);
1461 					}
1462 
1463 					// re-running since by consuming the boundary, we invalidate the old index.
1464 					auto headerEndLocation = locationOf(pps.buffer, "\r\n\r\n");
1465 					assert(headerEndLocation >= 0, "no header");
1466 					auto thisOnesHeaders = pps.buffer[0..headerEndLocation];
1467 
1468 					consume(headerEndLocation + 4); // The +4 is the \r\n\r\n that caps it off
1469 
1470 					pps.thisOnesHeaders = split(cast(string) thisOnesHeaders, "\r\n");
1471 
1472 					// now we'll parse the headers
1473 					foreach(h; pps.thisOnesHeaders) {
1474 						auto p = h.indexOf(":");
1475 						assert(p != -1, "no colon in header, got " ~ to!string(pps.thisOnesHeaders));
1476 						string hn = h[0..p];
1477 						string hv = h[p+2..$];
1478 
1479 						switch(hn.toLower) {
1480 							default: assert(0);
1481 							case "content-disposition":
1482 								auto info = hv.split("; ");
1483 								foreach(i; info[1..$]) { // skipping the form-data
1484 									auto o = i.split("="); // FIXME
1485 									string pn = o[0];
1486 									string pv = o[1][1..$-1];
1487 
1488 									if(pn == "name") {
1489 										pps.piece.name = pv;
1490 									} else if (pn == "filename") {
1491 										pps.piece.filename = pv;
1492 										pps.isFile = true;
1493 									}
1494 								}
1495 							break;
1496 							case "content-type":
1497 								pps.piece.contentType = hv;
1498 							break;
1499 						}
1500 					}
1501 
1502 					pps.whatDoWeWant++; // move to the next step - the data
1503 				break;
1504 				case 2:
1505 					// when we get here, pps.buffer should contain our first chunk of data
1506 
1507 					if(pps.buffer.length + chunk.length > 8 * 1024 * 1024) // we might buffer quite a bit but not much
1508 						throw new Exception("wtf is up with the huge mime part buffer");
1509 
1510 					acceptChunk();
1511 
1512 					// so the trick is, we want to process all the data up to the boundary,
1513 					// but what if the chunk's end cuts the boundary off? If we're unsure, we
1514 					// want to wait for the next chunk. We start by looking for the whole boundary
1515 					// in the buffer somewhere.
1516 
1517 					auto boundaryLocation = locationOf(pps.buffer, pps.localBoundary);
1518 					// assert(boundaryLocation != -1, "should have seen "~to!string(cast(ubyte[]) pps.localBoundary)~" in " ~ to!string(pps.buffer));
1519 					if(boundaryLocation != -1) {
1520 						// this is easy - we can see it in it's entirety!
1521 
1522 						pps.piece.content ~= consume(boundaryLocation);
1523 
1524 						assert(pps.buffer[0] == '\r');
1525 						consume(1);
1526 						assert(pps.buffer[0] == '\n');
1527 						consume(1);
1528 						assert(pps.buffer[0] == '-');
1529 						consume(1);
1530 						assert(pps.buffer[0] == '-');
1531 						consume(1);
1532 						// the boundary here is always preceded by \r\n--, which is why we used localBoundary instead of boundary to locate it. Cut that off.
1533 						pps.weHaveAPart = true;
1534 						pps.whatDoWeWant = 1; // back to getting headers for the next part
1535 
1536 						commitPart(); // we're done here
1537 					} else {
1538 						// we can't see the whole thing, but what if there's a partial boundary?
1539 
1540 						enforce(pps.localBoundary.length < 128); // the boundary ought to be less than a line...
1541 						assert(pps.localBoundary.length > 1); // should already be sane but just in case
1542 						bool potentialBoundaryFound = false;
1543 
1544 						boundaryCheck: for(int a = 1; a < pps.localBoundary.length; a++) {
1545 							// we grow the boundary a bit each time. If we think it looks the
1546 							// same, better pull another chunk to be sure it's not the end.
1547 							// Starting small because exiting the loop early is desirable, since
1548 							// we're not keeping any ambiguity and 1 / 256 chance of exiting is
1549 							// the best we can do.
1550 							if(a > pps.buffer.length)
1551 								break; // FIXME: is this right?
1552 							assert(a <= pps.buffer.length);
1553 							assert(a > 0);
1554 							if(std.algorithm.endsWith(pps.buffer, pps.localBoundary[0 .. a])) {
1555 								// ok, there *might* be a boundary here, so let's
1556 								// not treat the end as data yet. The rest is good to
1557 								// use though, since if there was a boundary there, we'd
1558 								// have handled it up above after locationOf.
1559 
1560 								pps.piece.content ~= pps.buffer[0 .. $ - a];
1561 								consume(pps.buffer.length - a);
1562 								pieceHasNewContent();
1563 								potentialBoundaryFound = true;
1564 								break boundaryCheck;
1565 							}
1566 						}
1567 
1568 						if(!potentialBoundaryFound) {
1569 							// we can consume the whole thing
1570 							pps.piece.content ~= pps.buffer;
1571 							pieceHasNewContent();
1572 							consume(pps.buffer.length);
1573 						} else {
1574 							// we found a possible boundary, but there was
1575 							// insufficient data to be sure.
1576 							assert(pps.buffer == cast(const(ubyte[])) pps.localBoundary[0 .. pps.buffer.length]);
1577 
1578 							return; // wait for the next chunk.
1579 						}
1580 					}
1581 			}
1582 			} while(pps.buffer.length);
1583 
1584 			// btw all boundaries except the first should have a \r\n before them
1585 		} else {
1586 			// application/x-www-form-urlencoded and application/json
1587 
1588 				// not using maxContentLength because that might be cranked up to allow
1589 				// large file uploads. We can handle them, but a huge post[] isn't any good.
1590 			if(pps.buffer.length + chunk.length > 8 * 1024 * 1024) // surely this is plenty big enough
1591 				throw new Exception("wtf is up with such a gigantic form submission????");
1592 
1593 			pps.buffer ~= chunk;
1594 
1595 			// simple handling, but it works... until someone bombs us with gigabytes of crap at least...
1596 			if(pps.buffer.length == pps.expectedLength) {
1597 				if(pps.needsSavedBody)
1598 					pps.postBody = cast(string) pps.buffer;
1599 				else
1600 					pps._post = decodeVariables(cast(string) pps.buffer, "&", &allPostNamesInOrder, &allPostValuesInOrder);
1601 				version(preserveData)
1602 					originalPostData = pps.buffer;
1603 			} else {
1604 				// just for debugging
1605 			}
1606 		}
1607 	}
1608 
1609 	protected void cleanUpPostDataState() {
1610 		pps = PostParserState.init;
1611 	}
1612 
1613 	/// you can override this function to somehow react
1614 	/// to an upload in progress.
1615 	///
1616 	/// Take note that parts of the CGI object is not yet
1617 	/// initialized! Stuff from HTTP headers, including get[], is usable.
1618 	/// But, none of post[] is usable, and you cannot write here. That's
1619 	/// why this method is const - mutating the object won't do much anyway.
1620 	///
1621 	/// My idea here was so you can output a progress bar or
1622 	/// something to a cooperative client (see arsd.rtud for a potential helper)
1623 	///
1624 	/// The default is to do nothing. Subclass cgi and use the
1625 	/// CustomCgiMain mixin to do something here.
1626 	void onRequestBodyDataReceived(size_t receivedSoFar, size_t totalExpected) const {
1627 		// This space intentionally left blank.
1628 	}
1629 
1630 	/// Initializes the cgi from completely raw HTTP data. The ir must have a Socket source.
1631 	/// *closeConnection will be set to true if you should close the connection after handling this request
1632 	this(BufferedInputRange ir, bool* closeConnection) {
1633 		isCalledWithCommandLineArguments = false;
1634 		import al = std.algorithm;
1635 
1636 		immutable(ubyte)[] data;
1637 
1638 		void rdo(const(ubyte)[] d) {
1639 		//import std.stdio; writeln(d);
1640 			sendAll(ir.source, d);
1641 		}
1642 
1643 		auto ira = ir.source.remoteAddress();
1644 		auto irLocalAddress = ir.source.localAddress();
1645 
1646 		ushort port = 80;
1647 		if(auto ia = cast(InternetAddress) irLocalAddress) {
1648 			port = ia.port;
1649 		} else if(auto ia = cast(Internet6Address) irLocalAddress) {
1650 			port = ia.port;
1651 		}
1652 
1653 		// that check for UnixAddress is to work around a Phobos bug
1654 		// see: https://github.com/dlang/phobos/pull/7383
1655 		// but this might be more useful anyway tbh for this case
1656 		version(Posix)
1657 		this(ir, ira is null ? null : cast(UnixAddress) ira ? "unix:" : ira.toString(), port, 0, false, &rdo, null, closeConnection);
1658 		else
1659 		this(ir, ira is null ? null : ira.toString(), port, 0, false, &rdo, null, closeConnection);
1660 	}
1661 
1662 	/**
1663 		Initializes it from raw HTTP request data. GenericMain uses this when you compile with -version=embedded_httpd.
1664 
1665 		NOTE: If you are behind a reverse proxy, the values here might not be what you expect.... it will use X-Forwarded-For for remote IP and X-Forwarded-Host for host
1666 
1667 		Params:
1668 			inputData = the incoming data, including headers and other raw http data.
1669 				When the constructor exits, it will leave this range exactly at the start of
1670 				the next request on the connection (if there is one).
1671 
1672 			address = the IP address of the remote user
1673 			_port = the port number of the connection
1674 			pathInfoStarts = the offset into the path component of the http header where the SCRIPT_NAME ends and the PATH_INFO begins.
1675 			_https = if this connection is encrypted (note that the input data must not actually be encrypted)
1676 			_rawDataOutput = delegate to accept response data. It should write to the socket or whatever; Cgi does all the needed processing to speak http.
1677 			_flush = if _rawDataOutput buffers, this delegate should flush the buffer down the wire
1678 			closeConnection = if the request asks to close the connection, *closeConnection == true.
1679 	*/
1680 	this(
1681 		BufferedInputRange inputData,
1682 //		string[] headers, immutable(ubyte)[] data,
1683 		string address, ushort _port,
1684 		int pathInfoStarts = 0, // use this if you know the script name, like if this is in a folder in a bigger web environment
1685 		bool _https = false,
1686 		void delegate(const(ubyte)[]) _rawDataOutput = null,
1687 		void delegate() _flush = null,
1688 		// this pointer tells if the connection is supposed to be closed after we handle this
1689 		bool* closeConnection = null)
1690 	{
1691 		// these are all set locally so the loop works
1692 		// without triggering errors in dmd 2.064
1693 		// we go ahead and set them at the end of it to the this version
1694 		int port;
1695 		string referrer;
1696 		string remoteAddress;
1697 		string userAgent;
1698 		string authorization;
1699 		string origin;
1700 		string accept;
1701 		string lastEventId;
1702 		bool https;
1703 		string host;
1704 		RequestMethod requestMethod;
1705 		string requestUri;
1706 		string pathInfo;
1707 		string queryString;
1708 		string scriptName;
1709 		string[string] get;
1710 		string[][string] getArray;
1711 		bool keepAliveRequested;
1712 		bool acceptsGzip;
1713 		string cookie;
1714 
1715 
1716 
1717 		environmentVariables = cast(const) environment.toAA;
1718 
1719 		idlol = inputData;
1720 
1721 		isCalledWithCommandLineArguments = false;
1722 
1723 		https = _https;
1724 		port = _port;
1725 
1726 		rawDataOutput = _rawDataOutput;
1727 		flushDelegate = _flush;
1728 		nph = true;
1729 
1730 		remoteAddress = address;
1731 
1732 		// streaming parser
1733 		import al = std.algorithm;
1734 
1735 			// FIXME: tis cast is technically wrong, but Phobos deprecated al.indexOf... for some reason.
1736 		auto idx = indexOf(cast(string) inputData.front(), "\r\n\r\n");
1737 		while(idx == -1) {
1738 			inputData.popFront(0);
1739 			idx = indexOf(cast(string) inputData.front(), "\r\n\r\n");
1740 		}
1741 
1742 		assert(idx != -1);
1743 
1744 
1745 		string contentType = "";
1746 		string[string] requestHeadersHere;
1747 
1748 		size_t contentLength;
1749 
1750 		bool isChunked;
1751 
1752 		{
1753 			import core.runtime;
1754 			scriptFileName = Runtime.args.length ? Runtime.args[0] : null;
1755 		}
1756 
1757 
1758 		int headerNumber = 0;
1759 		foreach(line; al.splitter(inputData.front()[0 .. idx], "\r\n"))
1760 		if(line.length) {
1761 			headerNumber++;
1762 			auto header = cast(string) line.idup;
1763 			if(headerNumber == 1) {
1764 				// request line
1765 				auto parts = al.splitter(header, " ");
1766 				requestMethod = to!RequestMethod(parts.front);
1767 				parts.popFront();
1768 				requestUri = parts.front;
1769 
1770 				// FIXME:  the requestUri could be an absolute path!!! should I rename it or something?
1771 				scriptName = requestUri[0 .. pathInfoStarts];
1772 
1773 				auto question = requestUri.indexOf("?");
1774 				if(question == -1) {
1775 					queryString = "";
1776 					// FIXME: double check, this might be wrong since it could be url encoded
1777 					pathInfo = requestUri[pathInfoStarts..$];
1778 				} else {
1779 					queryString = requestUri[question+1..$];
1780 					pathInfo = requestUri[pathInfoStarts..question];
1781 				}
1782 
1783 				auto ugh = decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
1784 				getArray = cast(string[][string]) assumeUnique(ugh);
1785 
1786 				if(header.indexOf("HTTP/1.0") != -1) {
1787 					http10 = true;
1788 					autoBuffer = true;
1789 					if(closeConnection) {
1790 						// on http 1.0, close is assumed (unlike http/1.1 where we assume keep alive)
1791 						*closeConnection = true;
1792 					}
1793 				}
1794 			} else {
1795 				// other header
1796 				auto colon = header.indexOf(":");
1797 				if(colon == -1)
1798 					throw new Exception("HTTP headers should have a colon!");
1799 				string name = header[0..colon].toLower;
1800 				string value = header[colon+2..$]; // skip the colon and the space
1801 
1802 				requestHeadersHere[name] = value;
1803 
1804 				if (name == "accept") {
1805 					accept = value;
1806 				}
1807 				else if (name == "origin") {
1808 					origin = value;
1809 				}
1810 				else if (name == "connection") {
1811 					if(value == "close" && closeConnection)
1812 						*closeConnection = true;
1813 					if(value.asLowerCase().canFind("keep-alive")) {
1814 						keepAliveRequested = true;
1815 
1816 						// on http 1.0, the connection is closed by default,
1817 						// but not if they request keep-alive. then we don't close
1818 						// anymore - undoing the set above
1819 						if(http10 && closeConnection) {
1820 							*closeConnection = false;
1821 						}
1822 					}
1823 				}
1824 				else if (name == "transfer-encoding") {
1825 					if(value == "chunked")
1826 						isChunked = true;
1827 				}
1828 				else if (name == "last-event-id") {
1829 					lastEventId = value;
1830 				}
1831 				else if (name == "authorization") {
1832 					authorization = value;
1833 				}
1834 				else if (name == "content-type") {
1835 					contentType = value;
1836 				}
1837 				else if (name == "content-length") {
1838 					contentLength = to!size_t(value);
1839 				}
1840 				else if (name == "x-forwarded-for") {
1841 					remoteAddress = value;
1842 				}
1843 				else if (name == "x-forwarded-host" || name == "host") {
1844 					if(name != "host" || host is null)
1845 						host = value;
1846 				}
1847 				// FIXME: https://tools.ietf.org/html/rfc7239
1848 				else if (name == "accept-encoding") {
1849 					if(value.indexOf("gzip") != -1)
1850 						acceptsGzip = true;
1851 				}
1852 				else if (name == "user-agent") {
1853 					userAgent = value;
1854 				}
1855 				else if (name == "referer") {
1856 					referrer = value;
1857 				}
1858 				else if (name == "cookie") {
1859 					cookie ~= value;
1860 				} else if(name == "expect") {
1861 					if(value == "100-continue") {
1862 						// FIXME we should probably give user code a chance
1863 						// to process and reject but that needs to be virtual,
1864 						// perhaps part of the CGI redesign.
1865 
1866 						// FIXME: if size is > max content length it should
1867 						// also fail at this point.
1868 						_rawDataOutput(cast(ubyte[]) "HTTP/1.1 100 Continue\r\n\r\n");
1869 
1870 						// FIXME: let the user write out 103 early hints too
1871 					}
1872 				}
1873 				// else
1874 				// ignore it
1875 
1876 			}
1877 		}
1878 
1879 		inputData.consume(idx + 4);
1880 		// done
1881 
1882 		requestHeaders = assumeUnique(requestHeadersHere);
1883 
1884 		ByChunkRange dataByChunk;
1885 
1886 		// reading Content-Length type data
1887 		// We need to read up the data we have, and write it out as a chunk.
1888 		if(!isChunked) {
1889 			dataByChunk = byChunk(inputData, contentLength);
1890 		} else {
1891 			// chunked requests happen, but not every day. Since we need to know
1892 			// the content length (for now, maybe that should change), we'll buffer
1893 			// the whole thing here instead of parse streaming. (I think this is what Apache does anyway in cgi modes)
1894 			auto data = dechunk(inputData);
1895 
1896 			// set the range here
1897 			dataByChunk = byChunk(data);
1898 			contentLength = data.length;
1899 		}
1900 
1901 		assert(dataByChunk !is null);
1902 
1903 		if(contentLength) {
1904 			prepareForIncomingDataChunks(contentType, contentLength);
1905 			foreach(dataChunk; dataByChunk) {
1906 				handleIncomingDataChunk(dataChunk);
1907 			}
1908 			postArray = assumeUnique(pps._post);
1909 			filesArray = assumeUnique(pps._files);
1910 			files = keepLastOf(filesArray);
1911 			post = keepLastOf(postArray);
1912 			postBody = pps.postBody;
1913 			cleanUpPostDataState();
1914 		}
1915 
1916 		this.port = port;
1917 		this.referrer = referrer;
1918 		this.remoteAddress = remoteAddress;
1919 		this.userAgent = userAgent;
1920 		this.authorization = authorization;
1921 		this.origin = origin;
1922 		this.accept = accept;
1923 		this.lastEventId = lastEventId;
1924 		this.https = https;
1925 		this.host = host;
1926 		this.requestMethod = requestMethod;
1927 		this.requestUri = requestUri;
1928 		this.pathInfo = pathInfo;
1929 		this.queryString = queryString;
1930 
1931 		this.scriptName = scriptName;
1932 		this.get = keepLastOf(getArray);
1933 		this.getArray = cast(immutable) getArray;
1934 		this.keepAliveRequested = keepAliveRequested;
1935 		this.acceptsGzip = acceptsGzip;
1936 		this.cookie = cookie;
1937 
1938 		cookiesArray = getCookieArray();
1939 		cookies = keepLastOf(cookiesArray);
1940 
1941 	}
1942 	BufferedInputRange idlol;
1943 
1944 	private immutable(string[string]) keepLastOf(in string[][string] arr) {
1945 		string[string] ca;
1946 		foreach(k, v; arr)
1947 			ca[k] = v[$-1];
1948 
1949 		return assumeUnique(ca);
1950 	}
1951 
1952 	// FIXME duplication
1953 	private immutable(UploadedFile[string]) keepLastOf(in UploadedFile[][string] arr) {
1954 		UploadedFile[string] ca;
1955 		foreach(k, v; arr)
1956 			ca[k] = v[$-1];
1957 
1958 		return assumeUnique(ca);
1959 	}
1960 
1961 
1962 	private immutable(string[][string]) getCookieArray() {
1963 		auto forTheLoveOfGod = decodeVariables(cookie, "; ");
1964 		return assumeUnique(forTheLoveOfGod);
1965 	}
1966 
1967 	/// Very simple method to require a basic auth username and password.
1968 	/// If the http request doesn't include the required credentials, it throws a
1969 	/// HTTP 401 error, and an exception.
1970 	///
1971 	/// Note: basic auth does not provide great security, especially over unencrypted HTTP;
1972 	/// the user's credentials are sent in plain text on every request.
1973 	///
1974 	/// If you are using Apache, the HTTP_AUTHORIZATION variable may not be sent to the
1975 	/// application. Either use Apache's built in methods for basic authentication, or add
1976 	/// something along these lines to your server configuration:
1977 	///
1978 	///      RewriteEngine On
1979 	///      RewriteCond %{HTTP:Authorization} ^(.*)
1980 	///      RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
1981 	///
1982 	/// To ensure the necessary data is available to cgi.d.
1983 	void requireBasicAuth(string user, string pass, string message = null) {
1984 		if(authorization != "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (user ~ ":" ~ pass))) {
1985 			setResponseStatus("401 Authorization Required");
1986 			header ("WWW-Authenticate: Basic realm=\""~message~"\"");
1987 			close();
1988 			throw new Exception("Not authorized; got " ~ authorization);
1989 		}
1990 	}
1991 
1992 	/// Very simple caching controls - setCache(false) means it will never be cached. Good for rapidly updated or sensitive sites.
1993 	/// setCache(true) means it will always be cached for as long as possible. Best for static content.
1994 	/// Use setResponseExpires and updateResponseExpires for more control
1995 	void setCache(bool allowCaching) {
1996 		noCache = !allowCaching;
1997 	}
1998 
1999 	/// Set to true and use cgi.write(data, true); to send a gzipped response to browsers
2000 	/// who can accept it
2001 	bool gzipResponse;
2002 
2003 	immutable bool acceptsGzip;
2004 	immutable bool keepAliveRequested;
2005 
2006 	/// Set to true if and only if this was initialized with command line arguments
2007 	immutable bool isCalledWithCommandLineArguments;
2008 
2009 	/// This gets a full url for the current request, including port, protocol, host, path, and query
2010 	string getCurrentCompleteUri() const {
2011 		ushort defaultPort = https ? 443 : 80;
2012 
2013 		string uri = "http";
2014 		if(https)
2015 			uri ~= "s";
2016 		uri ~= "://";
2017 		uri ~= host;
2018 		/+ // the host has the port so p sure this never needed, cgi on apache and embedded http all do the right hting now
2019 		version(none)
2020 		if(!(!port || port == defaultPort)) {
2021 			uri ~= ":";
2022 			uri ~= to!string(port);
2023 		}
2024 		+/
2025 		uri ~= requestUri;
2026 		return uri;
2027 	}
2028 
2029 	/// You can override this if your site base url isn't the same as the script name
2030 	string logicalScriptName() const {
2031 		return scriptName;
2032 	}
2033 
2034 	/++
2035 		Sets the HTTP status of the response. For example, "404 File Not Found" or "500 Internal Server Error".
2036 		It assumes "200 OK", and automatically changes to "302 Found" if you call setResponseLocation().
2037 		Note setResponseStatus() must be called *before* you write() any data to the output.
2038 
2039 		History:
2040 			The `int` overload was added on January 11, 2021.
2041 	+/
2042 	void setResponseStatus(string status) {
2043 		assert(!outputtedResponseData);
2044 		responseStatus = status;
2045 	}
2046 	/// ditto
2047 	void setResponseStatus(int statusCode) {
2048 		setResponseStatus(getHttpCodeText(statusCode));
2049 	}
2050 	private string responseStatus = null;
2051 
2052 	/// Returns true if it is still possible to output headers
2053 	bool canOutputHeaders() {
2054 		return !isClosed && !outputtedResponseData;
2055 	}
2056 
2057 	/// Sets the location header, which the browser will redirect the user to automatically.
2058 	/// Note setResponseLocation() must be called *before* you write() any data to the output.
2059 	/// The optional important argument is used if it's a default suggestion rather than something to insist upon.
2060 	void setResponseLocation(string uri, bool important = true, string status = null) {
2061 		if(!important && isCurrentResponseLocationImportant)
2062 			return; // important redirects always override unimportant ones
2063 
2064 		if(uri is null) {
2065 			responseStatus = "200 OK";
2066 			responseLocation = null;
2067 			isCurrentResponseLocationImportant = important;
2068 			return; // this just cancels the redirect
2069 		}
2070 
2071 		assert(!outputtedResponseData);
2072 		if(status is null)
2073 			responseStatus = "302 Found";
2074 		else
2075 			responseStatus = status;
2076 
2077 		responseLocation = uri.strip;
2078 		isCurrentResponseLocationImportant = important;
2079 	}
2080 	protected string responseLocation = null;
2081 	private bool isCurrentResponseLocationImportant = false;
2082 
2083 	/// Sets the Expires: http header. See also: updateResponseExpires, setPublicCaching
2084 	/// The parameter is in unix_timestamp * 1000. Try setResponseExpires(getUTCtime() + SOME AMOUNT) for normal use.
2085 	/// Note: the when parameter is different than setCookie's expire parameter.
2086 	void setResponseExpires(long when, bool isPublic = false) {
2087 		responseExpires = when;
2088 		setCache(true); // need to enable caching so the date has meaning
2089 
2090 		responseIsPublic = isPublic;
2091 		responseExpiresRelative = false;
2092 	}
2093 
2094 	/// Sets a cache-control max-age header for whenFromNow, in seconds.
2095 	void setResponseExpiresRelative(int whenFromNow, bool isPublic = false) {
2096 		responseExpires = whenFromNow;
2097 		setCache(true); // need to enable caching so the date has meaning
2098 
2099 		responseIsPublic = isPublic;
2100 		responseExpiresRelative = true;
2101 	}
2102 	private long responseExpires = long.min;
2103 	private bool responseIsPublic = false;
2104 	private bool responseExpiresRelative = false;
2105 
2106 	/// This is like setResponseExpires, but it can be called multiple times. The setting most in the past is the one kept.
2107 	/// If you have multiple functions, they all might call updateResponseExpires about their own return value. The program
2108 	/// output as a whole is as cacheable as the least cachable part in the chain.
2109 
2110 	/// setCache(false) always overrides this - it is, by definition, the strictest anti-cache statement available. If your site outputs sensitive user data, you should probably call setCache(false) when you do, to ensure no other functions will cache the content, as it may be a privacy risk.
2111 	/// Conversely, setting here overrides setCache(true), since any expiration date is in the past of infinity.
2112 	void updateResponseExpires(long when, bool isPublic) {
2113 		if(responseExpires == long.min)
2114 			setResponseExpires(when, isPublic);
2115 		else if(when < responseExpires)
2116 			setResponseExpires(when, responseIsPublic && isPublic); // if any part of it is private, it all is
2117 	}
2118 
2119 	/*
2120 	/// Set to true if you want the result to be cached publically - that is, is the content shared?
2121 	/// Should generally be false if the user is logged in. It assumes private cache only.
2122 	/// setCache(true) also turns on public caching, and setCache(false) sets to private.
2123 	void setPublicCaching(bool allowPublicCaches) {
2124 		publicCaching = allowPublicCaches;
2125 	}
2126 	private bool publicCaching = false;
2127 	*/
2128 
2129 	/++
2130 		History:
2131 			Added January 11, 2021
2132 	+/
2133 	enum SameSitePolicy {
2134 		Lax,
2135 		Strict,
2136 		None
2137 	}
2138 
2139 	/++
2140 		Sets an HTTP cookie, automatically encoding the data to the correct string.
2141 		expiresIn is how many milliseconds in the future the cookie will expire.
2142 		TIP: to make a cookie accessible from subdomains, set the domain to .yourdomain.com.
2143 		Note setCookie() must be called *before* you write() any data to the output.
2144 
2145 		History:
2146 			Parameter `sameSitePolicy` was added on January 11, 2021.
2147 	+/
2148 	void setCookie(string name, string data, long expiresIn = 0, string path = null, string domain = null, bool httpOnly = false, bool secure = false, SameSitePolicy sameSitePolicy = SameSitePolicy.Lax) {
2149 		assert(!outputtedResponseData);
2150 		string cookie = std.uri.encodeComponent(name) ~ "=";
2151 		cookie ~= std.uri.encodeComponent(data);
2152 		if(path !is null)
2153 			cookie ~= "; path=" ~ path;
2154 		// FIXME: should I just be using max-age here? (also in cache below)
2155 		if(expiresIn != 0)
2156 			cookie ~= "; expires=" ~ printDate(cast(DateTime) Clock.currTime(UTC()) + dur!"msecs"(expiresIn));
2157 		if(domain !is null)
2158 			cookie ~= "; domain=" ~ domain;
2159 		if(secure == true)
2160 			cookie ~= "; Secure";
2161 		if(httpOnly == true )
2162 			cookie ~= "; HttpOnly";
2163 		final switch(sameSitePolicy) {
2164 			case SameSitePolicy.Lax:
2165 				cookie ~= "; SameSite=Lax";
2166 			break;
2167 			case SameSitePolicy.Strict:
2168 				cookie ~= "; SameSite=Strict";
2169 			break;
2170 			case SameSitePolicy.None:
2171 				cookie ~= "; SameSite=None";
2172 				assert(secure); // cookie spec requires this now, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
2173 			break;
2174 		}
2175 
2176 		if(auto idx = name in cookieIndexes) {
2177 			responseCookies[*idx] = cookie;
2178 		} else {
2179 			cookieIndexes[name] = responseCookies.length;
2180 			responseCookies ~= cookie;
2181 		}
2182 	}
2183 	private string[] responseCookies;
2184 	private size_t[string] cookieIndexes;
2185 
2186 	/// Clears a previously set cookie with the given name, path, and domain.
2187 	void clearCookie(string name, string path = null, string domain = null) {
2188 		assert(!outputtedResponseData);
2189 		setCookie(name, "", 1, path, domain);
2190 	}
2191 
2192 	/// Sets the content type of the response, for example "text/html" (the default) for HTML, or "image/png" for a PNG image
2193 	void setResponseContentType(string ct) {
2194 		assert(!outputtedResponseData);
2195 		responseContentType = ct;
2196 	}
2197 	private string responseContentType = null;
2198 
2199 	/// Adds a custom header. It should be the name: value, but without any line terminator.
2200 	/// For example: header("X-My-Header: Some value");
2201 	/// Note you should use the specialized functions in this object if possible to avoid
2202 	/// duplicates in the output.
2203 	void header(string h) {
2204 		customHeaders ~= h;
2205 	}
2206 
2207 	/++
2208 		I named the original function `header` after PHP, but this pattern more fits
2209 		the rest of the Cgi object.
2210 
2211 		Either name are allowed.
2212 
2213 		History:
2214 			Alias added June 17, 2022.
2215 	+/
2216 	alias setResponseHeader = header;
2217 
2218 	private string[] customHeaders;
2219 	private bool websocketMode;
2220 
2221 	void flushHeaders(const(void)[] t, bool isAll = false) {
2222 		StackBuffer buffer = StackBuffer(0);
2223 
2224 		prepHeaders(t, isAll, &buffer);
2225 
2226 		if(rawDataOutput !is null)
2227 			rawDataOutput(cast(const(ubyte)[]) buffer.get());
2228 		else {
2229 			stdout.rawWrite(buffer.get());
2230 		}
2231 	}
2232 
2233 	private void prepHeaders(const(void)[] t, bool isAll, StackBuffer* buffer) {
2234 		string terminator = "\n";
2235 		if(rawDataOutput !is null)
2236 			terminator = "\r\n";
2237 
2238 		if(responseStatus !is null) {
2239 			if(nph) {
2240 				if(http10)
2241 					buffer.add("HTTP/1.0 ", responseStatus, terminator);
2242 				else
2243 					buffer.add("HTTP/1.1 ", responseStatus, terminator);
2244 			} else
2245 				buffer.add("Status: ", responseStatus, terminator);
2246 		} else if (nph) {
2247 			if(http10)
2248 				buffer.add("HTTP/1.0 200 OK", terminator);
2249 			else
2250 				buffer.add("HTTP/1.1 200 OK", terminator);
2251 		}
2252 
2253 		if(websocketMode)
2254 			goto websocket;
2255 
2256 		if(nph) { // we're responsible for setting the date too according to http 1.1
2257 			char[29] db = void;
2258 			printDateToBuffer(cast(DateTime) Clock.currTime(UTC()), db[]);
2259 			buffer.add("Date: ", db[], terminator);
2260 		}
2261 
2262 		// FIXME: what if the user wants to set his own content-length?
2263 		// The custom header function can do it, so maybe that's best.
2264 		// Or we could reuse the isAll param.
2265 		if(responseLocation !is null) {
2266 			buffer.add("Location: ", responseLocation, terminator);
2267 		}
2268 		if(!noCache && responseExpires != long.min) { // an explicit expiration date is set
2269 			if(responseExpiresRelative) {
2270 				buffer.add("Cache-Control: ", responseIsPublic ? "public" : "private", ", max-age=");
2271 				buffer.add(responseExpires);
2272 				buffer.add(", no-cache=\"set-cookie, set-cookie2\"", terminator);
2273 			} else {
2274 				auto expires = SysTime(unixTimeToStdTime(cast(int)(responseExpires / 1000)), UTC());
2275 				char[29] db = void;
2276 				printDateToBuffer(cast(DateTime) expires, db[]);
2277 				buffer.add("Expires: ", db[], terminator);
2278 				// FIXME: assuming everything is private unless you use nocache - generally right for dynamic pages, but not necessarily
2279 				buffer.add("Cache-Control: ", (responseIsPublic ? "public" : "private"), ", no-cache=\"set-cookie, set-cookie2\"");
2280 				buffer.add(terminator);
2281 			}
2282 		}
2283 		if(responseCookies !is null && responseCookies.length > 0) {
2284 			foreach(c; responseCookies)
2285 				buffer.add("Set-Cookie: ", c, terminator);
2286 		}
2287 		if(noCache) { // we specifically do not want caching (this is actually the default)
2288 			buffer.add("Cache-Control: private, no-cache=\"set-cookie\"", terminator);
2289 			buffer.add("Expires: 0", terminator);
2290 			buffer.add("Pragma: no-cache", terminator);
2291 		} else {
2292 			if(responseExpires == long.min) { // caching was enabled, but without a date set - that means assume cache forever
2293 				buffer.add("Cache-Control: public", terminator);
2294 				buffer.add("Expires: Tue, 31 Dec 2030 14:00:00 GMT", terminator); // FIXME: should not be more than one year in the future
2295 			}
2296 		}
2297 		if(responseContentType !is null) {
2298 			buffer.add("Content-Type: ", responseContentType, terminator);
2299 		} else
2300 			buffer.add("Content-Type: text/html; charset=utf-8", terminator);
2301 
2302 		if(gzipResponse && acceptsGzip && isAll) { // FIXME: isAll really shouldn't be necessary
2303 			buffer.add("Content-Encoding: gzip", terminator);
2304 		}
2305 
2306 
2307 		if(!isAll) {
2308 			if(nph && !http10) {
2309 				buffer.add("Transfer-Encoding: chunked", terminator);
2310 				responseChunked = true;
2311 			}
2312 		} else {
2313 			buffer.add("Content-Length: ");
2314 			buffer.add(t.length);
2315 			buffer.add(terminator);
2316 			if(nph && keepAliveRequested) {
2317 				buffer.add("Connection: Keep-Alive", terminator);
2318 			}
2319 		}
2320 
2321 		websocket:
2322 
2323 		foreach(hd; customHeaders)
2324 			buffer.add(hd, terminator);
2325 
2326 		// FIXME: what about duplicated headers?
2327 
2328 		// end of header indicator
2329 		buffer.add(terminator);
2330 
2331 		outputtedResponseData = true;
2332 	}
2333 
2334 	/// Writes the data to the output, flushing headers if they have not yet been sent.
2335 	void write(const(void)[] t, bool isAll = false, bool maybeAutoClose = true) {
2336 		assert(!closed, "Output has already been closed");
2337 
2338 		StackBuffer buffer = StackBuffer(0);
2339 
2340 		if(gzipResponse && acceptsGzip && isAll) { // FIXME: isAll really shouldn't be necessary
2341 			// actually gzip the data here
2342 
2343 			auto c = new Compress(HeaderFormat.gzip); // want gzip
2344 
2345 			auto data = c.compress(t);
2346 			data ~= c.flush();
2347 
2348 			// std.file.write("/tmp/last-item", data);
2349 
2350 			t = data;
2351 		}
2352 
2353 		if(!outputtedResponseData && (!autoBuffer || isAll)) {
2354 			prepHeaders(t, isAll, &buffer);
2355 		}
2356 
2357 		if(requestMethod != RequestMethod.HEAD && t.length > 0) {
2358 			if (autoBuffer && !isAll) {
2359 				outputBuffer ~= cast(ubyte[]) t;
2360 			}
2361 			if(!autoBuffer || isAll) {
2362 				if(rawDataOutput !is null)
2363 					if(nph && responseChunked) {
2364 						//rawDataOutput(makeChunk(cast(const(ubyte)[]) t));
2365 						// we're making the chunk here instead of in a function
2366 						// to avoid unneeded gc pressure
2367 						buffer.add(toHex(t.length));
2368 						buffer.add("\r\n");
2369 						buffer.add(cast(char[]) t, "\r\n");
2370 					} else {
2371 						buffer.add(cast(char[]) t);
2372 					}
2373 				else
2374 					buffer.add(cast(char[]) t);
2375 			}
2376 		}
2377 
2378 		if(rawDataOutput !is null)
2379 			rawDataOutput(cast(const(ubyte)[]) buffer.get());
2380 		else
2381 			stdout.rawWrite(buffer.get());
2382 
2383 		if(maybeAutoClose && isAll)
2384 			close(); // if you say it is all, that means we're definitely done
2385 				// maybeAutoClose can be false though to avoid this (important if you call from inside close()!
2386 	}
2387 
2388 	/++
2389 		Convenience method to set content type to json and write the string as the complete response.
2390 
2391 		History:
2392 			Added January 16, 2020
2393 	+/
2394 	void writeJson(string json) {
2395 		this.setResponseContentType("application/json");
2396 		this.write(json, true);
2397 	}
2398 
2399 	/// Flushes the pending buffer, leaving the connection open so you can send more.
2400 	void flush() {
2401 		if(rawDataOutput is null)
2402 			stdout.flush();
2403 		else if(flushDelegate !is null)
2404 			flushDelegate();
2405 	}
2406 
2407 	version(autoBuffer)
2408 		bool autoBuffer = true;
2409 	else
2410 		bool autoBuffer = false;
2411 	ubyte[] outputBuffer;
2412 
2413 	/// Flushes the buffers to the network, signifying that you are done.
2414 	/// You should always call this explicitly when you are done outputting data.
2415 	void close() {
2416 		if(closed)
2417 			return; // don't double close
2418 
2419 		if(!outputtedResponseData)
2420 			write("", false, false);
2421 
2422 		// writing auto buffered data
2423 		if(requestMethod != RequestMethod.HEAD && autoBuffer) {
2424 			if(!nph)
2425 				stdout.rawWrite(outputBuffer);
2426 			else
2427 				write(outputBuffer, true, false); // tell it this is everything
2428 		}
2429 
2430 		// closing the last chunk...
2431 		if(nph && rawDataOutput !is null && responseChunked)
2432 			rawDataOutput(cast(const(ubyte)[]) "0\r\n\r\n");
2433 
2434 		if(flushDelegate)
2435 			flushDelegate();
2436 
2437 		closed = true;
2438 	}
2439 
2440 	// Closes without doing anything, shouldn't be used often
2441 	void rawClose() {
2442 		closed = true;
2443 	}
2444 
2445 	/++
2446 		Gets a request variable as a specific type, or the default value of it isn't there
2447 		or isn't convertible to the request type.
2448 
2449 		Checks both GET and POST variables, preferring the POST variable, if available.
2450 
2451 		A nice trick is using the default value to choose the type:
2452 
2453 		---
2454 			/*
2455 				The return value will match the type of the default.
2456 				Here, I gave 10 as a default, so the return value will
2457 				be an int.
2458 
2459 				If the user-supplied value cannot be converted to the
2460 				requested type, you will get the default value back.
2461 			*/
2462 			int a = cgi.request("number", 10);
2463 
2464 			if(cgi.get["number"] == "11")
2465 				assert(a == 11); // conversion succeeds
2466 
2467 			if("number" !in cgi.get)
2468 				assert(a == 10); // no value means you can't convert - give the default
2469 
2470 			if(cgi.get["number"] == "twelve")
2471 				assert(a == 10); // conversion from string to int would fail, so we get the default
2472 		---
2473 
2474 		You can use an enum as an easy whitelist, too:
2475 
2476 		---
2477 			enum Operations {
2478 				add, remove, query
2479 			}
2480 
2481 			auto op = cgi.request("op", Operations.query);
2482 
2483 			if(cgi.get["op"] == "add")
2484 				assert(op == Operations.add);
2485 			if(cgi.get["op"] == "remove")
2486 				assert(op == Operations.remove);
2487 			if(cgi.get["op"] == "query")
2488 				assert(op == Operations.query);
2489 
2490 			if(cgi.get["op"] == "random string")
2491 				assert(op == Operations.query); // the value can't be converted to the enum, so we get the default
2492 		---
2493 	+/
2494 	T request(T = string)(in string name, in T def = T.init) const nothrow {
2495 		try {
2496 			return
2497 				(name in post) ? to!T(post[name]) :
2498 				(name in get)  ? to!T(get[name]) :
2499 				def;
2500 		} catch(Exception e) { return def; }
2501 	}
2502 
2503 	/// Is the output already closed?
2504 	bool isClosed() const {
2505 		return closed;
2506 	}
2507 
2508 	/++
2509 		Gets a session object associated with the `cgi` request. You can use different type throughout your application.
2510 	+/
2511 	Session!Data getSessionObject(Data)() {
2512 		if(testInProcess !is null) {
2513 			// test mode
2514 			auto obj = testInProcess.getSessionOverride(typeid(typeof(return)));
2515 			if(obj !is null)
2516 				return cast(typeof(return)) obj;
2517 			else {
2518 				auto o = new MockSession!Data();
2519 				testInProcess.setSessionOverride(typeid(typeof(return)), o);
2520 				return o;
2521 			}
2522 		} else {
2523 			// normal operation
2524 			return new BasicDataServerSession!Data(this);
2525 		}
2526 	}
2527 
2528 	// if it is in test mode; triggers mock sessions. Used by CgiTester
2529 	version(with_breaking_cgi_features)
2530 	private CgiTester testInProcess;
2531 
2532 	/* Hooks for redirecting input and output */
2533 	private void delegate(const(ubyte)[]) rawDataOutput = null;
2534 	private void delegate() flushDelegate = null;
2535 
2536 	/* This info is used when handling a more raw HTTP protocol */
2537 	private bool nph;
2538 	private bool http10;
2539 	private bool closed;
2540 	private bool responseChunked = false;
2541 
2542 	version(preserveData) // note: this can eat lots of memory; don't use unless you're sure you need it.
2543 	immutable(ubyte)[] originalPostData;
2544 
2545 	public immutable string postBody;
2546 	alias postJson = postBody; // old name
2547 
2548 	/* Internal state flags */
2549 	private bool outputtedResponseData;
2550 	private bool noCache = true;
2551 
2552 	const(string[string]) environmentVariables;
2553 
2554 	/** What follows is data gotten from the HTTP request. It is all fully immutable,
2555 	    partially because it logically is (your code doesn't change what the user requested...)
2556 	    and partially because I hate how bad programs in PHP change those superglobals to do
2557 	    all kinds of hard to follow ugliness. I don't want that to ever happen in D.
2558 
2559 	    For some of these, you'll want to refer to the http or cgi specs for more details.
2560 	*/
2561 	immutable(string[string]) requestHeaders; /// All the raw headers in the request as name/value pairs. The name is stored as all lower case, but otherwise the same as it is in HTTP; words separated by dashes. For example, "cookie" or "accept-encoding". Many HTTP headers have specialized variables below for more convenience and static name checking; you should generally try to use them.
2562 
2563 	immutable(char[]) host; 	/// The hostname in the request. If one program serves multiple domains, you can use this to differentiate between them.
2564 	immutable(char[]) origin; 	/// The origin header in the request, if present. Some HTML5 cross-domain apis set this and you should check it on those cross domain requests and websockets.
2565 	immutable(char[]) userAgent; 	/// The browser's user-agent string. Can be used to identify the browser.
2566 	immutable(char[]) pathInfo; 	/// This is any stuff sent after your program's name on the url, but before the query string. For example, suppose your program is named "app". If the user goes to site.com/app, pathInfo is empty. But, he can also go to site.com/app/some/sub/path; treating your program like a virtual folder. In this case, pathInfo == "/some/sub/path".
2567 	immutable(char[]) scriptName;   /// The full base path of your program, as seen by the user. If your program is located at site.com/programs/apps, scriptName == "/programs/apps".
2568 	immutable(char[]) scriptFileName;   /// The physical filename of your script
2569 	immutable(char[]) authorization; /// The full authorization string from the header, undigested. Useful for implementing auth schemes such as OAuth 1.0. Note that some web servers do not forward this to the app without taking extra steps. See requireBasicAuth's comment for more info.
2570 	immutable(char[]) accept; 	/// The HTTP accept header is the user agent telling what content types it is willing to accept. This is often */*; they accept everything, so it's not terribly useful. (The similar sounding Accept-Encoding header is handled automatically for chunking and gzipping. Simply set gzipResponse = true and cgi.d handles the details, zipping if the user's browser is willing to accept it.)
2571 	immutable(char[]) lastEventId; 	/// The HTML 5 draft includes an EventSource() object that connects to the server, and remains open to take a stream of events. My arsd.rtud module can help with the server side part of that. The Last-Event-Id http header is defined in the draft to help handle loss of connection. When the browser reconnects to you, it sets this header to the last event id it saw, so you can catch it up. This member has the contents of that header.
2572 
2573 	immutable(RequestMethod) requestMethod; /// The HTTP request verb: GET, POST, etc. It is represented as an enum in cgi.d (which, like many enums, you can convert back to string with std.conv.to()). A HTTP GET is supposed to, according to the spec, not have side effects; a user can GET something over and over again and always have the same result. On all requests, the get[] and getArray[] members may be filled in. The post[] and postArray[] members are only filled in on POST methods.
2574 	immutable(char[]) queryString; 	/// The unparsed content of the request query string - the stuff after the ? in your URL. See get[] and getArray[] for a parse view of it. Sometimes, the unparsed string is useful though if you want a custom format of data up there (probably not a good idea, unless it is really simple, like "?username" perhaps.)
2575 	immutable(char[]) cookie; 	/// The unparsed content of the Cookie: header in the request. See also the cookies[string] member for a parsed view of the data.
2576 	/** The Referer header from the request. (It is misspelled in the HTTP spec, and thus the actual request and cgi specs too, but I spelled the word correctly here because that's sane. The spec's misspelling is an implementation detail.) It contains the site url that referred the user to your program; the site that linked to you, or if you're serving images, the site that has you as an image. Also, if you're in an iframe, the referrer is the site that is framing you.
2577 
2578 	Important note: if the user copy/pastes your url, this is blank, and, just like with all other user data, their browsers can also lie to you. Don't rely on it for real security.
2579 	*/
2580 	immutable(char[]) referrer;
2581 	immutable(char[]) requestUri; 	/// The full url if the current request, excluding the protocol and host. requestUri == scriptName ~ pathInfo ~ (queryString.length ? "?" ~ queryString : "");
2582 
2583 	immutable(char[]) remoteAddress; /// The IP address of the user, as we see it. (Might not match the IP of the user's computer due to things like proxies and NAT.)
2584 
2585 	immutable bool https; 	/// Was the request encrypted via https?
2586 	immutable int port; 	/// On what TCP port number did the server receive the request?
2587 
2588 	/** Here come the parsed request variables - the things that come close to PHP's _GET, _POST, etc. superglobals in content. */
2589 
2590 	immutable(string[string]) get; 	/// The data from your query string in the url, only showing the last string of each name. If you want to handle multiple values with the same name, use getArray. This only works right if the query string is x-www-form-urlencoded; the default you see on the web with name=value pairs separated by the & character.
2591 	immutable(string[string]) post; /// The data from the request's body, on POST requests. It parses application/x-www-form-urlencoded data (used by most web requests, including typical forms), and multipart/form-data requests (used by file uploads on web forms) into the same container, so you can always access them the same way. It makes no attempt to parse other content types. If you want to accept an XML Post body (for a web api perhaps), you'll need to handle the raw data yourself.
2592 	immutable(string[string]) cookies; /// Separates out the cookie header into individual name/value pairs (which is how you set them!)
2593 
2594 	/**
2595 		Represents user uploaded files.
2596 
2597 		When making a file upload form, be sure to follow the standard: set method="POST" and enctype="multipart/form-data" in your html <form> tag attributes. The key into this array is the name attribute on your input tag, just like with other post variables. See the comments on the UploadedFile struct for more information about the data inside, including important notes on max size and content location.
2598 	*/
2599 	immutable(UploadedFile[][string]) filesArray;
2600 	immutable(UploadedFile[string]) files;
2601 
2602 	/// Use these if you expect multiple items submitted with the same name. btw, assert(get[name] is getArray[name][$-1); should pass. Same for post and cookies.
2603 	/// the order of the arrays is the order the data arrives
2604 	immutable(string[][string]) getArray; /// like get, but an array of values per name
2605 	immutable(string[][string]) postArray; /// ditto for post
2606 	immutable(string[][string]) cookiesArray; /// ditto for cookies
2607 
2608 	// convenience function for appending to a uri without extra ?
2609 	// matches the name and effect of javascript's location.search property
2610 	string search() const {
2611 		if(queryString.length)
2612 			return "?" ~ queryString;
2613 		return "";
2614 	}
2615 
2616 	// FIXME: what about multiple files with the same name?
2617   private:
2618 	//RequestMethod _requestMethod;
2619 }
2620 
2621 /// use this for testing or other isolated things when you want it to be no-ops
2622 Cgi dummyCgi(Cgi.RequestMethod method = Cgi.RequestMethod.GET, string url = null, in ubyte[] data = null, void delegate(const(ubyte)[]) outputSink = null) {
2623 	// we want to ignore, not use stdout
2624 	if(outputSink is null)
2625 		outputSink = delegate void(const(ubyte)[]) { };
2626 
2627 	string[string] env;
2628 	env["REQUEST_METHOD"] = to!string(method);
2629 	env["CONTENT_LENGTH"] = to!string(data.length);
2630 
2631 	auto cgi = new Cgi(
2632 		0,
2633 		env,
2634 		{ return data; },
2635 		outputSink,
2636 		null);
2637 
2638 	return cgi;
2639 }
2640 
2641 /++
2642 	A helper test class for request handler unittests.
2643 +/
2644 version(with_breaking_cgi_features)
2645 class CgiTester {
2646 	private {
2647 		SessionObject[TypeInfo] mockSessions;
2648 		SessionObject getSessionOverride(TypeInfo ti) {
2649 			if(auto o = ti in mockSessions)
2650 				return *o;
2651 			else
2652 				return null;
2653 		}
2654 		void setSessionOverride(TypeInfo ti, SessionObject so) {
2655 			mockSessions[ti] = so;
2656 		}
2657 	}
2658 
2659 	/++
2660 		Gets (and creates if necessary) a mock session object for this test. Note
2661 		it will be the same one used for any test operations through this CgiTester instance.
2662 	+/
2663 	Session!Data getSessionObject(Data)() {
2664 		auto obj = getSessionOverride(typeid(typeof(return)));
2665 		if(obj !is null)
2666 			return cast(typeof(return)) obj;
2667 		else {
2668 			auto o = new MockSession!Data();
2669 			setSessionOverride(typeid(typeof(return)), o);
2670 			return o;
2671 		}
2672 	}
2673 
2674 	/++
2675 		Pass a reference to your request handler when creating the tester.
2676 	+/
2677 	this(void function(Cgi) requestHandler) {
2678 		this.requestHandler = requestHandler;
2679 	}
2680 
2681 	/++
2682 		You can check response information with these methods after you call the request handler.
2683 	+/
2684 	struct Response {
2685 		int code;
2686 		string[string] headers;
2687 		string responseText;
2688 		ubyte[] responseBody;
2689 	}
2690 
2691 	/++
2692 		Executes a test request on your request handler, and returns the response.
2693 
2694 		Params:
2695 			url = The URL to test. Should be an absolute path, but excluding domain. e.g. `"/test"`.
2696 			args = additional arguments. Same format as cgi's command line handler.
2697 	+/
2698 	Response GET(string url, string[] args = null) {
2699 		return executeTest("GET", url, args);
2700 	}
2701 	/// ditto
2702 	Response POST(string url, string[] args = null) {
2703 		return executeTest("POST", url, args);
2704 	}
2705 
2706 	/// ditto
2707 	Response executeTest(string method, string url, string[] args) {
2708 		ubyte[] outputtedRawData;
2709 		void outputSink(const(ubyte)[] data) {
2710 			outputtedRawData ~= data;
2711 		}
2712 		auto cgi = new Cgi(["test", method, url] ~ args, &outputSink);
2713 		cgi.testInProcess = this;
2714 		scope(exit) cgi.dispose();
2715 
2716 		requestHandler(cgi);
2717 
2718 		cgi.close();
2719 
2720 		Response response;
2721 
2722 		if(outputtedRawData.length) {
2723 			enum LINE = "\r\n";
2724 
2725 			auto idx = outputtedRawData.locationOf(LINE ~ LINE);
2726 			assert(idx != -1, to!string(outputtedRawData));
2727 			auto headers = cast(string) outputtedRawData[0 .. idx];
2728 			response.code = 200;
2729 			while(headers.length) {
2730 				auto i = headers.locationOf(LINE);
2731 				if(i == -1) i = cast(int) headers.length;
2732 
2733 				auto header = headers[0 .. i];
2734 
2735 				auto c = header.locationOf(":");
2736 				if(c != -1) {
2737 					auto name = header[0 .. c];
2738 					auto value = header[c + 2 ..$];
2739 
2740 					if(name == "Status")
2741 						response.code = value[0 .. value.locationOf(" ")].to!int;
2742 
2743 					response.headers[name] = value;
2744 				} else {
2745 					assert(0);
2746 				}
2747 
2748 				if(i != headers.length)
2749 					i += 2;
2750 				headers = headers[i .. $];
2751 			}
2752 			response.responseBody = outputtedRawData[idx + 4 .. $];
2753 			response.responseText = cast(string) response.responseBody;
2754 		}
2755 
2756 		return response;
2757 	}
2758 
2759 	private void function(Cgi) requestHandler;
2760 }
2761 
2762 
2763 // should this be a separate module? Probably, but that's a hassle.
2764 
2765 /// Makes a data:// uri that can be used as links in most newer browsers (IE8+).
2766 string makeDataUrl(string mimeType, in void[] data) {
2767 	auto data64 = Base64.encode(cast(const(ubyte[])) data);
2768 	return "data:" ~ mimeType ~ ";base64," ~ assumeUnique(data64);
2769 }
2770 
2771 // FIXME: I don't think this class correctly decodes/encodes the individual parts
2772 /// Represents a url that can be broken down or built up through properties
2773 struct Uri {
2774 	alias toString this; // blargh idk a url really is a string, but should it be implicit?
2775 
2776 	// scheme//userinfo@host:port/path?query#fragment
2777 
2778 	string scheme; /// e.g. "http" in "http://example.com/"
2779 	string userinfo; /// the username (and possibly a password) in the uri
2780 	string host; /// the domain name
2781 	int port; /// port number, if given. Will be zero if a port was not explicitly given
2782 	string path; /// e.g. "/folder/file.html" in "http://example.com/folder/file.html"
2783 	string query; /// the stuff after the ? in a uri
2784 	string fragment; /// the stuff after the # in a uri.
2785 
2786 	// idk if i want to keep these, since the functions they wrap are used many, many, many times in existing code, so this is either an unnecessary alias or a gratuitous break of compatibility
2787 	// the decode ones need to keep different names anyway because we can't overload on return values...
2788 	static string encode(string s) { return std.uri.encodeComponent(s); }
2789 	static string encode(string[string] s) { return encodeVariables(s); }
2790 	static string encode(string[][string] s) { return encodeVariables(s); }
2791 
2792 	/// Breaks down a uri string to its components
2793 	this(string uri) {
2794 		reparse(uri);
2795 	}
2796 
2797 	private void reparse(string uri) {
2798 		// from RFC 3986
2799 		// the ctRegex triples the compile time and makes ugly errors for no real benefit
2800 		// it was a nice experiment but just not worth it.
2801 		// enum ctr = ctRegex!r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?";
2802 		/*
2803 			Captures:
2804 				0 = whole url
2805 				1 = scheme, with :
2806 				2 = scheme, no :
2807 				3 = authority, with //
2808 				4 = authority, no //
2809 				5 = path
2810 				6 = query string, with ?
2811 				7 = query string, no ?
2812 				8 = anchor, with #
2813 				9 = anchor, no #
2814 		*/
2815 		// Yikes, even regular, non-CT regex is also unacceptably slow to compile. 1.9s on my computer!
2816 		// instead, I will DIY and cut that down to 0.6s on the same computer.
2817 		/*
2818 
2819 				Note that authority is
2820 					user:password@domain:port
2821 				where the user:password@ part is optional, and the :port is optional.
2822 
2823 				Regex translation:
2824 
2825 				Scheme cannot have :, /, ?, or # in it, and must have one or more chars and end in a :. It is optional, but must be first.
2826 				Authority must start with //, but cannot have any other /, ?, or # in it. It is optional.
2827 				Path cannot have any ? or # in it. It is optional.
2828 				Query must start with ? and must not have # in it. It is optional.
2829 				Anchor must start with # and can have anything else in it to end of string. It is optional.
2830 		*/
2831 
2832 		this = Uri.init; // reset all state
2833 
2834 		// empty uri = nothing special
2835 		if(uri.length == 0) {
2836 			return;
2837 		}
2838 
2839 		size_t idx;
2840 
2841 		scheme_loop: foreach(char c; uri[idx .. $]) {
2842 			switch(c) {
2843 				case ':':
2844 				case '/':
2845 				case '?':
2846 				case '#':
2847 					break scheme_loop;
2848 				default:
2849 			}
2850 			idx++;
2851 		}
2852 
2853 		if(idx == 0 && uri[idx] == ':') {
2854 			// this is actually a path! we skip way ahead
2855 			goto path_loop;
2856 		}
2857 
2858 		if(idx == uri.length) {
2859 			// the whole thing is a path, apparently
2860 			path = uri;
2861 			return;
2862 		}
2863 
2864 		if(idx > 0 && uri[idx] == ':') {
2865 			scheme = uri[0 .. idx];
2866 			idx++;
2867 		} else {
2868 			// we need to rewind; it found a / but no :, so the whole thing is prolly a path...
2869 			idx = 0;
2870 		}
2871 
2872 		if(idx + 2 < uri.length && uri[idx .. idx + 2] == "//") {
2873 			// we have an authority....
2874 			idx += 2;
2875 
2876 			auto authority_start = idx;
2877 			authority_loop: foreach(char c; uri[idx .. $]) {
2878 				switch(c) {
2879 					case '/':
2880 					case '?':
2881 					case '#':
2882 						break authority_loop;
2883 					default:
2884 				}
2885 				idx++;
2886 			}
2887 
2888 			auto authority = uri[authority_start .. idx];
2889 
2890 			auto idx2 = authority.indexOf("@");
2891 			if(idx2 != -1) {
2892 				userinfo = authority[0 .. idx2];
2893 				authority = authority[idx2 + 1 .. $];
2894 			}
2895 
2896 			if(authority.length && authority[0] == '[') {
2897 				// ipv6 address special casing
2898 				idx2 = authority.indexOf(']');
2899 				if(idx2 != -1) {
2900 					auto end = authority[idx2 + 1 .. $];
2901 					if(end.length && end[0] == ':')
2902 						idx2 = idx2 + 1;
2903 					else
2904 						idx2 = -1;
2905 				}
2906 			} else {
2907 				idx2 = authority.indexOf(":");
2908 			}
2909 
2910 			if(idx2 == -1) {
2911 				port = 0; // 0 means not specified; we should use the default for the scheme
2912 				host = authority;
2913 			} else {
2914 				host = authority[0 .. idx2];
2915 				if(idx2 + 1 < authority.length)
2916 					port = to!int(authority[idx2 + 1 .. $]);
2917 				else
2918 					port = 0;
2919 			}
2920 		}
2921 
2922 		path_loop:
2923 		auto path_start = idx;
2924 
2925 		foreach(char c; uri[idx .. $]) {
2926 			if(c == '?' || c == '#')
2927 				break;
2928 			idx++;
2929 		}
2930 
2931 		path = uri[path_start .. idx];
2932 
2933 		if(idx == uri.length)
2934 			return; // nothing more to examine...
2935 
2936 		if(uri[idx] == '?') {
2937 			idx++;
2938 			auto query_start = idx;
2939 			foreach(char c; uri[idx .. $]) {
2940 				if(c == '#')
2941 					break;
2942 				idx++;
2943 			}
2944 			query = uri[query_start .. idx];
2945 		}
2946 
2947 		if(idx < uri.length && uri[idx] == '#') {
2948 			idx++;
2949 			fragment = uri[idx .. $];
2950 		}
2951 
2952 		// uriInvalidated = false;
2953 	}
2954 
2955 	private string rebuildUri() const {
2956 		string ret;
2957 		if(scheme.length)
2958 			ret ~= scheme ~ ":";
2959 		if(userinfo.length || host.length)
2960 			ret ~= "//";
2961 		if(userinfo.length)
2962 			ret ~= userinfo ~ "@";
2963 		if(host.length)
2964 			ret ~= host;
2965 		if(port)
2966 			ret ~= ":" ~ to!string(port);
2967 
2968 		ret ~= path;
2969 
2970 		if(query.length)
2971 			ret ~= "?" ~ query;
2972 
2973 		if(fragment.length)
2974 			ret ~= "#" ~ fragment;
2975 
2976 		// uri = ret;
2977 		// uriInvalidated = false;
2978 		return ret;
2979 	}
2980 
2981 	/// Converts the broken down parts back into a complete string
2982 	string toString() const {
2983 		// if(uriInvalidated)
2984 			return rebuildUri();
2985 	}
2986 
2987 	/// Returns a new absolute Uri given a base. It treats this one as
2988 	/// relative where possible, but absolute if not. (If protocol, domain, or
2989 	/// other info is not set, the new one inherits it from the base.)
2990 	///
2991 	/// Browsers use a function like this to figure out links in html.
2992 	Uri basedOn(in Uri baseUrl) const {
2993 		Uri n = this; // copies
2994 		if(n.scheme == "data")
2995 			return n;
2996 		// n.uriInvalidated = true; // make sure we regenerate...
2997 
2998 		// userinfo is not inherited... is this wrong?
2999 
3000 		// if anything is given in the existing url, we don't use the base anymore.
3001 		if(n.scheme.empty) {
3002 			n.scheme = baseUrl.scheme;
3003 			if(n.host.empty) {
3004 				n.host = baseUrl.host;
3005 				if(n.port == 0) {
3006 					n.port = baseUrl.port;
3007 					if(n.path.length > 0 && n.path[0] != '/') {
3008 						auto b = baseUrl.path[0 .. baseUrl.path.lastIndexOf("/") + 1];
3009 						if(b.length == 0)
3010 							b = "/";
3011 						n.path = b ~ n.path;
3012 					} else if(n.path.length == 0) {
3013 						n.path = baseUrl.path;
3014 					}
3015 				}
3016 			}
3017 		}
3018 
3019 		n.removeDots();
3020 
3021 		return n;
3022 	}
3023 
3024 	void removeDots() {
3025 		auto parts = this.path.split("/");
3026 		string[] toKeep;
3027 		foreach(part; parts) {
3028 			if(part == ".") {
3029 				continue;
3030 			} else if(part == "..") {
3031 				//if(toKeep.length > 1)
3032 					toKeep = toKeep[0 .. $-1];
3033 				//else
3034 					//toKeep = [""];
3035 				continue;
3036 			} else {
3037 				//if(toKeep.length && toKeep[$-1].length == 0 && part.length == 0)
3038 					//continue; // skip a `//` situation
3039 				toKeep ~= part;
3040 			}
3041 		}
3042 
3043 		auto path = toKeep.join("/");
3044 		if(path.length && path[0] != '/')
3045 			path = "/" ~ path;
3046 
3047 		this.path = path;
3048 	}
3049 
3050 	unittest {
3051 		auto uri = Uri("test.html");
3052 		assert(uri.path == "test.html");
3053 		uri = Uri("path/1/lol");
3054 		assert(uri.path == "path/1/lol");
3055 		uri = Uri("http://me@example.com");
3056 		assert(uri.scheme == "http");
3057 		assert(uri.userinfo == "me");
3058 		assert(uri.host == "example.com");
3059 		uri = Uri("http://example.com/#a");
3060 		assert(uri.scheme == "http");
3061 		assert(uri.host == "example.com");
3062 		assert(uri.fragment == "a");
3063 		uri = Uri("#foo");
3064 		assert(uri.fragment == "foo");
3065 		uri = Uri("?lol");
3066 		assert(uri.query == "lol");
3067 		uri = Uri("#foo?lol");
3068 		assert(uri.fragment == "foo?lol");
3069 		uri = Uri("?lol#foo");
3070 		assert(uri.fragment == "foo");
3071 		assert(uri.query == "lol");
3072 
3073 		uri = Uri("http://127.0.0.1/");
3074 		assert(uri.host == "127.0.0.1");
3075 		assert(uri.port == 0);
3076 
3077 		uri = Uri("http://127.0.0.1:123/");
3078 		assert(uri.host == "127.0.0.1");
3079 		assert(uri.port == 123);
3080 
3081 		uri = Uri("http://[ff:ff::0]/");
3082 		assert(uri.host == "[ff:ff::0]");
3083 
3084 		uri = Uri("http://[ff:ff::0]:123/");
3085 		assert(uri.host == "[ff:ff::0]");
3086 		assert(uri.port == 123);
3087 	}
3088 
3089 	// This can sometimes be a big pain in the butt for me, so lots of copy/paste here to cover
3090 	// the possibilities.
3091 	unittest {
3092 		auto url = Uri("cool.html"); // checking relative links
3093 
3094 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/what/cool.html");
3095 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/what/cool.html");
3096 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/what/cool.html");
3097 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/cool.html");
3098 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/cool.html");
3099 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/what/cool.html");
3100 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/what/cool.html");
3101 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/what/cool.html");
3102 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/cool.html");
3103 
3104 		url = Uri("/something/cool.html"); // same server, different path
3105 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/something/cool.html");
3106 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/something/cool.html");
3107 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/something/cool.html");
3108 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/something/cool.html");
3109 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/something/cool.html");
3110 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/something/cool.html");
3111 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/something/cool.html");
3112 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/something/cool.html");
3113 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/something/cool.html");
3114 
3115 		url = Uri("?query=answer"); // same path. server, protocol, and port, just different query string and fragment
3116 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/what/test.html?query=answer");
3117 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/what/test.html?query=answer");
3118 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/what/?query=answer");
3119 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/?query=answer");
3120 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com?query=answer");
3121 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/what/test.html?query=answer");
3122 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/what/test.html?query=answer");
3123 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/what/test.html?query=answer");
3124 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com?query=answer");
3125 
3126 		url = Uri("/test/bar");
3127 		assert(Uri("./").basedOn(url) == "/test/", Uri("./").basedOn(url));
3128 		assert(Uri("../").basedOn(url) == "/");
3129 
3130 		url = Uri("http://example.com/");
3131 		assert(Uri("../foo").basedOn(url) == "http://example.com/foo");
3132 
3133 		//auto uriBefore = url;
3134 		url = Uri("#anchor"); // everything should remain the same except the anchor
3135 		//uriBefore.anchor = "anchor");
3136 		//assert(url == uriBefore);
3137 
3138 		url = Uri("//example.com"); // same protocol, but different server. the path here should be blank.
3139 
3140 		url = Uri("//example.com/example.html"); // same protocol, but different server and path
3141 
3142 		url = Uri("http://example.com/test.html"); // completely absolute link should never be modified
3143 
3144 		url = Uri("http://example.com"); // completely absolute link should never be modified, even if it has no path
3145 
3146 		// FIXME: add something for port too
3147 	}
3148 
3149 	// these are like javascript's location.search and location.hash
3150 	string search() const {
3151 		return query.length ? ("?" ~ query) : "";
3152 	}
3153 	string hash() const {
3154 		return fragment.length ? ("#" ~ fragment) : "";
3155 	}
3156 }
3157 
3158 
3159 /*
3160 	for session, see web.d
3161 */
3162 
3163 /// breaks down a url encoded string
3164 string[][string] decodeVariables(string data, string separator = "&", string[]* namesInOrder = null, string[]* valuesInOrder = null) {
3165 	auto vars = data.split(separator);
3166 	string[][string] _get;
3167 	foreach(var; vars) {
3168 		auto equal = var.indexOf("=");
3169 		string name;
3170 		string value;
3171 		if(equal == -1) {
3172 			name = decodeComponent(var);
3173 			value = "";
3174 		} else {
3175 			//_get[decodeComponent(var[0..equal])] ~= decodeComponent(var[equal + 1 .. $].replace("+", " "));
3176 			// stupid + -> space conversion.
3177 			name = decodeComponent(var[0..equal].replace("+", " "));
3178 			value = decodeComponent(var[equal + 1 .. $].replace("+", " "));
3179 		}
3180 
3181 		_get[name] ~= value;
3182 		if(namesInOrder)
3183 			(*namesInOrder) ~= name;
3184 		if(valuesInOrder)
3185 			(*valuesInOrder) ~= value;
3186 	}
3187 	return _get;
3188 }
3189 
3190 /// breaks down a url encoded string, but only returns the last value of any array
3191 string[string] decodeVariablesSingle(string data) {
3192 	string[string] va;
3193 	auto varArray = decodeVariables(data);
3194 	foreach(k, v; varArray)
3195 		va[k] = v[$-1];
3196 
3197 	return va;
3198 }
3199 
3200 /// url encodes the whole string
3201 string encodeVariables(in string[string] data) {
3202 	string ret;
3203 
3204 	bool outputted = false;
3205 	foreach(k, v; data) {
3206 		if(outputted)
3207 			ret ~= "&";
3208 		else
3209 			outputted = true;
3210 
3211 		ret ~= std.uri.encodeComponent(k) ~ "=" ~ std.uri.encodeComponent(v);
3212 	}
3213 
3214 	return ret;
3215 }
3216 
3217 /// url encodes a whole string
3218 string encodeVariables(in string[][string] data) {
3219 	string ret;
3220 
3221 	bool outputted = false;
3222 	foreach(k, arr; data) {
3223 		foreach(v; arr) {
3224 			if(outputted)
3225 				ret ~= "&";
3226 			else
3227 				outputted = true;
3228 			ret ~= std.uri.encodeComponent(k) ~ "=" ~ std.uri.encodeComponent(v);
3229 		}
3230 	}
3231 
3232 	return ret;
3233 }
3234 
3235 /// Encodes all but the explicitly unreserved characters per rfc 3986
3236 /// Alphanumeric and -_.~ are the only ones left unencoded
3237 /// name is borrowed from php
3238 string rawurlencode(in char[] data) {
3239 	string ret;
3240 	ret.reserve(data.length * 2);
3241 	foreach(char c; data) {
3242 		if(
3243 			(c >= 'a' && c <= 'z') ||
3244 			(c >= 'A' && c <= 'Z') ||
3245 			(c >= '0' && c <= '9') ||
3246 			c == '-' || c == '_' || c == '.' || c == '~')
3247 		{
3248 			ret ~= c;
3249 		} else {
3250 			ret ~= '%';
3251 			// since we iterate on char, this should give us the octets of the full utf8 string
3252 			ret ~= toHexUpper(c);
3253 		}
3254 	}
3255 
3256 	return ret;
3257 }
3258 
3259 
3260 // http helper functions
3261 
3262 // for chunked responses (which embedded http does whenever possible)
3263 version(none) // this is moved up above to avoid making a copy of the data
3264 const(ubyte)[] makeChunk(const(ubyte)[] data) {
3265 	const(ubyte)[] ret;
3266 
3267 	ret = cast(const(ubyte)[]) toHex(data.length);
3268 	ret ~= cast(const(ubyte)[]) "\r\n";
3269 	ret ~= data;
3270 	ret ~= cast(const(ubyte)[]) "\r\n";
3271 
3272 	return ret;
3273 }
3274 
3275 string toHex(long num) {
3276 	string ret;
3277 	while(num) {
3278 		int v = num % 16;
3279 		num /= 16;
3280 		char d = cast(char) ((v < 10) ? v + '0' : (v-10) + 'a');
3281 		ret ~= d;
3282 	}
3283 
3284 	return to!string(array(ret.retro));
3285 }
3286 
3287 string toHexUpper(long num) {
3288 	string ret;
3289 	while(num) {
3290 		int v = num % 16;
3291 		num /= 16;
3292 		char d = cast(char) ((v < 10) ? v + '0' : (v-10) + 'A');
3293 		ret ~= d;
3294 	}
3295 
3296 	if(ret.length == 1)
3297 		ret ~= "0"; // url encoding requires two digits and that's what this function is used for...
3298 
3299 	return to!string(array(ret.retro));
3300 }
3301 
3302 
3303 // the generic mixins
3304 
3305 /// Use this instead of writing your own main
3306 mixin template GenericMain(alias fun, long maxContentLength = defaultMaxContentLength) {
3307 	mixin CustomCgiMain!(Cgi, fun, maxContentLength);
3308 }
3309 
3310 /++
3311 	Boilerplate mixin for a main function that uses the [dispatcher] function.
3312 
3313 	You can send `typeof(null)` as the `Presenter` argument to use a generic one.
3314 
3315 	History:
3316 		Added July 9, 2021
3317 +/
3318 mixin template DispatcherMain(Presenter, DispatcherArgs...) {
3319 	/++
3320 		Handler to the generated presenter you can use from your objects, etc.
3321 	+/
3322 	Presenter activePresenter;
3323 
3324 	/++
3325 		Request handler that creates the presenter then forwards to the [dispatcher] function.
3326 		Renders 404 if the dispatcher did not handle the request.
3327 
3328 		Will automatically serve the presenter.style and presenter.script as "style.css" and "script.js"
3329 	+/
3330 	void handler(Cgi cgi) {
3331 		auto presenter = new Presenter;
3332 		activePresenter = presenter;
3333 		scope(exit) activePresenter = null;
3334 
3335 		if(cgi.dispatcher!DispatcherArgs(presenter))
3336 			return;
3337 
3338 		switch(cgi.pathInfo) {
3339 			case "/style.css":
3340 				cgi.setCache(true);
3341 				cgi.setResponseContentType("text/css");
3342 				cgi.write(presenter.style(), true);
3343 			break;
3344 			case "/script.js":
3345 				cgi.setCache(true);
3346 				cgi.setResponseContentType("application/javascript");
3347 				cgi.write(presenter.script(), true);
3348 			break;
3349 			default:
3350 				presenter.renderBasicError(cgi, 404);
3351 		}
3352 	}
3353 	mixin GenericMain!handler;
3354 }
3355 
3356 mixin template DispatcherMain(DispatcherArgs...) if(!is(DispatcherArgs[0] : WebPresenter!T, T)) {
3357 	class GenericPresenter : WebPresenter!GenericPresenter {}
3358 	mixin DispatcherMain!(GenericPresenter, DispatcherArgs);
3359 }
3360 
3361 private string simpleHtmlEncode(string s) {
3362 	return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />\n");
3363 }
3364 
3365 string messageFromException(Throwable t) {
3366 	string message;
3367 	if(t !is null) {
3368 		debug message = t.toString();
3369 		else  message = "An unexpected error has occurred.";
3370 	} else {
3371 		message = "Unknown error";
3372 	}
3373 	return message;
3374 }
3375 
3376 string plainHttpError(bool isCgi, string type, Throwable t) {
3377 	auto message = messageFromException(t);
3378 	message = simpleHtmlEncode(message);
3379 
3380 	return format("%s %s\r\nContent-Length: %s\r\n\r\n%s",
3381 		isCgi ? "Status:" : "HTTP/1.0",
3382 		type, message.length, message);
3383 }
3384 
3385 // returns true if we were able to recover reasonably
3386 bool handleException(Cgi cgi, Throwable t) {
3387 	if(cgi.isClosed) {
3388 		// if the channel has been explicitly closed, we can't handle it here
3389 		return true;
3390 	}
3391 
3392 	if(cgi.outputtedResponseData) {
3393 		// the headers are sent, but the channel is open... since it closes if all was sent, we can append an error message here.
3394 		return false; // but I don't want to, since I don't know what condition the output is in; I don't want to inject something (nor check the content-type for that matter. So we say it was not a clean handling.
3395 	} else {
3396 		// no headers are sent, we can send a full blown error and recover
3397 		cgi.setCache(false);
3398 		cgi.setResponseContentType("text/html");
3399 		cgi.setResponseLocation(null); // cancel the redirect
3400 		cgi.setResponseStatus("500 Internal Server Error");
3401 		cgi.write(simpleHtmlEncode(messageFromException(t)));
3402 		cgi.close();
3403 		return true;
3404 	}
3405 }
3406 
3407 bool isCgiRequestMethod(string s) {
3408 	s = s.toUpper();
3409 	if(s == "COMMANDLINE")
3410 		return true;
3411 	foreach(member; __traits(allMembers, Cgi.RequestMethod))
3412 		if(s == member)
3413 			return true;
3414 	return false;
3415 }
3416 
3417 /// If you want to use a subclass of Cgi with generic main, use this mixin.
3418 mixin template CustomCgiMain(CustomCgi, alias fun, long maxContentLength = defaultMaxContentLength) if(is(CustomCgi : Cgi)) {
3419 	// kinda hacky - the T... is passed to Cgi's constructor in standard cgi mode, and ignored elsewhere
3420 	void main(string[] args) {
3421 		cgiMainImpl!(fun, CustomCgi, maxContentLength)(args);
3422 	}
3423 }
3424 
3425 version(embedded_httpd_processes)
3426 	__gshared int processPoolSize = 8;
3427 
3428 // Returns true if run. You should exit the program after that.
3429 bool tryAddonServers(string[] args) {
3430 	if(args.length > 1) {
3431 		// run the special separate processes if needed
3432 		switch(args[1]) {
3433 			case "--websocket-server":
3434 				version(with_addon_servers)
3435 					websocketServers[args[2]](args[3 .. $]);
3436 				else
3437 					printf("Add-on servers not compiled in.\n");
3438 				return true;
3439 			case "--websocket-servers":
3440 				import core.demangle;
3441 				version(with_addon_servers_connections)
3442 				foreach(k, v; websocketServers)
3443 					writeln(k, "\t", demangle(k));
3444 				return true;
3445 			case "--session-server":
3446 				version(with_addon_servers)
3447 					runSessionServer();
3448 				else
3449 					printf("Add-on servers not compiled in.\n");
3450 				return true;
3451 			case "--event-server":
3452 				version(with_addon_servers)
3453 					runEventServer();
3454 				else
3455 					printf("Add-on servers not compiled in.\n");
3456 				return true;
3457 			case "--timer-server":
3458 				version(with_addon_servers)
3459 					runTimerServer();
3460 				else
3461 					printf("Add-on servers not compiled in.\n");
3462 				return true;
3463 			case "--timed-jobs":
3464 				import core.demangle;
3465 				version(with_addon_servers_connections)
3466 				foreach(k, v; scheduledJobHandlers)
3467 					writeln(k, "\t", demangle(k));
3468 				return true;
3469 			case "--timed-job":
3470 				scheduledJobHandlers[args[2]](args[3 .. $]);
3471 				return true;
3472 			default:
3473 				// intentionally blank - do nothing and carry on to run normally
3474 		}
3475 	}
3476 	return false;
3477 }
3478 
3479 /// Tries to simulate a request from the command line. Returns true if it does, false if it didn't find the args.
3480 bool trySimulatedRequest(alias fun, CustomCgi = Cgi)(string[] args) if(is(CustomCgi : Cgi)) {
3481 	// we support command line thing for easy testing everywhere
3482 	// it needs to be called ./app method uri [other args...]
3483 	if(args.length >= 3 && isCgiRequestMethod(args[1])) {
3484 		Cgi cgi = new CustomCgi(args);
3485 		scope(exit) cgi.dispose();
3486 		fun(cgi);
3487 		cgi.close();
3488 		return true;
3489 	}
3490 	return false;
3491 }
3492 
3493 /++
3494 	A server control and configuration struct, as a potential alternative to calling [GenericMain] or [cgiMainImpl]. See the source of [cgiMainImpl] to an example of how you can use it.
3495 
3496 	History:
3497 		Added Sept 26, 2020 (release version 8.5).
3498 +/
3499 struct RequestServer {
3500 	///
3501 	string listeningHost = defaultListeningHost();
3502 	///
3503 	ushort listeningPort = defaultListeningPort();
3504 
3505 	/++
3506 		Uses a fork() call, if available, to provide additional crash resiliency and possibly improved performance. On the
3507 		other hand, if you fork, you must not assume any memory is shared between requests (you shouldn't be anyway though! But
3508 		if you have to, you probably want to set this to false and use an explicit threaded server with [serveEmbeddedHttp]) and
3509 		[stop] may not work as well.
3510 
3511 		History:
3512 			Added August 12, 2022  (dub v10.9). Previously, this was only configurable through the `-version=cgi_no_fork`
3513 			argument to dmd. That version still defines the value of `cgi_use_fork_default`, used to initialize this, for
3514 			compatibility.
3515 	+/
3516 	bool useFork = cgi_use_fork_default;
3517 
3518 	/++
3519 		Determines the number of worker threads to spawn per process, for server modes that use worker threads. 0 will use a
3520 		default based on the number of cpus modified by the server mode.
3521 
3522 		History:
3523 			Added August 12, 2022 (dub v10.9)
3524 	+/
3525 	int numberOfThreads = 0;
3526 
3527 	///
3528 	this(string defaultHost, ushort defaultPort) {
3529 		this.listeningHost = defaultHost;
3530 		this.listeningPort = defaultPort;
3531 	}
3532 
3533 	///
3534 	this(ushort defaultPort) {
3535 		listeningPort = defaultPort;
3536 	}
3537 
3538 	/++
3539 		Reads the command line arguments into the values here.
3540 
3541 		Possible arguments are `--listening-host`, `--listening-port` (or `--port`), `--uid`, and `--gid`.
3542 	+/
3543 	void configureFromCommandLine(string[] args) {
3544 		bool foundPort = false;
3545 		bool foundHost = false;
3546 		bool foundUid = false;
3547 		bool foundGid = false;
3548 		foreach(arg; args) {
3549 			if(foundPort) {
3550 				listeningPort = to!ushort(arg);
3551 				foundPort = false;
3552 			}
3553 			if(foundHost) {
3554 				listeningHost = arg;
3555 				foundHost = false;
3556 			}
3557 			if(foundUid) {
3558 				privilegesDropToUid = to!uid_t(arg);
3559 				foundUid = false;
3560 			}
3561 			if(foundGid) {
3562 				privilegesDropToGid = to!gid_t(arg);
3563 				foundGid = false;
3564 			}
3565 			if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host")
3566 				foundHost = true;
3567 			else if(arg == "--port" || arg == "-p" || arg == "/port" || arg == "--listening-port")
3568 				foundPort = true;
3569 			else if(arg == "--uid")
3570 				foundUid = true;
3571 			else if(arg == "--gid")
3572 				foundGid = true;
3573 		}
3574 	}
3575 
3576 	version(Windows) {
3577 		private alias uid_t = int;
3578 		private alias gid_t = int;
3579 	}
3580 
3581 	/// user (uid) to drop privileges to
3582 	/// 0 … do nothing
3583 	uid_t privilegesDropToUid = 0;
3584 	/// group (gid) to drop privileges to
3585 	/// 0 … do nothing
3586 	gid_t privilegesDropToGid = 0;
3587 
3588 	private void dropPrivileges() {
3589 		version(Posix) {
3590 			import core.sys.posix.unistd;
3591 
3592 			if (privilegesDropToGid != 0 && setgid(privilegesDropToGid) != 0)
3593 				throw new Exception("Dropping privileges via setgid() failed.");
3594 
3595 			if (privilegesDropToUid != 0 && setuid(privilegesDropToUid) != 0)
3596 				throw new Exception("Dropping privileges via setuid() failed.");
3597 		}
3598 		else {
3599 			// FIXME: Windows?
3600 			//pragma(msg, "Dropping privileges is not implemented for this platform");
3601 		}
3602 
3603 		// done, set zero
3604 		privilegesDropToGid = 0;
3605 		privilegesDropToUid = 0;
3606 	}
3607 
3608 	/++
3609 		Serves a single HTTP request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders
3610 
3611 		History:
3612 			Added Oct 10, 2020.
3613 		Example:
3614 
3615 		---
3616 		import arsd.cgi;
3617 		void main() {
3618 			RequestServer server = RequestServer("127.0.0.1", 6789);
3619 			string oauthCode;
3620 			string oauthScope;
3621 			server.serveHttpOnce!((cgi) {
3622 				oauthCode = cgi.request("code");
3623 				oauthScope = cgi.request("scope");
3624 				cgi.write("Thank you, please return to the application.");
3625 			});
3626 			// use the code and scope given
3627 		}
3628 		---
3629 	+/
3630 	void serveHttpOnce(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
3631 		import std.socket;
3632 
3633 		bool tcp;
3634 		void delegate() cleanup;
3635 		auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1, &dropPrivileges);
3636 		auto connection = socket.accept();
3637 		doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection);
3638 
3639 		if(cleanup)
3640 			cleanup();
3641 	}
3642 
3643 	/++
3644 		Starts serving requests according to the current configuration.
3645 	+/
3646 	void serve(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
3647 		version(netman_httpd) {
3648 			// Obsolete!
3649 
3650 			import arsd.httpd;
3651 			// what about forwarding the other constructor args?
3652 			// this probably needs a whole redoing...
3653 			serveHttp!CustomCgi(&fun, listeningPort);//5005);
3654 			return;
3655 		} else
3656 		version(embedded_httpd_processes) {
3657 			serveEmbeddedHttpdProcesses!(fun, CustomCgi)(this);
3658 		} else
3659 		version(embedded_httpd_threads) {
3660 			serveEmbeddedHttp!(fun, CustomCgi, maxContentLength)();
3661 		} else
3662 		version(scgi) {
3663 			serveScgi!(fun, CustomCgi, maxContentLength)();
3664 		} else
3665 		version(fastcgi) {
3666 			serveFastCgi!(fun, CustomCgi, maxContentLength)(this);
3667 		} else
3668 		version(stdio_http) {
3669 			serveSingleHttpConnectionOnStdio!(fun, CustomCgi, maxContentLength)();
3670 		} else {
3671 			//version=plain_cgi;
3672 			handleCgiRequest!(fun, CustomCgi, maxContentLength)();
3673 		}
3674 	}
3675 
3676 	/++
3677 		Runs the embedded HTTP thread server specifically, regardless of which build configuration you have.
3678 
3679 		If you want the forking worker process server, you do need to compile with the embedded_httpd_processes config though.
3680 	+/
3681 	void serveEmbeddedHttp(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(ThisFor!fun _this) {
3682 		globalStopFlag = false;
3683 		static if(__traits(isStaticFunction, fun))
3684 			alias funToUse = fun;
3685 		else
3686 			void funToUse(CustomCgi cgi) {
3687 				static if(__VERSION__ > 2097)
3688 					__traits(child, _this, fun)(cgi);
3689 				else static assert(0, "Not implemented in your compiler version!");
3690 			}
3691 		auto manager = new ListeningConnectionManager(listeningHost, listeningPort, &doThreadHttpConnection!(CustomCgi, funToUse), null, useFork, numberOfThreads);
3692 		manager.listen();
3693 	}
3694 
3695 	/++
3696 		Runs the embedded SCGI server specifically, regardless of which build configuration you have.
3697 	+/
3698 	void serveScgi(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
3699 		globalStopFlag = false;
3700 		auto manager = new ListeningConnectionManager(listeningHost, listeningPort, &doThreadScgiConnection!(CustomCgi, fun, maxContentLength), null, useFork, numberOfThreads);
3701 		manager.listen();
3702 	}
3703 
3704 	/++
3705 		Serves a single "connection", but the connection is spoken on stdin and stdout instead of on a socket.
3706 
3707 		Intended for cases like working from systemd, like discussed here: https://forum.dlang.org/post/avmkfdiitirnrenzljwc@forum.dlang.org
3708 
3709 		History:
3710 			Added May 29, 2021
3711 	+/
3712 	void serveSingleHttpConnectionOnStdio(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
3713 		doThreadHttpConnectionGuts!(CustomCgi, fun, true)(new FakeSocketForStdin());
3714 	}
3715 
3716 	/++
3717 		The [stop] function sets a flag that request handlers can (and should) check periodically. If a handler doesn't
3718 		respond to this flag, the library will force the issue. This determines when and how the issue will be forced.
3719 	+/
3720 	enum ForceStop {
3721 		/++
3722 			Stops accepting new requests, but lets ones already in the queue start and complete before exiting.
3723 		+/
3724 		afterQueuedRequestsComplete,
3725 		/++
3726 			Finishes requests already started their handlers, but drops any others in the queue. Streaming handlers
3727 			should cooperate and exit gracefully, but if they don't, it will continue waiting for them.
3728 		+/
3729 		afterCurrentRequestsComplete,
3730 		/++
3731 			Partial response writes will throw an exception, cancelling any streaming response, but complete
3732 			writes will continue to process. Request handlers that respect the stop token will also gracefully cancel.
3733 		+/
3734 		cancelStreamingRequestsEarly,
3735 		/++
3736 			All writes will throw.
3737 		+/
3738 		cancelAllRequestsEarly,
3739 		/++
3740 			Use OS facilities to forcibly kill running threads. The server process will be in an undefined state after this call (if this call ever returns).
3741 		+/
3742 		forciblyTerminate,
3743 	}
3744 
3745 	version(embedded_httpd_processes) {} else
3746 	/++
3747 		Stops serving after the current requests are completed.
3748 
3749 		Bugs:
3750 			Not implemented on version=embedded_httpd_processes, version=fastcgi on any system, or embedded_httpd on Windows (it does work on embedded_httpd_hybrid
3751 			on Windows however). Only partially implemented on non-Linux posix systems.
3752 
3753 			You might also try SIGINT perhaps.
3754 
3755 			The stopPriority is not yet fully implemented.
3756 	+/
3757 	static void stop(ForceStop stopPriority = ForceStop.afterCurrentRequestsComplete) {
3758 		globalStopFlag = true;
3759 
3760 		version(Posix)
3761 		if(cancelfd > 0) {
3762 			ulong a = 1;
3763 			core.sys.posix.unistd.write(cancelfd, &a, a.sizeof);
3764 		}
3765 		version(Windows)
3766 		if(iocp) {
3767 			foreach(i; 0 .. 16) // FIXME
3768 			PostQueuedCompletionStatus(iocp, 0, cast(ULONG_PTR) null, null);
3769 		}
3770 	}
3771 }
3772 
3773 private alias AliasSeq(T...) = T;
3774 
3775 version(with_breaking_cgi_features)
3776 mixin(q{
3777 	template ThisFor(alias t) {
3778 		static if(__traits(isStaticFunction, t)) {
3779 			alias ThisFor = AliasSeq!();
3780 		} else {
3781 			alias ThisFor = __traits(parent, t);
3782 		}
3783 	}
3784 });
3785 else
3786 	alias ThisFor(alias t) = AliasSeq!();
3787 
3788 private __gshared bool globalStopFlag = false;
3789 
3790 version(embedded_httpd_processes)
3791 void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer params) {
3792 	import core.sys.posix.unistd;
3793 	import core.sys.posix.sys.socket;
3794 	import core.sys.posix.netinet.in_;
3795 	//import std.c.linux.socket;
3796 
3797 	int sock = socket(AF_INET, SOCK_STREAM, 0);
3798 	if(sock == -1)
3799 		throw new Exception("socket");
3800 
3801 	cloexec(sock);
3802 
3803 	{
3804 
3805 		sockaddr_in addr;
3806 		addr.sin_family = AF_INET;
3807 		addr.sin_port = htons(params.listeningPort);
3808 		auto lh = params.listeningHost;
3809 		if(lh.length) {
3810 			if(inet_pton(AF_INET, lh.toStringz(), &addr.sin_addr.s_addr) != 1)
3811 				throw new Exception("bad listening host given, please use an IP address.\nExample: --listening-host 127.0.0.1 means listen only on Localhost.\nExample: --listening-host 0.0.0.0 means listen on all interfaces.\nOr you can pass any other single numeric IPv4 address.");
3812 		} else
3813 			addr.sin_addr.s_addr = INADDR_ANY;
3814 
3815 		// HACKISH
3816 		int on = 1;
3817 		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, on.sizeof);
3818 		// end hack
3819 
3820 
3821 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
3822 			close(sock);
3823 			throw new Exception("bind");
3824 		}
3825 
3826 		// FIXME: if this queue is full, it will just ignore it
3827 		// and wait for the client to retransmit it. This is an
3828 		// obnoxious timeout condition there.
3829 		if(sock.listen(128) == -1) {
3830 			close(sock);
3831 			throw new Exception("listen");
3832 		}
3833 		params.dropPrivileges();
3834 	}
3835 
3836 	version(embedded_httpd_processes_accept_after_fork) {} else {
3837 		int pipeReadFd;
3838 		int pipeWriteFd;
3839 
3840 		{
3841 			int[2] pipeFd;
3842 			if(socketpair(AF_UNIX, SOCK_DGRAM, 0, pipeFd)) {
3843 				import core.stdc.errno;
3844 				throw new Exception("pipe failed " ~ to!string(errno));
3845 			}
3846 
3847 			pipeReadFd = pipeFd[0];
3848 			pipeWriteFd = pipeFd[1];
3849 		}
3850 	}
3851 
3852 
3853 	int processCount;
3854 	pid_t newPid;
3855 	reopen:
3856 	while(processCount < processPoolSize) {
3857 		newPid = fork();
3858 		if(newPid == 0) {
3859 			// start serving on the socket
3860 			//ubyte[4096] backingBuffer;
3861 			for(;;) {
3862 				bool closeConnection;
3863 				uint i;
3864 				sockaddr addr;
3865 				i = addr.sizeof;
3866 				version(embedded_httpd_processes_accept_after_fork) {
3867 					int s = accept(sock, &addr, &i);
3868 					int opt = 1;
3869 					import core.sys.posix.netinet.tcp;
3870 					// the Cgi class does internal buffering, so disabling this
3871 					// helps with latency in many cases...
3872 					setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
3873 					cloexec(s);
3874 				} else {
3875 					int s;
3876 					auto readret = read_fd(pipeReadFd, &s, s.sizeof, &s);
3877 					if(readret != s.sizeof) {
3878 						import core.stdc.errno;
3879 						throw new Exception("pipe read failed " ~ to!string(errno));
3880 					}
3881 
3882 					//writeln("process ", getpid(), " got socket ", s);
3883 				}
3884 
3885 				try {
3886 
3887 					if(s == -1)
3888 						throw new Exception("accept");
3889 
3890 					scope(failure) close(s);
3891 					//ubyte[__traits(classInstanceSize, BufferedInputRange)] bufferedRangeContainer;
3892 					auto ir = new BufferedInputRange(s);
3893 					//auto ir = emplace!BufferedInputRange(bufferedRangeContainer, s, backingBuffer);
3894 
3895 					while(!ir.empty) {
3896 						//ubyte[__traits(classInstanceSize, CustomCgi)] cgiContainer;
3897 
3898 						Cgi cgi;
3899 						try {
3900 							cgi = new CustomCgi(ir, &closeConnection);
3901 							cgi._outputFileHandle = cast(CgiConnectionHandle) s;
3902 							// if we have a single process and the browser tries to leave the connection open while concurrently requesting another, it will block everything an deadlock since there's no other server to accept it. By closing after each request in this situation, it tells the browser to serialize for us.
3903 							if(processPoolSize <= 1)
3904 								closeConnection = true;
3905 							//cgi = emplace!CustomCgi(cgiContainer, ir, &closeConnection);
3906 						} catch(Throwable t) {
3907 							// a construction error is either bad code or bad request; bad request is what it should be since this is bug free :P
3908 							// anyway let's kill the connection
3909 							version(CRuntime_Musl) {
3910 								// LockingTextWriter fails here
3911 								// so working around it
3912 								auto estr = t.toString();
3913 								stderr.rawWrite(estr);
3914 								stderr.rawWrite("\n");
3915 							} else
3916 								stderr.writeln(t.toString());
3917 							sendAll(ir.source, plainHttpError(false, "400 Bad Request", t));
3918 							closeConnection = true;
3919 							break;
3920 						}
3921 						assert(cgi !is null);
3922 						scope(exit)
3923 							cgi.dispose();
3924 
3925 						try {
3926 							fun(cgi);
3927 							cgi.close();
3928 							if(cgi.websocketMode)
3929 								closeConnection = true;
3930 						} catch(ConnectionException ce) {
3931 							closeConnection = true;
3932 						} catch(Throwable t) {
3933 							// a processing error can be recovered from
3934 							version(CRuntime_Musl) {
3935 								// LockingTextWriter fails here
3936 								// so working around it
3937 								auto estr = t.toString();
3938 								stderr.rawWrite(estr);
3939 							} else {
3940 								stderr.writeln(t.toString);
3941 							}
3942 							if(!handleException(cgi, t))
3943 								closeConnection = true;
3944 						}
3945 
3946 						if(closeConnection) {
3947 							ir.source.close();
3948 							break;
3949 						} else {
3950 							if(!ir.empty)
3951 								ir.popFront(); // get the next
3952 							else if(ir.sourceClosed) {
3953 								ir.source.close();
3954 							}
3955 						}
3956 					}
3957 
3958 					ir.source.close();
3959 				} catch(Throwable t) {
3960 					version(CRuntime_Musl) {} else
3961 						debug writeln(t);
3962 					// most likely cause is a timeout
3963 				}
3964 			}
3965 		} else if(newPid < 0) {
3966 			throw new Exception("fork failed");
3967 		} else {
3968 			processCount++;
3969 		}
3970 	}
3971 
3972 	// the parent should wait for its children...
3973 	if(newPid) {
3974 		import core.sys.posix.sys.wait;
3975 
3976 		version(embedded_httpd_processes_accept_after_fork) {} else {
3977 			import core.sys.posix.sys.select;
3978 			int[] fdQueue;
3979 			while(true) {
3980 				// writeln("select call");
3981 				int nfds = pipeWriteFd;
3982 				if(sock > pipeWriteFd)
3983 					nfds = sock;
3984 				nfds += 1;
3985 				fd_set read_fds;
3986 				fd_set write_fds;
3987 				FD_ZERO(&read_fds);
3988 				FD_ZERO(&write_fds);
3989 				FD_SET(sock, &read_fds);
3990 				if(fdQueue.length)
3991 					FD_SET(pipeWriteFd, &write_fds);
3992 				auto ret = select(nfds, &read_fds, &write_fds, null, null);
3993 				if(ret == -1) {
3994 					import core.stdc.errno;
3995 					if(errno == EINTR)
3996 						goto try_wait;
3997 					else
3998 						throw new Exception("wtf select");
3999 				}
4000 
4001 				int s = -1;
4002 				if(FD_ISSET(sock, &read_fds)) {
4003 					uint i;
4004 					sockaddr addr;
4005 					i = addr.sizeof;
4006 					s = accept(sock, &addr, &i);
4007 					cloexec(s);
4008 					import core.sys.posix.netinet.tcp;
4009 					int opt = 1;
4010 					setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
4011 				}
4012 
4013 				if(FD_ISSET(pipeWriteFd, &write_fds)) {
4014 					if(s == -1 && fdQueue.length) {
4015 						s = fdQueue[0];
4016 						fdQueue = fdQueue[1 .. $]; // FIXME reuse buffer
4017 					}
4018 					write_fd(pipeWriteFd, &s, s.sizeof, s);
4019 					close(s); // we are done with it, let the other process take ownership
4020 				} else
4021 					fdQueue ~= s;
4022 			}
4023 		}
4024 
4025 		try_wait:
4026 
4027 		int status;
4028 		while(-1 != wait(&status)) {
4029 			version(CRuntime_Musl) {} else {
4030 				import std.stdio; writeln("Process died ", status);
4031 			}
4032 			processCount--;
4033 			goto reopen;
4034 		}
4035 		close(sock);
4036 	}
4037 }
4038 
4039 version(fastcgi)
4040 void serveFastCgi(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(RequestServer params) {
4041 	//         SetHandler fcgid-script
4042 	FCGX_Stream* input, output, error;
4043 	FCGX_ParamArray env;
4044 
4045 
4046 
4047 	const(ubyte)[] getFcgiChunk() {
4048 		const(ubyte)[] ret;
4049 		while(FCGX_HasSeenEOF(input) != -1)
4050 			ret ~= cast(ubyte) FCGX_GetChar(input);
4051 		return ret;
4052 	}
4053 
4054 	void writeFcgi(const(ubyte)[] data) {
4055 		FCGX_PutStr(data.ptr, data.length, output);
4056 	}
4057 
4058 	void doARequest() {
4059 		string[string] fcgienv;
4060 
4061 		for(auto e = env; e !is null && *e !is null; e++) {
4062 			string cur = to!string(*e);
4063 			auto idx = cur.indexOf("=");
4064 			string name, value;
4065 			if(idx == -1)
4066 				name = cur;
4067 			else {
4068 				name = cur[0 .. idx];
4069 				value = cur[idx + 1 .. $];
4070 			}
4071 
4072 			fcgienv[name] = value;
4073 		}
4074 
4075 		void flushFcgi() {
4076 			FCGX_FFlush(output);
4077 		}
4078 
4079 		Cgi cgi;
4080 		try {
4081 			cgi = new CustomCgi(maxContentLength, fcgienv, &getFcgiChunk, &writeFcgi, &flushFcgi);
4082 		} catch(Throwable t) {
4083 			FCGX_PutStr(cast(ubyte*) t.msg.ptr, t.msg.length, error);
4084 			writeFcgi(cast(const(ubyte)[]) plainHttpError(true, "400 Bad Request", t));
4085 			return; //continue;
4086 		}
4087 		assert(cgi !is null);
4088 		scope(exit) cgi.dispose();
4089 		try {
4090 			fun(cgi);
4091 			cgi.close();
4092 		} catch(Throwable t) {
4093 			// log it to the error stream
4094 			FCGX_PutStr(cast(ubyte*) t.msg.ptr, t.msg.length, error);
4095 			// handle it for the user, if we can
4096 			if(!handleException(cgi, t))
4097 				return; // continue;
4098 		}
4099 	}
4100 
4101 	auto lp = params.listeningPort;
4102 	auto host = params.listeningHost;
4103 
4104 	FCGX_Request request;
4105 	if(lp || !host.empty) {
4106 		// if a listening port was specified on the command line, we want to spawn ourself
4107 		// (needed for nginx without spawn-fcgi, e.g. on Windows)
4108 		FCGX_Init();
4109 
4110 		int sock;
4111 
4112 		if(host.startsWith("unix:")) {
4113 			sock = FCGX_OpenSocket(toStringz(params.listeningHost["unix:".length .. $]), 12);
4114 		} else if(host.startsWith("abstract:")) {
4115 			sock = FCGX_OpenSocket(toStringz("\0" ~ params.listeningHost["abstract:".length .. $]), 12);
4116 		} else {
4117 			sock = FCGX_OpenSocket(toStringz(params.listeningHost ~ ":" ~ to!string(lp)), 12);
4118 		}
4119 
4120 		if(sock < 0)
4121 			throw new Exception("Couldn't listen on the port");
4122 		FCGX_InitRequest(&request, sock, 0);
4123 		while(FCGX_Accept_r(&request) >= 0) {
4124 			input = request.inStream;
4125 			output = request.outStream;
4126 			error = request.errStream;
4127 			env = request.envp;
4128 			doARequest();
4129 		}
4130 	} else {
4131 		// otherwise, assume the httpd is doing it (the case for Apache, IIS, and Lighttpd)
4132 		// using the version with a global variable since we are separate processes anyway
4133 		while(FCGX_Accept(&input, &output, &error, &env) >= 0) {
4134 			doARequest();
4135 		}
4136 	}
4137 }
4138 
4139 /// Returns the default listening port for the current cgi configuration. 8085 for embedded httpd, 4000 for scgi, irrelevant for others.
4140 ushort defaultListeningPort() {
4141 	version(netman_httpd)
4142 		return 8080;
4143 	else version(embedded_httpd_processes)
4144 		return 8085;
4145 	else version(embedded_httpd_threads)
4146 		return 8085;
4147 	else version(scgi)
4148 		return 4000;
4149 	else
4150 		return 0;
4151 }
4152 
4153 /// Default host for listening. 127.0.0.1 for scgi, null (aka all interfaces) for all others. If you want the server directly accessible from other computers on the network, normally use null. If not, 127.0.0.1 is a bit better. Settable with default handlers with --listening-host command line argument.
4154 string defaultListeningHost() {
4155 	version(netman_httpd)
4156 		return null;
4157 	else version(embedded_httpd_processes)
4158 		return null;
4159 	else version(embedded_httpd_threads)
4160 		return null;
4161 	else version(scgi)
4162 		return "127.0.0.1";
4163 	else
4164 		return null;
4165 
4166 }
4167 
4168 /++
4169 	This is the function [GenericMain] calls. View its source for some simple boilerplate you can copy/paste and modify, or you can call it yourself from your `main`.
4170 
4171 	Params:
4172 		fun = Your request handler
4173 		CustomCgi = a subclass of Cgi, if you wise to customize it further
4174 		maxContentLength = max POST size you want to allow
4175 		args = command-line arguments
4176 
4177 	History:
4178 	Documented Sept 26, 2020.
4179 +/
4180 void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(string[] args) if(is(CustomCgi : Cgi)) {
4181 	if(tryAddonServers(args))
4182 		return;
4183 
4184 	if(trySimulatedRequest!(fun, CustomCgi)(args))
4185 		return;
4186 
4187 	RequestServer server;
4188 	// you can change the port here if you like
4189 	// server.listeningPort = 9000;
4190 
4191 	// then call this to let the command line args override your default
4192 	server.configureFromCommandLine(args);
4193 
4194 	// and serve the request(s).
4195 	server.serve!(fun, CustomCgi, maxContentLength)();
4196 }
4197 
4198 //version(plain_cgi)
4199 void handleCgiRequest(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4200 	// standard CGI is the default version
4201 
4202 
4203 	// Set stdin to binary mode if necessary to avoid mangled newlines
4204 	// the fact that stdin is global means this could be trouble but standard cgi request
4205 	// handling is one per process anyway so it shouldn't actually be threaded here or anything.
4206 	version(Windows) {
4207 		version(Win64)
4208 		_setmode(std.stdio.stdin.fileno(), 0x8000);
4209 		else
4210 		setmode(std.stdio.stdin.fileno(), 0x8000);
4211 	}
4212 
4213 	Cgi cgi;
4214 	try {
4215 		cgi = new CustomCgi(maxContentLength);
4216 		version(Posix)
4217 			cgi._outputFileHandle = cast(CgiConnectionHandle) 1; // stdout
4218 		else version(Windows)
4219 			cgi._outputFileHandle = cast(CgiConnectionHandle) GetStdHandle(STD_OUTPUT_HANDLE);
4220 		else static assert(0);
4221 	} catch(Throwable t) {
4222 		version(CRuntime_Musl) {
4223 			// LockingTextWriter fails here
4224 			// so working around it
4225 			auto s = t.toString();
4226 			stderr.rawWrite(s);
4227 			stdout.rawWrite(plainHttpError(true, "400 Bad Request", t));
4228 		} else {
4229 			stderr.writeln(t.msg);
4230 			// the real http server will probably handle this;
4231 			// most likely, this is a bug in Cgi. But, oh well.
4232 			stdout.write(plainHttpError(true, "400 Bad Request", t));
4233 		}
4234 		return;
4235 	}
4236 	assert(cgi !is null);
4237 	scope(exit) cgi.dispose();
4238 
4239 	try {
4240 		fun(cgi);
4241 		cgi.close();
4242 	} catch (Throwable t) {
4243 		version(CRuntime_Musl) {
4244 			// LockingTextWriter fails here
4245 			// so working around it
4246 			auto s = t.msg;
4247 			stderr.rawWrite(s);
4248 		} else {
4249 			stderr.writeln(t.msg);
4250 		}
4251 		if(!handleException(cgi, t))
4252 			return;
4253 	}
4254 }
4255 
4256 private __gshared int cancelfd = -1;
4257 
4258 /+
4259 	The event loop for embedded_httpd_threads will prolly fiber dispatch
4260 	cgi constructors too, so slow posts will not monopolize a worker thread.
4261 
4262 	May want to provide the worker task system just need to ensure all the fibers
4263 	has a big enough stack for real work... would also ideally like to reuse them.
4264 
4265 
4266 	So prolly bir would switch it to nonblocking. If it would block, it epoll
4267 	registers one shot with this existing fiber to take it over.
4268 
4269 		new connection comes in. it picks a fiber off the free list,
4270 		or if there is none, it creates a new one. this fiber handles
4271 		this connection the whole time.
4272 
4273 		epoll triggers the fiber when something comes in. it is called by
4274 		a random worker thread, it might change at any time. at least during
4275 		the constructor. maybe into the main body it will stay tied to a thread
4276 		just so TLS stuff doesn't randomly change in the middle. but I could
4277 		specify if you yield all bets are off.
4278 
4279 		when the request is finished, if there's more data buffered, it just
4280 		keeps going. if there is no more data buffered, it epoll ctls to
4281 		get triggered when more data comes in. all one shot.
4282 
4283 		when a connection is closed, the fiber returns and is then reset
4284 		and added to the free list. if the free list is full, the fiber is
4285 		just freed, this means it will balloon to a certain size but not generally
4286 		grow beyond that unless the activity keeps going.
4287 
4288 		256 KB stack i thnk per fiber. 4,000 active fibers per gigabyte of memory.
4289 
4290 	So the fiber has its own magic methods to read and write. if they would block, it registers
4291 	for epoll and yields. when it returns, it read/writes and then returns back normal control.
4292 
4293 	basically you issue the command and it tells you when it is done
4294 
4295 	it needs to DEL the epoll thing when it is closed. add it when opened. mod it when anther thing issued
4296 
4297 +/
4298 
4299 /++
4300 	The stack size when a fiber is created. You can set this from your main or from a shared static constructor
4301 	to optimize your memory use if you know you don't need this much space. Be careful though, some functions use
4302 	more stack space than you realize and a recursive function (including ones like in dom.d) can easily grow fast!
4303 
4304 	History:
4305 		Added July 10, 2021. Previously, it used the druntime default of 16 KB.
4306 +/
4307 version(cgi_use_fiber)
4308 __gshared size_t fiberStackSize = 4096 * 100;
4309 
4310 version(cgi_use_fiber)
4311 class CgiFiber : Fiber {
4312 	private void function(Socket) f_handler;
4313 	private void f_handler_dg(Socket s) { // to avoid extra allocation w/ function
4314 		f_handler(s);
4315 	}
4316 	this(void function(Socket) handler) {
4317 		this.f_handler = handler;
4318 		this(&f_handler_dg);
4319 	}
4320 
4321 	this(void delegate(Socket) handler) {
4322 		this.handler = handler;
4323 		super(&run, fiberStackSize);
4324 	}
4325 
4326 	Socket connection;
4327 	void delegate(Socket) handler;
4328 
4329 	void run() {
4330 		handler(connection);
4331 	}
4332 
4333 	void delegate() postYield;
4334 
4335 	private void setPostYield(scope void delegate() py) @nogc {
4336 		postYield = cast(void delegate()) py;
4337 	}
4338 
4339 	void proceed() {
4340 		try {
4341 			call();
4342 			auto py = postYield;
4343 			postYield = null;
4344 			if(py !is null)
4345 				py();
4346 		} catch(Exception e) {
4347 			if(connection)
4348 				connection.close();
4349 			goto terminate;
4350 		}
4351 
4352 		if(state == State.TERM) {
4353 			terminate:
4354 			import core.memory;
4355 			GC.removeRoot(cast(void*) this);
4356 		}
4357 	}
4358 }
4359 
4360 version(cgi_use_fiber)
4361 version(Windows) {
4362 
4363 extern(Windows) private {
4364 
4365 	import core.sys.windows.mswsock;
4366 
4367 	alias GROUP=uint;
4368 	alias LPWSAPROTOCOL_INFOW = void*;
4369 	SOCKET WSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo, GROUP g, DWORD dwFlags);
4370 	int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
4371 	int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
4372 
4373 	struct WSABUF {
4374 		ULONG len;
4375 		CHAR  *buf;
4376 	}
4377 	alias LPWSABUF = WSABUF*;
4378 
4379 	alias WSAOVERLAPPED = OVERLAPPED;
4380 	alias LPWSAOVERLAPPED = LPOVERLAPPED;
4381 	/+
4382 
4383 	alias LPFN_ACCEPTEX =
4384 		BOOL
4385 		function(
4386 				SOCKET sListenSocket,
4387 				SOCKET sAcceptSocket,
4388 				//_Out_writes_bytes_(dwReceiveDataLength+dwLocalAddressLength+dwRemoteAddressLength) PVOID lpOutputBuffer,
4389 				void* lpOutputBuffer,
4390 				WORD dwReceiveDataLength,
4391 				WORD dwLocalAddressLength,
4392 				WORD dwRemoteAddressLength,
4393 				LPDWORD lpdwBytesReceived,
4394 				LPOVERLAPPED lpOverlapped
4395 			);
4396 
4397 	enum WSAID_ACCEPTEX = GUID([0xb5367df1,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]]);
4398 	+/
4399 
4400 	enum WSAID_GETACCEPTEXSOCKADDRS = GUID(0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]);
4401 }
4402 
4403 private class PseudoblockingOverlappedSocket : Socket {
4404 	SOCKET handle;
4405 
4406 	CgiFiber fiber;
4407 
4408 	this(AddressFamily af, SocketType st) {
4409 		auto handle = WSASocketW(af, st, 0, null, 0, 1 /*WSA_FLAG_OVERLAPPED*/);
4410 		if(!handle)
4411 			throw new Exception("WSASocketW");
4412 		this.handle = handle;
4413 
4414 		iocp = CreateIoCompletionPort(cast(HANDLE) handle, iocp, cast(ULONG_PTR) cast(void*) this, 0);
4415 
4416 		if(iocp is null) {
4417 			writeln(GetLastError());
4418 			throw new Exception("CreateIoCompletionPort");
4419 		}
4420 
4421 		super(cast(socket_t) handle, af);
4422 	}
4423 	this() pure nothrow @trusted { assert(0); }
4424 
4425 	override void blocking(bool) {} // meaningless to us, just ignore it.
4426 
4427 	protected override Socket accepting() pure nothrow {
4428 		assert(0);
4429 	}
4430 
4431 	bool addressesParsed;
4432 	Address la;
4433 	Address ra;
4434 
4435 	private void populateAddresses() {
4436 		if(addressesParsed)
4437 			return;
4438 		addressesParsed = true;
4439 
4440 		int lalen, ralen;
4441 
4442 		sockaddr_in* la;
4443 		sockaddr_in* ra;
4444 
4445 		lpfnGetAcceptExSockaddrs(
4446 			scratchBuffer.ptr,
4447 			0, // same as in the AcceptEx call!
4448 			sockaddr_in.sizeof + 16,
4449 			sockaddr_in.sizeof + 16,
4450 			cast(sockaddr**) &la,
4451 			&lalen,
4452 			cast(sockaddr**) &ra,
4453 			&ralen
4454 		);
4455 
4456 		if(la)
4457 			this.la = new InternetAddress(*la);
4458 		if(ra)
4459 			this.ra = new InternetAddress(*ra);
4460 
4461 	}
4462 
4463 	override @property @trusted Address localAddress() {
4464 		populateAddresses();
4465 		return la;
4466 	}
4467 	override @property @trusted Address remoteAddress() {
4468 		populateAddresses();
4469 		return ra;
4470 	}
4471 
4472 	PseudoblockingOverlappedSocket accepted;
4473 
4474 	__gshared static LPFN_ACCEPTEX lpfnAcceptEx;
4475 	__gshared static typeof(&GetAcceptExSockaddrs) lpfnGetAcceptExSockaddrs;
4476 
4477 	override Socket accept() @trusted {
4478 		__gshared static LPFN_ACCEPTEX lpfnAcceptEx;
4479 
4480 		if(lpfnAcceptEx is null) {
4481 			DWORD dwBytes;
4482 			GUID GuidAcceptEx = WSAID_ACCEPTEX;
4483 
4484 			auto iResult = WSAIoctl(handle, 0xc8000006 /*SIO_GET_EXTENSION_FUNCTION_POINTER*/,
4485 					&GuidAcceptEx, GuidAcceptEx.sizeof,
4486 					&lpfnAcceptEx, lpfnAcceptEx.sizeof,
4487 					&dwBytes, null, null);
4488 
4489 			GuidAcceptEx = WSAID_GETACCEPTEXSOCKADDRS;
4490 			iResult = WSAIoctl(handle, 0xc8000006 /*SIO_GET_EXTENSION_FUNCTION_POINTER*/,
4491 					&GuidAcceptEx, GuidAcceptEx.sizeof,
4492 					&lpfnGetAcceptExSockaddrs, lpfnGetAcceptExSockaddrs.sizeof,
4493 					&dwBytes, null, null);
4494 
4495 		}
4496 
4497 		auto pfa = new PseudoblockingOverlappedSocket(AddressFamily.INET, SocketType.STREAM);
4498 		accepted = pfa;
4499 
4500 		SOCKET pendingForAccept = pfa.handle;
4501 		DWORD ignored;
4502 
4503 		auto ret = lpfnAcceptEx(handle,
4504 			pendingForAccept,
4505 			// buffer to receive up front
4506 			pfa.scratchBuffer.ptr,
4507 			0,
4508 			// size of local and remote addresses. normally + 16.
4509 			sockaddr_in.sizeof + 16,
4510 			sockaddr_in.sizeof + 16,
4511 			&ignored, // bytes would be given through the iocp instead but im not even requesting the thing
4512 			&overlapped
4513 		);
4514 
4515 		return pfa;
4516 	}
4517 
4518 	override void connect(Address to) { assert(0); }
4519 
4520 	DWORD lastAnswer;
4521 	ubyte[1024] scratchBuffer;
4522 	static assert(scratchBuffer.length > sockaddr_in.sizeof * 2 + 32);
4523 
4524 	WSABUF[1] buffer;
4525 	OVERLAPPED overlapped;
4526 	override ptrdiff_t send(scope const(void)[] buf, SocketFlags flags) @trusted {
4527 		overlapped = overlapped.init;
4528 		buffer[0].len = cast(DWORD) buf.length;
4529 		buffer[0].buf = cast(CHAR*) buf.ptr;
4530 		fiber.setPostYield( () {
4531 			if(!WSASend(handle, buffer.ptr, cast(DWORD) buffer.length, null, 0, &overlapped, null)) {
4532 				if(GetLastError() != 997) {
4533 					//throw new Exception("WSASend fail");
4534 				}
4535 			}
4536 		});
4537 
4538 		Fiber.yield();
4539 		return lastAnswer;
4540 	}
4541 	override ptrdiff_t receive(scope void[] buf, SocketFlags flags) @trusted {
4542 		overlapped = overlapped.init;
4543 		buffer[0].len = cast(DWORD) buf.length;
4544 		buffer[0].buf = cast(CHAR*) buf.ptr;
4545 
4546 		DWORD flags2 = 0;
4547 
4548 		fiber.setPostYield(() {
4549 			if(!WSARecv(handle, buffer.ptr, cast(DWORD) buffer.length, null, &flags2 /* flags */, &overlapped, null)) {
4550 				if(GetLastError() != 997) {
4551 					//writeln("WSARecv ", WSAGetLastError());
4552 					//throw new Exception("WSARecv fail");
4553 				}
4554 			}
4555 		});
4556 
4557 		Fiber.yield();
4558 		return lastAnswer;
4559 	}
4560 
4561 	// I might go back and implement these for udp things.
4562 	override ptrdiff_t receiveFrom(scope void[] buf, SocketFlags flags, ref Address from) @trusted {
4563 		assert(0);
4564 	}
4565 	override ptrdiff_t receiveFrom(scope void[] buf, SocketFlags flags) @trusted {
4566 		assert(0);
4567 	}
4568 	override ptrdiff_t sendTo(scope const(void)[] buf, SocketFlags flags, Address to) @trusted {
4569 		assert(0);
4570 	}
4571 	override ptrdiff_t sendTo(scope const(void)[] buf, SocketFlags flags) @trusted {
4572 		assert(0);
4573 	}
4574 
4575 	// lol overload sets
4576 	alias send = typeof(super).send;
4577 	alias receive = typeof(super).receive;
4578 	alias sendTo = typeof(super).sendTo;
4579 	alias receiveFrom = typeof(super).receiveFrom;
4580 
4581 }
4582 }
4583 
4584 void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
4585 	assert(connection !is null);
4586 	version(cgi_use_fiber) {
4587 		auto fiber = new CgiFiber(&doThreadHttpConnectionGuts!(CustomCgi, fun));
4588 
4589 		version(Windows) {
4590 			(cast(PseudoblockingOverlappedSocket) connection).fiber = fiber;
4591 		}
4592 
4593 		import core.memory;
4594 		GC.addRoot(cast(void*) fiber);
4595 		fiber.connection = connection;
4596 		fiber.proceed();
4597 	} else {
4598 		doThreadHttpConnectionGuts!(CustomCgi, fun)(connection);
4599 	}
4600 }
4601 
4602 void doThreadHttpConnectionGuts(CustomCgi, alias fun, bool alwaysCloseConnection = false)(Socket connection) {
4603 	scope(failure) {
4604 		// catch all for other errors
4605 		try {
4606 			sendAll(connection, plainHttpError(false, "500 Internal Server Error", null));
4607 			connection.close();
4608 		} catch(Exception e) {} // swallow it, we're aborting anyway.
4609 	}
4610 
4611 	bool closeConnection = alwaysCloseConnection;
4612 
4613 	/+
4614 	ubyte[4096] inputBuffer = void;
4615 	ubyte[__traits(classInstanceSize, BufferedInputRange)] birBuffer = void;
4616 	ubyte[__traits(classInstanceSize, CustomCgi)] cgiBuffer = void;
4617 
4618 	birBuffer[] = cast(ubyte[]) typeid(BufferedInputRange).initializer()[];
4619 	BufferedInputRange ir = cast(BufferedInputRange) cast(void*) birBuffer.ptr;
4620 	ir.__ctor(connection, inputBuffer[], true);
4621 	+/
4622 
4623 	auto ir = new BufferedInputRange(connection);
4624 
4625 	while(!ir.empty) {
4626 
4627 		if(ir.view.length == 0) {
4628 			ir.popFront();
4629 			if(ir.sourceClosed) {
4630 				connection.close();
4631 				closeConnection = true;
4632 				break;
4633 			}
4634 		}
4635 
4636 		Cgi cgi;
4637 		try {
4638 			cgi = new CustomCgi(ir, &closeConnection);
4639 			// There's a bunch of these casts around because the type matches up with
4640 			// the -version=.... specifiers, just you can also create a RequestServer
4641 			// and instantiate the things where the types don't match up. It isn't exactly
4642 			// correct but I also don't care rn. Might FIXME and either remove it later or something.
4643 			cgi._outputFileHandle = cast(CgiConnectionHandle) connection.handle;
4644 		} catch(ConnectionClosedException ce) {
4645 			closeConnection = true;
4646 			break;
4647 		} catch(ConnectionException ce) {
4648 			// broken pipe or something, just abort the connection
4649 			closeConnection = true;
4650 			break;
4651 		} catch(Throwable t) {
4652 			// a construction error is either bad code or bad request; bad request is what it should be since this is bug free :P
4653 			// anyway let's kill the connection
4654 			version(CRuntime_Musl) {
4655 				stderr.rawWrite(t.toString());
4656 				stderr.rawWrite("\n");
4657 			} else {
4658 				stderr.writeln(t.toString());
4659 			}
4660 			sendAll(connection, plainHttpError(false, "400 Bad Request", t));
4661 			closeConnection = true;
4662 			break;
4663 		}
4664 		assert(cgi !is null);
4665 		scope(exit)
4666 			cgi.dispose();
4667 
4668 		try {
4669 			fun(cgi);
4670 			cgi.close();
4671 			if(cgi.websocketMode)
4672 				closeConnection = true;
4673 		} catch(ConnectionException ce) {
4674 			// broken pipe or something, just abort the connection
4675 			closeConnection = true;
4676 		} catch(ConnectionClosedException ce) {
4677 			// broken pipe or something, just abort the connection
4678 			closeConnection = true;
4679 		} catch(Throwable t) {
4680 			// a processing error can be recovered from
4681 			version(CRuntime_Musl) {} else
4682 			stderr.writeln(t.toString);
4683 			if(!handleException(cgi, t))
4684 				closeConnection = true;
4685 		}
4686 
4687 		if(globalStopFlag)
4688 			closeConnection = true;
4689 
4690 		if(closeConnection || alwaysCloseConnection) {
4691 			connection.shutdown(SocketShutdown.BOTH);
4692 			connection.close();
4693 			ir.dispose();
4694 			closeConnection = false; // don't reclose after loop
4695 			break;
4696 		} else {
4697 			if(ir.front.length) {
4698 				ir.popFront(); // we can't just discard the buffer, so get the next bit and keep chugging along
4699 			} else if(ir.sourceClosed) {
4700 				ir.source.shutdown(SocketShutdown.BOTH);
4701 				ir.source.close();
4702 				ir.dispose();
4703 				closeConnection = false;
4704 			} else {
4705 				continue;
4706 				// break; // this was for a keepalive experiment
4707 			}
4708 		}
4709 	}
4710 
4711 	if(closeConnection) {
4712 		connection.shutdown(SocketShutdown.BOTH);
4713 		connection.close();
4714 		ir.dispose();
4715 	}
4716 
4717 	// I am otherwise NOT closing it here because the parent thread might still be able to make use of the keep-alive connection!
4718 }
4719 
4720 void doThreadScgiConnection(CustomCgi, alias fun, long maxContentLength)(Socket connection) {
4721 	// and now we can buffer
4722 	scope(failure)
4723 		connection.close();
4724 
4725 	import al = std.algorithm;
4726 
4727 	size_t size;
4728 
4729 	string[string] headers;
4730 
4731 	auto range = new BufferedInputRange(connection);
4732 	more_data:
4733 	auto chunk = range.front();
4734 	// waiting for colon for header length
4735 	auto idx = indexOf(cast(string) chunk, ':');
4736 	if(idx == -1) {
4737 		try {
4738 			range.popFront();
4739 		} catch(Exception e) {
4740 			// it is just closed, no big deal
4741 			connection.close();
4742 			return;
4743 		}
4744 		goto more_data;
4745 	}
4746 
4747 	size = to!size_t(cast(string) chunk[0 .. idx]);
4748 	chunk = range.consume(idx + 1);
4749 	// reading headers
4750 	if(chunk.length < size)
4751 		range.popFront(0, size + 1);
4752 	// we are now guaranteed to have enough
4753 	chunk = range.front();
4754 	assert(chunk.length > size);
4755 
4756 	idx = 0;
4757 	string key;
4758 	string value;
4759 	foreach(part; al.splitter(chunk, '\0')) {
4760 		if(idx & 1) { // odd is value
4761 			value = cast(string)(part.idup);
4762 			headers[key] = value; // commit
4763 		} else
4764 			key = cast(string)(part.idup);
4765 		idx++;
4766 	}
4767 
4768 	enforce(chunk[size] == ','); // the terminator
4769 
4770 	range.consume(size + 1);
4771 	// reading data
4772 	// this will be done by Cgi
4773 
4774 	const(ubyte)[] getScgiChunk() {
4775 		// we are already primed
4776 		auto data = range.front();
4777 		if(data.length == 0 && !range.sourceClosed) {
4778 			range.popFront(0);
4779 			data = range.front();
4780 		} else if (range.sourceClosed)
4781 			range.source.close();
4782 
4783 		return data;
4784 	}
4785 
4786 	void writeScgi(const(ubyte)[] data) {
4787 		sendAll(connection, data);
4788 	}
4789 
4790 	void flushScgi() {
4791 		// I don't *think* I have to do anything....
4792 	}
4793 
4794 	Cgi cgi;
4795 	try {
4796 		cgi = new CustomCgi(maxContentLength, headers, &getScgiChunk, &writeScgi, &flushScgi);
4797 		cgi._outputFileHandle = cast(CgiConnectionHandle) connection.handle;
4798 	} catch(Throwable t) {
4799 		sendAll(connection, plainHttpError(true, "400 Bad Request", t));
4800 		connection.close();
4801 		return; // this connection is dead
4802 	}
4803 	assert(cgi !is null);
4804 	scope(exit) cgi.dispose();
4805 	try {
4806 		fun(cgi);
4807 		cgi.close();
4808 		connection.close();
4809 	} catch(Throwable t) {
4810 		// no std err
4811 		if(!handleException(cgi, t)) {
4812 			connection.close();
4813 			return;
4814 		} else {
4815 			connection.close();
4816 			return;
4817 		}
4818 	}
4819 }
4820 
4821 string printDate(DateTime date) {
4822 	char[29] buffer = void;
4823 	printDateToBuffer(date, buffer[]);
4824 	return buffer.idup;
4825 }
4826 
4827 int printDateToBuffer(DateTime date, char[] buffer) @nogc {
4828 	assert(buffer.length >= 29);
4829 	// 29 static length ?
4830 
4831 	static immutable daysOfWeek = [
4832 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
4833 	];
4834 
4835 	static immutable months = [
4836 		null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
4837 	];
4838 
4839 	buffer[0 .. 3] = daysOfWeek[date.dayOfWeek];
4840 	buffer[3 .. 5] = ", ";
4841 	buffer[5] = date.day / 10 + '0';
4842 	buffer[6] = date.day % 10 + '0';
4843 	buffer[7] = ' ';
4844 	buffer[8 .. 11] = months[date.month];
4845 	buffer[11] = ' ';
4846 	auto y = date.year;
4847 	buffer[12] = cast(char) (y / 1000 + '0'); y %= 1000;
4848 	buffer[13] = cast(char) (y / 100 + '0'); y %= 100;
4849 	buffer[14] = cast(char) (y / 10 + '0'); y %= 10;
4850 	buffer[15] = cast(char) (y + '0');
4851 	buffer[16] = ' ';
4852 	buffer[17] = date.hour / 10 + '0';
4853 	buffer[18] = date.hour % 10 + '0';
4854 	buffer[19] = ':';
4855 	buffer[20] = date.minute / 10 + '0';
4856 	buffer[21] = date.minute % 10 + '0';
4857 	buffer[22] = ':';
4858 	buffer[23] = date.second / 10 + '0';
4859 	buffer[24] = date.second % 10 + '0';
4860 	buffer[25 .. $] = " GMT";
4861 
4862 	return 29;
4863 }
4864 
4865 
4866 // Referencing this gigantic typeid seems to remind the compiler
4867 // to actually put the symbol in the object file. I guess the immutable
4868 // assoc array array isn't actually included in druntime
4869 void hackAroundLinkerError() {
4870       stdout.rawWrite(typeid(const(immutable(char)[][])[immutable(char)[]]).toString());
4871       stdout.rawWrite(typeid(immutable(char)[][][immutable(char)[]]).toString());
4872       stdout.rawWrite(typeid(Cgi.UploadedFile[immutable(char)[]]).toString());
4873       stdout.rawWrite(typeid(Cgi.UploadedFile[][immutable(char)[]]).toString());
4874       stdout.rawWrite(typeid(immutable(Cgi.UploadedFile)[immutable(char)[]]).toString());
4875       stdout.rawWrite(typeid(immutable(Cgi.UploadedFile[])[immutable(char)[]]).toString());
4876       stdout.rawWrite(typeid(immutable(char[])[immutable(char)[]]).toString());
4877       // this is getting kinda ridiculous btw. Moving assoc arrays
4878       // to the library is the pain that keeps on coming.
4879 
4880       // eh this broke the build on the work server
4881       // stdout.rawWrite(typeid(immutable(char)[][immutable(string[])]));
4882       stdout.rawWrite(typeid(immutable(string[])[immutable(char)[]]).toString());
4883 }
4884 
4885 
4886 
4887 
4888 
4889 version(fastcgi) {
4890 	pragma(lib, "fcgi");
4891 
4892 	static if(size_t.sizeof == 8) // 64 bit
4893 		alias long c_int;
4894 	else
4895 		alias int c_int;
4896 
4897 	extern(C) {
4898 		struct FCGX_Stream {
4899 			ubyte* rdNext;
4900 			ubyte* wrNext;
4901 			ubyte* stop;
4902 			ubyte* stopUnget;
4903 			c_int isReader;
4904 			c_int isClosed;
4905 			c_int wasFCloseCalled;
4906 			c_int FCGI_errno;
4907 			void* function(FCGX_Stream* stream) fillBuffProc;
4908 			void* function(FCGX_Stream* stream, c_int doClose) emptyBuffProc;
4909 			void* data;
4910 		}
4911 
4912 		// note: this is meant to be opaque, so don't access it directly
4913 		struct FCGX_Request {
4914 			int requestId;
4915 			int role;
4916 			FCGX_Stream* inStream;
4917 			FCGX_Stream* outStream;
4918 			FCGX_Stream* errStream;
4919 			char** envp;
4920 			void* paramsPtr;
4921 			int ipcFd;
4922 			int isBeginProcessed;
4923 			int keepConnection;
4924 			int appStatus;
4925 			int nWriters;
4926 			int flags;
4927 			int listen_sock;
4928 		}
4929 
4930 		int FCGX_InitRequest(FCGX_Request *request, int sock, int flags);
4931 		void FCGX_Init();
4932 
4933 		int FCGX_Accept_r(FCGX_Request *request);
4934 
4935 
4936 		alias char** FCGX_ParamArray;
4937 
4938 		c_int FCGX_Accept(FCGX_Stream** stdin, FCGX_Stream** stdout, FCGX_Stream** stderr, FCGX_ParamArray* envp);
4939 		c_int FCGX_GetChar(FCGX_Stream* stream);
4940 		c_int FCGX_PutStr(const ubyte* str, c_int n, FCGX_Stream* stream);
4941 		int FCGX_HasSeenEOF(FCGX_Stream* stream);
4942 		c_int FCGX_FFlush(FCGX_Stream *stream);
4943 
4944 		int FCGX_OpenSocket(in char*, int);
4945 	}
4946 }
4947 
4948 
4949 /* This might go int a separate module eventually. It is a network input helper class. */
4950 
4951 import std.socket;
4952 
4953 version(cgi_use_fiber) {
4954 	import core.thread;
4955 
4956 	version(linux) {
4957 		import core.sys.linux.epoll;
4958 
4959 		int epfd = -1; // thread local because EPOLLEXCLUSIVE works much better this way... weirdly.
4960 	} else version(Windows) {
4961 		// declaring the iocp thing below...
4962 	} else static assert(0, "The hybrid fiber server is not implemented on your OS.");
4963 }
4964 
4965 version(Windows)
4966 	__gshared HANDLE iocp;
4967 
4968 version(cgi_use_fiber) {
4969 	version(linux)
4970 	private enum WakeupEvent {
4971 		Read = EPOLLIN,
4972 		Write = EPOLLOUT
4973 	}
4974 	else version(Windows)
4975 	private enum WakeupEvent {
4976 		Read, Write
4977 	}
4978 	else static assert(0);
4979 }
4980 
4981 version(cgi_use_fiber)
4982 private void registerEventWakeup(bool* registered, Socket source, WakeupEvent e) @nogc {
4983 
4984 	// static cast since I know what i have in here and don't want to pay for dynamic cast
4985 	auto f = cast(CgiFiber) cast(void*) Fiber.getThis();
4986 
4987 	version(linux) {
4988 		f.setPostYield = () {
4989 			if(*registered) {
4990 				// rearm
4991 				epoll_event evt;
4992 				evt.events = e | EPOLLONESHOT;
4993 				evt.data.ptr = cast(void*) f;
4994 				if(epoll_ctl(epfd, EPOLL_CTL_MOD, source.handle, &evt) == -1)
4995 					throw new Exception("epoll_ctl");
4996 			} else {
4997 				// initial registration
4998 				*registered = true ;
4999 				int fd = source.handle;
5000 				epoll_event evt;
5001 				evt.events = e | EPOLLONESHOT;
5002 				evt.data.ptr = cast(void*) f;
5003 				if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt) == -1)
5004 					throw new Exception("epoll_ctl");
5005 			}
5006 		};
5007 
5008 		Fiber.yield();
5009 
5010 		f.setPostYield(null);
5011 	} else version(Windows) {
5012 		Fiber.yield();
5013 	}
5014 	else static assert(0);
5015 }
5016 
5017 version(cgi_use_fiber)
5018 void unregisterSource(Socket s) {
5019 	version(linux) {
5020 		epoll_event evt;
5021 		epoll_ctl(epfd, EPOLL_CTL_DEL, s.handle(), &evt);
5022 	} else version(Windows) {
5023 		// intentionally blank
5024 	}
5025 	else static assert(0);
5026 }
5027 
5028 // it is a class primarily for reference semantics
5029 // I might change this interface
5030 /// This is NOT ACTUALLY an input range! It is too different. Historical mistake kinda.
5031 class BufferedInputRange {
5032 	version(Posix)
5033 	this(int source, ubyte[] buffer = null) {
5034 		this(new Socket(cast(socket_t) source, AddressFamily.INET), buffer);
5035 	}
5036 
5037 	this(Socket source, ubyte[] buffer = null, bool allowGrowth = true) {
5038 		// if they connect but never send stuff to us, we don't want it wasting the process
5039 		// so setting a time out
5040 		version(cgi_use_fiber)
5041 			source.blocking = false;
5042 		else
5043 			source.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(3));
5044 
5045 		this.source = source;
5046 		if(buffer is null) {
5047 			underlyingBuffer = new ubyte[4096];
5048 			this.allowGrowth = true;
5049 		} else {
5050 			underlyingBuffer = buffer;
5051 			this.allowGrowth = allowGrowth;
5052 		}
5053 
5054 		assert(underlyingBuffer.length);
5055 
5056 		// we assume view.ptr is always inside underlyingBuffer
5057 		view = underlyingBuffer[0 .. 0];
5058 
5059 		popFront(); // prime
5060 	}
5061 
5062 	version(cgi_use_fiber) {
5063 		bool registered;
5064 	}
5065 
5066 	void dispose() {
5067 		version(cgi_use_fiber) {
5068 			if(registered)
5069 				unregisterSource(source);
5070 		}
5071 	}
5072 
5073 	/**
5074 		A slight difference from regular ranges is you can give it the maximum
5075 		number of bytes to consume.
5076 
5077 		IMPORTANT NOTE: the default is to consume nothing, so if you don't call
5078 		consume() yourself and use a regular foreach, it will infinitely loop!
5079 
5080 		The default is to do what a normal range does, and consume the whole buffer
5081 		and wait for additional input.
5082 
5083 		You can also specify 0, to append to the buffer, or any other number
5084 		to remove the front n bytes and wait for more.
5085 	*/
5086 	void popFront(size_t maxBytesToConsume = 0 /*size_t.max*/, size_t minBytesToSettleFor = 0, bool skipConsume = false) {
5087 		if(sourceClosed)
5088 			throw new ConnectionClosedException("can't get any more data from a closed source");
5089 		if(!skipConsume)
5090 			consume(maxBytesToConsume);
5091 
5092 		// we might have to grow the buffer
5093 		if(minBytesToSettleFor > underlyingBuffer.length || view.length == underlyingBuffer.length) {
5094 			if(allowGrowth) {
5095 			//import std.stdio; writeln("growth");
5096 				auto viewStart = view.ptr - underlyingBuffer.ptr;
5097 				size_t growth = 4096;
5098 				// make sure we have enough for what we're being asked for
5099 				if(minBytesToSettleFor > 0 && minBytesToSettleFor - underlyingBuffer.length > growth)
5100 					growth = minBytesToSettleFor - underlyingBuffer.length;
5101 				//import std.stdio; writeln(underlyingBuffer.length, " ", viewStart, " ", view.length, " ", growth,  " ", minBytesToSettleFor, " ", minBytesToSettleFor - underlyingBuffer.length);
5102 				underlyingBuffer.length += growth;
5103 				view = underlyingBuffer[viewStart .. view.length];
5104 			} else
5105 				throw new Exception("No room left in the buffer");
5106 		}
5107 
5108 		do {
5109 			auto freeSpace = underlyingBuffer[view.ptr - underlyingBuffer.ptr + view.length .. $];
5110 			try_again:
5111 			auto ret = source.receive(freeSpace);
5112 			if(ret == Socket.ERROR) {
5113 				if(wouldHaveBlocked()) {
5114 					version(cgi_use_fiber) {
5115 						registerEventWakeup(&registered, source, WakeupEvent.Read);
5116 						goto try_again;
5117 					} else {
5118 						// gonna treat a timeout here as a close
5119 						sourceClosed = true;
5120 						return;
5121 					}
5122 				}
5123 				version(Posix) {
5124 					import core.stdc.errno;
5125 					if(errno == EINTR || errno == EAGAIN) {
5126 						goto try_again;
5127 					}
5128 					if(errno == ECONNRESET) {
5129 						sourceClosed = true;
5130 						return;
5131 					}
5132 				}
5133 				throw new Exception(lastSocketError); // FIXME
5134 			}
5135 			if(ret == 0) {
5136 				sourceClosed = true;
5137 				return;
5138 			}
5139 
5140 			//import std.stdio; writeln(view.ptr); writeln(underlyingBuffer.ptr); writeln(view.length, " ", ret, " = ", view.length + ret);
5141 			view = underlyingBuffer[view.ptr - underlyingBuffer.ptr .. view.length + ret];
5142 			//import std.stdio; writeln(cast(string) view);
5143 		} while(view.length < minBytesToSettleFor);
5144 	}
5145 
5146 	/// Removes n bytes from the front of the buffer, and returns the new buffer slice.
5147 	/// You might want to idup the data you are consuming if you store it, since it may
5148 	/// be overwritten on the new popFront.
5149 	///
5150 	/// You do not need to call this if you always want to wait for more data when you
5151 	/// consume some.
5152 	ubyte[] consume(size_t bytes) {
5153 		//import std.stdio; writeln("consuime ", bytes, "/", view.length);
5154 		view = view[bytes > $ ? $ : bytes .. $];
5155 		if(view.length == 0) {
5156 			view = underlyingBuffer[0 .. 0]; // go ahead and reuse the beginning
5157 			/*
5158 			writeln("HERE");
5159 			popFront(0, 0, true); // try to load more if we can, checks if the source is closed
5160 			writeln(cast(string)front);
5161 			writeln("DONE");
5162 			*/
5163 		}
5164 		return front;
5165 	}
5166 
5167 	bool empty() {
5168 		return sourceClosed && view.length == 0;
5169 	}
5170 
5171 	ubyte[] front() {
5172 		return view;
5173 	}
5174 
5175 	invariant() {
5176 		assert(view.ptr >= underlyingBuffer.ptr);
5177 		// it should never be equal, since if that happens view ought to be empty, and thus reusing the buffer
5178 		assert(view.ptr < underlyingBuffer.ptr + underlyingBuffer.length);
5179 	}
5180 
5181 	ubyte[] underlyingBuffer;
5182 	bool allowGrowth;
5183 	ubyte[] view;
5184 	Socket source;
5185 	bool sourceClosed;
5186 }
5187 
5188 private class FakeSocketForStdin : Socket {
5189 	import std.stdio;
5190 
5191 	this() {
5192 
5193 	}
5194 
5195 	private bool closed;
5196 
5197 	override ptrdiff_t receive(scope void[] buffer, std.socket.SocketFlags) @trusted {
5198 		if(closed)
5199 			throw new Exception("Closed");
5200 		return stdin.rawRead(buffer).length;
5201 	}
5202 
5203 	override ptrdiff_t send(const scope void[] buffer, std.socket.SocketFlags) @trusted {
5204 		if(closed)
5205 			throw new Exception("Closed");
5206 		stdout.rawWrite(buffer);
5207 		return buffer.length;
5208 	}
5209 
5210 	override void close() @trusted scope {
5211 		(cast(void delegate() @nogc nothrow) &realClose)();
5212 	}
5213 
5214 	override void shutdown(SocketShutdown s) {
5215 		// FIXME
5216 	}
5217 
5218 	override void setOption(SocketOptionLevel, SocketOption, scope void[]) {}
5219 	override void setOption(SocketOptionLevel, SocketOption, Duration) {}
5220 
5221 	override @property @trusted Address remoteAddress() { return null; }
5222 	override @property @trusted Address localAddress() { return null; }
5223 
5224 	void realClose() {
5225 		closed = true;
5226 		try {
5227 			stdin.close();
5228 			stdout.close();
5229 		} catch(Exception e) {
5230 
5231 		}
5232 	}
5233 }
5234 
5235 import core.sync.semaphore;
5236 import core.atomic;
5237 
5238 /**
5239 	To use this thing:
5240 
5241 	---
5242 	void handler(Socket s) { do something... }
5243 	auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler, &delegateThatDropsPrivileges);
5244 	manager.listen();
5245 	---
5246 
5247 	The 4th parameter is optional.
5248 
5249 	I suggest you use BufferedInputRange(connection) to handle the input. As a packet
5250 	comes in, you will get control. You can just continue; though to fetch more.
5251 
5252 
5253 	FIXME: should I offer an event based async thing like netman did too? Yeah, probably.
5254 */
5255 class ListeningConnectionManager {
5256 	Semaphore semaphore;
5257 	Socket[256] queue;
5258 	shared(ubyte) nextIndexFront;
5259 	ubyte nextIndexBack;
5260 	shared(int) queueLength;
5261 
5262 	Socket acceptCancelable() {
5263 		version(Posix) {
5264 			import core.sys.posix.sys.select;
5265 			fd_set read_fds;
5266 			FD_ZERO(&read_fds);
5267 			FD_SET(listener.handle, &read_fds);
5268 			FD_SET(cancelfd, &read_fds);
5269 			auto max = listener.handle > cancelfd ? listener.handle : cancelfd;
5270 			auto ret = select(max + 1, &read_fds, null, null, null);
5271 			if(ret == -1) {
5272 				import core.stdc.errno;
5273 				if(errno == EINTR)
5274 					return null;
5275 				else
5276 					throw new Exception("wtf select");
5277 			}
5278 
5279 			if(FD_ISSET(cancelfd, &read_fds)) {
5280 				return null;
5281 			}
5282 
5283 			if(FD_ISSET(listener.handle, &read_fds))
5284 				return listener.accept();
5285 
5286 			return null;
5287 		} else
5288 			return listener.accept(); // FIXME: check the cancel flag!
5289 	}
5290 
5291 	int defaultNumberOfThreads() {
5292 		import std.parallelism;
5293 		version(cgi_use_fiber) {
5294 			return totalCPUs * 1 + 1;
5295 		} else {
5296 			// I times 4 here because there's a good chance some will be blocked on i/o.
5297 			return totalCPUs * 4;
5298 		}
5299 
5300 	}
5301 
5302 	void listen() {
5303 		shared(int) loopBroken;
5304 
5305 		version(Posix) {
5306 			import core.sys.posix.signal;
5307 			signal(SIGPIPE, SIG_IGN);
5308 		}
5309 
5310 		version(linux) {
5311 			if(cancelfd == -1)
5312 				cancelfd = eventfd(0, 0);
5313 		}
5314 
5315 		version(cgi_no_threads) {
5316 			// NEVER USE THIS
5317 			// it exists only for debugging and other special occasions
5318 
5319 			// the thread mode is faster and less likely to stall the whole
5320 			// thing when a request is slow
5321 			while(!loopBroken && !globalStopFlag) {
5322 				auto sn = acceptCancelable();
5323 				if(sn is null) continue;
5324 				cloexec(sn);
5325 				try {
5326 					handler(sn);
5327 				} catch(Exception e) {
5328 					// if a connection goes wrong, we want to just say no, but try to carry on unless it is an Error of some sort (in which case, we'll die. You might want an external helper program to revive the server when it dies)
5329 					sn.close();
5330 				}
5331 			}
5332 		} else {
5333 
5334 			if(useFork) {
5335 				version(linux) {
5336 					//asm { int 3; }
5337 					fork();
5338 				}
5339 			}
5340 
5341 			version(cgi_use_fiber) {
5342 
5343 				version(Windows) {
5344 					listener.accept();
5345 				}
5346 
5347 				WorkerThread[] threads = new WorkerThread[](numberOfThreads);
5348 				foreach(i, ref thread; threads) {
5349 					thread = new WorkerThread(this, handler, cast(int) i);
5350 					thread.start();
5351 				}
5352 
5353 				bool fiber_crash_check() {
5354 					bool hasAnyRunning;
5355 					foreach(thread; threads) {
5356 						if(!thread.isRunning) {
5357 							thread.join();
5358 						} else hasAnyRunning = true;
5359 					}
5360 
5361 					return (!hasAnyRunning);
5362 				}
5363 
5364 
5365 				while(!globalStopFlag) {
5366 					Thread.sleep(1.seconds);
5367 					if(fiber_crash_check())
5368 						break;
5369 				}
5370 
5371 			} else {
5372 				semaphore = new Semaphore();
5373 
5374 				ConnectionThread[] threads = new ConnectionThread[](numberOfThreads);
5375 				foreach(i, ref thread; threads) {
5376 					thread = new ConnectionThread(this, handler, cast(int) i);
5377 					thread.start();
5378 				}
5379 
5380 				while(!loopBroken && !globalStopFlag) {
5381 					Socket sn;
5382 
5383 					bool crash_check() {
5384 						bool hasAnyRunning;
5385 						foreach(thread; threads) {
5386 							if(!thread.isRunning) {
5387 								thread.join();
5388 							} else hasAnyRunning = true;
5389 						}
5390 
5391 						return (!hasAnyRunning);
5392 					}
5393 
5394 
5395 					void accept_new_connection() {
5396 						sn = acceptCancelable();
5397 						if(sn is null) return;
5398 						cloexec(sn);
5399 						if(tcp) {
5400 							// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
5401 							// on the socket because we do some buffering internally. I think this helps,
5402 							// certainly does for small requests, and I think it does for larger ones too
5403 							sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
5404 
5405 							sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
5406 						}
5407 					}
5408 
5409 					void existing_connection_new_data() {
5410 						// wait until a slot opens up
5411 						//int waited = 0;
5412 						while(queueLength >= queue.length) {
5413 							Thread.sleep(1.msecs);
5414 							//waited ++;
5415 						}
5416 						//if(waited) {import std.stdio; writeln(waited);}
5417 						synchronized(this) {
5418 							queue[nextIndexBack] = sn;
5419 							nextIndexBack++;
5420 							atomicOp!"+="(queueLength, 1);
5421 						}
5422 						semaphore.notify();
5423 					}
5424 
5425 
5426 					accept_new_connection();
5427 					if(sn !is null)
5428 						existing_connection_new_data();
5429 					else if(sn is null && globalStopFlag) {
5430 						foreach(thread; threads) {
5431 							semaphore.notify();
5432 						}
5433 						Thread.sleep(50.msecs);
5434 					}
5435 
5436 					if(crash_check())
5437 						break;
5438 				}
5439 			}
5440 
5441 			// FIXME: i typically stop this with ctrl+c which never
5442 			// actually gets here. i need to do a sigint handler.
5443 			if(cleanup)
5444 				cleanup();
5445 		}
5446 	}
5447 
5448 	//version(linux)
5449 		//int epoll_fd;
5450 
5451 	bool tcp;
5452 	void delegate() cleanup;
5453 
5454 	private void function(Socket) fhandler;
5455 	private void dg_handler(Socket s) {
5456 		fhandler(s);
5457 	}
5458 	this(string host, ushort port, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
5459 		fhandler = handler;
5460 		this(host, port, &dg_handler, dropPrivs, useFork, numberOfThreads);
5461 	}
5462 
5463 	this(string host, ushort port, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
5464 		this.handler = handler;
5465 		this.useFork = useFork;
5466 		this.numberOfThreads = numberOfThreads ? numberOfThreads : defaultNumberOfThreads();
5467 
5468 		listener = startListening(host, port, tcp, cleanup, 128, dropPrivs);
5469 
5470 		version(cgi_use_fiber)
5471 		if(useFork)
5472 			listener.blocking = false;
5473 
5474 		// this is the UI control thread and thus gets more priority
5475 		Thread.getThis.priority = Thread.PRIORITY_MAX;
5476 	}
5477 
5478 	Socket listener;
5479 	void delegate(Socket) handler;
5480 
5481 	immutable bool useFork;
5482 	int numberOfThreads;
5483 }
5484 
5485 Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue, void delegate() dropPrivs) {
5486 	Socket listener;
5487 	if(host.startsWith("unix:")) {
5488 		version(Posix) {
5489 			listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
5490 			cloexec(listener);
5491 			string filename = host["unix:".length .. $].idup;
5492 			listener.bind(new UnixAddress(filename));
5493 			cleanup = delegate() {
5494 				listener.close();
5495 				import std.file;
5496 				remove(filename);
5497 			};
5498 			tcp = false;
5499 		} else {
5500 			throw new Exception("unix sockets not supported on this system");
5501 		}
5502 	} else if(host.startsWith("abstract:")) {
5503 		version(linux) {
5504 			listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
5505 			cloexec(listener);
5506 			string filename = "\0" ~ host["abstract:".length .. $];
5507 			import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
5508 			listener.bind(new UnixAddress(filename));
5509 			tcp = false;
5510 		} else {
5511 			throw new Exception("abstract unix sockets not supported on this system");
5512 		}
5513 	} else {
5514 		version(cgi_use_fiber) {
5515 			version(Windows)
5516 				listener = new PseudoblockingOverlappedSocket(AddressFamily.INET, SocketType.STREAM);
5517 			else
5518 				listener = new TcpSocket();
5519 		} else {
5520 			listener = new TcpSocket();
5521 		}
5522 		cloexec(listener);
5523 		listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
5524 		listener.bind(host.length ? parseAddress(host, port) : new InternetAddress(port));
5525 		cleanup = delegate() {
5526 			listener.close();
5527 		};
5528 		tcp = true;
5529 	}
5530 
5531 	listener.listen(backQueue);
5532 
5533 	if (dropPrivs !is null) // can be null, backwards compatibility
5534 		dropPrivs();
5535 
5536 	return listener;
5537 }
5538 
5539 // helper function to send a lot to a socket. Since this blocks for the buffer (possibly several times), you should probably call it in a separate thread or something.
5540 void sendAll(Socket s, const(void)[] data, string file = __FILE__, size_t line = __LINE__) {
5541 	if(data.length == 0) return;
5542 	ptrdiff_t amount;
5543 	//import std.stdio; writeln("***",cast(string) data,"///");
5544 	do {
5545 		amount = s.send(data);
5546 		if(amount == Socket.ERROR) {
5547 			version(cgi_use_fiber) {
5548 				if(wouldHaveBlocked()) {
5549 					bool registered = true;
5550 					registerEventWakeup(&registered, s, WakeupEvent.Write);
5551 					continue;
5552 				}
5553 			}
5554 			throw new ConnectionException(s, lastSocketError, file, line);
5555 		}
5556 		assert(amount > 0);
5557 
5558 		data = data[amount .. $];
5559 	} while(data.length);
5560 }
5561 
5562 class ConnectionException : Exception {
5563 	Socket socket;
5564 	this(Socket s, string msg, string file = __FILE__, size_t line = __LINE__) {
5565 		this.socket = s;
5566 		super(msg, file, line);
5567 	}
5568 }
5569 
5570 alias void delegate(Socket) CMT;
5571 
5572 import core.thread;
5573 /+
5574 	cgi.d now uses a hybrid of event i/o and threads at the top level.
5575 
5576 	Top level thread is responsible for accepting sockets and selecting on them.
5577 
5578 	It then indicates to a child that a request is pending, and any random worker
5579 	thread that is free handles it. It goes into blocking mode and handles that
5580 	http request to completion.
5581 
5582 	At that point, it goes back into the waiting queue.
5583 
5584 
5585 	This concept is only implemented on Linux. On all other systems, it still
5586 	uses the worker threads and semaphores (which is perfectly fine for a lot of
5587 	things! Just having a great number of keep-alive connections will break that.)
5588 
5589 
5590 	So the algorithm is:
5591 
5592 	select(accept, event, pending)
5593 		if accept -> send socket to free thread, if any. if not, add socket to queue
5594 		if event -> send the signaling thread a socket from the queue, if not, mark it free
5595 			- event might block until it can be *written* to. it is a fifo sending socket fds!
5596 
5597 	A worker only does one http request at a time, then signals its availability back to the boss.
5598 
5599 	The socket the worker was just doing should be added to the one-off epoll read. If it is closed,
5600 	great, we can get rid of it. Otherwise, it is considered `pending`. The *kernel* manages that; the
5601 	actual FD will not be kept out here.
5602 
5603 	So:
5604 		queue = sockets we know are ready to read now, but no worker thread is available
5605 		idle list = worker threads not doing anything else. they signal back and forth
5606 
5607 	the workers all read off the event fd. This is the semaphore wait
5608 
5609 	the boss waits on accept or other sockets read events (one off! and level triggered). If anything happens wrt ready read,
5610 	it puts it in the queue and writes to the event fd.
5611 
5612 	The child could put the socket back in the epoll thing itself.
5613 
5614 	The child needs to be able to gracefully handle being given a socket that just closed with no work.
5615 +/
5616 class ConnectionThread : Thread {
5617 	this(ListeningConnectionManager lcm, CMT dg, int myThreadNumber) {
5618 		this.lcm = lcm;
5619 		this.dg = dg;
5620 		this.myThreadNumber = myThreadNumber;
5621 		super(&run);
5622 	}
5623 
5624 	void run() {
5625 		while(true) {
5626 			// so if there's a bunch of idle keep-alive connections, it can
5627 			// consume all the worker threads... just sitting there.
5628 			lcm.semaphore.wait();
5629 			if(globalStopFlag)
5630 				return;
5631 			Socket socket;
5632 			synchronized(lcm) {
5633 				auto idx = lcm.nextIndexFront;
5634 				socket = lcm.queue[idx];
5635 				lcm.queue[idx] = null;
5636 				atomicOp!"+="(lcm.nextIndexFront, 1);
5637 				atomicOp!"-="(lcm.queueLength, 1);
5638 			}
5639 			try {
5640 			//import std.stdio; writeln(myThreadNumber, " taking it");
5641 				dg(socket);
5642 				/+
5643 				if(socket.isAlive) {
5644 					// process it more later
5645 					version(linux) {
5646 						import core.sys.linux.epoll;
5647 						epoll_event ev;
5648 						ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
5649 						ev.data.fd = socket.handle;
5650 						import std.stdio; writeln("adding");
5651 						if(epoll_ctl(lcm.epoll_fd, EPOLL_CTL_ADD, socket.handle, &ev) == -1) {
5652 							if(errno == EEXIST) {
5653 								ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
5654 								ev.data.fd = socket.handle;
5655 								if(epoll_ctl(lcm.epoll_fd, EPOLL_CTL_MOD, socket.handle, &ev) == -1)
5656 									throw new Exception("epoll_ctl " ~ to!string(errno));
5657 							} else
5658 								throw new Exception("epoll_ctl " ~ to!string(errno));
5659 						}
5660 						//import std.stdio; writeln("keep alive");
5661 						// writing to this private member is to prevent the GC from closing my precious socket when I'm trying to use it later
5662 						__traits(getMember, socket, "sock") = cast(socket_t) -1;
5663 					} else {
5664 						continue; // hope it times out in a reasonable amount of time...
5665 					}
5666 				}
5667 				+/
5668 			} catch(ConnectionClosedException e) {
5669 				// can just ignore this, it is fairly normal
5670 				socket.close();
5671 			} catch(Throwable e) {
5672 				import std.stdio; stderr.rawWrite(e.toString); stderr.rawWrite("\n");
5673 				socket.close();
5674 			}
5675 		}
5676 	}
5677 
5678 	ListeningConnectionManager lcm;
5679 	CMT dg;
5680 	int myThreadNumber;
5681 }
5682 
5683 version(cgi_use_fiber)
5684 class WorkerThread : Thread {
5685 	this(ListeningConnectionManager lcm, CMT dg, int myThreadNumber) {
5686 		this.lcm = lcm;
5687 		this.dg = dg;
5688 		this.myThreadNumber = myThreadNumber;
5689 		super(&run);
5690 	}
5691 
5692 	version(Windows)
5693 	void run() {
5694 		auto timeout = INFINITE;
5695 		PseudoblockingOverlappedSocket key;
5696 		OVERLAPPED* overlapped;
5697 		DWORD bytes;
5698 		while(!globalStopFlag && GetQueuedCompletionStatus(iocp, &bytes, cast(PULONG_PTR) &key, &overlapped, timeout)) {
5699 			if(key is null)
5700 				continue;
5701 			key.lastAnswer = bytes;
5702 			if(key.fiber) {
5703 				key.fiber.proceed();
5704 			} else {
5705 				// we have a new connection, issue the first receive on it and issue the next accept
5706 
5707 				auto sn = key.accepted;
5708 
5709 				key.accept();
5710 
5711 				cloexec(sn);
5712 				if(lcm.tcp) {
5713 					// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
5714 					// on the socket because we do some buffering internally. I think this helps,
5715 					// certainly does for small requests, and I think it does for larger ones too
5716 					sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
5717 
5718 					sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
5719 				}
5720 
5721 				dg(sn);
5722 			}
5723 		}
5724 		//SleepEx(INFINITE, TRUE);
5725 	}
5726 
5727 	version(linux)
5728 	void run() {
5729 
5730 		import core.sys.linux.epoll;
5731 		epfd = epoll_create1(EPOLL_CLOEXEC);
5732 		if(epfd == -1)
5733 			throw new Exception("epoll_create1 " ~ to!string(errno));
5734 		scope(exit) {
5735 			import core.sys.posix.unistd;
5736 			close(epfd);
5737 		}
5738 
5739 		{
5740 			epoll_event ev;
5741 			ev.events = EPOLLIN;
5742 			ev.data.fd = cancelfd;
5743 			epoll_ctl(epfd, EPOLL_CTL_ADD, cancelfd, &ev);
5744 		}
5745 
5746 		epoll_event ev;
5747 		ev.events = EPOLLIN | EPOLLEXCLUSIVE; // EPOLLEXCLUSIVE is only available on kernels since like 2017 but that's prolly good enough.
5748 		ev.data.fd = lcm.listener.handle;
5749 		if(epoll_ctl(epfd, EPOLL_CTL_ADD, lcm.listener.handle, &ev) == -1)
5750 			throw new Exception("epoll_ctl " ~ to!string(errno));
5751 
5752 
5753 
5754 		while(!globalStopFlag) {
5755 			Socket sn;
5756 
5757 			epoll_event[64] events;
5758 			auto nfds = epoll_wait(epfd, events.ptr, events.length, -1);
5759 			if(nfds == -1) {
5760 				if(errno == EINTR)
5761 					continue;
5762 				throw new Exception("epoll_wait " ~ to!string(errno));
5763 			}
5764 
5765 			foreach(idx; 0 .. nfds) {
5766 				auto flags = events[idx].events;
5767 
5768 				if(cast(size_t) events[idx].data.ptr == cast(size_t) cancelfd) {
5769 					globalStopFlag = true;
5770 					//import std.stdio; writeln("exit heard");
5771 					break;
5772 				} else if(cast(size_t) events[idx].data.ptr == cast(size_t) lcm.listener.handle) {
5773 					//import std.stdio; writeln(myThreadNumber, " woken up ", flags);
5774 					// this try/catch is because it is set to non-blocking mode
5775 					// and Phobos' stupid api throws an exception instead of returning
5776 					// if it would block. Why would it block? because a forked process
5777 					// might have beat us to it, but the wakeup event thundered our herds.
5778 						try
5779 						sn = lcm.listener.accept(); // don't need to do the acceptCancelable here since the epoll checks it better
5780 						catch(SocketAcceptException e) { continue; }
5781 
5782 					cloexec(sn);
5783 					if(lcm.tcp) {
5784 						// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
5785 						// on the socket because we do some buffering internally. I think this helps,
5786 						// certainly does for small requests, and I think it does for larger ones too
5787 						sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
5788 
5789 						sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
5790 					}
5791 
5792 					dg(sn);
5793 				} else {
5794 					if(cast(size_t) events[idx].data.ptr < 1024) {
5795 						throw new Exception("this doesn't look like a fiber pointer...");
5796 					}
5797 					auto fiber = cast(CgiFiber) events[idx].data.ptr;
5798 					fiber.proceed();
5799 				}
5800 			}
5801 		}
5802 	}
5803 
5804 	ListeningConnectionManager lcm;
5805 	CMT dg;
5806 	int myThreadNumber;
5807 }
5808 
5809 
5810 /* Done with network helper */
5811 
5812 /* Helpers for doing temporary files. Used both here and in web.d */
5813 
5814 version(Windows) {
5815 	import core.sys.windows.windows;
5816 	extern(Windows) DWORD GetTempPathW(DWORD, LPWSTR);
5817 	alias GetTempPathW GetTempPath;
5818 }
5819 
5820 version(Posix) {
5821 	static import linux = core.sys.posix.unistd;
5822 }
5823 
5824 string getTempDirectory() {
5825 	string path;
5826 	version(Windows) {
5827 		wchar[1024] buffer;
5828 		auto len = GetTempPath(1024, buffer.ptr);
5829 		if(len == 0)
5830 			throw new Exception("couldn't find a temporary path");
5831 
5832 		auto b = buffer[0 .. len];
5833 
5834 		path = to!string(b);
5835 	} else
5836 		path = "/tmp/";
5837 
5838 	return path;
5839 }
5840 
5841 
5842 // I like std.date. These functions help keep my old code and data working with phobos changing.
5843 
5844 long sysTimeToDTime(in SysTime sysTime) {
5845     return convert!("hnsecs", "msecs")(sysTime.stdTime - 621355968000000000L);
5846 }
5847 
5848 long dateTimeToDTime(in DateTime dt) {
5849 	return sysTimeToDTime(cast(SysTime) dt);
5850 }
5851 
5852 long getUtcTime() { // renamed primarily to avoid conflict with std.date itself
5853 	return sysTimeToDTime(Clock.currTime(UTC()));
5854 }
5855 
5856 // NOTE: new SimpleTimeZone(minutes); can perhaps work with the getTimezoneOffset() JS trick
5857 SysTime dTimeToSysTime(long dTime, immutable TimeZone tz = null) {
5858 	immutable hnsecs = convert!("msecs", "hnsecs")(dTime) + 621355968000000000L;
5859 	return SysTime(hnsecs, tz);
5860 }
5861 
5862 
5863 
5864 // this is a helper to read HTTP transfer-encoding: chunked responses
5865 immutable(ubyte[]) dechunk(BufferedInputRange ir) {
5866 	immutable(ubyte)[] ret;
5867 
5868 	another_chunk:
5869 	// If here, we are at the beginning of a chunk.
5870 	auto a = ir.front();
5871 	int chunkSize;
5872 	int loc = locationOf(a, "\r\n");
5873 	while(loc == -1) {
5874 		ir.popFront();
5875 		a = ir.front();
5876 		loc = locationOf(a, "\r\n");
5877 	}
5878 
5879 	string hex;
5880 	hex = "";
5881 	for(int i = 0; i < loc; i++) {
5882 		char c = a[i];
5883 		if(c >= 'A' && c <= 'Z')
5884 			c += 0x20;
5885 		if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
5886 			hex ~= c;
5887 		} else {
5888 			break;
5889 		}
5890 	}
5891 
5892 	assert(hex.length);
5893 
5894 	int power = 1;
5895 	int size = 0;
5896 	foreach(cc1; retro(hex)) {
5897 		dchar cc = cc1;
5898 		if(cc >= 'a' && cc <= 'z')
5899 			cc -= 0x20;
5900 		int val = 0;
5901 		if(cc >= '0' && cc <= '9')
5902 			val = cc - '0';
5903 		else
5904 			val = cc - 'A' + 10;
5905 
5906 		size += power * val;
5907 		power *= 16;
5908 	}
5909 
5910 	chunkSize = size;
5911 	assert(size >= 0);
5912 
5913 	if(loc + 2 > a.length) {
5914 		ir.popFront(0, a.length + loc + 2);
5915 		a = ir.front();
5916 	}
5917 
5918 	a = ir.consume(loc + 2);
5919 
5920 	if(chunkSize == 0) { // we're done with the response
5921 		// if we got here, will change must be true....
5922 		more_footers:
5923 		loc = locationOf(a, "\r\n");
5924 		if(loc == -1) {
5925 			ir.popFront();
5926 			a = ir.front;
5927 			goto more_footers;
5928 		} else {
5929 			assert(loc == 0);
5930 			ir.consume(loc + 2);
5931 			goto finish;
5932 		}
5933 	} else {
5934 		// if we got here, will change must be true....
5935 		if(a.length < chunkSize + 2) {
5936 			ir.popFront(0, chunkSize + 2);
5937 			a = ir.front();
5938 		}
5939 
5940 		ret ~= (a[0..chunkSize]);
5941 
5942 		if(!(a.length > chunkSize + 2)) {
5943 			ir.popFront(0, chunkSize + 2);
5944 			a = ir.front();
5945 		}
5946 		assert(a[chunkSize] == 13);
5947 		assert(a[chunkSize+1] == 10);
5948 		a = ir.consume(chunkSize + 2);
5949 		chunkSize = 0;
5950 		goto another_chunk;
5951 	}
5952 
5953 	finish:
5954 	return ret;
5955 }
5956 
5957 // I want to be able to get data from multiple sources the same way...
5958 interface ByChunkRange {
5959 	bool empty();
5960 	void popFront();
5961 	const(ubyte)[] front();
5962 }
5963 
5964 ByChunkRange byChunk(const(ubyte)[] data) {
5965 	return new class ByChunkRange {
5966 		override bool empty() {
5967 			return !data.length;
5968 		}
5969 
5970 		override void popFront() {
5971 			if(data.length > 4096)
5972 				data = data[4096 .. $];
5973 			else
5974 				data = null;
5975 		}
5976 
5977 		override const(ubyte)[] front() {
5978 			return data[0 .. $ > 4096 ? 4096 : $];
5979 		}
5980 	};
5981 }
5982 
5983 ByChunkRange byChunk(BufferedInputRange ir, size_t atMost) {
5984 	const(ubyte)[] f;
5985 
5986 	f = ir.front;
5987 	if(f.length > atMost)
5988 		f = f[0 .. atMost];
5989 
5990 	return new class ByChunkRange {
5991 		override bool empty() {
5992 			return atMost == 0;
5993 		}
5994 
5995 		override const(ubyte)[] front() {
5996 			return f;
5997 		}
5998 
5999 		override void popFront() {
6000 			ir.consume(f.length);
6001 			atMost -= f.length;
6002 			auto a = ir.front();
6003 
6004 			if(a.length <= atMost) {
6005 				f = a;
6006 				atMost -= a.length;
6007 				a = ir.consume(a.length);
6008 				if(atMost != 0)
6009 					ir.popFront();
6010 				if(f.length == 0) {
6011 					f = ir.front();
6012 				}
6013 			} else {
6014 				// we actually have *more* here than we need....
6015 				f = a[0..atMost];
6016 				atMost = 0;
6017 				ir.consume(atMost);
6018 			}
6019 		}
6020 	};
6021 }
6022 
6023 version(cgi_with_websocket) {
6024 	// http://tools.ietf.org/html/rfc6455
6025 
6026 	/**
6027 		WEBSOCKET SUPPORT:
6028 
6029 		Full example:
6030 		---
6031 			import arsd.cgi;
6032 
6033 			void websocketEcho(Cgi cgi) {
6034 				if(cgi.websocketRequested()) {
6035 					if(cgi.origin != "http://arsdnet.net")
6036 						throw new Exception("bad origin");
6037 					auto websocket = cgi.acceptWebsocket();
6038 
6039 					websocket.send("hello");
6040 					websocket.send(" world!");
6041 
6042 					auto msg = websocket.recv();
6043 					while(msg.opcode != WebSocketOpcode.close) {
6044 						if(msg.opcode == WebSocketOpcode.text) {
6045 							websocket.send(msg.textData);
6046 						} else if(msg.opcode == WebSocketOpcode.binary) {
6047 							websocket.send(msg.data);
6048 						}
6049 
6050 						msg = websocket.recv();
6051 					}
6052 
6053 					websocket.close();
6054 				} else assert(0, "i want a web socket!");
6055 			}
6056 
6057 			mixin GenericMain!websocketEcho;
6058 		---
6059 	*/
6060 
6061 	class WebSocket {
6062 		Cgi cgi;
6063 
6064 		private this(Cgi cgi) {
6065 			this.cgi = cgi;
6066 
6067 			Socket socket = cgi.idlol.source;
6068 			socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"minutes"(5));
6069 		}
6070 
6071 		// returns true if data available, false if it timed out
6072 		bool recvAvailable(Duration timeout = dur!"msecs"(0)) {
6073 			if(!waitForNextMessageWouldBlock())
6074 				return true;
6075 			if(isDataPending(timeout))
6076 				return true; // this is kinda a lie.
6077 
6078 			return false;
6079 		}
6080 
6081 		public bool lowLevelReceive() {
6082 			auto bfr = cgi.idlol;
6083 			top:
6084 			auto got = bfr.front;
6085 			if(got.length) {
6086 				if(receiveBuffer.length < receiveBufferUsedLength + got.length)
6087 					receiveBuffer.length += receiveBufferUsedLength + got.length;
6088 
6089 				receiveBuffer[receiveBufferUsedLength .. receiveBufferUsedLength + got.length] = got[];
6090 				receiveBufferUsedLength += got.length;
6091 				bfr.consume(got.length);
6092 
6093 				return true;
6094 			}
6095 
6096 			if(bfr.sourceClosed)
6097 				return false;
6098 
6099 			bfr.popFront(0);
6100 			if(bfr.sourceClosed)
6101 				return false;
6102 			goto top;
6103 		}
6104 
6105 
6106 		bool isDataPending(Duration timeout = 0.seconds) {
6107 			Socket socket = cgi.idlol.source;
6108 
6109 			auto check = new SocketSet();
6110 			check.add(socket);
6111 
6112 			auto got = Socket.select(check, null, null, timeout);
6113 			if(got > 0)
6114 				return true;
6115 			return false;
6116 		}
6117 
6118 		// note: this blocks
6119 		WebSocketFrame recv() {
6120 			return waitForNextMessage();
6121 		}
6122 
6123 
6124 
6125 
6126 		private void llclose() {
6127 			cgi.close();
6128 		}
6129 
6130 		private void llsend(ubyte[] data) {
6131 			cgi.write(data);
6132 			cgi.flush();
6133 		}
6134 
6135 		void unregisterActiveSocket(WebSocket) {}
6136 
6137 		/* copy/paste section { */
6138 
6139 		private int readyState_;
6140 		private ubyte[] receiveBuffer;
6141 		private size_t receiveBufferUsedLength;
6142 
6143 		private Config config;
6144 
6145 		enum CONNECTING = 0; /// Socket has been created. The connection is not yet open.
6146 		enum OPEN = 1; /// The connection is open and ready to communicate.
6147 		enum CLOSING = 2; /// The connection is in the process of closing.
6148 		enum CLOSED = 3; /// The connection is closed or couldn't be opened.
6149 
6150 		/++
6151 
6152 		+/
6153 		/// Group: foundational
6154 		static struct Config {
6155 			/++
6156 				These control the size of the receive buffer.
6157 
6158 				It starts at the initial size, will temporarily
6159 				balloon up to the maximum size, and will reuse
6160 				a buffer up to the likely size.
6161 
6162 				Anything larger than the maximum size will cause
6163 				the connection to be aborted and an exception thrown.
6164 				This is to protect you against a peer trying to
6165 				exhaust your memory, while keeping the user-level
6166 				processing simple.
6167 			+/
6168 			size_t initialReceiveBufferSize = 4096;
6169 			size_t likelyReceiveBufferSize = 4096; /// ditto
6170 			size_t maximumReceiveBufferSize = 10 * 1024 * 1024; /// ditto
6171 
6172 			/++
6173 				Maximum combined size of a message.
6174 			+/
6175 			size_t maximumMessageSize = 10 * 1024 * 1024;
6176 
6177 			string[string] cookies; /// Cookies to send with the initial request. cookies[name] = value;
6178 			string origin; /// Origin URL to send with the handshake, if desired.
6179 			string protocol; /// the protocol header, if desired.
6180 
6181 			int pingFrequency = 5000; /// Amount of time (in msecs) of idleness after which to send an automatic ping
6182 		}
6183 
6184 		/++
6185 			Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED].
6186 		+/
6187 		int readyState() {
6188 			return readyState_;
6189 		}
6190 
6191 		/++
6192 			Closes the connection, sending a graceful teardown message to the other side.
6193 		+/
6194 		/// Group: foundational
6195 		void close(int code = 0, string reason = null)
6196 			//in (reason.length < 123)
6197 			in { assert(reason.length < 123); } do
6198 		{
6199 			if(readyState_ != OPEN)
6200 				return; // it cool, we done
6201 			WebSocketFrame wss;
6202 			wss.fin = true;
6203 			wss.opcode = WebSocketOpcode.close;
6204 			wss.data = cast(ubyte[]) reason.dup;
6205 			wss.send(&llsend);
6206 
6207 			readyState_ = CLOSING;
6208 
6209 			llclose();
6210 		}
6211 
6212 		/++
6213 			Sends a ping message to the server. This is done automatically by the library if you set a non-zero [Config.pingFrequency], but you can also send extra pings explicitly as well with this function.
6214 		+/
6215 		/// Group: foundational
6216 		void ping() {
6217 			WebSocketFrame wss;
6218 			wss.fin = true;
6219 			wss.opcode = WebSocketOpcode.ping;
6220 			wss.send(&llsend);
6221 		}
6222 
6223 		// automatically handled....
6224 		void pong() {
6225 			WebSocketFrame wss;
6226 			wss.fin = true;
6227 			wss.opcode = WebSocketOpcode.pong;
6228 			wss.send(&llsend);
6229 		}
6230 
6231 		/++
6232 			Sends a text message through the websocket.
6233 		+/
6234 		/// Group: foundational
6235 		void send(in char[] textData) {
6236 			WebSocketFrame wss;
6237 			wss.fin = true;
6238 			wss.opcode = WebSocketOpcode.text;
6239 			wss.data = cast(ubyte[]) textData.dup;
6240 			wss.send(&llsend);
6241 		}
6242 
6243 		/++
6244 			Sends a binary message through the websocket.
6245 		+/
6246 		/// Group: foundational
6247 		void send(in ubyte[] binaryData) {
6248 			WebSocketFrame wss;
6249 			wss.fin = true;
6250 			wss.opcode = WebSocketOpcode.binary;
6251 			wss.data = cast(ubyte[]) binaryData.dup;
6252 			wss.send(&llsend);
6253 		}
6254 
6255 		/++
6256 			Waits for and returns the next complete message on the socket.
6257 
6258 			Note that the onmessage function is still called, right before
6259 			this returns.
6260 		+/
6261 		/// Group: blocking_api
6262 		public WebSocketFrame waitForNextMessage() {
6263 			do {
6264 				auto m = processOnce();
6265 				if(m.populated)
6266 					return m;
6267 			} while(lowLevelReceive());
6268 
6269 			throw new ConnectionClosedException("Websocket receive timed out");
6270 			//return WebSocketFrame.init; // FIXME? maybe.
6271 		}
6272 
6273 		/++
6274 			Tells if [waitForNextMessage] would block.
6275 		+/
6276 		/// Group: blocking_api
6277 		public bool waitForNextMessageWouldBlock() {
6278 			checkAgain:
6279 			if(isMessageBuffered())
6280 				return false;
6281 			if(!isDataPending())
6282 				return true;
6283 			while(isDataPending())
6284 				lowLevelReceive();
6285 			goto checkAgain;
6286 		}
6287 
6288 		/++
6289 			Is there a message in the buffer already?
6290 			If `true`, [waitForNextMessage] is guaranteed to return immediately.
6291 			If `false`, check [isDataPending] as the next step.
6292 		+/
6293 		/// Group: blocking_api
6294 		public bool isMessageBuffered() {
6295 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
6296 			auto s = d;
6297 			if(d.length) {
6298 				auto orig = d;
6299 				auto m = WebSocketFrame.read(d);
6300 				// that's how it indicates that it needs more data
6301 				if(d !is orig)
6302 					return true;
6303 			}
6304 
6305 			return false;
6306 		}
6307 
6308 		private ubyte continuingType;
6309 		private ubyte[] continuingData;
6310 		//private size_t continuingDataLength;
6311 
6312 		private WebSocketFrame processOnce() {
6313 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
6314 			auto s = d;
6315 			// FIXME: handle continuation frames more efficiently. it should really just reuse the receive buffer.
6316 			WebSocketFrame m;
6317 			if(d.length) {
6318 				auto orig = d;
6319 				m = WebSocketFrame.read(d);
6320 				// that's how it indicates that it needs more data
6321 				if(d is orig)
6322 					return WebSocketFrame.init;
6323 				m.unmaskInPlace();
6324 				switch(m.opcode) {
6325 					case WebSocketOpcode.continuation:
6326 						if(continuingData.length + m.data.length > config.maximumMessageSize)
6327 							throw new Exception("message size exceeded");
6328 
6329 						continuingData ~= m.data;
6330 						if(m.fin) {
6331 							if(ontextmessage)
6332 								ontextmessage(cast(char[]) continuingData);
6333 							if(onbinarymessage)
6334 								onbinarymessage(continuingData);
6335 
6336 							continuingData = null;
6337 						}
6338 					break;
6339 					case WebSocketOpcode.text:
6340 						if(m.fin) {
6341 							if(ontextmessage)
6342 								ontextmessage(m.textData);
6343 						} else {
6344 							continuingType = m.opcode;
6345 							//continuingDataLength = 0;
6346 							continuingData = null;
6347 							continuingData ~= m.data;
6348 						}
6349 					break;
6350 					case WebSocketOpcode.binary:
6351 						if(m.fin) {
6352 							if(onbinarymessage)
6353 								onbinarymessage(m.data);
6354 						} else {
6355 							continuingType = m.opcode;
6356 							//continuingDataLength = 0;
6357 							continuingData = null;
6358 							continuingData ~= m.data;
6359 						}
6360 					break;
6361 					case WebSocketOpcode.close:
6362 						readyState_ = CLOSED;
6363 						if(onclose)
6364 							onclose();
6365 
6366 						unregisterActiveSocket(this);
6367 					break;
6368 					case WebSocketOpcode.ping:
6369 						pong();
6370 					break;
6371 					case WebSocketOpcode.pong:
6372 						// just really references it is still alive, nbd.
6373 					break;
6374 					default: // ignore though i could and perhaps should throw too
6375 				}
6376 			}
6377 
6378 			// the recv thing can be invalidated so gotta copy it over ugh
6379 			if(d.length) {
6380 				m.data = m.data.dup();
6381 			}
6382 
6383 			import core.stdc.string;
6384 			memmove(receiveBuffer.ptr, d.ptr, d.length);
6385 			receiveBufferUsedLength = d.length;
6386 
6387 			return m;
6388 		}
6389 
6390 		private void autoprocess() {
6391 			// FIXME
6392 			do {
6393 				processOnce();
6394 			} while(lowLevelReceive());
6395 		}
6396 
6397 
6398 		void delegate() onclose; ///
6399 		void delegate() onerror; ///
6400 		void delegate(in char[]) ontextmessage; ///
6401 		void delegate(in ubyte[]) onbinarymessage; ///
6402 		void delegate() onopen; ///
6403 
6404 		/++
6405 
6406 		+/
6407 		/// Group: browser_api
6408 		void onmessage(void delegate(in char[]) dg) {
6409 			ontextmessage = dg;
6410 		}
6411 
6412 		/// ditto
6413 		void onmessage(void delegate(in ubyte[]) dg) {
6414 			onbinarymessage = dg;
6415 		}
6416 
6417 		/* } end copy/paste */
6418 
6419 
6420 	}
6421 
6422 	bool websocketRequested(Cgi cgi) {
6423 		return
6424 			"sec-websocket-key" in cgi.requestHeaders
6425 			&&
6426 			"connection" in cgi.requestHeaders &&
6427 				cgi.requestHeaders["connection"].asLowerCase().canFind("upgrade")
6428 			&&
6429 			"upgrade" in cgi.requestHeaders &&
6430 				cgi.requestHeaders["upgrade"].asLowerCase().equal("websocket")
6431 			;
6432 	}
6433 
6434 	WebSocket acceptWebsocket(Cgi cgi) {
6435 		assert(!cgi.closed);
6436 		assert(!cgi.outputtedResponseData);
6437 		cgi.setResponseStatus("101 Switching Protocols");
6438 		cgi.header("Upgrade: WebSocket");
6439 		cgi.header("Connection: upgrade");
6440 
6441 		string key = cgi.requestHeaders["sec-websocket-key"];
6442 		key ~= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // the defined guid from the websocket spec
6443 
6444 		import std.digest.sha;
6445 		auto hash = sha1Of(key);
6446 		auto accept = Base64.encode(hash);
6447 
6448 		cgi.header(("Sec-WebSocket-Accept: " ~ accept).idup);
6449 
6450 		cgi.websocketMode = true;
6451 		cgi.write("");
6452 
6453 		cgi.flush();
6454 
6455 		return new WebSocket(cgi);
6456 	}
6457 
6458 	// FIXME get websocket to work on other modes, not just embedded_httpd
6459 
6460 	/* copy/paste in http2.d { */
6461 	enum WebSocketOpcode : ubyte {
6462 		continuation = 0,
6463 		text = 1,
6464 		binary = 2,
6465 		// 3, 4, 5, 6, 7 RESERVED
6466 		close = 8,
6467 		ping = 9,
6468 		pong = 10,
6469 		// 11,12,13,14,15 RESERVED
6470 	}
6471 
6472 	public struct WebSocketFrame {
6473 		private bool populated;
6474 		bool fin;
6475 		bool rsv1;
6476 		bool rsv2;
6477 		bool rsv3;
6478 		WebSocketOpcode opcode; // 4 bits
6479 		bool masked;
6480 		ubyte lengthIndicator; // don't set this when building one to send
6481 		ulong realLength; // don't use when sending
6482 		ubyte[4] maskingKey; // don't set this when sending
6483 		ubyte[] data;
6484 
6485 		static WebSocketFrame simpleMessage(WebSocketOpcode opcode, void[] data) {
6486 			WebSocketFrame msg;
6487 			msg.fin = true;
6488 			msg.opcode = opcode;
6489 			msg.data = cast(ubyte[]) data.dup;
6490 
6491 			return msg;
6492 		}
6493 
6494 		private void send(scope void delegate(ubyte[]) llsend) {
6495 			ubyte[64] headerScratch;
6496 			int headerScratchPos = 0;
6497 
6498 			realLength = data.length;
6499 
6500 			{
6501 				ubyte b1;
6502 				b1 |= cast(ubyte) opcode;
6503 				b1 |= rsv3 ? (1 << 4) : 0;
6504 				b1 |= rsv2 ? (1 << 5) : 0;
6505 				b1 |= rsv1 ? (1 << 6) : 0;
6506 				b1 |= fin  ? (1 << 7) : 0;
6507 
6508 				headerScratch[0] = b1;
6509 				headerScratchPos++;
6510 			}
6511 
6512 			{
6513 				headerScratchPos++; // we'll set header[1] at the end of this
6514 				auto rlc = realLength;
6515 				ubyte b2;
6516 				b2 |= masked ? (1 << 7) : 0;
6517 
6518 				assert(headerScratchPos == 2);
6519 
6520 				if(realLength > 65535) {
6521 					// use 64 bit length
6522 					b2 |= 0x7f;
6523 
6524 					// FIXME: double check endinaness
6525 					foreach(i; 0 .. 8) {
6526 						headerScratch[2 + 7 - i] = rlc & 0x0ff;
6527 						rlc >>>= 8;
6528 					}
6529 
6530 					headerScratchPos += 8;
6531 				} else if(realLength > 125) {
6532 					// use 16 bit length
6533 					b2 |= 0x7e;
6534 
6535 					// FIXME: double check endinaness
6536 					foreach(i; 0 .. 2) {
6537 						headerScratch[2 + 1 - i] = rlc & 0x0ff;
6538 						rlc >>>= 8;
6539 					}
6540 
6541 					headerScratchPos += 2;
6542 				} else {
6543 					// use 7 bit length
6544 					b2 |= realLength & 0b_0111_1111;
6545 				}
6546 
6547 				headerScratch[1] = b2;
6548 			}
6549 
6550 			//assert(!masked, "masking key not properly implemented");
6551 			if(masked) {
6552 				// FIXME: randomize this
6553 				headerScratch[headerScratchPos .. headerScratchPos + 4] = maskingKey[];
6554 				headerScratchPos += 4;
6555 
6556 				// we'll just mask it in place...
6557 				int keyIdx = 0;
6558 				foreach(i; 0 .. data.length) {
6559 					data[i] = data[i] ^ maskingKey[keyIdx];
6560 					if(keyIdx == 3)
6561 						keyIdx = 0;
6562 					else
6563 						keyIdx++;
6564 				}
6565 			}
6566 
6567 			//writeln("SENDING ", headerScratch[0 .. headerScratchPos], data);
6568 			llsend(headerScratch[0 .. headerScratchPos]);
6569 			llsend(data);
6570 		}
6571 
6572 		static WebSocketFrame read(ref ubyte[] d) {
6573 			WebSocketFrame msg;
6574 
6575 			auto orig = d;
6576 
6577 			WebSocketFrame needsMoreData() {
6578 				d = orig;
6579 				return WebSocketFrame.init;
6580 			}
6581 
6582 			if(d.length < 2)
6583 				return needsMoreData();
6584 
6585 			ubyte b = d[0];
6586 
6587 			msg.populated = true;
6588 
6589 			msg.opcode = cast(WebSocketOpcode) (b & 0x0f);
6590 			b >>= 4;
6591 			msg.rsv3 = b & 0x01;
6592 			b >>= 1;
6593 			msg.rsv2 = b & 0x01;
6594 			b >>= 1;
6595 			msg.rsv1 = b & 0x01;
6596 			b >>= 1;
6597 			msg.fin = b & 0x01;
6598 
6599 			b = d[1];
6600 			msg.masked = (b & 0b1000_0000) ? true : false;
6601 			msg.lengthIndicator = b & 0b0111_1111;
6602 
6603 			d = d[2 .. $];
6604 
6605 			if(msg.lengthIndicator == 0x7e) {
6606 				// 16 bit length
6607 				msg.realLength = 0;
6608 
6609 				if(d.length < 2) return needsMoreData();
6610 
6611 				foreach(i; 0 .. 2) {
6612 					msg.realLength |= d[0] << ((1-i) * 8);
6613 					d = d[1 .. $];
6614 				}
6615 			} else if(msg.lengthIndicator == 0x7f) {
6616 				// 64 bit length
6617 				msg.realLength = 0;
6618 
6619 				if(d.length < 8) return needsMoreData();
6620 
6621 				foreach(i; 0 .. 8) {
6622 					msg.realLength |= ulong(d[0]) << ((7-i) * 8);
6623 					d = d[1 .. $];
6624 				}
6625 			} else {
6626 				// 7 bit length
6627 				msg.realLength = msg.lengthIndicator;
6628 			}
6629 
6630 			if(msg.masked) {
6631 
6632 				if(d.length < 4) return needsMoreData();
6633 
6634 				msg.maskingKey = d[0 .. 4];
6635 				d = d[4 .. $];
6636 			}
6637 
6638 			if(msg.realLength > d.length) {
6639 				return needsMoreData();
6640 			}
6641 
6642 			msg.data = d[0 .. cast(size_t) msg.realLength];
6643 			d = d[cast(size_t) msg.realLength .. $];
6644 
6645 			return msg;
6646 		}
6647 
6648 		void unmaskInPlace() {
6649 			if(this.masked) {
6650 				int keyIdx = 0;
6651 				foreach(i; 0 .. this.data.length) {
6652 					this.data[i] = this.data[i] ^ this.maskingKey[keyIdx];
6653 					if(keyIdx == 3)
6654 						keyIdx = 0;
6655 					else
6656 						keyIdx++;
6657 				}
6658 			}
6659 		}
6660 
6661 		char[] textData() {
6662 			return cast(char[]) data;
6663 		}
6664 	}
6665 	/* } */
6666 }
6667 
6668 
6669 version(Windows)
6670 {
6671     version(CRuntime_DigitalMars)
6672     {
6673         extern(C) int setmode(int, int) nothrow @nogc;
6674     }
6675     else version(CRuntime_Microsoft)
6676     {
6677         extern(C) int _setmode(int, int) nothrow @nogc;
6678         alias setmode = _setmode;
6679     }
6680     else static assert(0);
6681 }
6682 
6683 version(Posix) {
6684 	version(CRuntime_Musl) {} else {
6685 		import core.sys.posix.unistd;
6686 		private extern(C) int posix_spawn(pid_t*, const char*, void*, void*, const char**, const char**);
6687 	}
6688 }
6689 
6690 
6691 // FIXME: these aren't quite public yet.
6692 //private:
6693 
6694 // template for laziness
6695 void startAddonServer()(string arg) {
6696 	version(OSX) {
6697 		assert(0, "Not implemented");
6698 	} else version(linux) {
6699 		import core.sys.posix.unistd;
6700 		pid_t pid;
6701 		const(char)*[16] args;
6702 		args[0] = "ARSD_CGI_ADDON_SERVER";
6703 		args[1] = arg.ptr;
6704 		posix_spawn(&pid, "/proc/self/exe",
6705 			null,
6706 			null,
6707 			args.ptr,
6708 			null // env
6709 		);
6710 	} else version(Windows) {
6711 		wchar[2048] filename;
6712 		auto len = GetModuleFileNameW(null, filename.ptr, cast(DWORD) filename.length);
6713 		if(len == 0 || len == filename.length)
6714 			throw new Exception("could not get process name to start helper server");
6715 
6716 		STARTUPINFOW startupInfo;
6717 		startupInfo.cb = cast(DWORD) startupInfo.sizeof;
6718 		PROCESS_INFORMATION processInfo;
6719 
6720 		import std.utf;
6721 
6722 		// I *MIGHT* need to run it as a new job or a service...
6723 		auto ret = CreateProcessW(
6724 			filename.ptr,
6725 			toUTF16z(arg),
6726 			null, // process attributes
6727 			null, // thread attributes
6728 			false, // inherit handles
6729 			0, // creation flags
6730 			null, // environment
6731 			null, // working directory
6732 			&startupInfo,
6733 			&processInfo
6734 		);
6735 
6736 		if(!ret)
6737 			throw new Exception("create process failed");
6738 
6739 		// when done with those, if we set them
6740 		/*
6741 		CloseHandle(hStdInput);
6742 		CloseHandle(hStdOutput);
6743 		CloseHandle(hStdError);
6744 		*/
6745 
6746 	} else static assert(0, "Websocket server not implemented on this system yet (email me, i can prolly do it if you need it)");
6747 }
6748 
6749 // template for laziness
6750 /*
6751 	The websocket server is a single-process, single-thread, event
6752 	I/O thing. It is passed websockets from other CGI processes
6753 	and is then responsible for handling their messages and responses.
6754 	Note that the CGI process is responsible for websocket setup,
6755 	including authentication, etc.
6756 
6757 	It also gets data sent to it by other processes and is responsible
6758 	for distributing that, as necessary.
6759 */
6760 void runWebsocketServer()() {
6761 	assert(0, "not implemented");
6762 }
6763 
6764 void sendToWebsocketServer(WebSocket ws, string group) {
6765 	assert(0, "not implemented");
6766 }
6767 
6768 void sendToWebsocketServer(string content, string group) {
6769 	assert(0, "not implemented");
6770 }
6771 
6772 
6773 void runEventServer()() {
6774 	runAddonServer("/tmp/arsd_cgi_event_server", new EventSourceServerImplementation());
6775 }
6776 
6777 void runTimerServer()() {
6778 	runAddonServer("/tmp/arsd_scheduled_job_server", new ScheduledJobServerImplementation());
6779 }
6780 
6781 version(Posix) {
6782 	alias LocalServerConnectionHandle = int;
6783 	alias CgiConnectionHandle = int;
6784 	alias SocketConnectionHandle = int;
6785 
6786 	enum INVALID_CGI_CONNECTION_HANDLE = -1;
6787 } else version(Windows) {
6788 	alias LocalServerConnectionHandle = HANDLE;
6789 	version(embedded_httpd_threads) {
6790 		alias CgiConnectionHandle = SOCKET;
6791 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
6792 	} else version(fastcgi) {
6793 		alias CgiConnectionHandle = void*; // Doesn't actually work! But I don't want compile to fail pointlessly at this point.
6794 		enum INVALID_CGI_CONNECTION_HANDLE = null;
6795 	} else version(scgi) {
6796 		alias CgiConnectionHandle = SOCKET;
6797 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
6798 	} else { /* version(plain_cgi) */
6799 		alias CgiConnectionHandle = HANDLE;
6800 		enum INVALID_CGI_CONNECTION_HANDLE = null;
6801 	}
6802 	alias SocketConnectionHandle = SOCKET;
6803 }
6804 
6805 version(with_addon_servers_connections)
6806 LocalServerConnectionHandle openLocalServerConnection()(string name, string arg) {
6807 	version(Posix) {
6808 		import core.sys.posix.unistd;
6809 		import core.sys.posix.sys.un;
6810 
6811 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
6812 		if(sock == -1)
6813 			throw new Exception("socket " ~ to!string(errno));
6814 
6815 		scope(failure)
6816 			close(sock);
6817 
6818 		cloexec(sock);
6819 
6820 		// add-on server processes are assumed to be local, and thus will
6821 		// use unix domain sockets. Besides, I want to pass sockets to them,
6822 		// so it basically must be local (except for the session server, but meh).
6823 		sockaddr_un addr;
6824 		addr.sun_family = AF_UNIX;
6825 		version(linux) {
6826 			// on linux, we will use the abstract namespace
6827 			addr.sun_path[0] = 0;
6828 			addr.sun_path[1 .. name.length + 1] = cast(typeof(addr.sun_path[])) name[];
6829 		} else {
6830 			// but otherwise, just use a file cuz we must.
6831 			addr.sun_path[0 .. name.length] = cast(typeof(addr.sun_path[])) name[];
6832 		}
6833 
6834 		bool alreadyTried;
6835 
6836 		try_again:
6837 
6838 		if(connect(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
6839 			if(!alreadyTried && errno == ECONNREFUSED) {
6840 				// try auto-spawning the server, then attempt connection again
6841 				startAddonServer(arg);
6842 				import core.thread;
6843 				Thread.sleep(50.msecs);
6844 				alreadyTried = true;
6845 				goto try_again;
6846 			} else
6847 				throw new Exception("connect " ~ to!string(errno));
6848 		}
6849 
6850 		return sock;
6851 	} else version(Windows) {
6852 		return null; // FIXME
6853 	}
6854 }
6855 
6856 version(with_addon_servers_connections)
6857 void closeLocalServerConnection(LocalServerConnectionHandle handle) {
6858 	version(Posix) {
6859 		import core.sys.posix.unistd;
6860 		close(handle);
6861 	} else version(Windows)
6862 		CloseHandle(handle);
6863 }
6864 
6865 void runSessionServer()() {
6866 	runAddonServer("/tmp/arsd_session_server", new BasicDataServerImplementation());
6867 }
6868 
6869 version(Posix)
6870 private void makeNonBlocking(int fd) {
6871 	import core.sys.posix.fcntl;
6872 	auto flags = fcntl(fd, F_GETFL, 0);
6873 	if(flags == -1)
6874 		throw new Exception("fcntl get");
6875 	flags |= O_NONBLOCK;
6876 	auto s = fcntl(fd, F_SETFL, flags);
6877 	if(s == -1)
6878 		throw new Exception("fcntl set");
6879 }
6880 
6881 import core.stdc.errno;
6882 
6883 struct IoOp {
6884 	@disable this();
6885 	@disable this(this);
6886 
6887 	/*
6888 		So we want to be able to eventually handle generic sockets too.
6889 	*/
6890 
6891 	enum Read = 1;
6892 	enum Write = 2;
6893 	enum Accept = 3;
6894 	enum ReadSocketHandle = 4;
6895 
6896 	// Your handler may be called in a different thread than the one that initiated the IO request!
6897 	// It is also possible to have multiple io requests being called simultaneously. Use proper thread safety caution.
6898 	private bool delegate(IoOp*, int) handler; // returns true if you are done and want it to be closed
6899 	private void delegate(IoOp*) closeHandler;
6900 	private void delegate(IoOp*) completeHandler;
6901 	private int internalFd;
6902 	private int operation;
6903 	private int bufferLengthAllocated;
6904 	private int bufferLengthUsed;
6905 	private ubyte[1] internalBuffer; // it can be overallocated!
6906 
6907 	ubyte[] allocatedBuffer() return {
6908 		return internalBuffer.ptr[0 .. bufferLengthAllocated];
6909 	}
6910 
6911 	ubyte[] usedBuffer() return {
6912 		return allocatedBuffer[0 .. bufferLengthUsed];
6913 	}
6914 
6915 	void reset() {
6916 		bufferLengthUsed = 0;
6917 	}
6918 
6919 	int fd() {
6920 		return internalFd;
6921 	}
6922 }
6923 
6924 IoOp* allocateIoOp(int fd, int operation, int bufferSize, bool delegate(IoOp*, int) handler) {
6925 	import core.stdc.stdlib;
6926 
6927 	auto ptr = calloc(IoOp.sizeof + bufferSize, 1);
6928 	if(ptr is null)
6929 		assert(0); // out of memory!
6930 
6931 	auto op = cast(IoOp*) ptr;
6932 
6933 	op.handler = handler;
6934 	op.internalFd = fd;
6935 	op.operation = operation;
6936 	op.bufferLengthAllocated = bufferSize;
6937 	op.bufferLengthUsed = 0;
6938 
6939 	import core.memory;
6940 
6941 	GC.addRoot(ptr);
6942 
6943 	return op;
6944 }
6945 
6946 void freeIoOp(ref IoOp* ptr) {
6947 
6948 	import core.memory;
6949 	GC.removeRoot(ptr);
6950 
6951 	import core.stdc.stdlib;
6952 	free(ptr);
6953 	ptr = null;
6954 }
6955 
6956 version(Posix)
6957 version(with_addon_servers_connections)
6958 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
6959 
6960 	//import std.stdio : writeln; writeln(cast(string) data);
6961 
6962 	import core.sys.posix.unistd;
6963 
6964 	auto ret = write(connection, data.ptr, data.length);
6965 	if(ret != data.length) {
6966 		if(ret == 0 || (ret == -1 && (errno == EPIPE || errno == ETIMEDOUT))) {
6967 			// the file is closed, remove it
6968 			eis.fileClosed(connection);
6969 		} else
6970 			throw new Exception("alas " ~ to!string(ret) ~ " " ~ to!string(errno)); // FIXME
6971 	}
6972 }
6973 version(Windows)
6974 version(with_addon_servers_connections)
6975 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
6976 	// FIXME
6977 }
6978 
6979 bool isInvalidHandle(CgiConnectionHandle h) {
6980 	return h == INVALID_CGI_CONNECTION_HANDLE;
6981 }
6982 
6983 /+
6984 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsarecv
6985 https://support.microsoft.com/en-gb/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode
6986 https://stackoverflow.com/questions/18018489/should-i-use-iocps-or-overlapped-wsasend-receive
6987 https://docs.microsoft.com/en-us/windows/desktop/fileio/i-o-completion-ports
6988 https://docs.microsoft.com/en-us/windows/desktop/fileio/createiocompletionport
6989 https://docs.microsoft.com/en-us/windows/desktop/api/mswsock/nf-mswsock-acceptex
6990 https://docs.microsoft.com/en-us/windows/desktop/Sync/waitable-timer-objects
6991 https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-setwaitabletimer
6992 https://docs.microsoft.com/en-us/windows/desktop/Sync/using-a-waitable-timer-with-an-asynchronous-procedure-call
6993 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsagetoverlappedresult
6994 
6995 +/
6996 
6997 /++
6998 	You can customize your server by subclassing the appropriate server. Then, register your
6999 	subclass at compile time with the [registerEventIoServer] template, or implement your own
7000 	main function and call it yourself.
7001 
7002 	$(TIP If you make your subclass a `final class`, there is a slight performance improvement.)
7003 +/
7004 version(with_addon_servers_connections)
7005 interface EventIoServer {
7006 	bool handleLocalConnectionData(IoOp* op, int receivedFd);
7007 	void handleLocalConnectionClose(IoOp* op);
7008 	void handleLocalConnectionComplete(IoOp* op);
7009 	void wait_timeout();
7010 	void fileClosed(int fd);
7011 
7012 	void epoll_fd(int fd);
7013 }
7014 
7015 // the sink should buffer it
7016 private void serialize(T)(scope void delegate(scope ubyte[]) sink, T t) {
7017 	static if(is(T == struct)) {
7018 		foreach(member; __traits(allMembers, T))
7019 			serialize(sink, __traits(getMember, t, member));
7020 	} else static if(is(T : int)) {
7021 		// no need to think of endianness just because this is only used
7022 		// for local, same-machine stuff anyway. thanks private lol
7023 		sink((cast(ubyte*) &t)[0 .. t.sizeof]);
7024 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
7025 		// these are common enough to optimize
7026 		int len = cast(int) t.length; // want length consistent size tho, in case 32 bit program sends to 64 bit server, etc.
7027 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7028 		sink(cast(ubyte[]) t[]);
7029 	} else static if(is(T : A[], A)) {
7030 		// generic array is less optimal but still prolly ok
7031 		int len = cast(int) t.length;
7032 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7033 		foreach(item; t)
7034 			serialize(sink, item);
7035 	} else static assert(0, T.stringof);
7036 }
7037 
7038 // all may be stack buffers, so use cautio
7039 private void deserialize(T)(scope ubyte[] delegate(int sz) get, scope void delegate(T) dg) {
7040 	static if(is(T == struct)) {
7041 		T t;
7042 		foreach(member; __traits(allMembers, T))
7043 			deserialize!(typeof(__traits(getMember, T, member)))(get, (mbr) { __traits(getMember, t, member) = mbr; });
7044 		dg(t);
7045 	} else static if(is(T : int)) {
7046 		// no need to think of endianness just because this is only used
7047 		// for local, same-machine stuff anyway. thanks private lol
7048 		T t;
7049 		auto data = get(t.sizeof);
7050 		t = (cast(T[]) data)[0];
7051 		dg(t);
7052 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
7053 		// these are common enough to optimize
7054 		int len;
7055 		auto data = get(len.sizeof);
7056 		len = (cast(int[]) data)[0];
7057 
7058 		/*
7059 		typeof(T[0])[2000] stackBuffer;
7060 		T buffer;
7061 
7062 		if(len < stackBuffer.length)
7063 			buffer = stackBuffer[0 .. len];
7064 		else
7065 			buffer = new T(len);
7066 
7067 		data = get(len * typeof(T[0]).sizeof);
7068 		*/
7069 
7070 		T t = cast(T) get(len * cast(int) typeof(T.init[0]).sizeof);
7071 
7072 		dg(t);
7073 	} else static if(is(T == E[], E)) {
7074 		T t;
7075 		int len;
7076 		auto data = get(len.sizeof);
7077 		len = (cast(int[]) data)[0];
7078 		t.length = len;
7079 		foreach(ref e; t) {
7080 			deserialize!E(get, (ele) { e = ele; });
7081 		}
7082 		dg(t);
7083 	} else static assert(0, T.stringof);
7084 }
7085 
7086 unittest {
7087 	serialize((ubyte[] b) {
7088 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 1); });
7089 	}, 1);
7090 	serialize((ubyte[] b) {
7091 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 56674); });
7092 	}, 56674);
7093 	ubyte[1000] buffer;
7094 	int bufferPoint;
7095 	void add(ubyte[] b) {
7096 		buffer[bufferPoint ..  bufferPoint + b.length] = b[];
7097 		bufferPoint += b.length;
7098 	}
7099 	ubyte[] get(int sz) {
7100 		auto b = buffer[bufferPoint .. bufferPoint + sz];
7101 		bufferPoint += sz;
7102 		return b;
7103 	}
7104 	serialize(&add, "test here");
7105 	bufferPoint = 0;
7106 	deserialize!string(&get, (t) { assert(t == "test here"); });
7107 	bufferPoint = 0;
7108 
7109 	struct Foo {
7110 		int a;
7111 		ubyte c;
7112 		string d;
7113 	}
7114 	serialize(&add, Foo(403, 37, "amazing"));
7115 	bufferPoint = 0;
7116 	deserialize!Foo(&get, (t) {
7117 		assert(t.a == 403);
7118 		assert(t.c == 37);
7119 		assert(t.d == "amazing");
7120 	});
7121 	bufferPoint = 0;
7122 }
7123 
7124 /*
7125 	Here's the way the RPC interface works:
7126 
7127 	You define the interface that lists the functions you can call on the remote process.
7128 	The interface may also have static methods for convenience. These forward to a singleton
7129 	instance of an auto-generated class, which actually sends the args over the pipe.
7130 
7131 	An impl class actually implements it. A receiving server deserializes down the pipe and
7132 	calls methods on the class.
7133 
7134 	I went with the interface to get some nice compiler checking and documentation stuff.
7135 
7136 	I could have skipped the interface and just implemented it all from the server class definition
7137 	itself, but then the usage may call the method instead of rpcing it; I just like having the user
7138 	interface and the implementation separate so you aren't tempted to `new impl` to call the methods.
7139 
7140 
7141 	I fiddled with newlines in the mixin string to ensure the assert line numbers matched up to the source code line number. Idk why dmd didn't do this automatically, but it was important to me.
7142 
7143 	Realistically though the bodies would just be
7144 		connection.call(this.mangleof, args...) sooooo.
7145 
7146 	FIXME: overloads aren't supported
7147 */
7148 
7149 /// Base for storing sessions in an array. Exists primarily for internal purposes and you should generally not use this.
7150 interface SessionObject {}
7151 
7152 private immutable void delegate(string[])[string] scheduledJobHandlers;
7153 private immutable void delegate(string[])[string] websocketServers;
7154 
7155 version(with_breaking_cgi_features)
7156 mixin(q{
7157 
7158 mixin template ImplementRpcClientInterface(T, string serverPath, string cmdArg) {
7159 	static import std.traits;
7160 
7161 	// derivedMembers on an interface seems to give exactly what I want: the virtual functions we need to implement. so I am just going to use it directly without more filtering.
7162 	static foreach(idx, member; __traits(derivedMembers, T)) {
7163 	static if(__traits(isVirtualFunction, __traits(getMember, T, member)))
7164 		mixin( q{
7165 		std.traits.ReturnType!(__traits(getMember, T, member))
7166 		} ~ member ~ q{(std.traits.Parameters!(__traits(getMember, T, member)) params)
7167 		{
7168 			SerializationBuffer buffer;
7169 			auto i = cast(ushort) idx;
7170 			serialize(&buffer.sink, i);
7171 			serialize(&buffer.sink, __traits(getMember, T, member).mangleof);
7172 			foreach(param; params)
7173 				serialize(&buffer.sink, param);
7174 
7175 			auto sendable = buffer.sendable;
7176 
7177 			version(Posix) {{
7178 				auto ret = send(connectionHandle, sendable.ptr, sendable.length, 0);
7179 
7180 				if(ret == -1) {
7181 					throw new Exception("send returned -1, errno: " ~ to!string(errno));
7182 				} else if(ret == 0) {
7183 					throw new Exception("Connection to addon server lost");
7184 				} if(ret < sendable.length)
7185 					throw new Exception("Send failed to send all");
7186 				assert(ret == sendable.length);
7187 			}} // FIXME Windows impl
7188 
7189 			static if(!is(typeof(return) == void)) {
7190 				// there is a return value; we need to wait for it too
7191 				version(Posix) {
7192 					ubyte[3000] revBuffer;
7193 					auto ret = recv(connectionHandle, revBuffer.ptr, revBuffer.length, 0);
7194 					auto got = revBuffer[0 .. ret];
7195 
7196 					int dataLocation;
7197 					ubyte[] grab(int sz) {
7198 						auto dataLocation1 = dataLocation;
7199 						dataLocation += sz;
7200 						return got[dataLocation1 .. dataLocation];
7201 					}
7202 
7203 					typeof(return) retu;
7204 					deserialize!(typeof(return))(&grab, (a) { retu = a; });
7205 					return retu;
7206 				} else {
7207 					// FIXME Windows impl
7208 					return typeof(return).init;
7209 				}
7210 
7211 			}
7212 		}});
7213 	}
7214 
7215 	private static typeof(this) singletonInstance;
7216 	private LocalServerConnectionHandle connectionHandle;
7217 
7218 	static typeof(this) connection() {
7219 		if(singletonInstance is null) {
7220 			singletonInstance = new typeof(this)();
7221 			singletonInstance.connect();
7222 		}
7223 		return singletonInstance;
7224 	}
7225 
7226 	void connect() {
7227 		connectionHandle = openLocalServerConnection(serverPath, cmdArg);
7228 	}
7229 
7230 	void disconnect() {
7231 		closeLocalServerConnection(connectionHandle);
7232 	}
7233 }
7234 
7235 void dispatchRpcServer(Interface, Class)(Class this_, ubyte[] data, int fd) if(is(Class : Interface)) {
7236 	ushort calledIdx;
7237 	string calledFunction;
7238 
7239 	int dataLocation;
7240 	ubyte[] grab(int sz) {
7241 		if(sz == 0) assert(0);
7242 		auto d = data[dataLocation .. dataLocation + sz];
7243 		dataLocation += sz;
7244 		return d;
7245 	}
7246 
7247 	again:
7248 
7249 	deserialize!ushort(&grab, (a) { calledIdx = a; });
7250 	deserialize!string(&grab, (a) { calledFunction = a; });
7251 
7252 	import std.traits;
7253 
7254 	sw: switch(calledIdx) {
7255 		foreach(idx, memberName; __traits(derivedMembers, Interface))
7256 		static if(__traits(isVirtualFunction, __traits(getMember, Interface, memberName))) {
7257 			case idx:
7258 				assert(calledFunction == __traits(getMember, Interface, memberName).mangleof);
7259 
7260 				Parameters!(__traits(getMember, Interface, memberName)) params;
7261 				foreach(ref param; params)
7262 					deserialize!(typeof(param))(&grab, (a) { param = a; });
7263 
7264 				static if(is(ReturnType!(__traits(getMember, Interface, memberName)) == void)) {
7265 					__traits(getMember, this_, memberName)(params);
7266 				} else {
7267 					auto ret = __traits(getMember, this_, memberName)(params);
7268 					SerializationBuffer buffer;
7269 					serialize(&buffer.sink, ret);
7270 
7271 					auto sendable = buffer.sendable;
7272 
7273 					version(Posix) {
7274 						auto r = send(fd, sendable.ptr, sendable.length, 0);
7275 						if(r == -1) {
7276 							throw new Exception("send returned -1, errno: " ~ to!string(errno));
7277 						} else if(r == 0) {
7278 							throw new Exception("Connection to addon client lost");
7279 						} if(r < sendable.length)
7280 							throw new Exception("Send failed to send all");
7281 
7282 					} // FIXME Windows impl
7283 				}
7284 			break sw;
7285 		}
7286 		default: assert(0);
7287 	}
7288 
7289 	if(dataLocation != data.length)
7290 		goto again;
7291 }
7292 
7293 
7294 private struct SerializationBuffer {
7295 	ubyte[2048] bufferBacking;
7296 	int bufferLocation;
7297 	void sink(scope ubyte[] data) {
7298 		bufferBacking[bufferLocation .. bufferLocation + data.length] = data[];
7299 		bufferLocation += data.length;
7300 	}
7301 
7302 	ubyte[] sendable() return {
7303 		return bufferBacking[0 .. bufferLocation];
7304 	}
7305 }
7306 
7307 /*
7308 	FIXME:
7309 		add a version command line arg
7310 		version data in the library
7311 		management gui as external program
7312 
7313 		at server with event_fd for each run
7314 		use .mangleof in the at function name
7315 
7316 		i think the at server will have to:
7317 			pipe args to the child
7318 			collect child output for logging
7319 			get child return value for logging
7320 
7321 			on windows timers work differently. idk how to best combine with the io stuff.
7322 
7323 			will have to have dump and restore too, so i can restart without losing stuff.
7324 */
7325 
7326 /++
7327 	A convenience object for talking to the [BasicDataServer] from a higher level.
7328 	See: [Cgi.getSessionObject].
7329 
7330 	You pass it a `Data` struct describing the data you want saved in the session.
7331 	Then, this class will generate getter and setter properties that allow access
7332 	to that data.
7333 
7334 	Note that each load and store will be done as-accessed; it doesn't front-load
7335 	mutable data nor does it batch updates out of fear of read-modify-write race
7336 	conditions. (In fact, right now it does this for everything, but in the future,
7337 	I might batch load `immutable` members of the Data struct.)
7338 
7339 	At some point in the future, I might also let it do different backends, like
7340 	a client-side cookie store too, but idk.
7341 
7342 	Note that the plain-old-data members of your `Data` struct are wrapped by this
7343 	interface via a static foreach to make property functions.
7344 
7345 	See_Also: [MockSession]
7346 +/
7347 interface Session(Data) : SessionObject {
7348 	@property string sessionId() const;
7349 
7350 	/++
7351 		Starts a new session. Note that a session is also
7352 		implicitly started as soon as you write data to it,
7353 		so if you need to alter these parameters from their
7354 		defaults, be sure to explicitly call this BEFORE doing
7355 		any writes to session data.
7356 
7357 		Params:
7358 			idleLifetime = How long, in seconds, the session
7359 			should remain in memory when not being read from
7360 			or written to. The default is one day.
7361 
7362 			NOT IMPLEMENTED
7363 
7364 			useExtendedLifetimeCookie = The session ID is always
7365 			stored in a HTTP cookie, and by default, that cookie
7366 			is discarded when the user closes their browser.
7367 
7368 			But if you set this to true, it will use a non-perishable
7369 			cookie for the given idleLifetime.
7370 
7371 			NOT IMPLEMENTED
7372 	+/
7373 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false);
7374 
7375 	/++
7376 		Regenerates the session ID and updates the associated
7377 		cookie.
7378 
7379 		This is also your chance to change immutable data
7380 		(not yet implemented).
7381 	+/
7382 	void regenerateId();
7383 
7384 	/++
7385 		Terminates this session, deleting all saved data.
7386 	+/
7387 	void terminate();
7388 
7389 	/++
7390 		Plain-old-data members of your `Data` struct are wrapped here via
7391 		the property getters and setters.
7392 
7393 		If the member is a non-string array, it returns a magical array proxy
7394 		object which allows for atomic appends and replaces via overloaded operators.
7395 		You can slice this to get a range representing a $(B const) view of the array.
7396 		This is to protect you against read-modify-write race conditions.
7397 	+/
7398 	static foreach(memberName; __traits(allMembers, Data))
7399 		static if(is(typeof(__traits(getMember, Data, memberName))))
7400 		mixin(q{
7401 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout;
7402 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value);
7403 		});
7404 
7405 }
7406 
7407 /++
7408 	An implementation of [Session] that works on real cgi connections utilizing the
7409 	[BasicDataServer].
7410 
7411 	As opposed to a [MockSession] which is made for testing purposes.
7412 
7413 	You will not construct one of these directly. See [Cgi.getSessionObject] instead.
7414 +/
7415 class BasicDataServerSession(Data) : Session!Data {
7416 	private Cgi cgi;
7417 	private string sessionId_;
7418 
7419 	public @property string sessionId() const {
7420 		return sessionId_;
7421 	}
7422 
7423 	protected @property string sessionId(string s) {
7424 		return this.sessionId_ = s;
7425 	}
7426 
7427 	private this(Cgi cgi) {
7428 		this.cgi = cgi;
7429 		if(auto ptr = "sessionId" in cgi.cookies)
7430 			sessionId = (*ptr).length ? *ptr : null;
7431 	}
7432 
7433 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {
7434 		assert(sessionId is null);
7435 
7436 		// FIXME: what if there is a session ID cookie, but no corresponding session on the server?
7437 
7438 		import std.random, std.conv;
7439 		sessionId = to!string(uniform(1, long.max));
7440 
7441 		BasicDataServer.connection.createSession(sessionId, idleLifetime);
7442 		setCookie();
7443 	}
7444 
7445 	protected void setCookie() {
7446 		cgi.setCookie(
7447 			"sessionId", sessionId,
7448 			0 /* expiration */,
7449 			"/" /* path */,
7450 			null /* domain */,
7451 			true /* http only */,
7452 			cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
7453 	}
7454 
7455 	void regenerateId() {
7456 		if(sessionId is null) {
7457 			start();
7458 			return;
7459 		}
7460 		import std.random, std.conv;
7461 		auto oldSessionId = sessionId;
7462 		sessionId = to!string(uniform(1, long.max));
7463 		BasicDataServer.connection.renameSession(oldSessionId, sessionId);
7464 		setCookie();
7465 	}
7466 
7467 	void terminate() {
7468 		BasicDataServer.connection.destroySession(sessionId);
7469 		sessionId = null;
7470 		setCookie();
7471 	}
7472 
7473 	static foreach(memberName; __traits(allMembers, Data))
7474 		static if(is(typeof(__traits(getMember, Data, memberName))))
7475 		mixin(q{
7476 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
7477 				if(sessionId is null)
7478 					return typeof(return).init;
7479 
7480 				import std.traits;
7481 				auto v = BasicDataServer.connection.getSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName);
7482 				if(v.length == 0)
7483 					return typeof(return).init;
7484 				import std.conv;
7485 				// why this cast? to doesn't like being given an inout argument. so need to do it without that, then
7486 				// we need to return it and that needed the cast. It should be fine since we basically respect constness..
7487 				// basically. Assuming the session is POD this should be fine.
7488 				return cast(typeof(return)) to!(typeof(__traits(getMember, Data, memberName)))(v);
7489 			}
7490 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
7491 				if(sessionId is null)
7492 					start();
7493 				import std.conv;
7494 				import std.traits;
7495 				BasicDataServer.connection.setSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName, to!string(value));
7496 				return value;
7497 			}
7498 		});
7499 }
7500 
7501 /++
7502 	A mock object that works like the real session, but doesn't actually interact with any actual database or http connection.
7503 	Simply stores the data in its instance members.
7504 +/
7505 class MockSession(Data) : Session!Data {
7506 	pure {
7507 		@property string sessionId() const { return "mock"; }
7508 		void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {}
7509 		void regenerateId() {}
7510 		void terminate() {}
7511 
7512 		private Data store_;
7513 
7514 		static foreach(memberName; __traits(allMembers, Data))
7515 			static if(is(typeof(__traits(getMember, Data, memberName))))
7516 			mixin(q{
7517 				@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
7518 					return __traits(getMember, store_, memberName);
7519 				}
7520 				@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
7521 					return __traits(getMember, store_, memberName) = value;
7522 				}
7523 			});
7524 	}
7525 }
7526 
7527 /++
7528 	Direct interface to the basic data add-on server. You can
7529 	typically use [Cgi.getSessionObject] as a more convenient interface.
7530 +/
7531 version(with_addon_servers_connections)
7532 interface BasicDataServer {
7533 	///
7534 	void createSession(string sessionId, int lifetime);
7535 	///
7536 	void renewSession(string sessionId, int lifetime);
7537 	///
7538 	void destroySession(string sessionId);
7539 	///
7540 	void renameSession(string oldSessionId, string newSessionId);
7541 
7542 	///
7543 	void setSessionData(string sessionId, string dataKey, string dataValue);
7544 	///
7545 	string getSessionData(string sessionId, string dataKey);
7546 
7547 	///
7548 	static BasicDataServerConnection connection() {
7549 		return BasicDataServerConnection.connection();
7550 	}
7551 }
7552 
7553 version(with_addon_servers_connections)
7554 class BasicDataServerConnection : BasicDataServer {
7555 	mixin ImplementRpcClientInterface!(BasicDataServer, "/tmp/arsd_session_server", "--session-server");
7556 }
7557 
7558 version(with_addon_servers)
7559 final class BasicDataServerImplementation : BasicDataServer, EventIoServer {
7560 
7561 	void createSession(string sessionId, int lifetime) {
7562 		sessions[sessionId.idup] = Session(lifetime);
7563 	}
7564 	void destroySession(string sessionId) {
7565 		sessions.remove(sessionId);
7566 	}
7567 	void renewSession(string sessionId, int lifetime) {
7568 		sessions[sessionId].lifetime = lifetime;
7569 	}
7570 	void renameSession(string oldSessionId, string newSessionId) {
7571 		sessions[newSessionId.idup] = sessions[oldSessionId];
7572 		sessions.remove(oldSessionId);
7573 	}
7574 	void setSessionData(string sessionId, string dataKey, string dataValue) {
7575 		if(sessionId !in sessions)
7576 			createSession(sessionId, 3600); // FIXME?
7577 		sessions[sessionId].values[dataKey.idup] = dataValue.idup;
7578 	}
7579 	string getSessionData(string sessionId, string dataKey) {
7580 		if(auto session = sessionId in sessions) {
7581 			if(auto data = dataKey in (*session).values)
7582 				return *data;
7583 			else
7584 				return null; // no such data
7585 
7586 		} else {
7587 			return null; // no session
7588 		}
7589 	}
7590 
7591 
7592 	protected:
7593 
7594 	struct Session {
7595 		int lifetime;
7596 
7597 		string[string] values;
7598 	}
7599 
7600 	Session[string] sessions;
7601 
7602 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
7603 		auto data = op.usedBuffer;
7604 		dispatchRpcServer!BasicDataServer(this, data, op.fd);
7605 		return false;
7606 	}
7607 
7608 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
7609 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
7610 	void wait_timeout() {}
7611 	void fileClosed(int fd) {} // stateless so irrelevant
7612 	void epoll_fd(int fd) {}
7613 }
7614 
7615 /++
7616 	See [schedule] to make one of these. You then call one of the methods here to set it up:
7617 
7618 	---
7619 		schedule!fn(args).at(DateTime(2019, 8, 7, 12, 00, 00)); // run the function at August 7, 2019, 12 noon UTC
7620 		schedule!fn(args).delay(6.seconds); // run it after waiting 6 seconds
7621 		schedule!fn(args).asap(); // run it in the background as soon as the event loop gets around to it
7622 	---
7623 +/
7624 version(with_addon_servers_connections)
7625 struct ScheduledJobHelper {
7626 	private string func;
7627 	private string[] args;
7628 	private bool consumed;
7629 
7630 	private this(string func, string[] args) {
7631 		this.func = func;
7632 		this.args = args;
7633 	}
7634 
7635 	~this() {
7636 		assert(consumed);
7637 	}
7638 
7639 	/++
7640 		Schedules the job to be run at the given time.
7641 	+/
7642 	void at(DateTime when, immutable TimeZone timezone = UTC()) {
7643 		consumed = true;
7644 
7645 		auto conn = ScheduledJobServerConnection.connection;
7646 		import std.file;
7647 		auto st = SysTime(when, timezone);
7648 		auto jobId = conn.scheduleJob(1, cast(int) st.toUnixTime(), thisExePath, func, args);
7649 	}
7650 
7651 	/++
7652 		Schedules the job to run at least after the specified delay.
7653 	+/
7654 	void delay(Duration delay) {
7655 		consumed = true;
7656 
7657 		auto conn = ScheduledJobServerConnection.connection;
7658 		import std.file;
7659 		auto jobId = conn.scheduleJob(0, cast(int) delay.total!"seconds", thisExePath, func, args);
7660 	}
7661 
7662 	/++
7663 		Runs the job in the background ASAP.
7664 
7665 		$(NOTE It may run in a background thread. Don't segfault!)
7666 	+/
7667 	void asap() {
7668 		consumed = true;
7669 
7670 		auto conn = ScheduledJobServerConnection.connection;
7671 		import std.file;
7672 		auto jobId = conn.scheduleJob(0, 1, thisExePath, func, args);
7673 	}
7674 
7675 	/+
7676 	/++
7677 		Schedules the job to recur on the given pattern.
7678 	+/
7679 	void recur(string spec) {
7680 
7681 	}
7682 	+/
7683 }
7684 
7685 /++
7686 	First step to schedule a job on the scheduled job server.
7687 
7688 	The scheduled job needs to be a top-level function that doesn't read any
7689 	variables from outside its arguments because it may be run in a new process,
7690 	without any context existing later.
7691 
7692 	You MUST set details on the returned object to actually do anything!
7693 +/
7694 template schedule(alias fn, T...) if(is(typeof(fn) == function)) {
7695 	///
7696 	ScheduledJobHelper schedule(T args) {
7697 		// this isn't meant to ever be called, but instead just to
7698 		// get the compiler to type check the arguments passed for us
7699 		auto sample = delegate() {
7700 			fn(args);
7701 		};
7702 		string[] sargs;
7703 		foreach(arg; args)
7704 			sargs ~= to!string(arg);
7705 		return ScheduledJobHelper(fn.mangleof, sargs);
7706 	}
7707 
7708 	shared static this() {
7709 		scheduledJobHandlers[fn.mangleof] = delegate(string[] sargs) {
7710 			import std.traits;
7711 			Parameters!fn args;
7712 			foreach(idx, ref arg; args)
7713 				arg = to!(typeof(arg))(sargs[idx]);
7714 			fn(args);
7715 		};
7716 	}
7717 }
7718 
7719 ///
7720 interface ScheduledJobServer {
7721 	/// Use the [schedule] function for a higher-level interface.
7722 	int scheduleJob(int whenIs, int when, string executable, string func, string[] args);
7723 	///
7724 	void cancelJob(int jobId);
7725 }
7726 
7727 version(with_addon_servers_connections)
7728 class ScheduledJobServerConnection : ScheduledJobServer {
7729 	mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server", "--timer-server");
7730 }
7731 
7732 version(with_addon_servers)
7733 final class ScheduledJobServerImplementation : ScheduledJobServer, EventIoServer {
7734 	// FIXME: we need to handle SIGCHLD in this somehow
7735 	// whenIs is 0 for relative, 1 for absolute
7736 	protected int scheduleJob(int whenIs, int when, string executable, string func, string[] args) {
7737 		auto nj = nextJobId;
7738 		nextJobId++;
7739 
7740 		version(linux) {
7741 			import core.sys.linux.timerfd;
7742 			import core.sys.linux.epoll;
7743 			import core.sys.posix.unistd;
7744 
7745 
7746 			auto fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
7747 			if(fd == -1)
7748 				throw new Exception("fd timer create failed");
7749 
7750 			foreach(ref arg; args)
7751 				arg = arg.idup;
7752 			auto job = Job(executable.idup, func.idup, .dup(args), fd, nj);
7753 
7754 			itimerspec value;
7755 			value.it_value.tv_sec = when;
7756 			value.it_value.tv_nsec = 0;
7757 
7758 			value.it_interval.tv_sec = 0;
7759 			value.it_interval.tv_nsec = 0;
7760 
7761 			if(timerfd_settime(fd, whenIs == 1 ? TFD_TIMER_ABSTIME : 0, &value, null) == -1)
7762 				throw new Exception("couldn't set fd timer");
7763 
7764 			auto op = allocateIoOp(fd, IoOp.Read, 16, (IoOp* op, int fd) {
7765 				jobs.remove(nj);
7766 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, null);
7767 				close(fd);
7768 
7769 
7770 				spawnProcess([job.executable, "--timed-job", job.func] ~ job.args);
7771 
7772 				return true;
7773 			});
7774 			scope(failure)
7775 				freeIoOp(op);
7776 
7777 			epoll_event ev;
7778 			ev.events = EPOLLIN | EPOLLET;
7779 			ev.data.ptr = op;
7780 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1)
7781 				throw new Exception("epoll_ctl " ~ to!string(errno));
7782 
7783 			jobs[nj] = job;
7784 			return nj;
7785 		} else assert(0);
7786 	}
7787 
7788 	protected void cancelJob(int jobId) {
7789 		version(linux) {
7790 			auto job = jobId in jobs;
7791 			if(job is null)
7792 				return;
7793 
7794 			jobs.remove(jobId);
7795 
7796 			version(linux) {
7797 				import core.sys.linux.timerfd;
7798 				import core.sys.linux.epoll;
7799 				import core.sys.posix.unistd;
7800 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, job.timerfd, null);
7801 				close(job.timerfd);
7802 			}
7803 		}
7804 		jobs.remove(jobId);
7805 	}
7806 
7807 	int nextJobId = 1;
7808 	static struct Job {
7809 		string executable;
7810 		string func;
7811 		string[] args;
7812 		int timerfd;
7813 		int id;
7814 	}
7815 	Job[int] jobs;
7816 
7817 
7818 	// event io server methods below
7819 
7820 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
7821 		auto data = op.usedBuffer;
7822 		dispatchRpcServer!ScheduledJobServer(this, data, op.fd);
7823 		return false;
7824 	}
7825 
7826 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
7827 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
7828 	void wait_timeout() {}
7829 	void fileClosed(int fd) {} // stateless so irrelevant
7830 
7831 	int epoll_fd_;
7832 	void epoll_fd(int fd) {this.epoll_fd_ = fd; }
7833 	int epoll_fd() { return epoll_fd_; }
7834 }
7835 
7836 ///
7837 version(with_addon_servers_connections)
7838 interface EventSourceServer {
7839 	/++
7840 		sends this cgi request to the event server so it will be fed events. You should not do anything else with the cgi object after this.
7841 
7842 		$(WARNING This API is extremely unstable. I might change it or remove it without notice.)
7843 
7844 		See_Also:
7845 			[sendEvent]
7846 	+/
7847 	public static void adoptConnection(Cgi cgi, in char[] eventUrl) {
7848 		/*
7849 			If lastEventId is missing or empty, you just get new events as they come.
7850 
7851 			If it is set from something else, it sends all since then (that are still alive)
7852 			down the pipe immediately.
7853 
7854 			The reason it can come from the header is that's what the standard defines for
7855 			browser reconnects. The reason it can come from a query string is just convenience
7856 			in catching up in a user-defined manner.
7857 
7858 			The reason the header overrides the query string is if the browser tries to reconnect,
7859 			it will send the header AND the query (it reconnects to the same url), so we just
7860 			want to do the restart thing.
7861 
7862 			Note that if you ask for "0" as the lastEventId, it will get ALL still living events.
7863 		*/
7864 		string lastEventId = cgi.lastEventId;
7865 		if(lastEventId.length == 0 && "lastEventId" in cgi.get)
7866 			lastEventId = cgi.get["lastEventId"];
7867 
7868 		cgi.setResponseContentType("text/event-stream");
7869 		cgi.write(":\n", false); // to initialize the chunking and send headers before keeping the fd for later
7870 		cgi.flush();
7871 
7872 		cgi.closed = true;
7873 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
7874 		scope(exit)
7875 			closeLocalServerConnection(s);
7876 
7877 		version(fastcgi)
7878 			throw new Exception("sending fcgi connections not supported");
7879 		else {
7880 			auto fd = cgi.getOutputFileHandle();
7881 			if(isInvalidHandle(fd))
7882 				throw new Exception("bad fd from cgi!");
7883 
7884 			EventSourceServerImplementation.SendableEventConnection sec;
7885 			sec.populate(cgi.responseChunked, eventUrl, lastEventId);
7886 
7887 			version(Posix) {
7888 				auto res = write_fd(s, cast(void*) &sec, sec.sizeof, fd);
7889 				assert(res == sec.sizeof);
7890 			} else version(Windows) {
7891 				// FIXME
7892 			}
7893 		}
7894 	}
7895 
7896 	/++
7897 		Sends an event to the event server, starting it if necessary. The event server will distribute it to any listening clients, and store it for `lifetime` seconds for any later listening clients to catch up later.
7898 
7899 		$(WARNING This API is extremely unstable. I might change it or remove it without notice.)
7900 
7901 		Params:
7902 			url = A string identifying this event "bucket". Listening clients must also connect to this same string. I called it `url` because I envision it being just passed as the url of the request.
7903 			event = the event type string, which is used in the Javascript addEventListener API on EventSource
7904 			data = the event data. Available in JS as `event.data`.
7905 			lifetime = the amount of time to keep this event for replaying on the event server.
7906 
7907 		See_Also:
7908 			[sendEventToEventServer]
7909 	+/
7910 	public static void sendEvent(string url, string event, string data, int lifetime) {
7911 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
7912 		scope(exit)
7913 			closeLocalServerConnection(s);
7914 
7915 		EventSourceServerImplementation.SendableEvent sev;
7916 		sev.populate(url, event, data, lifetime);
7917 
7918 		version(Posix) {
7919 			auto ret = send(s, &sev, sev.sizeof, 0);
7920 			assert(ret == sev.sizeof);
7921 		} else version(Windows) {
7922 			// FIXME
7923 		}
7924 	}
7925 
7926 	/++
7927 		Messages sent to `url` will also be sent to anyone listening on `forwardUrl`.
7928 
7929 		See_Also: [disconnect]
7930 	+/
7931 	void connect(string url, string forwardUrl);
7932 
7933 	/++
7934 		Disconnects `forwardUrl` from `url`
7935 
7936 		See_Also: [connect]
7937 	+/
7938 	void disconnect(string url, string forwardUrl);
7939 }
7940 
7941 ///
7942 version(with_addon_servers)
7943 final class EventSourceServerImplementation : EventSourceServer, EventIoServer {
7944 
7945 	protected:
7946 
7947 	void connect(string url, string forwardUrl) {
7948 		pipes[url] ~= forwardUrl;
7949 	}
7950 	void disconnect(string url, string forwardUrl) {
7951 		auto t = url in pipes;
7952 		if(t is null)
7953 			return;
7954 		foreach(idx, n; (*t))
7955 			if(n == forwardUrl) {
7956 				(*t)[idx] = (*t)[$-1];
7957 				(*t) = (*t)[0 .. $-1];
7958 				break;
7959 			}
7960 	}
7961 
7962 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
7963 		if(receivedFd != -1) {
7964 			//writeln("GOT FD ", receivedFd, " -- ", op.usedBuffer);
7965 
7966 			//core.sys.posix.unistd.write(receivedFd, "hello".ptr, 5);
7967 
7968 			SendableEventConnection* got = cast(SendableEventConnection*) op.usedBuffer.ptr;
7969 
7970 			auto url = got.url.idup;
7971 			eventConnectionsByUrl[url] ~= EventConnection(receivedFd, got.responseChunked > 0 ? true : false);
7972 
7973 			// FIXME: catch up on past messages here
7974 		} else {
7975 			auto data = op.usedBuffer;
7976 			auto event = cast(SendableEvent*) data.ptr;
7977 
7978 			if(event.magic == 0xdeadbeef) {
7979 				handleInputEvent(event);
7980 
7981 				if(event.url in pipes)
7982 				foreach(pipe; pipes[event.url]) {
7983 					event.url = pipe;
7984 					handleInputEvent(event);
7985 				}
7986 			} else {
7987 				dispatchRpcServer!EventSourceServer(this, data, op.fd);
7988 			}
7989 		}
7990 		return false;
7991 	}
7992 	void handleLocalConnectionClose(IoOp* op) {
7993 		fileClosed(op.fd);
7994 	}
7995 	void handleLocalConnectionComplete(IoOp* op) {}
7996 
7997 	void wait_timeout() {
7998 		// just keeping alive
7999 		foreach(url, connections; eventConnectionsByUrl)
8000 		foreach(connection; connections)
8001 			if(connection.needsChunking)
8002 				nonBlockingWrite(this, connection.fd, "1b\r\nevent: keepalive\ndata: ok\n\n\r\n");
8003 			else
8004 				nonBlockingWrite(this, connection.fd, "event: keepalive\ndata: ok\n\n\r\n");
8005 	}
8006 
8007 	void fileClosed(int fd) {
8008 		outer: foreach(url, ref connections; eventConnectionsByUrl) {
8009 			foreach(idx, conn; connections) {
8010 				if(fd == conn.fd) {
8011 					connections[idx] = connections[$-1];
8012 					connections = connections[0 .. $ - 1];
8013 					continue outer;
8014 				}
8015 			}
8016 		}
8017 	}
8018 
8019 	void epoll_fd(int fd) {}
8020 
8021 
8022 	private:
8023 
8024 
8025 	struct SendableEventConnection {
8026 		ubyte responseChunked;
8027 
8028 		int urlLength;
8029 		char[256] urlBuffer = 0;
8030 
8031 		int lastEventIdLength;
8032 		char[32] lastEventIdBuffer = 0;
8033 
8034 		char[] url() return {
8035 			return urlBuffer[0 .. urlLength];
8036 		}
8037 		void url(in char[] u) {
8038 			urlBuffer[0 .. u.length] = u[];
8039 			urlLength = cast(int) u.length;
8040 		}
8041 		char[] lastEventId() return {
8042 			return lastEventIdBuffer[0 .. lastEventIdLength];
8043 		}
8044 		void populate(bool responseChunked, in char[] url, in char[] lastEventId)
8045 		in {
8046 			assert(url.length < this.urlBuffer.length);
8047 			assert(lastEventId.length < this.lastEventIdBuffer.length);
8048 		}
8049 		do {
8050 			this.responseChunked = responseChunked ? 1 : 0;
8051 			this.urlLength = cast(int) url.length;
8052 			this.lastEventIdLength = cast(int) lastEventId.length;
8053 
8054 			this.urlBuffer[0 .. url.length] = url[];
8055 			this.lastEventIdBuffer[0 .. lastEventId.length] = lastEventId[];
8056 		}
8057 	}
8058 
8059 	struct SendableEvent {
8060 		int magic = 0xdeadbeef;
8061 		int urlLength;
8062 		char[256] urlBuffer = 0;
8063 		int typeLength;
8064 		char[32] typeBuffer = 0;
8065 		int messageLength;
8066 		char[2048 * 4] messageBuffer = 0; // this is an arbitrary limit, it needs to fit comfortably in stack (including in a fiber) and be a single send on the kernel side cuz of the impl... i think this is ok for a unix socket.
8067 		int _lifetime;
8068 
8069 		char[] message() return {
8070 			return messageBuffer[0 .. messageLength];
8071 		}
8072 		char[] type() return {
8073 			return typeBuffer[0 .. typeLength];
8074 		}
8075 		char[] url() return {
8076 			return urlBuffer[0 .. urlLength];
8077 		}
8078 		void url(in char[] u) {
8079 			urlBuffer[0 .. u.length] = u[];
8080 			urlLength = cast(int) u.length;
8081 		}
8082 		int lifetime() {
8083 			return _lifetime;
8084 		}
8085 
8086 		///
8087 		void populate(string url, string type, string message, int lifetime)
8088 		in {
8089 			assert(url.length < this.urlBuffer.length);
8090 			assert(type.length < this.typeBuffer.length);
8091 			assert(message.length < this.messageBuffer.length);
8092 		}
8093 		do {
8094 			this.urlLength = cast(int) url.length;
8095 			this.typeLength = cast(int) type.length;
8096 			this.messageLength = cast(int) message.length;
8097 			this._lifetime = lifetime;
8098 
8099 			this.urlBuffer[0 .. url.length] = url[];
8100 			this.typeBuffer[0 .. type.length] = type[];
8101 			this.messageBuffer[0 .. message.length] = message[];
8102 		}
8103 	}
8104 
8105 	struct EventConnection {
8106 		int fd;
8107 		bool needsChunking;
8108 	}
8109 
8110 	private EventConnection[][string] eventConnectionsByUrl;
8111 	private string[][string] pipes;
8112 
8113 	private void handleInputEvent(scope SendableEvent* event) {
8114 		static int eventId;
8115 
8116 		static struct StoredEvent {
8117 			int id;
8118 			string type;
8119 			string message;
8120 			int lifetimeRemaining;
8121 		}
8122 
8123 		StoredEvent[][string] byUrl;
8124 
8125 		int thisId = ++eventId;
8126 
8127 		if(event.lifetime)
8128 			byUrl[event.url.idup] ~= StoredEvent(thisId, event.type.idup, event.message.idup, event.lifetime);
8129 
8130 		auto connectionsPtr = event.url in eventConnectionsByUrl;
8131 		EventConnection[] connections;
8132 		if(connectionsPtr is null)
8133 			return;
8134 		else
8135 			connections = *connectionsPtr;
8136 
8137 		char[4096] buffer;
8138 		char[] formattedMessage;
8139 
8140 		void append(const char[] a) {
8141 			// the 6's here are to leave room for a HTTP chunk header, if it proves necessary
8142 			buffer[6 + formattedMessage.length .. 6 + formattedMessage.length + a.length] = a[];
8143 			formattedMessage = buffer[6 .. 6 + formattedMessage.length + a.length];
8144 		}
8145 
8146 		import std.algorithm.iteration;
8147 
8148 		if(connections.length) {
8149 			append("id: ");
8150 			append(to!string(thisId));
8151 			append("\n");
8152 
8153 			append("event: ");
8154 			append(event.type);
8155 			append("\n");
8156 
8157 			foreach(line; event.message.splitter("\n")) {
8158 				append("data: ");
8159 				append(line);
8160 				append("\n");
8161 			}
8162 
8163 			append("\n");
8164 		}
8165 
8166 		// chunk it for HTTP!
8167 		auto len = toHex(formattedMessage.length);
8168 		buffer[4 .. 6] = "\r\n"[];
8169 		buffer[4 - len.length .. 4] = len[];
8170 		buffer[6 + formattedMessage.length] = '\r';
8171 		buffer[6 + formattedMessage.length + 1] = '\n';
8172 
8173 		auto chunkedMessage = buffer[4 - len.length .. 6 + formattedMessage.length +2];
8174 		// done
8175 
8176 		// FIXME: send back requests when needed
8177 		// FIXME: send a single ":\n" every 15 seconds to keep alive
8178 
8179 		foreach(connection; connections) {
8180 			if(connection.needsChunking) {
8181 				nonBlockingWrite(this, connection.fd, chunkedMessage);
8182 			} else {
8183 				nonBlockingWrite(this, connection.fd, formattedMessage);
8184 			}
8185 		}
8186 	}
8187 }
8188 
8189 void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoServer)) {
8190 	version(Posix) {
8191 
8192 		import core.sys.posix.unistd;
8193 		import core.sys.posix.fcntl;
8194 		import core.sys.posix.sys.un;
8195 
8196 		import core.sys.posix.signal;
8197 		signal(SIGPIPE, SIG_IGN);
8198 
8199 		static extern(C) void sigchldhandler(int) {
8200 			int status;
8201 			import w = core.sys.posix.sys.wait;
8202 			w.wait(&status);
8203 		}
8204 		signal(SIGCHLD, &sigchldhandler);
8205 
8206 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
8207 		if(sock == -1)
8208 			throw new Exception("socket " ~ to!string(errno));
8209 
8210 		scope(failure)
8211 			close(sock);
8212 
8213 		cloexec(sock);
8214 
8215 		// add-on server processes are assumed to be local, and thus will
8216 		// use unix domain sockets. Besides, I want to pass sockets to them,
8217 		// so it basically must be local (except for the session server, but meh).
8218 		sockaddr_un addr;
8219 		addr.sun_family = AF_UNIX;
8220 		version(linux) {
8221 			// on linux, we will use the abstract namespace
8222 			addr.sun_path[0] = 0;
8223 			addr.sun_path[1 .. localListenerName.length + 1] = cast(typeof(addr.sun_path[])) localListenerName[];
8224 		} else {
8225 			// but otherwise, just use a file cuz we must.
8226 			addr.sun_path[0 .. localListenerName.length] = cast(typeof(addr.sun_path[])) localListenerName[];
8227 		}
8228 
8229 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1)
8230 			throw new Exception("bind " ~ to!string(errno));
8231 
8232 		if(listen(sock, 128) == -1)
8233 			throw new Exception("listen " ~ to!string(errno));
8234 
8235 		makeNonBlocking(sock);
8236 
8237 		version(linux) {
8238 			import core.sys.linux.epoll;
8239 			auto epoll_fd = epoll_create1(EPOLL_CLOEXEC);
8240 			if(epoll_fd == -1)
8241 				throw new Exception("epoll_create1 " ~ to!string(errno));
8242 			scope(failure)
8243 				close(epoll_fd);
8244 		} else {
8245 			import core.sys.posix.poll;
8246 		}
8247 
8248 		version(linux)
8249 		eis.epoll_fd = epoll_fd;
8250 
8251 		auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
8252 		scope(exit)
8253 			freeIoOp(acceptOp);
8254 
8255 		version(linux) {
8256 			epoll_event ev;
8257 			ev.events = EPOLLIN | EPOLLET;
8258 			ev.data.ptr = acceptOp;
8259 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev) == -1)
8260 				throw new Exception("epoll_ctl " ~ to!string(errno));
8261 
8262 			epoll_event[64] events;
8263 		} else {
8264 			pollfd[] pollfds;
8265 			IoOp*[int] ioops;
8266 			pollfds ~= pollfd(sock, POLLIN);
8267 			ioops[sock] = acceptOp;
8268 		}
8269 
8270 		import core.time : MonoTime, seconds;
8271 
8272 		MonoTime timeout = MonoTime.currTime + 15.seconds;
8273 
8274 		while(true) {
8275 
8276 			// FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently
8277 
8278 			int timeout_milliseconds = 0; //  -1; // infinite
8279 
8280 			timeout_milliseconds = cast(int) (timeout - MonoTime.currTime).total!"msecs";
8281 			if(timeout_milliseconds < 0)
8282 				timeout_milliseconds = 0;
8283 
8284 			//writeln("waiting for ", name);
8285 
8286 			version(linux) {
8287 				auto nfds = epoll_wait(epoll_fd, events.ptr, events.length, timeout_milliseconds);
8288 				if(nfds == -1) {
8289 					if(errno == EINTR)
8290 						continue;
8291 					throw new Exception("epoll_wait " ~ to!string(errno));
8292 				}
8293 			} else {
8294 				int nfds = poll(pollfds.ptr, cast(int) pollfds.length, timeout_milliseconds);
8295 				size_t lastIdx = 0;
8296 			}
8297 
8298 			if(nfds == 0) {
8299 				eis.wait_timeout();
8300 				timeout += 15.seconds;
8301 			}
8302 
8303 			foreach(idx; 0 .. nfds) {
8304 				version(linux) {
8305 					auto flags = events[idx].events;
8306 					auto ioop = cast(IoOp*) events[idx].data.ptr;
8307 				} else {
8308 					IoOp* ioop;
8309 					foreach(tidx, thing; pollfds[lastIdx .. $]) {
8310 						if(thing.revents) {
8311 							ioop = ioops[thing.fd];
8312 							lastIdx += tidx + 1;
8313 							break;
8314 						}
8315 					}
8316 				}
8317 
8318 				//writeln(flags, " ", ioop.fd);
8319 
8320 				void newConnection() {
8321 					// on edge triggering, it is important that we get it all
8322 					while(true) {
8323 						version(Android) {
8324 							auto size = cast(int) addr.sizeof;
8325 						} else {
8326 							auto size = cast(uint) addr.sizeof;
8327 						}
8328 						auto ns = accept(sock, cast(sockaddr*) &addr, &size);
8329 						if(ns == -1) {
8330 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
8331 								// all done, got it all
8332 								break;
8333 							}
8334 							throw new Exception("accept " ~ to!string(errno));
8335 						}
8336 						cloexec(ns);
8337 
8338 						makeNonBlocking(ns);
8339 						auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096 * 4, &eis.handleLocalConnectionData);
8340 						niop.closeHandler = &eis.handleLocalConnectionClose;
8341 						niop.completeHandler = &eis.handleLocalConnectionComplete;
8342 						scope(failure) freeIoOp(niop);
8343 
8344 						version(linux) {
8345 							epoll_event nev;
8346 							nev.events = EPOLLIN | EPOLLET;
8347 							nev.data.ptr = niop;
8348 							if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ns, &nev) == -1)
8349 								throw new Exception("epoll_ctl " ~ to!string(errno));
8350 						} else {
8351 							bool found = false;
8352 							foreach(ref pfd; pollfds) {
8353 								if(pfd.fd < 0) {
8354 									pfd.fd = ns;
8355 									found = true;
8356 								}
8357 							}
8358 							if(!found)
8359 								pollfds ~= pollfd(ns, POLLIN);
8360 							ioops[ns] = niop;
8361 						}
8362 					}
8363 				}
8364 
8365 				bool newConnectionCondition() {
8366 					version(linux)
8367 						return ioop.fd == sock && (flags & EPOLLIN);
8368 					else
8369 						return pollfds[idx].fd == sock && (pollfds[idx].revents & POLLIN);
8370 				}
8371 
8372 				if(newConnectionCondition()) {
8373 					newConnection();
8374 				} else if(ioop.operation == IoOp.ReadSocketHandle) {
8375 					while(true) {
8376 						int in_fd;
8377 						auto got = read_fd(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, &in_fd);
8378 						if(got == -1) {
8379 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
8380 								// all done, got it all
8381 								if(ioop.completeHandler)
8382 									ioop.completeHandler(ioop);
8383 								break;
8384 							}
8385 							throw new Exception("recv " ~ to!string(errno));
8386 						}
8387 
8388 						if(got == 0) {
8389 							if(ioop.closeHandler) {
8390 								ioop.closeHandler(ioop);
8391 								version(linux) {} // nothing needed
8392 								else {
8393 									foreach(ref pfd; pollfds) {
8394 										if(pfd.fd == ioop.fd)
8395 											pfd.fd = -1;
8396 									}
8397 								}
8398 							}
8399 							close(ioop.fd);
8400 							freeIoOp(ioop);
8401 							break;
8402 						}
8403 
8404 						ioop.bufferLengthUsed = cast(int) got;
8405 						ioop.handler(ioop, in_fd);
8406 					}
8407 				} else if(ioop.operation == IoOp.Read) {
8408 					while(true) {
8409 						auto got = read(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length);
8410 						if(got == -1) {
8411 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
8412 								// all done, got it all
8413 								if(ioop.completeHandler)
8414 									ioop.completeHandler(ioop);
8415 								break;
8416 							}
8417 							throw new Exception("recv " ~ to!string(ioop.fd) ~ " errno " ~ to!string(errno));
8418 						}
8419 
8420 						if(got == 0) {
8421 							if(ioop.closeHandler)
8422 								ioop.closeHandler(ioop);
8423 							close(ioop.fd);
8424 							freeIoOp(ioop);
8425 							break;
8426 						}
8427 
8428 						ioop.bufferLengthUsed = cast(int) got;
8429 						if(ioop.handler(ioop, ioop.fd)) {
8430 							close(ioop.fd);
8431 							freeIoOp(ioop);
8432 							break;
8433 						}
8434 					}
8435 				}
8436 
8437 				// EPOLLHUP?
8438 			}
8439 		}
8440 	} else version(Windows) {
8441 
8442 		// set up a named pipe
8443 		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx
8444 		// https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsaduplicatesocketw
8445 		// https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getnamedpipeserverprocessid
8446 
8447 	} else static assert(0);
8448 }
8449 
8450 
8451 version(with_sendfd)
8452 // copied from the web and ported from C
8453 // see https://stackoverflow.com/questions/2358684/can-i-share-a-file-descriptor-to-another-process-on-linux-or-are-they-local-to-t
8454 ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd) {
8455 	msghdr msg;
8456 	iovec[1] iov;
8457 
8458 	version(OSX) {
8459 		//msg.msg_accrights = cast(cattr_t) &sendfd;
8460 		//msg.msg_accrightslen = int.sizeof;
8461 	} else version(Android) {
8462 	} else {
8463 		union ControlUnion {
8464 			cmsghdr cm;
8465 			char[CMSG_SPACE(int.sizeof)] control;
8466 		}
8467 
8468 		ControlUnion control_un;
8469 		cmsghdr* cmptr;
8470 
8471 		msg.msg_control = control_un.control.ptr;
8472 		msg.msg_controllen = control_un.control.length;
8473 
8474 		cmptr = CMSG_FIRSTHDR(&msg);
8475 		cmptr.cmsg_len = CMSG_LEN(int.sizeof);
8476 		cmptr.cmsg_level = SOL_SOCKET;
8477 		cmptr.cmsg_type = SCM_RIGHTS;
8478 		*(cast(int *) CMSG_DATA(cmptr)) = sendfd;
8479 	}
8480 
8481 	msg.msg_name = null;
8482 	msg.msg_namelen = 0;
8483 
8484 	iov[0].iov_base = ptr;
8485 	iov[0].iov_len = nbytes;
8486 	msg.msg_iov = iov.ptr;
8487 	msg.msg_iovlen = 1;
8488 
8489 	return sendmsg(fd, &msg, 0);
8490 }
8491 
8492 version(with_sendfd)
8493 // copied from the web and ported from C
8494 ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
8495 	msghdr msg;
8496 	iovec[1] iov;
8497 	ssize_t n;
8498 	int newfd;
8499 
8500 	version(OSX) {
8501 		//msg.msg_accrights = cast(cattr_t) recvfd;
8502 		//msg.msg_accrightslen = int.sizeof;
8503 	} else version(Android) {
8504 	} else {
8505 		union ControlUnion {
8506 			cmsghdr cm;
8507 			char[CMSG_SPACE(int.sizeof)] control;
8508 		}
8509 		ControlUnion control_un;
8510 		cmsghdr* cmptr;
8511 
8512 		msg.msg_control = control_un.control.ptr;
8513 		msg.msg_controllen = control_un.control.length;
8514 	}
8515 
8516 	msg.msg_name = null;
8517 	msg.msg_namelen = 0;
8518 
8519 	iov[0].iov_base = ptr;
8520 	iov[0].iov_len = nbytes;
8521 	msg.msg_iov = iov.ptr;
8522 	msg.msg_iovlen = 1;
8523 
8524 	if ( (n = recvmsg(fd, &msg, 0)) <= 0)
8525 		return n;
8526 
8527 	version(OSX) {
8528 		//if(msg.msg_accrightslen != int.sizeof)
8529 			//*recvfd = -1;
8530 	} else version(Android) {
8531 	} else {
8532 		if ( (cmptr = CMSG_FIRSTHDR(&msg)) != null &&
8533 				cmptr.cmsg_len == CMSG_LEN(int.sizeof)) {
8534 			if (cmptr.cmsg_level != SOL_SOCKET)
8535 				throw new Exception("control level != SOL_SOCKET");
8536 			if (cmptr.cmsg_type != SCM_RIGHTS)
8537 				throw new Exception("control type != SCM_RIGHTS");
8538 			*recvfd = *(cast(int *) CMSG_DATA(cmptr));
8539 		} else
8540 			*recvfd = -1;       /* descriptor was not passed */
8541 	}
8542 
8543 	return n;
8544 }
8545 /* end read_fd */
8546 
8547 
8548 /*
8549 	Event source stuff
8550 
8551 	The api is:
8552 
8553 	sendEvent(string url, string type, string data, int timeout = 60*10);
8554 
8555 	attachEventListener(string url, int fd, lastId)
8556 
8557 
8558 	It just sends to all attached listeners, and stores it until the timeout
8559 	for replaying via lastEventId.
8560 */
8561 
8562 /*
8563 	Session process stuff
8564 
8565 	it stores it all. the cgi object has a session object that can grab it
8566 
8567 	session may be done in the same process if possible, there is a version
8568 	switch to choose if you want to override.
8569 */
8570 
8571 struct DispatcherDefinition(alias dispatchHandler, DispatcherDetails = typeof(null)) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
8572 	alias handler = dispatchHandler;
8573 	string urlPrefix;
8574 	bool rejectFurther;
8575 	immutable(DispatcherDetails) details;
8576 }
8577 
8578 private string urlify(string name) pure {
8579 	return beautify(name, '-', true);
8580 }
8581 
8582 private string beautify(string name, char space = ' ', bool allLowerCase = false) pure {
8583 	if(name == "id")
8584 		return allLowerCase ? name : "ID";
8585 
8586 	char[160] buffer;
8587 	int bufferIndex = 0;
8588 	bool shouldCap = true;
8589 	bool shouldSpace;
8590 	bool lastWasCap;
8591 	foreach(idx, char ch; name) {
8592 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
8593 
8594 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
8595 			if(lastWasCap) {
8596 				// two caps in a row, don't change. Prolly acronym.
8597 			} else {
8598 				if(idx)
8599 					shouldSpace = true; // new word, add space
8600 			}
8601 
8602 			lastWasCap = true;
8603 		} else {
8604 			lastWasCap = false;
8605 		}
8606 
8607 		if(shouldSpace) {
8608 			buffer[bufferIndex++] = space;
8609 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
8610 			shouldSpace = false;
8611 		}
8612 		if(shouldCap) {
8613 			if(ch >= 'a' && ch <= 'z')
8614 				ch -= 32;
8615 			shouldCap = false;
8616 		}
8617 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
8618 			ch += 32;
8619 		buffer[bufferIndex++] = ch;
8620 	}
8621 	return buffer[0 .. bufferIndex].idup;
8622 }
8623 
8624 /*
8625 string urlFor(alias func)() {
8626 	return __traits(identifier, func);
8627 }
8628 */
8629 
8630 /++
8631 	UDA: The name displayed to the user in auto-generated HTML.
8632 
8633 	Default is `beautify(identifier)`.
8634 +/
8635 struct DisplayName {
8636 	string name;
8637 }
8638 
8639 /++
8640 	UDA: The name used in the URL or web parameter.
8641 
8642 	Default is `urlify(identifier)` for functions and `identifier` for parameters and data members.
8643 +/
8644 struct UrlName {
8645 	string name;
8646 }
8647 
8648 /++
8649 	UDA: default format to respond for this method
8650 +/
8651 struct DefaultFormat { string value; }
8652 
8653 class MissingArgumentException : Exception {
8654 	string functionName;
8655 	string argumentName;
8656 	string argumentType;
8657 
8658 	this(string functionName, string argumentName, string argumentType, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
8659 		this.functionName = functionName;
8660 		this.argumentName = argumentName;
8661 		this.argumentType = argumentType;
8662 
8663 		super("Missing Argument: " ~ this.argumentName, file, line, next);
8664 	}
8665 }
8666 
8667 /++
8668 	You can throw this from an api handler to indicate a 404 response. This is done by the presentExceptionAsHtml function in the presenter.
8669 
8670 	History:
8671 		Added December 15, 2021 (dub v10.5)
8672 +/
8673 class ResourceNotFoundException : Exception {
8674 	string resourceType;
8675 	string resourceId;
8676 
8677 	this(string resourceType, string resourceId, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
8678 		this.resourceType = resourceType;
8679 		this.resourceId = resourceId;
8680 
8681 		super("Resource not found: " ~ resourceType ~ " " ~ resourceId, file, line, next);
8682 	}
8683 
8684 }
8685 
8686 /++
8687 	This can be attached to any constructor or function called from the cgi system.
8688 
8689 	If it is present, the function argument can NOT be set from web params, but instead
8690 	is set to the return value of the given `func`.
8691 
8692 	If `func` can take a parameter of type [Cgi], it will be passed the one representing
8693 	the current request. Otherwise, it must take zero arguments.
8694 
8695 	Any params in your function of type `Cgi` are automatically assumed to take the cgi object
8696 	for the connection. Any of type [Session] (with an argument) is	also assumed to come from
8697 	the cgi object.
8698 
8699 	const arguments are also supported.
8700 +/
8701 struct ifCalledFromWeb(alias func) {}
8702 
8703 // it only looks at query params for GET requests, the rest must be in the body for a function argument.
8704 auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
8705 
8706 	// FIXME: any array of structs should also be settable or gettable from csv as well.
8707 
8708 	// FIXME: think more about checkboxes and bools.
8709 
8710 	import std.traits;
8711 
8712 	Parameters!method params;
8713 	alias idents = ParameterIdentifierTuple!method;
8714 	alias defaults = ParameterDefaults!method;
8715 
8716 	const(string)[] names;
8717 	const(string)[] values;
8718 
8719 	// first, check for missing arguments and initialize to defaults if necessary
8720 
8721 	static if(is(typeof(method) P == __parameters))
8722 	foreach(idx, param; P) {{
8723 		// see: mustNotBeSetFromWebParams
8724 		static if(is(param : Cgi)) {
8725 			static assert(!is(param == immutable));
8726 			cast() params[idx] = cgi;
8727 		} else static if(is(param == Session!D, D)) {
8728 			static assert(!is(param == immutable));
8729 			cast() params[idx] = cgi.getSessionObject!D();
8730 		} else {
8731 			bool populated;
8732 			foreach(uda; __traits(getAttributes, P[idx .. idx + 1])) {
8733 				static if(is(uda == ifCalledFromWeb!func, alias func)) {
8734 					static if(is(typeof(func(cgi))))
8735 						params[idx] = func(cgi);
8736 					else
8737 						params[idx] = func();
8738 
8739 					populated = true;
8740 				}
8741 			}
8742 
8743 			if(!populated) {
8744 				static if(__traits(compiles, { params[idx] = param.getAutomaticallyForCgi(cgi); } )) {
8745 					params[idx] = param.getAutomaticallyForCgi(cgi);
8746 					populated = true;
8747 				}
8748 			}
8749 
8750 			if(!populated) {
8751 				auto ident = idents[idx];
8752 				if(cgi.requestMethod == Cgi.RequestMethod.GET) {
8753 					if(ident !in cgi.get) {
8754 						static if(is(defaults[idx] == void)) {
8755 							static if(is(param == bool))
8756 								params[idx] = false;
8757 							else
8758 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
8759 						} else
8760 							params[idx] = defaults[idx];
8761 					}
8762 				} else {
8763 					if(ident !in cgi.post) {
8764 						static if(is(defaults[idx] == void)) {
8765 							static if(is(param == bool))
8766 								params[idx] = false;
8767 							else
8768 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
8769 						} else
8770 							params[idx] = defaults[idx];
8771 					}
8772 				}
8773 			}
8774 		}
8775 	}}
8776 
8777 	// second, parse the arguments in order to build up arrays, etc.
8778 
8779 	static bool setVariable(T)(string name, string paramName, T* what, string value) {
8780 		static if(is(T == struct)) {
8781 			if(name == paramName) {
8782 				*what = T.init;
8783 				return true;
8784 			} else {
8785 				// could be a child. gonna allow either obj.field OR obj[field]
8786 
8787 				string afterName;
8788 
8789 				if(name[paramName.length] == '[') {
8790 					int count = 1;
8791 					auto idx = paramName.length + 1;
8792 					while(idx < name.length && count > 0) {
8793 						if(name[idx] == '[')
8794 							count++;
8795 						else if(name[idx] == ']') {
8796 							count--;
8797 							if(count == 0) break;
8798 						}
8799 						idx++;
8800 					}
8801 
8802 					if(idx == name.length)
8803 						return false; // malformed
8804 
8805 					auto insideBrackets = name[paramName.length + 1 .. idx];
8806 					afterName = name[idx + 1 .. $];
8807 
8808 					name = name[0 .. paramName.length];
8809 
8810 					paramName = insideBrackets;
8811 
8812 				} else if(name[paramName.length] == '.') {
8813 					paramName = name[paramName.length + 1 .. $];
8814 					name = paramName;
8815 					int p = 0;
8816 					foreach(ch; paramName) {
8817 						if(ch == '.' || ch == '[')
8818 							break;
8819 						p++;
8820 					}
8821 
8822 					afterName = paramName[p .. $];
8823 					paramName = paramName[0 .. p];
8824 				} else {
8825 					return false;
8826 				}
8827 
8828 				if(paramName.length)
8829 				// set the child member
8830 				switch(paramName) {
8831 					foreach(idx, memberName; __traits(allMembers, T))
8832 					static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
8833 						// data member!
8834 						case memberName:
8835 							return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
8836 					}
8837 					default:
8838 						// ok, not a member
8839 				}
8840 			}
8841 
8842 			return false;
8843 		} else static if(is(T == enum)) {
8844 			*what = to!T(value);
8845 			return true;
8846 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
8847 			*what = to!T(value);
8848 			return true;
8849 		} else static if(is(T == bool)) {
8850 			*what = value == "1" || value == "yes" || value == "t" || value == "true" || value == "on";
8851 			return true;
8852 		} else static if(is(T == K[], K)) {
8853 			K tmp;
8854 			if(name == paramName) {
8855 				// direct - set and append
8856 				if(setVariable(name, paramName, &tmp, value)) {
8857 					(*what) ~= tmp;
8858 					return true;
8859 				} else {
8860 					return false;
8861 				}
8862 			} else {
8863 				// child, append to last element
8864 				// FIXME: what about range violations???
8865 				auto ptr = &(*what)[(*what).length - 1];
8866 				return setVariable(name, paramName, ptr, value);
8867 
8868 			}
8869 		} else static if(is(T == V[K], K, V)) {
8870 			// assoc array, name[key] is valid
8871 			if(name == paramName) {
8872 				// no action necessary
8873 				return true;
8874 			} else if(name[paramName.length] == '[') {
8875 				int count = 1;
8876 				auto idx = paramName.length + 1;
8877 				while(idx < name.length && count > 0) {
8878 					if(name[idx] == '[')
8879 						count++;
8880 					else if(name[idx] == ']') {
8881 						count--;
8882 						if(count == 0) break;
8883 					}
8884 					idx++;
8885 				}
8886 				if(idx == name.length)
8887 					return false; // malformed
8888 
8889 				auto insideBrackets = name[paramName.length + 1 .. idx];
8890 				auto afterName = name[idx + 1 .. $];
8891 
8892 				auto k = to!K(insideBrackets);
8893 				V v;
8894 				if(auto ptr = k in *what)
8895 					v = *ptr;
8896 
8897 				name = name[0 .. paramName.length];
8898 				//writeln(name, afterName, " ", paramName);
8899 
8900 				auto ret = setVariable(name ~ afterName, paramName, &v, value);
8901 				if(ret) {
8902 					(*what)[k] = v;
8903 					return true;
8904 				}
8905 			}
8906 
8907 			return false;
8908 		} else {
8909 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
8910 		}
8911 
8912 		//return false;
8913 	}
8914 
8915 	void setArgument(string name, string value) {
8916 		int p;
8917 		foreach(ch; name) {
8918 			if(ch == '.' || ch == '[')
8919 				break;
8920 			p++;
8921 		}
8922 
8923 		auto paramName = name[0 .. p];
8924 
8925 		sw: switch(paramName) {
8926 			static if(is(typeof(method) P == __parameters))
8927 			foreach(idx, param; P) {
8928 				static if(mustNotBeSetFromWebParams!(P[idx], __traits(getAttributes, P[idx .. idx + 1]))) {
8929 					// cannot be set from the outside
8930 				} else {
8931 					case idents[idx]:
8932 						static if(is(param == Cgi.UploadedFile)) {
8933 							params[idx] = cgi.files[name];
8934 						} else {
8935 							setVariable(name, paramName, &params[idx], value);
8936 						}
8937 					break sw;
8938 				}
8939 			}
8940 			default:
8941 				// ignore; not relevant argument
8942 		}
8943 	}
8944 
8945 	if(cgi.requestMethod == Cgi.RequestMethod.GET) {
8946 		names = cgi.allGetNamesInOrder;
8947 		values = cgi.allGetValuesInOrder;
8948 	} else {
8949 		names = cgi.allPostNamesInOrder;
8950 		values = cgi.allPostValuesInOrder;
8951 	}
8952 
8953 	foreach(idx, name; names) {
8954 		setArgument(name, values[idx]);
8955 	}
8956 
8957 	static if(is(ReturnType!method == void)) {
8958 		typeof(null) ret;
8959 		dg(params);
8960 	} else {
8961 		auto ret = dg(params);
8962 	}
8963 
8964 	// FIXME: format return values
8965 	// options are: json, html, csv.
8966 	// also may need to wrap in envelope format: none, html, or json.
8967 	return ret;
8968 }
8969 
8970 private bool mustNotBeSetFromWebParams(T, attrs...)() {
8971 	static if(is(T : const(Cgi))) {
8972 		return true;
8973 	} else static if(is(T : const(Session!D), D)) {
8974 		return true;
8975 	} else static if(__traits(compiles, T.getAutomaticallyForCgi(Cgi.init))) {
8976 		return true;
8977 	} else {
8978 		foreach(uda; attrs)
8979 			static if(is(uda == ifCalledFromWeb!func, alias func))
8980 				return true;
8981 		return false;
8982 	}
8983 }
8984 
8985 private bool hasIfCalledFromWeb(attrs...)() {
8986 	foreach(uda; attrs)
8987 		static if(is(uda == ifCalledFromWeb!func, alias func))
8988 			return true;
8989 	return false;
8990 }
8991 
8992 /++
8993 	Implies POST path for the thing itself, then GET will get the automatic form.
8994 
8995 	The given customizer, if present, will be called as a filter on the Form object.
8996 
8997 	History:
8998 		Added December 27, 2020
8999 +/
9000 template AutomaticForm(alias customizer) { }
9001 
9002 /++
9003 	This is meant to be returned by a function that takes a form POST submission. You
9004 	want to set the url of the new resource it created, which is set as the http
9005 	Location header for a "201 Created" result, and you can also set a separate
9006 	destination for browser users, which it sets via a "Refresh" header.
9007 
9008 	The `resourceRepresentation` should generally be the thing you just created, and
9009 	it will be the body of the http response when formatted through the presenter.
9010 	The exact thing is up to you - it could just return an id, or the whole object, or
9011 	perhaps a partial object.
9012 
9013 	Examples:
9014 	---
9015 	class Test : WebObject {
9016 		@(Cgi.RequestMethod.POST)
9017 		CreatedResource!int makeThing(string value) {
9018 			return CreatedResource!int(value.to!int, "/resources/id");
9019 		}
9020 	}
9021 	---
9022 
9023 	History:
9024 		Added December 18, 2021
9025 +/
9026 struct CreatedResource(T) {
9027 	static if(!is(T == void))
9028 		T resourceRepresentation;
9029 	string resourceUrl;
9030 	string refreshUrl;
9031 }
9032 
9033 /+
9034 /++
9035 	This can be attached as a UDA to a handler to add a http Refresh header on a
9036 	successful run. (It will not be attached if the function throws an exception.)
9037 	This will refresh the browser the given number of seconds after the page loads,
9038 	to the url returned by `urlFunc`, which can be either a static function or a
9039 	member method of the current handler object.
9040 
9041 	You might use this for a POST handler that is normally used from ajax, but you
9042 	want it to degrade gracefully to a temporarily flashed message before reloading
9043 	the main page.
9044 
9045 	History:
9046 		Added December 18, 2021
9047 +/
9048 struct Refresh(alias urlFunc) {
9049 	int waitInSeconds;
9050 
9051 	string url() {
9052 		static if(__traits(isStaticFunction, urlFunc))
9053 			return urlFunc();
9054 		else static if(is(urlFunc : string))
9055 			return urlFunc;
9056 	}
9057 }
9058 +/
9059 
9060 /+
9061 /++
9062 	Sets a filter to be run before
9063 
9064 	A before function can do validations of params and log and stop the function from running.
9065 +/
9066 template Before(alias b) {}
9067 template After(alias b) {}
9068 +/
9069 
9070 /+
9071 	Argument conversions: for the most part, it is to!Thing(string).
9072 
9073 	But arrays and structs are a bit different. Arrays come from the cgi array. Thus
9074 	they are passed
9075 
9076 	arr=foo&arr=bar <-- notice the same name.
9077 
9078 	Structs are first declared with an empty thing, then have their members set individually,
9079 	with dot notation. The members are not required, just the initial declaration.
9080 
9081 	struct Foo {
9082 		int a;
9083 		string b;
9084 	}
9085 	void test(Foo foo){}
9086 
9087 	foo&foo.a=5&foo.b=str <-- the first foo declares the arg, the others set the members
9088 
9089 	Arrays of structs use this declaration.
9090 
9091 	void test(Foo[] foo) {}
9092 
9093 	foo&foo.a=5&foo.b=bar&foo&foo.a=9
9094 
9095 	You can use a hidden input field in HTML forms to achieve this. The value of the naked name
9096 	declaration is ignored.
9097 
9098 	Mind that order matters! The declaration MUST come first in the string.
9099 
9100 	Arrays of struct members follow this rule recursively.
9101 
9102 	struct Foo {
9103 		int[] a;
9104 	}
9105 
9106 	foo&foo.a=1&foo.a=2&foo&foo.a=1
9107 
9108 
9109 	Associative arrays are formatted with brackets, after a declaration, like structs:
9110 
9111 	foo&foo[key]=value&foo[other_key]=value
9112 
9113 
9114 	Note: for maximum compatibility with outside code, keep your types simple. Some libraries
9115 	do not support the strict ordering requirements to work with these struct protocols.
9116 
9117 	FIXME: also perhaps accept application/json to better work with outside trash.
9118 
9119 
9120 	Return values are also auto-formatted according to user-requested type:
9121 		for json, it loops over and converts.
9122 		for html, basic types are strings. Arrays are <ol>. Structs are <dl>. Arrays of structs are tables!
9123 +/
9124 
9125 /++
9126 	A web presenter is responsible for rendering things to HTML to be usable
9127 	in a web browser.
9128 
9129 	They are passed as template arguments to the base classes of [WebObject]
9130 
9131 	Responsible for displaying stuff as HTML. You can put this into your own aggregate
9132 	and override it. Use forwarding and specialization to customize it.
9133 
9134 	When you inherit from it, pass your own class as the CRTP argument. This lets the base
9135 	class templates and your overridden templates work with each other.
9136 
9137 	---
9138 	class MyPresenter : WebPresenter!(MyPresenter) {
9139 		@Override
9140 		void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret, typeof(null) meta) {
9141 			// present the CustomType
9142 		}
9143 		@Override
9144 		void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
9145 			// handle everything else via the super class, which will call
9146 			// back to your class when appropriate
9147 			super.presentSuccessfulReturnAsHtml(cgi, ret);
9148 		}
9149 	}
9150 	---
9151 
9152 	The meta argument in there can be overridden by your own facility.
9153 
9154 +/
9155 class WebPresenter(CRTP) {
9156 
9157 	/// A UDA version of the built-in `override`, to be used for static template polymorphism
9158 	/// If you override a plain method, use `override`. If a template, use `@Override`.
9159 	enum Override;
9160 
9161 	string script() {
9162 		return `
9163 		`;
9164 	}
9165 
9166 	string style() {
9167 		return `
9168 			:root {
9169 				--mild-border: #ccc;
9170 				--middle-border: #999;
9171 				--accent-color: #f2f2f2;
9172 				--sidebar-color: #fefefe;
9173 			}
9174 		` ~ genericFormStyling() ~ genericSiteStyling();
9175 	}
9176 
9177 	string genericFormStyling() {
9178 		return
9179 q"css
9180 			table.automatic-data-display {
9181 				border-collapse: collapse;
9182 				border: solid 1px var(--mild-border);
9183 			}
9184 
9185 			table.automatic-data-display td {
9186 				vertical-align: top;
9187 				border: solid 1px var(--mild-border);
9188 				padding: 2px 4px;
9189 			}
9190 
9191 			table.automatic-data-display th {
9192 				border: solid 1px var(--mild-border);
9193 				border-bottom: solid 1px var(--middle-border);
9194 				padding: 2px 4px;
9195 			}
9196 
9197 			ol.automatic-data-display {
9198 				margin: 0px;
9199 				list-style-position: inside;
9200 				padding: 0px;
9201 			}
9202 
9203 			dl.automatic-data-display {
9204 
9205 			}
9206 
9207 			.automatic-form {
9208 				max-width: 600px;
9209 			}
9210 
9211 			.form-field {
9212 				margin: 0.5em;
9213 				padding-left: 0.5em;
9214 			}
9215 
9216 			.label-text {
9217 				display: block;
9218 				font-weight: bold;
9219 				margin-left: -0.5em;
9220 			}
9221 
9222 			.submit-button-holder {
9223 				padding-left: 2em;
9224 			}
9225 
9226 			.add-array-button {
9227 
9228 			}
9229 css";
9230 	}
9231 
9232 	string genericSiteStyling() {
9233 		return
9234 q"css
9235 			* { box-sizing: border-box; }
9236 			html, body { margin: 0px; }
9237 			body {
9238 				font-family: sans-serif;
9239 			}
9240 			header {
9241 				background: var(--accent-color);
9242 				height: 64px;
9243 			}
9244 			footer {
9245 				background: var(--accent-color);
9246 				height: 64px;
9247 			}
9248 			#site-container {
9249 				display: flex;
9250 			}
9251 			main {
9252 				flex: 1 1 auto;
9253 				order: 2;
9254 				min-height: calc(100vh - 64px - 64px);
9255 				padding: 4px;
9256 				padding-left: 1em;
9257 			}
9258 			#sidebar {
9259 				flex: 0 0 16em;
9260 				order: 1;
9261 				background: var(--sidebar-color);
9262 			}
9263 css";
9264 	}
9265 
9266 	import arsd.dom;
9267 	Element htmlContainer() {
9268 		auto document = new Document(q"html
9269 <!DOCTYPE html>
9270 <html>
9271 <head>
9272 	<title>D Application</title>
9273 	<link rel="stylesheet" href="style.css" />
9274 </head>
9275 <body>
9276 	<header></header>
9277 	<div id="site-container">
9278 		<main></main>
9279 		<div id="sidebar"></div>
9280 	</div>
9281 	<footer></footer>
9282 	<script src="script.js"></script>
9283 </body>
9284 </html>
9285 html", true, true);
9286 
9287 		return document.requireSelector("main");
9288 	}
9289 
9290 	/// Renders a response as an HTTP error
9291 	void renderBasicError(Cgi cgi, int httpErrorCode) {
9292 		cgi.setResponseStatus(getHttpCodeText(httpErrorCode));
9293 		auto c = htmlContainer();
9294 		c.innerText = getHttpCodeText(httpErrorCode);
9295 		cgi.setResponseContentType("text/html; charset=utf-8");
9296 		cgi.write(c.parentDocument.toString(), true);
9297 	}
9298 
9299 	template methodMeta(alias method) {
9300 		enum methodMeta = null;
9301 	}
9302 
9303 	void presentSuccessfulReturn(T, Meta)(Cgi cgi, T ret, Meta meta, string format) {
9304 		// FIXME? format?
9305 		(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret, meta);
9306 	}
9307 
9308 	/// typeof(null) (which is also used to represent functions returning `void`) do nothing
9309 	/// in the default presenter - allowing the function to have full low-level control over the
9310 	/// response.
9311 	void presentSuccessfulReturn(T : typeof(null), Meta)(Cgi cgi, T ret, Meta meta, string format) {
9312 		// nothing intentionally!
9313 	}
9314 
9315 	/// Redirections are forwarded to [Cgi.setResponseLocation]
9316 	void presentSuccessfulReturn(T : Redirection, Meta)(Cgi cgi, T ret, Meta meta, string format) {
9317 		cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
9318 	}
9319 
9320 	/// [CreatedResource]s send code 201 and will set the given urls, then present the given representation.
9321 	void presentSuccessfulReturn(T : CreatedResource!R, Meta, R)(Cgi cgi, T ret, Meta meta, string format) {
9322 		cgi.setResponseStatus(getHttpCodeText(201));
9323 		if(ret.resourceUrl.length)
9324 			cgi.header("Location: " ~ ret.resourceUrl);
9325 		if(ret.refreshUrl.length)
9326 			cgi.header("Refresh: 0;" ~ ret.refreshUrl);
9327 		static if(!is(R == void))
9328 			presentSuccessfulReturn(cgi, ret.resourceRepresentation, meta, format);
9329 	}
9330 
9331 	/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
9332 	void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
9333 		bool outputted = false;
9334 		foreach(index, type; Types) {
9335 			if(ret.contains == index) {
9336 				assert(!outputted);
9337 				outputted = true;
9338 				(cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
9339 			}
9340 		}
9341 		if(!outputted)
9342 			assert(0);
9343 	}
9344 
9345 	/// An instance of the [arsd.dom.FileResource] interface has its own content type; assume it is a download of some sort.
9346 	void presentSuccessfulReturn(T : FileResource, Meta)(Cgi cgi, T ret, Meta meta, string format) {
9347 		cgi.setCache(true); // not necessarily true but meh
9348 		cgi.setResponseContentType(ret.contentType);
9349 		cgi.write(ret.getData(), true);
9350 	}
9351 
9352 	/// And the default handler for HTML will call [formatReturnValueAsHtml] and place it inside the [htmlContainer].
9353 	void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
9354 		auto container = this.htmlContainer();
9355 		container.appendChild(formatReturnValueAsHtml(ret));
9356 		cgi.write(container.parentDocument.toString(), true);
9357 	}
9358 
9359 	/++
9360 		If you override this, you will need to cast the exception type `t` dynamically,
9361 		but can then use the template arguments here to refer back to the function.
9362 
9363 		`func` is an alias to the method itself, and `dg` is a callable delegate to the same
9364 		method on the live object. You could, in theory, change arguments and retry, but I
9365 		provide that information mostly with the expectation that you will use them to make
9366 		useful forms or richer error messages for the user.
9367 	+/
9368 	void presentExceptionAsHtml(alias func, T)(Cgi cgi, Throwable t, T dg) {
9369 		Form af;
9370 		foreach(attr; __traits(getAttributes, func)) {
9371 			static if(__traits(isSame, attr, AutomaticForm)) {
9372 				af = createAutomaticFormForFunction!(func)(dg);
9373 			}
9374 		}
9375 		presentExceptionAsHtmlImpl(cgi, t, af);
9376 	}
9377 
9378 	void presentExceptionAsHtmlImpl(Cgi cgi, Throwable t, Form automaticForm) {
9379 		if(auto e = cast(ResourceNotFoundException) t) {
9380 			auto container = this.htmlContainer();
9381 
9382 			container.addChild("p", e.msg);
9383 
9384 			if(!cgi.outputtedResponseData)
9385 				cgi.setResponseStatus("404 Not Found");
9386 			cgi.write(container.parentDocument.toString(), true);
9387 		} else if(auto mae = cast(MissingArgumentException) t) {
9388 			if(automaticForm is null)
9389 				goto generic;
9390 			auto container = this.htmlContainer();
9391 			if(cgi.requestMethod == Cgi.RequestMethod.POST)
9392 				container.appendChild(Element.make("p", "Argument `" ~ mae.argumentName ~ "` of type `" ~ mae.argumentType ~ "` is missing"));
9393 			container.appendChild(automaticForm);
9394 
9395 			cgi.write(container.parentDocument.toString(), true);
9396 		} else {
9397 			generic:
9398 			auto container = this.htmlContainer();
9399 
9400 			// import std.stdio; writeln(t.toString());
9401 
9402 			container.appendChild(exceptionToElement(t));
9403 
9404 			container.addChild("h4", "GET");
9405 			foreach(k, v; cgi.get) {
9406 				auto deets = container.addChild("details");
9407 				deets.addChild("summary", k);
9408 				deets.addChild("div", v);
9409 			}
9410 
9411 			container.addChild("h4", "POST");
9412 			foreach(k, v; cgi.post) {
9413 				auto deets = container.addChild("details");
9414 				deets.addChild("summary", k);
9415 				deets.addChild("div", v);
9416 			}
9417 
9418 
9419 			if(!cgi.outputtedResponseData)
9420 				cgi.setResponseStatus("500 Internal Server Error");
9421 			cgi.write(container.parentDocument.toString(), true);
9422 		}
9423 	}
9424 
9425 	Element exceptionToElement(Throwable t) {
9426 		auto div = Element.make("div");
9427 		div.addClass("exception-display");
9428 
9429 		div.addChild("p", t.msg);
9430 		div.addChild("p", "Inner code origin: " ~ typeid(t).name ~ "@" ~ t.file ~ ":" ~ to!string(t.line));
9431 
9432 		auto pre = div.addChild("pre");
9433 		string s;
9434 		s = t.toString();
9435 		Element currentBox;
9436 		bool on = false;
9437 		foreach(line; s.splitLines) {
9438 			if(!on && line.startsWith("-----"))
9439 				on = true;
9440 			if(!on) continue;
9441 			if(line.indexOf("arsd/") != -1) {
9442 				if(currentBox is null) {
9443 					currentBox = pre.addChild("details");
9444 					currentBox.addChild("summary", "Framework code");
9445 				}
9446 				currentBox.addChild("span", line ~ "\n");
9447 			} else {
9448 				pre.addChild("span", line ~ "\n");
9449 				currentBox = null;
9450 			}
9451 		}
9452 
9453 		return div;
9454 	}
9455 
9456 	/++
9457 		Returns an element for a particular type
9458 	+/
9459 	Element elementFor(T)(string displayName, string name, Element function() udaSuggestion) {
9460 		import std.traits;
9461 
9462 		auto div = Element.make("div");
9463 		div.addClass("form-field");
9464 
9465 		static if(is(T == Cgi.UploadedFile)) {
9466 			Element lbl;
9467 			if(displayName !is null) {
9468 				lbl = div.addChild("label");
9469 				lbl.addChild("span", displayName, "label-text");
9470 				lbl.appendText(" ");
9471 			} else {
9472 				lbl = div;
9473 			}
9474 			auto i = lbl.addChild("input", name);
9475 			i.attrs.name = name;
9476 			i.attrs.type = "file";
9477 		} else static if(is(T == enum)) {
9478 			Element lbl;
9479 			if(displayName !is null) {
9480 				lbl = div.addChild("label");
9481 				lbl.addChild("span", displayName, "label-text");
9482 				lbl.appendText(" ");
9483 			} else {
9484 				lbl = div;
9485 			}
9486 			auto i = lbl.addChild("select", name);
9487 			i.attrs.name = name;
9488 
9489 			foreach(memberName; __traits(allMembers, T))
9490 				i.addChild("option", memberName);
9491 
9492 		} else static if(is(T == struct)) {
9493 			if(displayName !is null)
9494 				div.addChild("span", displayName, "label-text");
9495 			auto fieldset = div.addChild("fieldset");
9496 			fieldset.addChild("legend", beautify(T.stringof)); // FIXME
9497 			fieldset.addChild("input", name);
9498 			foreach(idx, memberName; __traits(allMembers, T))
9499 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
9500 				fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName, null /* FIXME: pull off the UDA */));
9501 			}
9502 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
9503 			Element lbl;
9504 			if(displayName !is null) {
9505 				lbl = div.addChild("label");
9506 				lbl.addChild("span", displayName, "label-text");
9507 				lbl.appendText(" ");
9508 			} else {
9509 				lbl = div;
9510 			}
9511 			Element i;
9512 			if(udaSuggestion) {
9513 				i = udaSuggestion();
9514 				lbl.appendChild(i);
9515 			} else {
9516 				i = lbl.addChild("input", name);
9517 			}
9518 			i.attrs.name = name;
9519 			static if(isSomeString!T)
9520 				i.attrs.type = "text";
9521 			else
9522 				i.attrs.type = "number";
9523 			if(i.tagName == "textarea")
9524 				i.textContent = to!string(T.init);
9525 			else
9526 				i.attrs.value = to!string(T.init);
9527 		} else static if(is(T == bool)) {
9528 			Element lbl;
9529 			if(displayName !is null) {
9530 				lbl = div.addChild("label");
9531 				lbl.addChild("span", displayName, "label-text");
9532 				lbl.appendText(" ");
9533 			} else {
9534 				lbl = div;
9535 			}
9536 			auto i = lbl.addChild("input", name);
9537 			i.attrs.type = "checkbox";
9538 			i.attrs.value = "true";
9539 			i.attrs.name = name;
9540 		} else static if(is(T == K[], K)) {
9541 			auto templ = div.addChild("template");
9542 			templ.appendChild(elementFor!(K)(null, name, null /* uda??*/));
9543 			if(displayName !is null)
9544 				div.addChild("span", displayName, "label-text");
9545 			auto btn = div.addChild("button");
9546 			btn.addClass("add-array-button");
9547 			btn.attrs.type = "button";
9548 			btn.innerText = "Add";
9549 			btn.attrs.onclick = q{
9550 				var a = document.importNode(this.parentNode.firstChild.content, true);
9551 				this.parentNode.insertBefore(a, this);
9552 			};
9553 		} else static if(is(T == V[K], K, V)) {
9554 			div.innerText = "assoc array not implemented for automatic form at this time";
9555 		} else {
9556 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
9557 		}
9558 
9559 
9560 		return div;
9561 	}
9562 
9563 	/// creates a form for gathering the function's arguments
9564 	Form createAutomaticFormForFunction(alias method, T)(T dg) {
9565 
9566 		auto form = cast(Form) Element.make("form");
9567 
9568 		form.method = "POST"; // FIXME
9569 
9570 		form.addClass("automatic-form");
9571 
9572 		string formDisplayName = beautify(__traits(identifier, method));
9573 		foreach(attr; __traits(getAttributes, method))
9574 			static if(is(typeof(attr) == DisplayName))
9575 				formDisplayName = attr.name;
9576 		form.addChild("h3", formDisplayName);
9577 
9578 		import std.traits;
9579 
9580 		//Parameters!method params;
9581 		//alias idents = ParameterIdentifierTuple!method;
9582 		//alias defaults = ParameterDefaults!method;
9583 
9584 		static if(is(typeof(method) P == __parameters))
9585 		foreach(idx, _; P) {{
9586 
9587 			alias param = P[idx .. idx + 1];
9588 
9589 			static if(!mustNotBeSetFromWebParams!(param[0], __traits(getAttributes, param))) {
9590 				string displayName = beautify(__traits(identifier, param));
9591 				Element function() element;
9592 				foreach(attr; __traits(getAttributes, param)) {
9593 					static if(is(typeof(attr) == DisplayName))
9594 						displayName = attr.name;
9595 					else static if(is(typeof(attr) : typeof(element))) {
9596 						element = attr;
9597 					}
9598 				}
9599 				auto i = form.appendChild(elementFor!(param)(displayName, __traits(identifier, param), element));
9600 				if(i.querySelector("input[type=file]") !is null)
9601 					form.setAttribute("enctype", "multipart/form-data");
9602 			}
9603 		}}
9604 
9605 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
9606 
9607 		return form;
9608 	}
9609 
9610 	/// creates a form for gathering object members (for the REST object thing right now)
9611 	Form createAutomaticFormForObject(T)(T obj) {
9612 		auto form = cast(Form) Element.make("form");
9613 
9614 		form.addClass("automatic-form");
9615 
9616 		form.addChild("h3", beautify(__traits(identifier, T)));
9617 
9618 		import std.traits;
9619 
9620 		//Parameters!method params;
9621 		//alias idents = ParameterIdentifierTuple!method;
9622 		//alias defaults = ParameterDefaults!method;
9623 
9624 		foreach(idx, memberName; __traits(derivedMembers, T)) {{
9625 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
9626 			string displayName = beautify(memberName);
9627 			Element function() element;
9628 			foreach(attr; __traits(getAttributes,  __traits(getMember, T, memberName)))
9629 				static if(is(typeof(attr) == DisplayName))
9630 					displayName = attr.name;
9631 				else static if(is(typeof(attr) : typeof(element)))
9632 					element = attr;
9633 			form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName, element));
9634 
9635 			form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
9636 		}}}
9637 
9638 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
9639 
9640 		return form;
9641 	}
9642 
9643 	///
9644 	Element formatReturnValueAsHtml(T)(T t) {
9645 		import std.traits;
9646 
9647 		static if(is(T == typeof(null))) {
9648 			return Element.make("span");
9649 		} else static if(is(T : Element)) {
9650 			return t;
9651 		} else static if(is(T == MultipleResponses!Types, Types...)) {
9652 			foreach(index, type; Types) {
9653 				if(t.contains == index)
9654 					return formatReturnValueAsHtml(t.payload[index]);
9655 			}
9656 			assert(0);
9657 		} else static if(is(T == Paginated!E, E)) {
9658 			auto e = Element.make("div").addClass("paginated-result");
9659 			e.appendChild(formatReturnValueAsHtml(t.items));
9660 			if(t.nextPageUrl.length)
9661 				e.appendChild(Element.make("a", "Next Page", t.nextPageUrl));
9662 			return e;
9663 		} else static if(isIntegral!T || isSomeString!T || isFloatingPoint!T) {
9664 			return Element.make("span", to!string(t), "automatic-data-display");
9665 		} else static if(is(T == V[K], K, V)) {
9666 			auto dl = Element.make("dl");
9667 			dl.addClass("automatic-data-display associative-array");
9668 			foreach(k, v; t) {
9669 				dl.addChild("dt", to!string(k));
9670 				dl.addChild("dd", formatReturnValueAsHtml(v));
9671 			}
9672 			return dl;
9673 		} else static if(is(T == struct)) {
9674 			auto dl = Element.make("dl");
9675 			dl.addClass("automatic-data-display struct");
9676 
9677 			foreach(idx, memberName; __traits(allMembers, T))
9678 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
9679 				dl.addChild("dt", beautify(memberName));
9680 				dl.addChild("dd", formatReturnValueAsHtml(__traits(getMember, t, memberName)));
9681 			}
9682 
9683 			return dl;
9684 		} else static if(is(T == bool)) {
9685 			return Element.make("span", t ? "true" : "false", "automatic-data-display");
9686 		} else static if(is(T == E[], E)) {
9687 			static if(is(E : RestObject!Proxy, Proxy)) {
9688 				// treat RestObject similar to struct
9689 				auto table = cast(Table) Element.make("table");
9690 				table.addClass("automatic-data-display");
9691 				string[] names;
9692 				foreach(idx, memberName; __traits(derivedMembers, E))
9693 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
9694 					names ~= beautify(memberName);
9695 				}
9696 				table.appendHeaderRow(names);
9697 
9698 				foreach(l; t) {
9699 					auto tr = table.appendRow();
9700 					foreach(idx, memberName; __traits(derivedMembers, E))
9701 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
9702 						static if(memberName == "id") {
9703 							string val = to!string(__traits(getMember, l, memberName));
9704 							tr.addChild("td", Element.make("a", val, E.stringof.toLower ~ "s/" ~ val)); // FIXME
9705 						} else {
9706 							tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
9707 						}
9708 					}
9709 				}
9710 
9711 				return table;
9712 			} else static if(is(E == struct)) {
9713 				// an array of structs is kinda special in that I like
9714 				// having those formatted as tables.
9715 				auto table = cast(Table) Element.make("table");
9716 				table.addClass("automatic-data-display");
9717 				string[] names;
9718 				foreach(idx, memberName; __traits(allMembers, E))
9719 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
9720 					names ~= beautify(memberName);
9721 				}
9722 				table.appendHeaderRow(names);
9723 
9724 				foreach(l; t) {
9725 					auto tr = table.appendRow();
9726 					foreach(idx, memberName; __traits(allMembers, E))
9727 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
9728 						tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
9729 					}
9730 				}
9731 
9732 				return table;
9733 			} else {
9734 				// otherwise, I will just make a list.
9735 				auto ol = Element.make("ol");
9736 				ol.addClass("automatic-data-display");
9737 				foreach(e; t)
9738 					ol.addChild("li", formatReturnValueAsHtml(e));
9739 				return ol;
9740 			}
9741 		} else static if(is(T : Object)) {
9742 			static if(is(typeof(t.toHtml()))) // FIXME: maybe i will make this an interface
9743 				return Element.make("div", t.toHtml());
9744 			else
9745 				return Element.make("div", t.toString());
9746 		} else static assert(0, "bad return value for cgi call " ~ T.stringof);
9747 
9748 		assert(0);
9749 	}
9750 
9751 }
9752 
9753 /++
9754 	The base class for the [dispatcher] function and object support.
9755 +/
9756 class WebObject {
9757 	//protected Cgi cgi;
9758 
9759 	protected void initialize(Cgi cgi) {
9760 		//this.cgi = cgi;
9761 	}
9762 }
9763 
9764 /++
9765 	Can return one of the given types, decided at runtime. The syntax
9766 	is to declare all the possible types in the return value, then you
9767 	can `return typeof(return)(...value...)` to construct it.
9768 
9769 	It has an auto-generated constructor for each value it can hold.
9770 
9771 	---
9772 	MultipleResponses!(Redirection, string) getData(int how) {
9773 		if(how & 1)
9774 			return typeof(return)(Redirection("http://dpldocs.info/"));
9775 		else
9776 			return typeof(return)("hi there!");
9777 	}
9778 	---
9779 
9780 	If you have lots of returns, you could, inside the function, `alias r = typeof(return);` to shorten it a little.
9781 +/
9782 struct MultipleResponses(T...) {
9783 	private size_t contains;
9784 	private union {
9785 		private T payload;
9786 	}
9787 
9788 	static foreach(index, type; T)
9789 	public this(type t) {
9790 		contains = index;
9791 		payload[index] = t;
9792 	}
9793 
9794 	/++
9795 		This is primarily for testing. It is your way of getting to the response.
9796 
9797 		Let's say you wanted to test that one holding a Redirection and a string actually
9798 		holds a string, by name of "test":
9799 
9800 		---
9801 			auto valueToTest = your_test_function();
9802 
9803 			valueToTest.visit(
9804 				(Redirection r) { assert(0); }, // got a redirection instead of a string, fail the test
9805 				(string s) { assert(s == "test"); } // right value, go ahead and test it.
9806 			);
9807 		---
9808 
9809 		History:
9810 			Was horribly broken until June 16, 2022. Ironically, I wrote it for tests but never actually tested it.
9811 			It tried to use alias lambdas before, but runtime delegates work much better so I changed it.
9812 	+/
9813 	void visit(Handlers...)(Handlers handlers) {
9814 		template findHandler(type, int count, HandlersToCheck...) {
9815 			static if(HandlersToCheck.length == 0)
9816 				enum findHandler = -1;
9817 			else {
9818 				static if(is(typeof(HandlersToCheck[0].init(type.init))))
9819 					enum findHandler = count;
9820 				else
9821 					enum findHandler = findHandler!(type, count + 1, HandlersToCheck[1 .. $]);
9822 			}
9823 		}
9824 		foreach(index, type; T) {
9825 			enum handlerIndex = findHandler!(type, 0, Handlers);
9826 			static if(handlerIndex == -1)
9827 				static assert(0, "Type " ~ type.stringof ~ " was not handled by visitor");
9828 			else {
9829 				if(index == this.contains)
9830 					handlers[handlerIndex](this.payload[index]);
9831 			}
9832 		}
9833 	}
9834 
9835 	/+
9836 	auto toArsdJsvar()() {
9837 		import arsd.jsvar;
9838 		return var(null);
9839 	}
9840 	+/
9841 }
9842 
9843 // FIXME: implement this somewhere maybe
9844 struct RawResponse {
9845 	int code;
9846 	string[] headers;
9847 	const(ubyte)[] responseBody;
9848 }
9849 
9850 /++
9851 	You can return this from [WebObject] subclasses for redirections.
9852 
9853 	(though note the static types means that class must ALWAYS redirect if
9854 	you return this directly. You might want to return [MultipleResponses] if it
9855 	can be conditional)
9856 +/
9857 struct Redirection {
9858 	string to; /// The URL to redirect to.
9859 	int code = 303; /// The HTTP code to return.
9860 }
9861 
9862 /++
9863 	Serves a class' methods, as a kind of low-state RPC over the web. To be used with [dispatcher].
9864 
9865 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar] unless you have overriden
9866 	the presenter in the dispatcher.
9867 
9868 	FIXME: explain this better
9869 
9870 	You can overload functions to a limited extent: you can provide a zero-arg and non-zero-arg function,
9871 	and non-zero-arg functions can filter via UDAs for various http methods. Do not attempt other overloads,
9872 	the runtime result of that is undefined.
9873 
9874 	A method is assumed to allow any http method unless it lists some in UDAs, in which case it is limited to only those.
9875 	(this might change, like maybe i will use pure as an indicator GET is ok. idk.)
9876 
9877 	$(WARNING
9878 		---
9879 		// legal in D, undefined runtime behavior with cgi.d, it may call either method
9880 		// even if you put different URL udas on it, the current code ignores them.
9881 		void foo(int a) {}
9882 		void foo(string a) {}
9883 		---
9884 	)
9885 
9886 	See_Also: [serveRestObject], [serveStaticFile]
9887 +/
9888 auto serveApi(T)(string urlPrefix) {
9889 	assert(urlPrefix[$ - 1] == '/');
9890 	return serveApiInternal!T(urlPrefix);
9891 }
9892 
9893 private string nextPieceFromSlash(ref string remainingUrl) {
9894 	if(remainingUrl.length == 0)
9895 		return remainingUrl;
9896 	int slash = 0;
9897 	while(slash < remainingUrl.length && remainingUrl[slash] != '/') // && remainingUrl[slash] != '.')
9898 		slash++;
9899 
9900 	// I am specifically passing `null` to differentiate it vs empty string
9901 	// so in your ctor, `items` means new T(null) and `items/` means new T("")
9902 	auto ident = remainingUrl.length == 0 ? null : remainingUrl[0 .. slash];
9903 	// so if it is the last item, the dot can be used to load an alternative view
9904 	// otherwise tho the dot is considered part of the identifier
9905 	// FIXME
9906 
9907 	// again notice "" vs null here!
9908 	if(slash == remainingUrl.length)
9909 		remainingUrl = null;
9910 	else
9911 		remainingUrl = remainingUrl[slash + 1 .. $];
9912 
9913 	return ident;
9914 }
9915 
9916 /++
9917 	UDA used to indicate to the [dispatcher] that a trailing slash should always be added to or removed from the url. It will do it as a redirect header as-needed.
9918 +/
9919 enum AddTrailingSlash;
9920 /// ditto
9921 enum RemoveTrailingSlash;
9922 
9923 private auto serveApiInternal(T)(string urlPrefix) {
9924 
9925 	import arsd.dom;
9926 	import arsd.jsvar;
9927 
9928 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
9929 		string remainingUrl = cgi.pathInfo[urlPrefix.length .. $];
9930 
9931 		try {
9932 			// see duplicated code below by searching subresource_ctor
9933 			// also see mustNotBeSetFromWebParams
9934 
9935 			static if(is(typeof(T.__ctor) P == __parameters)) {
9936 				P params;
9937 
9938 				foreach(pidx, param; P) {
9939 					static if(is(param : Cgi)) {
9940 						static assert(!is(param == immutable));
9941 						cast() params[pidx] = cgi;
9942 					} else static if(is(param == Session!D, D)) {
9943 						static assert(!is(param == immutable));
9944 						cast() params[pidx] = cgi.getSessionObject!D();
9945 
9946 					} else {
9947 						static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
9948 							foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
9949 								static if(is(uda == ifCalledFromWeb!func, alias func)) {
9950 									static if(is(typeof(func(cgi))))
9951 										params[pidx] = func(cgi);
9952 									else
9953 										params[pidx] = func();
9954 								}
9955 							}
9956 						} else {
9957 
9958 							static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
9959 								params[pidx] = param.getAutomaticallyForCgi(cgi);
9960 							} else static if(is(param == string)) {
9961 								auto ident = nextPieceFromSlash(remainingUrl);
9962 								params[pidx] = ident;
9963 							} else static assert(0, "illegal type for subresource " ~ param.stringof);
9964 						}
9965 					}
9966 				}
9967 
9968 				auto obj = new T(params);
9969 			} else {
9970 				auto obj = new T();
9971 			}
9972 
9973 			return internalHandlerWithObject(obj, remainingUrl, cgi, presenter);
9974 		} catch(Throwable t) {
9975 			switch(cgi.request("format", "html")) {
9976 				case "html":
9977 					static void dummy() {}
9978 					presenter.presentExceptionAsHtml!(dummy)(cgi, t, &dummy);
9979 				return true;
9980 				case "json":
9981 					var envelope = var.emptyObject;
9982 					envelope.success = false;
9983 					envelope.result = null;
9984 					envelope.error = t.toString();
9985 					cgi.setResponseContentType("application/json");
9986 					cgi.write(envelope.toJson(), true);
9987 				return true;
9988 				default:
9989 					throw t;
9990 				// return true;
9991 			}
9992 			// return true;
9993 		}
9994 
9995 		assert(0);
9996 	}
9997 
9998 	static bool internalHandlerWithObject(T, Presenter)(T obj, string remainingUrl, Cgi cgi, Presenter presenter) {
9999 
10000 		obj.initialize(cgi);
10001 
10002 		/+
10003 			Overload rules:
10004 				Any unique combination of HTTP verb and url path can be dispatched to function overloads
10005 				statically.
10006 
10007 				Moreover, some args vs no args can be overloaded dynamically.
10008 		+/
10009 
10010 		auto methodNameFromUrl = nextPieceFromSlash(remainingUrl);
10011 		/+
10012 		auto orig = remainingUrl;
10013 		assert(0,
10014 			(orig is null ? "__null" : orig)
10015 			~ " .. " ~
10016 			(methodNameFromUrl is null ? "__null" : methodNameFromUrl));
10017 		+/
10018 
10019 		if(methodNameFromUrl is null)
10020 			methodNameFromUrl = "__null";
10021 
10022 		string hack = to!string(cgi.requestMethod) ~ " " ~ methodNameFromUrl;
10023 
10024 		if(remainingUrl.length)
10025 			hack ~= "/";
10026 
10027 		switch(hack) {
10028 			foreach(methodName; __traits(derivedMembers, T))
10029 			static if(methodName != "__ctor")
10030 			foreach(idx, overload; __traits(getOverloads, T, methodName)) {
10031 			static if(is(typeof(overload) P == __parameters))
10032 			static if(is(typeof(overload) R == return))
10033 			static if(__traits(getProtection, overload) == "public" || __traits(getProtection, overload) == "export")
10034 			{
10035 			static foreach(urlNameForMethod; urlNamesForMethod!(overload, urlify(methodName)))
10036 			case urlNameForMethod:
10037 
10038 				static if(is(R : WebObject)) {
10039 					// if it returns a WebObject, it is considered a subresource. That means the url is dispatched like the ctor above.
10040 
10041 					// the only argument it is allowed to take, outside of cgi, session, and set up thingies, is a single string
10042 
10043 					// subresource_ctor
10044 					// also see mustNotBeSetFromWebParams
10045 
10046 					P params;
10047 
10048 					string ident;
10049 
10050 					foreach(pidx, param; P) {
10051 						static if(is(param : Cgi)) {
10052 							static assert(!is(param == immutable));
10053 							cast() params[pidx] = cgi;
10054 						} else static if(is(param == typeof(presenter))) {
10055 							cast() param[pidx] = presenter;
10056 						} else static if(is(param == Session!D, D)) {
10057 							static assert(!is(param == immutable));
10058 							cast() params[pidx] = cgi.getSessionObject!D();
10059 						} else {
10060 							static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
10061 								foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
10062 									static if(is(uda == ifCalledFromWeb!func, alias func)) {
10063 										static if(is(typeof(func(cgi))))
10064 											params[pidx] = func(cgi);
10065 										else
10066 											params[pidx] = func();
10067 									}
10068 								}
10069 							} else {
10070 
10071 								static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
10072 									params[pidx] = param.getAutomaticallyForCgi(cgi);
10073 								} else static if(is(param == string)) {
10074 									ident = nextPieceFromSlash(remainingUrl);
10075 									if(ident is null) {
10076 										// trailing slash mandated on subresources
10077 										cgi.setResponseLocation(cgi.pathInfo ~ "/");
10078 										return true;
10079 									} else {
10080 										params[pidx] = ident;
10081 									}
10082 								} else static assert(0, "illegal type for subresource " ~ param.stringof);
10083 							}
10084 						}
10085 					}
10086 
10087 					auto nobj = (__traits(getOverloads, obj, methodName)[idx])(ident);
10088 					return internalHandlerWithObject!(typeof(nobj), Presenter)(nobj, remainingUrl, cgi, presenter);
10089 				} else {
10090 					// 404 it if any url left - not a subresource means we don't get to play with that!
10091 					if(remainingUrl.length)
10092 						return false;
10093 
10094 					bool automaticForm;
10095 
10096 					foreach(attr; __traits(getAttributes, overload))
10097 						static if(is(attr == AddTrailingSlash)) {
10098 							if(remainingUrl is null) {
10099 								cgi.setResponseLocation(cgi.pathInfo ~ "/");
10100 								return true;
10101 							}
10102 						} else static if(is(attr == RemoveTrailingSlash)) {
10103 							if(remainingUrl !is null) {
10104 								cgi.setResponseLocation(cgi.pathInfo[0 .. lastIndexOf(cgi.pathInfo, "/")]);
10105 								return true;
10106 							}
10107 
10108 						} else static if(__traits(isSame, AutomaticForm, attr)) {
10109 							automaticForm = true;
10110 						}
10111 
10112 				/+
10113 				int zeroArgOverload = -1;
10114 				int overloadCount = cast(int) __traits(getOverloads, T, methodName).length;
10115 				bool calledWithZeroArgs = true;
10116 				foreach(k, v; cgi.get)
10117 					if(k != "format") {
10118 						calledWithZeroArgs = false;
10119 						break;
10120 					}
10121 				foreach(k, v; cgi.post)
10122 					if(k != "format") {
10123 						calledWithZeroArgs = false;
10124 						break;
10125 					}
10126 
10127 				// first, we need to go through and see if there is an empty one, since that
10128 				// changes inside. But otherwise, all the stuff I care about can be done via
10129 				// simple looping (other improper overloads might be flagged for runtime semantic check)
10130 				//
10131 				// an argument of type Cgi is ignored for these purposes
10132 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
10133 					static if(is(typeof(overload) P == __parameters))
10134 						static if(P.length == 0)
10135 							zeroArgOverload = cast(int) idx;
10136 						else static if(P.length == 1 && is(P[0] : Cgi))
10137 							zeroArgOverload = cast(int) idx;
10138 				}}
10139 				// FIXME: static assert if there are multiple non-zero-arg overloads usable with a single http method.
10140 				bool overloadHasBeenCalled = false;
10141 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
10142 					bool callFunction = true;
10143 					// there is a zero arg overload and this is NOT it, and we have zero args - don't call this
10144 					if(overloadCount > 1 && zeroArgOverload != -1 && idx != zeroArgOverload && calledWithZeroArgs)
10145 						callFunction = false;
10146 					// if this is the zero-arg overload, obviously it cannot be called if we got any args.
10147 					if(overloadCount > 1 && idx == zeroArgOverload && !calledWithZeroArgs)
10148 						callFunction = false;
10149 
10150 					// FIXME: so if you just add ?foo it will give the error below even when. this might not be a great idea.
10151 
10152 					bool hadAnyMethodRestrictions = false;
10153 					bool foundAcceptableMethod = false;
10154 					foreach(attr; __traits(getAttributes, overload)) {
10155 						static if(is(typeof(attr) == Cgi.RequestMethod)) {
10156 							hadAnyMethodRestrictions = true;
10157 							if(attr == cgi.requestMethod)
10158 								foundAcceptableMethod = true;
10159 						}
10160 					}
10161 
10162 					if(hadAnyMethodRestrictions && !foundAcceptableMethod)
10163 						callFunction = false;
10164 
10165 					/+
10166 						The overloads we really want to allow are the sane ones
10167 						from the web perspective. Which is likely on HTTP verbs,
10168 						for the most part, but might also be potentially based on
10169 						some args vs zero args, or on argument names. Can't really
10170 						do argument types very reliable through the web though; those
10171 						should probably be different URLs.
10172 
10173 						Even names I feel is better done inside the function, so I'm not
10174 						going to support that here. But the HTTP verbs and zero vs some
10175 						args makes sense - it lets you define custom forms pretty easily.
10176 
10177 						Moreover, I'm of the opinion that empty overload really only makes
10178 						sense on GET for this case. On a POST, it is just a missing argument
10179 						exception and that should be handled by the presenter. But meh, I'll
10180 						let the user define that, D only allows one empty arg thing anyway
10181 						so the method UDAs are irrelevant.
10182 					+/
10183 					if(callFunction)
10184 				+/
10185 
10186 					if(automaticForm && cgi.requestMethod == Cgi.RequestMethod.GET) {
10187 						// Should I still show the form on a json thing? idk...
10188 						auto ret = presenter.createAutomaticFormForFunction!((__traits(getOverloads, obj, methodName)[idx]))(&(__traits(getOverloads, obj, methodName)[idx]));
10189 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), "html");
10190 						return true;
10191 					}
10192 					switch(cgi.request("format", defaultFormat!overload())) {
10193 						case "html":
10194 							// a void return (or typeof(null) lol) means you, the user, is doing it yourself. Gives full control.
10195 							try {
10196 
10197 								auto ret = callFromCgi!(__traits(getOverloads, obj, methodName)[idx])(&(__traits(getOverloads, obj, methodName)[idx]), cgi);
10198 								presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), "html");
10199 							} catch(Throwable t) {
10200 								presenter.presentExceptionAsHtml!(__traits(getOverloads, obj, methodName)[idx])(cgi, t, &(__traits(getOverloads, obj, methodName)[idx]));
10201 							}
10202 						return true;
10203 						case "json":
10204 							auto ret = callFromCgi!(__traits(getOverloads, obj, methodName)[idx])(&(__traits(getOverloads, obj, methodName)[idx]), cgi);
10205 							static if(is(typeof(ret) == MultipleResponses!Types, Types...)) {
10206 								var json;
10207 								foreach(index, type; Types) {
10208 									if(ret.contains == index)
10209 										json = ret.payload[index];
10210 								}
10211 							} else {
10212 								var json = ret;
10213 							}
10214 							var envelope = json; // var.emptyObject;
10215 							/*
10216 							envelope.success = true;
10217 							envelope.result = json;
10218 							envelope.error = null;
10219 							*/
10220 							cgi.setResponseContentType("application/json");
10221 							cgi.write(envelope.toJson(), true);
10222 						return true;
10223 						default:
10224 							cgi.setResponseStatus("406 Not Acceptable"); // not exactly but sort of.
10225 						return true;
10226 					}
10227 				//}}
10228 
10229 				//cgi.header("Accept: POST"); // FIXME list the real thing
10230 				//cgi.setResponseStatus("405 Method Not Allowed"); // again, not exactly, but sort of. no overload matched our args, almost certainly due to http verb filtering.
10231 				//return true;
10232 				}
10233 			}
10234 			}
10235 			case "GET script.js":
10236 				cgi.setResponseContentType("text/javascript");
10237 				cgi.gzipResponse = true;
10238 				cgi.write(presenter.script(), true);
10239 				return true;
10240 			case "GET style.css":
10241 				cgi.setResponseContentType("text/css");
10242 				cgi.gzipResponse = true;
10243 				cgi.write(presenter.style(), true);
10244 				return true;
10245 			default:
10246 				return false;
10247 		}
10248 
10249 		assert(0);
10250 	}
10251 	return DispatcherDefinition!internalHandler(urlPrefix, false);
10252 }
10253 
10254 string defaultFormat(alias method)() {
10255 	bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true;
10256 	foreach(attr; __traits(getAttributes, method)) {
10257 		static if(is(typeof(attr) == DefaultFormat)) {
10258 			if(nonConstConditionForWorkingAroundASpuriousDmdWarning)
10259 				return attr.value;
10260 		}
10261 	}
10262 	return "html";
10263 }
10264 
10265 struct Paginated(T) {
10266 	T[] items;
10267 	string nextPageUrl;
10268 }
10269 
10270 template urlNamesForMethod(alias method, string default_) {
10271 	string[] helper() {
10272 		auto verb = Cgi.RequestMethod.GET;
10273 		bool foundVerb = false;
10274 		bool foundNoun = false;
10275 
10276 		string def = default_;
10277 
10278 		bool hasAutomaticForm = false;
10279 
10280 		foreach(attr; __traits(getAttributes, method)) {
10281 			static if(is(typeof(attr) == Cgi.RequestMethod)) {
10282 				verb = attr;
10283 				if(foundVerb)
10284 					assert(0, "Multiple http verbs on one function is not currently supported");
10285 				foundVerb = true;
10286 			}
10287 			static if(is(typeof(attr) == UrlName)) {
10288 				if(foundNoun)
10289 					assert(0, "Multiple url names on one function is not currently supported");
10290 				foundNoun = true;
10291 				def = attr.name;
10292 			}
10293 			static if(__traits(isSame, attr, AutomaticForm)) {
10294 				hasAutomaticForm = true;
10295 			}
10296 		}
10297 
10298 		if(def is null)
10299 			def = "__null";
10300 
10301 		string[] ret;
10302 
10303 		static if(is(typeof(method) R == return)) {
10304 			static if(is(R : WebObject)) {
10305 				def ~= "/";
10306 				foreach(v; __traits(allMembers, Cgi.RequestMethod))
10307 					ret ~= v ~ " " ~ def;
10308 			} else {
10309 				if(hasAutomaticForm) {
10310 					ret ~= "GET " ~ def;
10311 					ret ~= "POST " ~ def;
10312 				} else {
10313 					ret ~= to!string(verb) ~ " " ~ def;
10314 				}
10315 			}
10316 		} else static assert(0);
10317 
10318 		return ret;
10319 	}
10320 	enum urlNamesForMethod = helper();
10321 }
10322 
10323 
10324 	enum AccessCheck {
10325 		allowed,
10326 		denied,
10327 		nonExistant,
10328 	}
10329 
10330 	enum Operation {
10331 		show,
10332 		create,
10333 		replace,
10334 		remove,
10335 		update
10336 	}
10337 
10338 	enum UpdateResult {
10339 		accessDenied,
10340 		noSuchResource,
10341 		success,
10342 		failure,
10343 		unnecessary
10344 	}
10345 
10346 	enum ValidationResult {
10347 		valid,
10348 		invalid
10349 	}
10350 
10351 
10352 /++
10353 	The base of all REST objects, to be used with [serveRestObject] and [serveRestCollectionOf].
10354 
10355 	WARNING: this is not stable.
10356 +/
10357 class RestObject(CRTP) : WebObject {
10358 
10359 	import arsd.dom;
10360 	import arsd.jsvar;
10361 
10362 	/// Prepare the object to be shown.
10363 	void show() {}
10364 	/// ditto
10365 	void show(string urlId) {
10366 		load(urlId);
10367 		show();
10368 	}
10369 
10370 	/// Override this to provide access control to this object.
10371 	AccessCheck accessCheck(string urlId, Operation operation) {
10372 		return AccessCheck.allowed;
10373 	}
10374 
10375 	ValidationResult validate() {
10376 		// FIXME
10377 		return ValidationResult.valid;
10378 	}
10379 
10380 	string getUrlSlug() {
10381 		import std.conv;
10382 		static if(is(typeof(CRTP.id)))
10383 			return to!string((cast(CRTP) this).id);
10384 		else
10385 			return null;
10386 	}
10387 
10388 	// The functions with more arguments are the low-level ones,
10389 	// they forward to the ones with fewer arguments by default.
10390 
10391 	// POST on a parent collection - this is called from a collection class after the members are updated
10392 	/++
10393 		Given a populated object, this creates a new entry. Returns the url identifier
10394 		of the new object.
10395 	+/
10396 	string create(scope void delegate() applyChanges) {
10397 		applyChanges();
10398 		save();
10399 		return getUrlSlug();
10400 	}
10401 
10402 	void replace() {
10403 		save();
10404 	}
10405 	void replace(string urlId, scope void delegate() applyChanges) {
10406 		load(urlId);
10407 		applyChanges();
10408 		replace();
10409 	}
10410 
10411 	void update(string[] fieldList) {
10412 		save();
10413 	}
10414 	void update(string urlId, scope void delegate() applyChanges, string[] fieldList) {
10415 		load(urlId);
10416 		applyChanges();
10417 		update(fieldList);
10418 	}
10419 
10420 	void remove() {}
10421 
10422 	void remove(string urlId) {
10423 		load(urlId);
10424 		remove();
10425 	}
10426 
10427 	abstract void load(string urlId);
10428 	abstract void save();
10429 
10430 	Element toHtml(Presenter)(Presenter presenter) {
10431 		import arsd.dom;
10432 		import std.conv;
10433 		auto obj = cast(CRTP) this;
10434 		auto div = Element.make("div");
10435 		div.addClass("Dclass_" ~ CRTP.stringof);
10436 		div.dataset.url = getUrlSlug();
10437 		bool first = true;
10438 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
10439 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
10440 			if(!first) div.addChild("br"); else first = false;
10441 			div.appendChild(presenter.formatReturnValueAsHtml(__traits(getMember, obj, memberName)));
10442 		}
10443 		return div;
10444 	}
10445 
10446 	var toJson() {
10447 		import arsd.jsvar;
10448 		var v = var.emptyObject();
10449 		auto obj = cast(CRTP) this;
10450 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
10451 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
10452 			v[memberName] = __traits(getMember, obj, memberName);
10453 		}
10454 		return v;
10455 	}
10456 
10457 	/+
10458 	auto structOf(this This) {
10459 
10460 	}
10461 	+/
10462 }
10463 
10464 // FIXME XSRF token, prolly can just put in a cookie and then it needs to be copied to header or form hidden value
10465 // https://use-the-index-luke.com/sql/partial-results/fetch-next-page
10466 
10467 /++
10468 	Base class for REST collections.
10469 +/
10470 class CollectionOf(Obj) : RestObject!(CollectionOf) {
10471 	/// You might subclass this and use the cgi object's query params
10472 	/// to implement a search filter, for example.
10473 	///
10474 	/// FIXME: design a way to auto-generate that form
10475 	/// (other than using the WebObject thing above lol
10476 	// it'll prolly just be some searchParams UDA or maybe an enum.
10477 	//
10478 	// pagination too perhaps.
10479 	//
10480 	// and sorting too
10481 	IndexResult index() { return IndexResult.init; }
10482 
10483 	string[] sortableFields() { return null; }
10484 	string[] searchableFields() { return null; }
10485 
10486 	struct IndexResult {
10487 		Obj[] results;
10488 
10489 		string[] sortableFields;
10490 
10491 		string previousPageIdentifier;
10492 		string nextPageIdentifier;
10493 		string firstPageIdentifier;
10494 		string lastPageIdentifier;
10495 
10496 		int numberOfPages;
10497 	}
10498 
10499 	override string create(scope void delegate() applyChanges) { assert(0); }
10500 	override void load(string urlId) { assert(0); }
10501 	override void save() { assert(0); }
10502 	override void show() {
10503 		index();
10504 	}
10505 	override void show(string urlId) {
10506 		show();
10507 	}
10508 
10509 	/// Proxy POST requests (create calls) to the child collection
10510 	alias PostProxy = Obj;
10511 }
10512 
10513 /++
10514 	Serves a REST object, similar to a Ruby on Rails resource.
10515 
10516 	You put data members in your class. cgi.d will automatically make something out of those.
10517 
10518 	It will call your constructor with the ID from the URL. This may be null.
10519 	It will then populate the data members from the request.
10520 	It will then call a method, if present, telling what happened. You don't need to write these!
10521 	It finally returns a reply.
10522 
10523 	Your methods are passed a list of fields it actually set.
10524 
10525 	The URL mapping - despite my general skepticism of the wisdom - matches up with what most REST
10526 	APIs I have used seem to follow. (I REALLY want to put trailing slashes on it though. Works better
10527 	with relative linking. But meh.)
10528 
10529 	GET /items -> index. all values not set.
10530 	GET /items/id -> get. only ID will be set, other params ignored.
10531 	POST /items -> create. values set as given
10532 	PUT /items/id -> replace. values set as given
10533 		or POST /items/id with cgi.post["_method"] (thus urlencoded or multipart content-type) set to "PUT" to work around browser/html limitation
10534 		a GET with cgi.get["_method"] (in the url) set to "PUT" will render a form.
10535 	PATCH /items/id -> update. values set as given, list of changed fields passed
10536 		or POST /items/id with cgi.post["_method"] == "PATCH"
10537 	DELETE /items/id -> destroy. only ID guaranteed to be set
10538 		or POST /items/id with cgi.post["_method"] == "DELETE"
10539 
10540 	Following the stupid convention, there will never be a trailing slash here, and if it is there, it will
10541 	redirect you away from it.
10542 
10543 	API clients should set the `Accept` HTTP header to application/json or the cgi.get["_format"] = "json" var.
10544 
10545 	I will also let you change the default, if you must.
10546 
10547 	// One add-on is validation. You can issue a HTTP GET to a resource with _method = VALIDATE to check potential changes.
10548 
10549 	You can define sub-resources on your object inside the object. These sub-resources are also REST objects
10550 	that follow the same thing. They may be individual resources or collections themselves.
10551 
10552 	Your class is expected to have at least the following methods:
10553 
10554 	FIXME: i kinda wanna add a routes object to the initialize call
10555 
10556 	create
10557 		Create returns the new address on success, some code on failure.
10558 	show
10559 	index
10560 	update
10561 	remove
10562 
10563 	You will want to be able to customize the HTTP, HTML, and JSON returns but generally shouldn't have to - the defaults
10564 	should usually work. The returned JSON will include a field "href" on all returned objects along with "id". Or omething like that.
10565 
10566 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar].
10567 
10568 	NOT IMPLEMENTED
10569 
10570 
10571 	Really, a collection is a resource with a bunch of subresources.
10572 
10573 		GET /items
10574 			index because it is GET on the top resource
10575 
10576 		GET /items/foo
10577 			item but different than items?
10578 
10579 		class Items {
10580 
10581 		}
10582 
10583 	... but meh, a collection can be automated. not worth making it
10584 	a separate thing, let's look at a real example. Users has many
10585 	items and a virtual one, /users/current.
10586 
10587 	the individual users have properties and two sub-resources:
10588 	session, which is just one, and comments, a collection.
10589 
10590 	class User : RestObject!() { // no parent
10591 		int id;
10592 		string name;
10593 
10594 		// the default implementations of the urlId ones is to call load(that_id) then call the arg-less one.
10595 		// but you can override them to do it differently.
10596 
10597 		// any member which is of type RestObject can be linked automatically via href btw.
10598 
10599 		void show() {}
10600 		void show(string urlId) {} // automated! GET of this specific thing
10601 		void create() {} // POST on a parent collection - this is called from a collection class after the members are updated
10602 		void replace(string urlId) {} // this is the PUT; really, it just updates all fields.
10603 		void update(string urlId, string[] fieldList) {} // PATCH, it updates some fields.
10604 		void remove(string urlId) {} // DELETE
10605 
10606 		void load(string urlId) {} // the default implementation of show() populates the id, then
10607 
10608 		this() {}
10609 
10610 		mixin Subresource!Session;
10611 		mixin Subresource!Comment;
10612 	}
10613 
10614 	class Session : RestObject!() {
10615 		// the parent object may not be fully constructed/loaded
10616 		this(User parent) {}
10617 
10618 	}
10619 
10620 	class Comment : CollectionOf!Comment {
10621 		this(User parent) {}
10622 	}
10623 
10624 	class Users : CollectionOf!User {
10625 		// but you don't strictly need ANYTHING on a collection; it will just... collect. Implement the subobjects.
10626 		void index() {} // GET on this specific thing; just like show really, just different name for the different semantics.
10627 		User create() {} // You MAY implement this, but the default is to create a new object, populate it from args, and then call create() on the child
10628 	}
10629 
10630 +/
10631 auto serveRestObject(T)(string urlPrefix) {
10632 	assert(urlPrefix[0] == '/');
10633 	assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
10634 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
10635 		string url = cgi.pathInfo[urlPrefix.length .. $];
10636 
10637 		if(url.length && url[$ - 1] == '/') {
10638 			// remove the final slash...
10639 			cgi.setResponseLocation(cgi.scriptName ~ cgi.pathInfo[0 .. $ - 1]);
10640 			return true;
10641 		}
10642 
10643 		return restObjectServeHandler!T(cgi, presenter, url);
10644 	}
10645 	return DispatcherDefinition!internalHandler(urlPrefix, false);
10646 }
10647 
10648 /+
10649 /// Convenience method for serving a collection. It will be named the same
10650 /// as type T, just with an s at the end. If you need any further, just
10651 /// write the class yourself.
10652 auto serveRestCollectionOf(T)(string urlPrefix) {
10653 	assert(urlPrefix[0] == '/');
10654 	mixin(`static class `~T.stringof~`s : CollectionOf!(T) {}`);
10655 	return serveRestObject!(mixin(T.stringof ~ "s"))(urlPrefix);
10656 }
10657 +/
10658 
10659 bool restObjectServeHandler(T, Presenter)(Cgi cgi, Presenter presenter, string url) {
10660 	string urlId = null;
10661 	if(url.length && url[0] == '/') {
10662 		// asking for a subobject
10663 		urlId = url[1 .. $];
10664 		foreach(idx, ch; urlId) {
10665 			if(ch == '/') {
10666 				urlId = urlId[0 .. idx];
10667 				break;
10668 			}
10669 		}
10670 	}
10671 
10672 	// FIXME handle other subresources
10673 
10674 	static if(is(T : CollectionOf!(C), C)) {
10675 		if(urlId !is null) {
10676 			return restObjectServeHandler!(C, Presenter)(cgi, presenter, url); // FIXME?  urlId);
10677 		}
10678 	}
10679 
10680 	// FIXME: support precondition failed, if-modified-since, expectation failed, etc.
10681 
10682 	auto obj = new T();
10683 	obj.initialize(cgi);
10684 	// FIXME: populate reflection info delegates
10685 
10686 
10687 	// FIXME: I am not happy with this.
10688 	switch(urlId) {
10689 		case "script.js":
10690 			cgi.setResponseContentType("text/javascript");
10691 			cgi.gzipResponse = true;
10692 			cgi.write(presenter.script(), true);
10693 			return true;
10694 		case "style.css":
10695 			cgi.setResponseContentType("text/css");
10696 			cgi.gzipResponse = true;
10697 			cgi.write(presenter.style(), true);
10698 			return true;
10699 		default:
10700 			// intentionally blank
10701 	}
10702 
10703 
10704 
10705 
10706 	static void applyChangesTemplate(Obj)(Cgi cgi, Obj obj) {
10707 		foreach(idx, memberName; __traits(derivedMembers, Obj))
10708 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
10709 			__traits(getMember, obj, memberName) = cgi.request(memberName, __traits(getMember, obj, memberName));
10710 		}
10711 	}
10712 	void applyChanges() {
10713 		applyChangesTemplate(cgi, obj);
10714 	}
10715 
10716 	string[] modifiedList;
10717 
10718 	void writeObject(bool addFormLinks) {
10719 		if(cgi.request("format") == "json") {
10720 			cgi.setResponseContentType("application/json");
10721 			cgi.write(obj.toJson().toString, true);
10722 		} else {
10723 			auto container = presenter.htmlContainer();
10724 			if(addFormLinks) {
10725 				static if(is(T : CollectionOf!(C), C))
10726 				container.appendHtml(`
10727 					<form>
10728 						<button type="submit" name="_method" value="POST">Create New</button>
10729 					</form>
10730 				`);
10731 				else
10732 				container.appendHtml(`
10733 					<a href="..">Back</a>
10734 					<form>
10735 						<button type="submit" name="_method" value="PATCH">Edit</button>
10736 						<button type="submit" name="_method" value="DELETE">Delete</button>
10737 					</form>
10738 				`);
10739 			}
10740 			container.appendChild(obj.toHtml(presenter));
10741 			cgi.write(container.parentDocument.toString, true);
10742 		}
10743 	}
10744 
10745 	// FIXME: I think I need a set type in here....
10746 	// it will be nice to pass sets of members.
10747 
10748 	try
10749 	switch(cgi.requestMethod) {
10750 		case Cgi.RequestMethod.GET:
10751 			// I could prolly use template this parameters in the implementation above for some reflection stuff.
10752 			// sure, it doesn't automatically work in subclasses... but I instantiate here anyway...
10753 
10754 			// automatic forms here for usable basic auto site from browser.
10755 			// even if the format is json, it could actually send out the links and formats, but really there i'ma be meh.
10756 			switch(cgi.request("_method", "GET")) {
10757 				case "GET":
10758 					static if(is(T : CollectionOf!(C), C)) {
10759 						auto results = obj.index();
10760 						if(cgi.request("format", "html") == "html") {
10761 							auto container = presenter.htmlContainer();
10762 							auto html = presenter.formatReturnValueAsHtml(results.results);
10763 							container.appendHtml(`
10764 								<form>
10765 									<button type="submit" name="_method" value="POST">Create New</button>
10766 								</form>
10767 							`);
10768 
10769 							container.appendChild(html);
10770 							cgi.write(container.parentDocument.toString, true);
10771 						} else {
10772 							cgi.setResponseContentType("application/json");
10773 							import arsd.jsvar;
10774 							var json = var.emptyArray;
10775 							foreach(r; results.results) {
10776 								var o = var.emptyObject;
10777 								foreach(idx, memberName; __traits(derivedMembers, typeof(r)))
10778 								static if(__traits(compiles, __traits(getMember, r, memberName).offsetof)) {
10779 									o[memberName] = __traits(getMember, r, memberName);
10780 								}
10781 
10782 								json ~= o;
10783 							}
10784 							cgi.write(json.toJson(), true);
10785 						}
10786 					} else {
10787 						obj.show(urlId);
10788 						writeObject(true);
10789 					}
10790 				break;
10791 				case "PATCH":
10792 					obj.load(urlId);
10793 				goto case;
10794 				case "PUT":
10795 				case "POST":
10796 					// an editing form for the object
10797 					auto container = presenter.htmlContainer();
10798 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
10799 						auto form = (cgi.request("_method") == "POST") ? presenter.createAutomaticFormForObject(new obj.PostProxy()) : presenter.createAutomaticFormForObject(obj);
10800 					} else {
10801 						auto form = presenter.createAutomaticFormForObject(obj);
10802 					}
10803 					form.attrs.method = "POST";
10804 					form.setValue("_method", cgi.request("_method", "GET"));
10805 					container.appendChild(form);
10806 					cgi.write(container.parentDocument.toString(), true);
10807 				break;
10808 				case "DELETE":
10809 					// FIXME: a delete form for the object (can be phrased "are you sure?")
10810 					auto container = presenter.htmlContainer();
10811 					container.appendHtml(`
10812 						<form method="POST">
10813 							Are you sure you want to delete this item?
10814 							<input type="hidden" name="_method" value="DELETE" />
10815 							<input type="submit" value="Yes, Delete It" />
10816 						</form>
10817 
10818 					`);
10819 					cgi.write(container.parentDocument.toString(), true);
10820 				break;
10821 				default:
10822 					cgi.write("bad method\n", true);
10823 			}
10824 		break;
10825 		case Cgi.RequestMethod.POST:
10826 			// this is to allow compatibility with HTML forms
10827 			switch(cgi.request("_method", "POST")) {
10828 				case "PUT":
10829 					goto PUT;
10830 				case "PATCH":
10831 					goto PATCH;
10832 				case "DELETE":
10833 					goto DELETE;
10834 				case "POST":
10835 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
10836 						auto p = new obj.PostProxy();
10837 						void specialApplyChanges() {
10838 							applyChangesTemplate(cgi, p);
10839 						}
10840 						string n = p.create(&specialApplyChanges);
10841 					} else {
10842 						string n = obj.create(&applyChanges);
10843 					}
10844 
10845 					auto newUrl = cgi.scriptName ~ cgi.pathInfo ~ "/" ~ n;
10846 					cgi.setResponseLocation(newUrl);
10847 					cgi.setResponseStatus("201 Created");
10848 					cgi.write(`The object has been created.`);
10849 				break;
10850 				default:
10851 					cgi.write("bad method\n", true);
10852 			}
10853 			// FIXME this should be valid on the collection, but not the child....
10854 			// 303 See Other
10855 		break;
10856 		case Cgi.RequestMethod.PUT:
10857 		PUT:
10858 			obj.replace(urlId, &applyChanges);
10859 			writeObject(false);
10860 		break;
10861 		case Cgi.RequestMethod.PATCH:
10862 		PATCH:
10863 			obj.update(urlId, &applyChanges, modifiedList);
10864 			writeObject(false);
10865 		break;
10866 		case Cgi.RequestMethod.DELETE:
10867 		DELETE:
10868 			obj.remove(urlId);
10869 			cgi.setResponseStatus("204 No Content");
10870 		break;
10871 		default:
10872 			// FIXME: OPTIONS, HEAD
10873 	}
10874 	catch(Throwable t) {
10875 		presenter.presentExceptionAsHtml!(DUMMY)(cgi, t, null);
10876 	}
10877 
10878 	return true;
10879 }
10880 
10881 struct DUMMY {}
10882 
10883 /+
10884 struct SetOfFields(T) {
10885 	private void[0][string] storage;
10886 	void set(string what) {
10887 		//storage[what] =
10888 	}
10889 	void unset(string what) {}
10890 	void setAll() {}
10891 	void unsetAll() {}
10892 	bool isPresent(string what) { return false; }
10893 }
10894 +/
10895 
10896 /+
10897 enum readonly;
10898 enum hideonindex;
10899 +/
10900 
10901 /++
10902 	Serves a static file. To be used with [dispatcher].
10903 
10904 	See_Also: [serveApi], [serveRestObject], [dispatcher], [serveRedirect]
10905 +/
10906 auto serveStaticFile(string urlPrefix, string filename = null, string contentType = null) {
10907 // https://baus.net/on-tcp_cork/
10908 // man 2 sendfile
10909 	assert(urlPrefix[0] == '/');
10910 	if(filename is null)
10911 		filename = decodeComponent(urlPrefix[1 .. $]); // FIXME is this actually correct?
10912 	if(contentType is null) {
10913 		contentType = contentTypeFromFileExtension(filename);
10914 	}
10915 
10916 	static struct DispatcherDetails {
10917 		string filename;
10918 		string contentType;
10919 	}
10920 
10921 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
10922 		if(details.contentType.indexOf("image/") == 0)
10923 			cgi.setCache(true);
10924 		cgi.setResponseContentType(details.contentType);
10925 		cgi.write(std.file.read(details.filename), true);
10926 		return true;
10927 	}
10928 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(filename, contentType));
10929 }
10930 
10931 /++
10932 	Serves static data. To be used with [dispatcher].
10933 
10934 	History:
10935 		Added October 31, 2021
10936 +/
10937 auto serveStaticData(string urlPrefix, immutable(void)[] data, string contentType = null) {
10938 	assert(urlPrefix[0] == '/');
10939 	if(contentType is null) {
10940 		contentType = contentTypeFromFileExtension(urlPrefix);
10941 	}
10942 
10943 	static struct DispatcherDetails {
10944 		immutable(void)[] data;
10945 		string contentType;
10946 	}
10947 
10948 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
10949 		cgi.setCache(true);
10950 		cgi.setResponseContentType(details.contentType);
10951 		cgi.write(details.data, true);
10952 		return true;
10953 	}
10954 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType));
10955 }
10956 
10957 string contentTypeFromFileExtension(string filename) {
10958 		if(filename.endsWith(".png"))
10959 			return "image/png";
10960 		if(filename.endsWith(".apng"))
10961 			return "image/apng";
10962 		if(filename.endsWith(".svg"))
10963 			return "image/svg+xml";
10964 		if(filename.endsWith(".jpg"))
10965 			return "image/jpeg";
10966 		if(filename.endsWith(".html"))
10967 			return "text/html";
10968 		if(filename.endsWith(".css"))
10969 			return "text/css";
10970 		if(filename.endsWith(".js"))
10971 			return "application/javascript";
10972 		if(filename.endsWith(".wasm"))
10973 			return "application/wasm";
10974 		if(filename.endsWith(".mp3"))
10975 			return "audio/mpeg";
10976 		return null;
10977 }
10978 
10979 /// This serves a directory full of static files, figuring out the content-types from file extensions.
10980 /// It does not let you to descend into subdirectories (or ascend out of it, of course)
10981 auto serveStaticFileDirectory(string urlPrefix, string directory = null) {
10982 	assert(urlPrefix[0] == '/');
10983 	assert(urlPrefix[$-1] == '/');
10984 
10985 	static struct DispatcherDetails {
10986 		string directory;
10987 	}
10988 
10989 	if(directory is null)
10990 		directory = urlPrefix[1 .. $];
10991 
10992 	assert(directory[$-1] == '/');
10993 
10994 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
10995 		auto file = decodeComponent(cgi.pathInfo[urlPrefix.length .. $]); // FIXME: is this actually correct
10996 		if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
10997 			return false;
10998 
10999 		auto contentType = contentTypeFromFileExtension(file);
11000 
11001 		auto fn = details.directory ~ file;
11002 		if(std.file.exists(fn)) {
11003 			//if(contentType.indexOf("image/") == 0)
11004 				//cgi.setCache(true);
11005 			//else if(contentType.indexOf("audio/") == 0)
11006 				cgi.setCache(true);
11007 			cgi.setResponseContentType(contentType);
11008 			cgi.write(std.file.read(fn), true);
11009 			return true;
11010 		} else {
11011 			return false;
11012 		}
11013 	}
11014 
11015 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, false, DispatcherDetails(directory));
11016 }
11017 
11018 /++
11019 	Redirects one url to another
11020 
11021 	See_Also: [dispatcher], [serveStaticFile]
11022 +/
11023 auto serveRedirect(string urlPrefix, string redirectTo, int code = 303) {
11024 	assert(urlPrefix[0] == '/');
11025 	static struct DispatcherDetails {
11026 		string redirectTo;
11027 		string code;
11028 	}
11029 
11030 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
11031 		cgi.setResponseLocation(details.redirectTo, true, details.code);
11032 		return true;
11033 	}
11034 
11035 
11036 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(redirectTo, getHttpCodeText(code)));
11037 }
11038 
11039 /// Used exclusively with `dispatchTo`
11040 struct DispatcherData(Presenter) {
11041 	Cgi cgi; /// You can use this cgi object.
11042 	Presenter presenter; /// This is the presenter from top level, and will be forwarded to the sub-dispatcher.
11043 	size_t pathInfoStart; /// This is forwarded to the sub-dispatcher. It may be marked private later, or at least read-only.
11044 }
11045 
11046 /++
11047 	Dispatches the URL to a specific function.
11048 +/
11049 auto handleWith(alias handler)(string urlPrefix) {
11050 	// cuz I'm too lazy to do it better right now
11051 	static class Hack : WebObject {
11052 		static import std.traits;
11053 		@UrlName("")
11054 		auto handle(std.traits.Parameters!handler args) {
11055 			return handler(args);
11056 		}
11057 	}
11058 
11059 	return urlPrefix.serveApiInternal!Hack;
11060 }
11061 
11062 /++
11063 	Dispatches the URL (and anything under it) to another dispatcher function. The function should look something like this:
11064 
11065 	---
11066 	bool other(DD)(DD dd) {
11067 		return dd.dispatcher!(
11068 			"/whatever".serveRedirect("/success"),
11069 			"/api/".serveApi!MyClass
11070 		);
11071 	}
11072 	---
11073 
11074 	The `DD` in there will be an instance of [DispatcherData] which you can inspect, or forward to another dispatcher
11075 	here. It is a template to account for any Presenter type, so you can do compile-time analysis in your presenters.
11076 	Or, of course, you could just use the exact type in your own code.
11077 
11078 	You return true if you handle the given url, or false if not. Just returning the result of [dispatcher] will do a
11079 	good job.
11080 
11081 
11082 +/
11083 auto dispatchTo(alias handler)(string urlPrefix) {
11084 	assert(urlPrefix[0] == '/');
11085 	assert(urlPrefix[$-1] != '/');
11086 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
11087 		return handler(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
11088 	}
11089 
11090 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
11091 }
11092 
11093 /+
11094 /++
11095 	See [serveStaticFile] if you want to serve a file off disk.
11096 +/
11097 auto serveStaticData(string urlPrefix, const(void)[] data, string contentType) {
11098 
11099 }
11100 +/
11101 
11102 /++
11103 	A URL dispatcher.
11104 
11105 	---
11106 	if(cgi.dispatcher!(
11107 		"/api/".serveApi!MyApiClass,
11108 		"/objects/lol".serveRestObject!MyRestObject,
11109 		"/file.js".serveStaticFile,
11110 		"/admin/".dispatchTo!adminHandler
11111 	)) return;
11112 	---
11113 
11114 
11115 	You define a series of url prefixes followed by handlers.
11116 
11117 	[dispatchTo] will send the request to another function for handling.
11118 	You may want to do different pre- and post- processing there, for example,
11119 	an authorization check and different page layout. You can use different
11120 	presenters and different function chains. NOT IMPLEMENTED
11121 +/
11122 template dispatcher(definitions...) {
11123 	bool dispatcher(Presenter)(Cgi cgi, Presenter presenterArg = null) {
11124 		static if(is(Presenter == typeof(null))) {
11125 			static class GenericWebPresenter : WebPresenter!(GenericWebPresenter) {}
11126 			auto presenter = new GenericWebPresenter();
11127 		} else
11128 			alias presenter = presenterArg;
11129 
11130 		return dispatcher(DispatcherData!(typeof(presenter))(cgi, presenter, 0));
11131 	}
11132 
11133 	bool dispatcher(DispatcherData)(DispatcherData dispatcherData) if(!is(DispatcherData : Cgi)) {
11134 		// I can prolly make this more efficient later but meh.
11135 		foreach(definition; definitions) {
11136 			if(definition.rejectFurther) {
11137 				if(dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $] == definition.urlPrefix) {
11138 					auto ret = definition.handler(
11139 						dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
11140 						dispatcherData.cgi, dispatcherData.presenter, definition.details);
11141 					if(ret)
11142 						return true;
11143 				}
11144 			} else if(
11145 				dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $].startsWith(definition.urlPrefix) &&
11146 				// cgi.d dispatcher urls must be complete or have a /;
11147 				// "foo" -> thing should NOT match "foobar", just "foo" or "foo/thing"
11148 				(definition.urlPrefix[$-1] == '/' || (dispatcherData.pathInfoStart + definition.urlPrefix.length) == dispatcherData.cgi.pathInfo.length
11149 				|| dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart + definition.urlPrefix.length] == '/')
11150 				) {
11151 				auto ret = definition.handler(
11152 					dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
11153 					dispatcherData.cgi, dispatcherData.presenter, definition.details);
11154 				if(ret)
11155 					return true;
11156 			}
11157 		}
11158 		return false;
11159 	}
11160 }
11161 
11162 });
11163 
11164 private struct StackBuffer {
11165 	char[1024] initial = void;
11166 	char[] buffer;
11167 	size_t position;
11168 
11169 	this(int a) {
11170 		buffer = initial[];
11171 		position = 0;
11172 	}
11173 
11174 	void add(in char[] what) {
11175 		if(position + what.length > buffer.length)
11176 			buffer.length = position + what.length + 1024; // reallocate with GC to handle special cases
11177 		buffer[position .. position + what.length] = what[];
11178 		position += what.length;
11179 	}
11180 
11181 	void add(in char[] w1, in char[] w2, in char[] w3 = null) {
11182 		add(w1);
11183 		add(w2);
11184 		add(w3);
11185 	}
11186 
11187 	void add(long v) {
11188 		char[16] buffer = void;
11189 		auto pos = buffer.length;
11190 		bool negative;
11191 		if(v < 0) {
11192 			negative = true;
11193 			v = -v;
11194 		}
11195 		do {
11196 			buffer[--pos] = cast(char) (v % 10 + '0');
11197 			v /= 10;
11198 		} while(v);
11199 
11200 		if(negative)
11201 			buffer[--pos] = '-';
11202 
11203 		auto res = buffer[pos .. $];
11204 
11205 		add(res[]);
11206 	}
11207 
11208 	char[] get() @nogc {
11209 		return buffer[0 .. position];
11210 	}
11211 }
11212 
11213 // duplicated in http2.d
11214 private static string getHttpCodeText(int code) pure nothrow @nogc {
11215 	switch(code) {
11216 		case 200: return "200 OK";
11217 		case 201: return "201 Created";
11218 		case 202: return "202 Accepted";
11219 		case 203: return "203 Non-Authoritative Information";
11220 		case 204: return "204 No Content";
11221 		case 205: return "205 Reset Content";
11222 		case 206: return "206 Partial Content";
11223 		//
11224 		case 300: return "300 Multiple Choices";
11225 		case 301: return "301 Moved Permanently";
11226 		case 302: return "302 Found";
11227 		case 303: return "303 See Other";
11228 		case 304: return "304 Not Modified";
11229 		case 305: return "305 Use Proxy";
11230 		case 307: return "307 Temporary Redirect";
11231 		case 308: return "308 Permanent Redirect";
11232 
11233 		//
11234 		case 400: return "400 Bad Request";
11235 		case 401: return "401 Unauthorized";
11236 		case 402: return "402 Payment Required";
11237 		case 403: return "403 Forbidden";
11238 		case 404: return "404 Not Found";
11239 		case 405: return "405 Method Not Allowed";
11240 		case 406: return "406 Not Acceptable";
11241 		case 407: return "407 Proxy Authentication Required";
11242 		case 408: return "408 Request Timeout";
11243 		case 409: return "409 Conflict";
11244 		case 410: return "410 Gone";
11245 		case 411: return "411 Length Required";
11246 		case 412: return "412 Precondition Failed";
11247 		case 413: return "413 Payload Too Large";
11248 		case 414: return "414 URI Too Long";
11249 		case 415: return "415 Unsupported Media Type";
11250 		case 416: return "416 Range Not Satisfiable";
11251 		case 417: return "417 Expectation Failed";
11252 		case 418: return "418 I'm a teapot";
11253 		case 421: return "421 Misdirected Request";
11254 		case 422: return "422 Unprocessable Entity (WebDAV)";
11255 		case 423: return "423 Locked (WebDAV)";
11256 		case 424: return "424 Failed Dependency (WebDAV)";
11257 		case 425: return "425 Too Early";
11258 		case 426: return "426 Upgrade Required";
11259 		case 428: return "428 Precondition Required";
11260 		case 431: return "431 Request Header Fields Too Large";
11261 		case 451: return "451 Unavailable For Legal Reasons";
11262 
11263 		case 500: return "500 Internal Server Error";
11264 		case 501: return "501 Not Implemented";
11265 		case 502: return "502 Bad Gateway";
11266 		case 503: return "503 Service Unavailable";
11267 		case 504: return "504 Gateway Timeout";
11268 		case 505: return "505 HTTP Version Not Supported";
11269 		case 506: return "506 Variant Also Negotiates";
11270 		case 507: return "507 Insufficient Storage (WebDAV)";
11271 		case 508: return "508 Loop Detected (WebDAV)";
11272 		case 510: return "510 Not Extended";
11273 		case 511: return "511 Network Authentication Required";
11274 		//
11275 		default: assert(0, "Unsupported http code");
11276 	}
11277 }
11278 
11279 
11280 /+
11281 /++
11282 	This is the beginnings of my web.d 2.0 - it dispatches web requests to a class object.
11283 
11284 	It relies on jsvar.d and dom.d.
11285 
11286 
11287 	You can get javascript out of it to call. The generated functions need to look
11288 	like
11289 
11290 	function name(a,b,c,d,e) {
11291 		return _call("name", {"realName":a,"sds":b});
11292 	}
11293 
11294 	And _call returns an object you can call or set up or whatever.
11295 +/
11296 bool apiDispatcher()(Cgi cgi) {
11297 	import arsd.jsvar;
11298 	import arsd.dom;
11299 }
11300 +/
11301 version(linux)
11302 private extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
11303 /*
11304 Copyright: Adam D. Ruppe, 2008 - 2022
11305 License:   [http://www.boost.org/LICENSE_1_0.txt|Boost License 1.0].
11306 Authors: Adam D. Ruppe
11307 
11308 	Copyright Adam D. Ruppe 2008 - 2022.
11309 Distributed under the Boost Software License, Version 1.0.
11310    (See accompanying file LICENSE_1_0.txt or copy at
11311 	http://www.boost.org/LICENSE_1_0.txt)
11312 */