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. Offers both lower- and higher- level api options among other common (optional) things like websocket and event source serving support, session management, and job scheduling.
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 	Or:
78 	---
79 	import arsd.cgi;
80 
81 	class MyApi : WebObject {
82 		@UrlName("")
83 		string hello(string name = null) {
84 			if(name is null)
85 				return "Hello, world!";
86 			else
87 				return "Hello, " ~ name;
88 		}
89 	}
90 	mixin DispatcherMain!(
91 		"/".serveApi!MyApi
92 	);
93 	---
94 
95 	$(NOTE
96 		Please note that using the higher-level api will add a dependency on arsd.dom and arsd.jsvar to your application.
97 		If you use `dmd -i` or `ldc2 -i` to build, it will just work, but with dub, you will have do `dub add arsd-official:jsvar`
98 		and `dub add arsd-official:dom` yourself.
99 	)
100 
101 	Test on console (works in any interface mode):
102 	$(CONSOLE
103 		$ ./cgi_hello GET / name=whatever
104 	)
105 
106 	If using http version (default on `dub` builds, or on custom builds when passing `-version=embedded_httpd` to dmd):
107 	$(CONSOLE
108 		$ ./cgi_hello --port 8080
109 		# now you can go to http://localhost:8080/?name=whatever
110 	)
111 
112 	Please note: the default port for http is 8085 and for scgi 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 code your own with [RequestServer], however.
113 
114 
115 	Build_Configurations:
116 
117 	cgi.d tries to be flexible to meet your needs. It is possible to configure it both at runtime (by writing your own `main` function and constructing a [RequestServer] object) or at compile time using the `version` switch to the compiler or a dub `subConfiguration`.
118 
119 	If you are using `dub`, use:
120 
121 	```sdlang
122 	subConfiguration "arsd-official:cgi" "VALUE_HERE"
123 	```
124 
125 	or to dub.json:
126 
127 	```json
128         	"subConfigurations": {"arsd-official:cgi": "VALUE_HERE"}
129 	```
130 
131 	to change versions. The possible options for `VALUE_HERE` are:
132 
133 	$(LIST
134 		* `embedded_httpd` for the embedded httpd version (built-in web server). This is the default for dub builds. You can run the program then connect directly to it from your browser. Note: prior to version 11, this would be embedded_httpd_processes on Linux and embedded_httpd_threads everywhere else. It now means embedded_httpd_hybrid everywhere supported and embedded_httpd_threads everywhere else.
135 		* `cgi` for traditional cgi binaries. These are run by an outside web server as-needed to handle requests.
136 		* `fastcgi` for FastCGI builds. FastCGI is managed from an outside helper, there's one built into Microsoft IIS, Apache httpd, and Lighttpd, and a generic program you can use with nginx called `spawn-fcgi`. If you don't already know how to use it, I suggest you use one of the other modes.
137 		* `scgi` for SCGI builds. SCGI is a simplified form of FastCGI, where you run the server as an application service which is proxied by your outside webserver. Please note: on nginx make sure you add `scgi_param  PATH_INFO          $document_uri;` to the config!
138 		* `stdio_http` for speaking raw http over stdin and stdout. This is made for systemd services. See [RequestServer.serveSingleHttpConnectionOnStdio] for more information.
139 	)
140 
141 	With dmd, use:
142 
143 	$(TABLE_ROWS
144 
145 		* + Interfaces
146 		  + (mutually exclusive)
147 
148 		* - `-version=plain_cgi`
149 			- The default building the module alone without dub - a traditional, plain CGI executable will be generated.
150 		* - `-version=embedded_httpd`
151 			- A HTTP server will be embedded in the generated executable. This is default when building with dub.
152 		* - `-version=fastcgi`
153 			- A FastCGI executable will be generated.
154 		* - `-version=scgi`
155 			- A SCGI (SimpleCGI) executable will be generated.
156 		* - `-version=embedded_httpd_hybrid`
157 			- A HTTP server that uses a combination of processes, threads, and fibers to better handle large numbers of idle connections. Recommended if you are going to serve websockets in a non-local application.
158 		* - `-version=embedded_httpd_threads`
159 			- 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)
160 		* - `-version=embedded_httpd_processes`
161 			- The embedded HTTP server will use a prefork style process pool. (use instead of plain `embedded_httpd` if you want this specific implementation)
162 		* - `-version=embedded_httpd_processes_accept_after_fork`
163 			- 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.
164 		* - `-version=stdio_http`
165 			- The embedded HTTP server will be spoken over stdin and stdout.
166 
167 		* + Tweaks
168 		  + (can be used together with others)
169 
170 		* - `-version=cgi_with_websocket`
171 			- The CGI class has websocket server support. (This is on by default now.)
172 
173 		* - `-version=with_openssl`
174 			- not currently used
175 		* - `-version=cgi_embedded_sessions`
176 			- The session server will be embedded in the cgi.d server process
177 		* - `-version=cgi_session_server_process`
178 			- The session will be provided in a separate process, provided by cgi.d.
179 	)
180 
181 	For example,
182 
183 	For CGI, `dmd yourfile.d cgi.d` then put the executable in your cgi-bin directory.
184 
185 	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).
186 
187 	For SCGI: `dmd yourfile.d cgi.d -version=scgi` and run the executable, providing a port number on the command line.
188 
189 	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.
190 
191 	Command_line_interface:
192 
193 	If using [GenericMain] or [DispatcherMain], an application using arsd.cgi will offer a command line interface out of the box.
194 
195 	See [RequestServer.listenSpec] for more information.
196 
197 	Simulating_requests:
198 
199 	If you are using one of the [GenericMain] or [DispatcherMain] mixins, or main with your own call to [RequestServer.trySimulatedRequest], you can simulate requests from your command-ine shell. Call the program like this:
200 
201 	$(CONSOLE
202 	./yourprogram GET / name=adr
203 	)
204 
205 	And it will print the result to stdout instead of running a server, regardless of build more..
206 
207 	CGI_Setup_tips:
208 
209 	On Apache, you may do `SetHandler cgi-script` in your `.htaccess` file to set a particular file to be run through the cgi program. Note that all "subdirectories" of it also run the program; if you configure `/foo` to be a cgi script, then going to `/foo/bar` will call your cgi handler function with `cgi.pathInfo == "/bar"`.
210 
211 	Overview_Of_Basic_Concepts:
212 
213 	cgi.d offers both lower-level handler apis as well as higher-level auto-dispatcher apis. For a lower-level handler function, you'll probably want to review the following functions:
214 
215 		Input: [Cgi.get], [Cgi.post], [Cgi.request], [Cgi.files], [Cgi.cookies], [Cgi.pathInfo], [Cgi.requestMethod],
216 		       and HTTP headers ([Cgi.headers], [Cgi.userAgent], [Cgi.referrer], [Cgi.accept], [Cgi.authorization], [Cgi.lastEventId])
217 
218 		Output: [Cgi.write], [Cgi.header], [Cgi.setResponseStatus], [Cgi.setResponseContentType], [Cgi.gzipResponse]
219 
220 		Cookies: [Cgi.setCookie], [Cgi.clearCookie], [Cgi.cookie], [Cgi.cookies]
221 
222 		Caching: [Cgi.setResponseExpires], [Cgi.updateResponseExpires], [Cgi.setCache]
223 
224 		Redirections: [Cgi.setResponseLocation]
225 
226 		Other Information: [Cgi.remoteAddress], [Cgi.https], [Cgi.port], [Cgi.scriptName], [Cgi.requestUri], [Cgi.getCurrentCompleteUri], [Cgi.onRequestBodyDataReceived]
227 
228 		Websockets: [Websocket], [websocketRequested], [acceptWebsocket]. For websockets, use the `embedded_httpd_hybrid` build mode for best results, because it is optimized for handling large numbers of idle connections compared to the other build modes.
229 
230 		Overriding behavior for special cases streaming input data: see the virtual functions [Cgi.handleIncomingDataChunk], [Cgi.prepareForIncomingDataChunks], [Cgi.cleanUpPostDataState]
231 
232 	A basic program using the lower-level api might look like:
233 
234 		---
235 		import arsd.cgi;
236 
237 		// you write a request handler which always takes a Cgi object
238 		void handler(Cgi cgi) {
239 			/+
240 				when the user goes to your site, suppose you are being hosted at http://example.com/yourapp
241 
242 				If the user goes to http://example.com/yourapp/test?name=value
243 				then the url will be parsed out into the following pieces:
244 
245 					cgi.pathInfo == "/test". This is everything after yourapp's name. (If you are doing an embedded http server, your app's name is blank, so pathInfo will be the whole path of the url.)
246 
247 					cgi.scriptName == "yourapp". With an embedded http server, this will be blank.
248 
249 					cgi.host == "example.com"
250 
251 					cgi.https == false
252 
253 					cgi.queryString == "name=value" (there's also cgi.search, which will be "?name=value", including the ?)
254 
255 					The query string is further parsed into the `get` and `getArray` members, so:
256 
257 					cgi.get == ["name": "value"], meaning you can do `cgi.get["name"] == "value"`
258 
259 					And
260 
261 					cgi.getArray == ["name": ["value"]].
262 
263 					Why is there both `get` and `getArray`? The standard allows names to be repeated. This can be very useful,
264 					it is how http forms naturally pass multiple items like a set of checkboxes. So `getArray` is the complete data
265 					if you need it. But since so often you only care about one value, the `get` member provides more convenient access.
266 
267 				We can use these members to process the request and build link urls. Other info from the request are in other members, we'll look at them later.
268 			+/
269 			switch(cgi.pathInfo) {
270 				// the home page will be a small html form that can set a cookie.
271 				case "/":
272 					cgi.write(`<!DOCTYPE html>
273 					<html>
274 					<body>
275 						<form method="POST" action="set-cookie">
276 							<label>Your name: <input type="text" name="name" /></label>
277 							<input type="submit" value="Submit" />
278 						</form>
279 					</body>
280 					</html>
281 					`, true); // the , true tells it that this is the one, complete response i want to send, allowing some optimizations.
282 				break;
283 				// POSTing to this will set a cookie with our submitted name
284 				case "/set-cookie":
285 					// HTTP has a number of request methods (also called "verbs") to tell
286 					// what you should do with the given resource.
287 					// The most common are GET and POST, the ones used in html forms.
288 					// You can check which one was used with the `cgi.requestMethod` property.
289 					if(cgi.requestMethod == Cgi.RequestMethod.POST) {
290 
291 						// headers like redirections need to be set before we call `write`
292 						cgi.setResponseLocation("read-cookie");
293 
294 						// just like how url params go into cgi.get/getArray, form data submitted in a POST
295 						// body go to cgi.post/postArray. Please note that a POST request can also have get
296 						// params in addition to post params.
297 						//
298 						// There's also a convenience function `cgi.request("name")` which checks post first,
299 						// then get if it isn't found there, and then returns a default value if it is in neither.
300 						if("name" in cgi.post) {
301 							// we can set cookies with a method too
302 							// again, cookies need to be set before calling `cgi.write`, since they
303 							// are a kind of header.
304 							cgi.setCookie("name" , cgi.post["name"]);
305 						}
306 
307 						// the user will probably never see this, since the response location
308 						// is an automatic redirect, but it is still best to say something anyway
309 						cgi.write("Redirecting you to see the cookie...", true);
310 					} else {
311 						// you can write out response codes and headers
312 						// as well as response bodies
313 						//
314 						// But always check the cgi docs before using the generic
315 						// `header` method - if there is a specific method for your
316 						// header, use it before resorting to the generic one to avoid
317 						// a header value from being sent twice.
318 						cgi.setResponseLocation("405 Method Not Allowed");
319 						// there is no special accept member, so you can use the generic header function
320 						cgi.header("Accept: POST");
321 						// but content type does have a method, so prefer to use it:
322 						cgi.setResponseContentType("text/plain");
323 
324 						// all the headers are buffered, and will be sent upon the first body
325 						// write. you can actually modify some of them before sending if need be.
326 						cgi.write("You must use the POST http verb on this resource.", true);
327 					}
328 				break;
329 				// and GETting this will read the cookie back out
330 				case "/read-cookie":
331 					// I did NOT pass `,true` here because this is writing a partial response.
332 					// It is possible to stream data to the user in chunks by writing partial
333 					// responses the calling `cgi.flush();` to send the partial response immediately.
334 					// normally, you'd only send partial chunks if you have to - it is better to build
335 					// a response as a whole and send it as a whole whenever possible - but here I want
336 					// to demo that you can.
337 					cgi.write("Hello, ");
338 					if("name" in cgi.cookies) {
339 						import arsd.dom; // dom.d provides a lot of helpers for html
340 						// since the cookie is set, we need to write it out properly to
341 						// avoid cross-site scripting attacks.
342 						//
343 						// Getting this stuff right automatically is a benefit of using the higher
344 						// level apis, but this demo is to show the fundamental building blocks, so
345 						// we're responsible to take care of it.
346 						cgi.write(htmlEntitiesEncode(cgi.cookies["name"]));
347 					} else {
348 						cgi.write("friend");
349 					}
350 
351 					// note that I never called cgi.setResponseContentType, since the default is text/html.
352 					// it doesn't hurt to do it explicitly though, just remember to do it before any cgi.write
353 					// calls.
354 				break;
355 				default:
356 					// no path matched
357 					cgi.setResponseStatus("404 Not Found");
358 					cgi.write("Resource not found.", true);
359 			}
360 		}
361 
362 		// and this adds the boilerplate to set up a server according to the
363 		// compile version configuration and call your handler as requests come in
364 		mixin GenericMain!handler; // the `handler` here is the name of your function
365 		---
366 
367 	Even if you plan to always use the higher-level apis, I still recommend you at least familiarize yourself with the lower level functions, since they provide the lightest weight, most flexible options to get down to business if you ever need them.
368 
369 	In the lower-level api, the [Cgi] object represents your HTTP transaction. It has functions to describe the request and for you to send your response. It leaves the details of how you o it up to you. The general guideline though is to avoid depending any variables outside your handler function, since there's no guarantee they will survive to another handler. You can use global vars as a lazy initialized cache, but you should always be ready in case it is empty. (One exception: if you use `-version=embedded_httpd_threads -version=cgi_no_fork`, then you can rely on it more, but you should still really write things assuming your function won't have anything survive beyond its return for max scalability and compatibility.)
370 
371 	A basic program using the higher-level apis might look like:
372 
373 		---
374 		/+
375 		import arsd.cgi;
376 
377 		struct LoginData {
378 			string currentUser;
379 		}
380 
381 		class AppClass : WebObject {
382 			string foo() {}
383 		}
384 
385 		mixin DispatcherMain!(
386 			"/assets/.serveStaticFileDirectory("assets/", true), // serve the files in the assets subdirectory
387 			"/".serveApi!AppClass,
388 			"/thing/".serveRestObject,
389 		);
390 		+/
391 		---
392 
393 	Guide_for_PHP_users:
394 		(Please note: I wrote this section in 2008. A lot of PHP hosts still ran 4.x back then, so it was common to avoid using classes - introduced in php 5 - to maintain compatibility! If you're coming from php more recently, this may not be relevant anymore, but still might help you.)
395 
396 		If you are coming from old-style PHP, here's a quick guide to help you get started:
397 
398 		$(SIDE_BY_SIDE
399 			$(COLUMN
400 				```php
401 				<?php
402 					$foo = $_POST["foo"];
403 					$bar = $_GET["bar"];
404 					$baz = $_COOKIE["baz"];
405 
406 					$user_ip = $_SERVER["REMOTE_ADDR"];
407 					$host = $_SERVER["HTTP_HOST"];
408 					$path = $_SERVER["PATH_INFO"];
409 
410 					setcookie("baz", "some value");
411 
412 					echo "hello!";
413 				?>
414 				```
415 			)
416 			$(COLUMN
417 				---
418 				import arsd.cgi;
419 				void app(Cgi cgi) {
420 					string foo = cgi.post["foo"];
421 					string bar = cgi.get["bar"];
422 					string baz = cgi.cookies["baz"];
423 
424 					string user_ip = cgi.remoteAddress;
425 					string host = cgi.host;
426 					string path = cgi.pathInfo;
427 
428 					cgi.setCookie("baz", "some value");
429 
430 					cgi.write("hello!");
431 				}
432 
433 				mixin GenericMain!app
434 				---
435 			)
436 		)
437 
438 		$(H3 Array elements)
439 
440 
441 		In PHP, you can give a form element a name like `"something[]"`, and then
442 		`$_POST["something"]` gives an array. In D, you can use whatever name
443 		you want, and access an array of values with the `cgi.getArray["name"]` and
444 		`cgi.postArray["name"]` members.
445 
446 		$(H3 Databases)
447 
448 		PHP has a lot of stuff in its standard library. cgi.d doesn't include most
449 		of these, but the rest of my arsd repository has much of it. For example,
450 		to access a MySQL database, download `database.d` and `mysql.d` from my
451 		github repo, and try this code (assuming, of course, your database is
452 		set up):
453 
454 		---
455 		import arsd.cgi;
456 		import arsd.mysql;
457 
458 		void app(Cgi cgi) {
459 			auto database = new MySql("localhost", "username", "password", "database_name");
460 			foreach(row; mysql.query("SELECT count(id) FROM people"))
461 				cgi.write(row[0] ~ " people in database");
462 		}
463 
464 		mixin GenericMain!app;
465 		---
466 
467 		Similar modules are available for PostgreSQL, Microsoft SQL Server, and SQLite databases,
468 		implementing the same basic interface.
469 
470 	See_Also:
471 
472 	You may also want to see [arsd.dom], [arsd.webtemplate], and maybe some functions from my old [arsd.html] for more code for making
473 	web applications. dom and webtemplate are used by the higher-level api here in cgi.d.
474 
475 	For working with json, try [arsd.jsvar].
476 
477 	[arsd.database], [arsd.mysql], [arsd.postgres], [arsd.mssql], and [arsd.sqlite] can help in
478 	accessing databases.
479 
480 	If you are looking to access a web application via HTTP, try [arsd.http2].
481 
482 	Copyright:
483 
484 	cgi.d copyright 2008-2023, Adam D. Ruppe. Provided under the Boost Software License.
485 
486 	Yes, this file is old, and yes, it is still actively maintained and used.
487 
488 	History:
489 		An import of `arsd.core` was added on March 21, 2023 (dub v11.0). Prior to this, the module's default configuration was completely stand-alone. You must now include the `core.d` file in your builds with `cgi.d`.
490 
491 		This change is primarily to integrate the event loops across the library, allowing you to more easily use cgi.d along with my other libraries like simpledisplay and http2.d. Previously, you'd have to run separate helper threads. Now, they can all automatically work together.
492 +/
493 module arsd.cgi;
494 
495 // FIXME: Nullable!T can be a checkbox that enables/disables the T on the automatic form
496 // and a SumType!(T, R) can be a radio box to pick between T and R to disclose the extra boxes on the automatic form
497 
498 /++
499 	This micro-example uses the [dispatcher] api to act as a simple http file server, serving files found in the current directory and its children.
500 +/
501 version(Demo)
502 unittest {
503 	import arsd.cgi;
504 
505 	mixin DispatcherMain!(
506 		"/".serveStaticFileDirectory(null, true)
507 	);
508 }
509 
510 /++
511 	Same as the previous example, but written out long-form without the use of [DispatcherMain] nor [GenericMain].
512 +/
513 version(Demo)
514 unittest {
515 	import arsd.cgi;
516 
517 	void requestHandler(Cgi cgi) {
518 		cgi.dispatcher!(
519 			"/".serveStaticFileDirectory(null, true)
520 		);
521 	}
522 
523 	// mixin GenericMain!requestHandler would add this function:
524 	void main(string[] args) {
525 		// this is all the content of [cgiMainImpl] which you can also call
526 
527 		// cgi.d embeds a few add on functions like real time event forwarders
528 		// and session servers it can run in other processes. this spawns them, if needed.
529 		if(tryAddonServers(args))
530 			return;
531 
532 		// cgi.d allows you to easily simulate http requests from the command line,
533 		// without actually starting a server. this function will do that.
534 		if(trySimulatedRequest!(requestHandler, Cgi)(args))
535 			return;
536 
537 		RequestServer server;
538 		// you can change the default port here if you like
539 		// server.listeningPort = 9000;
540 
541 		// then call this to let the command line args override your default
542 		server.configureFromCommandLine(args);
543 
544 		// here is where you could print out the listeningPort to the user if you wanted
545 
546 		// and serve the request(s) according to the compile configuration
547 		server.serve!(requestHandler)();
548 
549 		// or you could explicitly choose a serve mode like this:
550 		// server.serveEmbeddedHttp!requestHandler();
551 	}
552 }
553 
554 /++
555 	 cgi.d has built-in testing helpers too. These will provide mock requests and mock sessions that
556 	 otherwise run through the rest of the internal mechanisms to call your functions without actually
557 	 spinning up a server.
558 +/
559 version(Demo)
560 unittest {
561 	import arsd.cgi;
562 
563 	void requestHandler(Cgi cgi) {
564 
565 	}
566 
567 	// D doesn't let me embed a unittest inside an example unittest
568 	// so this is a function, but you can do it however in your real program
569 	/* unittest */ void runTests() {
570 		auto tester = new CgiTester(&requestHandler);
571 
572 		auto response = tester.GET("/");
573 		assert(response.code == 200);
574 	}
575 }
576 
577 /++
578 	The session system works via a built-in spawnable server.
579 
580 	Bugs:
581 		Requires addon servers, which are not implemented yet on Windows.
582 +/
583 version(Posix)
584 version(Demo)
585 unittest {
586 	import arsd.cgi;
587 
588 	struct SessionData {
589 		string userId;
590 	}
591 
592 	void handler(Cgi cgi) {
593 		auto session = cgi.getSessionObject!SessionData;
594 
595 		if(cgi.pathInfo == "/login") {
596 			session.userId = cgi.queryString;
597 			cgi.setResponseLocation("view");
598 		} else {
599 			cgi.write(session.userId);
600 		}
601 	}
602 
603 	mixin GenericMain!handler;
604 }
605 
606 static import std.file;
607 
608 static import arsd.core;
609 version(Posix)
610 import arsd.core : makeNonBlocking;
611 
612 import arsd.core : encodeUriComponent, decodeUriComponent;
613 
614 
615 // for a single thread, linear request thing, use:
616 // -version=embedded_httpd_threads -version=cgi_no_threads
617 
618 version(Posix) {
619 	version(CRuntime_Musl) {
620 
621 	} else version(minimal) {
622 
623 	} else {
624 		version(FreeBSD) {
625 			// not implemented on bsds
626 		} else version(OpenBSD) {
627 			// I never implemented the fancy stuff there either
628 		} else {
629 			version=with_breaking_cgi_features;
630 			version=with_sendfd;
631 			version=with_addon_servers;
632 		}
633 	}
634 }
635 
636 version(Windows) {
637 	version(minimal) {
638 
639 	} else {
640 		// not too concerned about gdc here since the mingw version is fairly new as well
641 		version=with_breaking_cgi_features;
642 	}
643 }
644 
645 // FIXME: can use the arsd.core function now but it is trivial anyway tbh
646 void cloexec(int fd) {
647 	version(Posix) {
648 		import core.sys.posix.fcntl;
649 		fcntl(fd, F_SETFD, FD_CLOEXEC);
650 	}
651 }
652 
653 void cloexec(Socket s) {
654 	version(Posix) {
655 		import core.sys.posix.fcntl;
656 		fcntl(s.handle, F_SETFD, FD_CLOEXEC);
657 	}
658 }
659 
660 // the servers must know about the connections to talk to them; the interfaces are vital
661 version(with_addon_servers)
662 	version=with_addon_servers_connections;
663 
664 version(embedded_httpd) {
665 	version(OSX)
666 		version = embedded_httpd_threads;
667 	else
668 		version=embedded_httpd_hybrid;
669 	/*
670 	version(with_openssl) {
671 		pragma(lib, "crypto");
672 		pragma(lib, "ssl");
673 	}
674 	*/
675 }
676 
677 version(embedded_httpd_hybrid) {
678 	version=embedded_httpd_threads;
679 	version(cgi_no_fork) {} else version(Posix)
680 		version=cgi_use_fork;
681 	version=cgi_use_fiber;
682 }
683 
684 version(cgi_use_fork)
685 	enum cgi_use_fork_default = true;
686 else
687 	enum cgi_use_fork_default = false;
688 
689 version(embedded_httpd_processes)
690 	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
691 
692 version(embedded_httpd_threads) {
693 	//  unless the user overrides the default..
694 	version(cgi_session_server_process)
695 		{}
696 	else
697 		version=cgi_embedded_sessions;
698 }
699 version(scgi) {
700 	//  unless the user overrides the default..
701 	version(cgi_session_server_process)
702 		{}
703 	else
704 		version=cgi_embedded_sessions;
705 }
706 
707 // fall back if the other is not defined so we can cleanly version it below
708 version(cgi_embedded_sessions) {}
709 else version=cgi_session_server_process;
710 
711 
712 version=cgi_with_websocket;
713 
714 enum long defaultMaxContentLength = 5_000_000;
715 
716 /*
717 
718 	To do a file download offer in the browser:
719 
720     cgi.setResponseContentType("text/csv");
721     cgi.header("Content-Disposition: attachment; filename=\"customers.csv\"");
722 */
723 
724 // FIXME: the location header is supposed to be an absolute url I guess.
725 
726 // FIXME: would be cool to flush part of a dom document before complete
727 // somehow in here and dom.d.
728 
729 
730 // these are public so you can mixin GenericMain.
731 // FIXME: use a function level import instead!
732 public import std.string;
733 public import std.stdio;
734 public import std.conv;
735 import std.uni;
736 import std.algorithm.comparison;
737 import std.algorithm.searching;
738 import std.exception;
739 import std.base64;
740 static import std.algorithm;
741 import std.datetime;
742 import std.range;
743 
744 import std.process;
745 
746 import std.zlib;
747 
748 
749 T[] consume(T)(T[] range, int count) {
750 	if(count > range.length)
751 		count = range.length;
752 	return range[count..$];
753 }
754 
755 int locationOf(T)(T[] data, string item) {
756 	const(ubyte[]) d = cast(const(ubyte[])) data;
757 	const(ubyte[]) i = cast(const(ubyte[])) item;
758 
759 	// this is a vague sanity check to ensure we aren't getting insanely
760 	// sized input that will infinite loop below. it should never happen;
761 	// even huge file uploads ought to come in smaller individual pieces.
762 	if(d.length > (int.max/2))
763 		throw new Exception("excessive block of input");
764 
765 	for(int a = 0; a < d.length; a++) {
766 		if(a + i.length > d.length)
767 			return -1;
768 		if(d[a..a+i.length] == i)
769 			return a;
770 	}
771 
772 	return -1;
773 }
774 
775 /// If you are doing a custom cgi class, mixing this in can take care of
776 /// the required constructors for you
777 mixin template ForwardCgiConstructors() {
778 	this(long maxContentLength = defaultMaxContentLength,
779 		string[string] env = null,
780 		const(ubyte)[] delegate() readdata = null,
781 		void delegate(const(ubyte)[]) _rawDataOutput = null,
782 		void delegate() _flush = null
783 		) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
784 
785 	this(string[] args) { super(args); }
786 
787 	this(
788 		BufferedInputRange inputData,
789 		string address, ushort _port,
790 		int pathInfoStarts = 0,
791 		bool _https = false,
792 		void delegate(const(ubyte)[]) _rawDataOutput = null,
793 		void delegate() _flush = null,
794 		// this pointer tells if the connection is supposed to be closed after we handle this
795 		bool* closeConnection = null)
796 	{
797 		super(inputData, address, _port, pathInfoStarts, _https, _rawDataOutput, _flush, closeConnection);
798 	}
799 
800 	this(BufferedInputRange ir, bool* closeConnection) { super(ir, closeConnection); }
801 }
802 
803 /// thrown when a connection is closed remotely while we waiting on data from it
804 class ConnectionClosedException : Exception {
805 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
806 		super(message, file, line, next);
807 	}
808 }
809 
810 
811 version(Windows) {
812 // FIXME: ugly hack to solve stdin exception problems on Windows:
813 // reading stdin results in StdioException (Bad file descriptor)
814 // this is probably due to http://d.puremagic.com/issues/show_bug.cgi?id=3425
815 private struct stdin {
816 	struct ByChunk { // Replicates std.stdio.ByChunk
817 	private:
818 		ubyte[] chunk_;
819 	public:
820 		this(size_t size)
821 		in {
822 			assert(size, "size must be larger than 0");
823 		}
824 		do {
825 			chunk_ = new ubyte[](size);
826 			popFront();
827 		}
828 
829 		@property bool empty() const {
830 			return !std.stdio.stdin.isOpen || std.stdio.stdin.eof; // Ugly, but seems to do the job
831 		}
832 		@property nothrow ubyte[] front() {	return chunk_; }
833 		void popFront()	{
834 			enforce(!empty, "Cannot call popFront on empty range");
835 			chunk_ = stdin.rawRead(chunk_);
836 		}
837 	}
838 
839 	import core.sys.windows.windows;
840 static:
841 
842 	T[] rawRead(T)(T[] buf) {
843 		uint bytesRead;
844 		auto result = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buf.ptr, cast(int) (buf.length * T.sizeof), &bytesRead, null);
845 
846 		if (!result) {
847 			auto err = GetLastError();
848 			if (err == 38/*ERROR_HANDLE_EOF*/ || err == 109/*ERROR_BROKEN_PIPE*/) // 'good' errors meaning end of input
849 				return buf[0..0];
850 			// Some other error, throw it
851 
852 			char* buffer;
853 			scope(exit) LocalFree(buffer);
854 
855 			// FORMAT_MESSAGE_ALLOCATE_BUFFER	= 0x00000100
856 			// FORMAT_MESSAGE_FROM_SYSTEM		= 0x00001000
857 			FormatMessageA(0x1100, null, err, 0, cast(char*)&buffer, 256, null);
858 			throw new Exception(to!string(buffer));
859 		}
860 		enforce(!(bytesRead % T.sizeof), "I/O error");
861 		return buf[0..bytesRead / T.sizeof];
862 	}
863 
864 	auto byChunk(size_t sz) { return ByChunk(sz); }
865 
866 	void close() {
867 		std.stdio.stdin.close;
868 	}
869 }
870 }
871 
872 /// The main interface with the web request
873 class Cgi {
874   public:
875 	/// the methods a request can be
876 	enum RequestMethod { GET, HEAD, POST, PUT, DELETE, // GET and POST are the ones that really work
877 		// these are defined in the standard, but idk if they are useful for anything
878 		OPTIONS, TRACE, CONNECT,
879 		// These seem new, I have only recently seen them
880 		PATCH, MERGE,
881 		// this is an extension for when the method is not specified and you want to assume
882 		CommandLine }
883 
884 
885 	/+
886 
887 	ubyte[] perRequestMemoryPool;
888 	void[] perRequestMemoryPoolWithPointers;
889 	// might want to just slice the buffer itself too when we happened to have gotten a full request inside it and don't need to decode
890 	// then the buffer also can be recycled if it is set.
891 
892 	// we might also be able to set memory recyclable true by default, but then the property getters set it to false. but not all the things are property getters. but realistically anything except benchmarks are gonna get something lol so meh.
893 
894 	/+
895 	struct VariableCollection {
896 		string[] opIndex(string name) {
897 
898 		}
899 	}
900 
901 	/++
902 		Call this to indicate that you've not retained any reference to the request-local memory (including all strings returned from the Cgi object) outside the request (you can .idup anything you need to store) and it is thus free to be freed or reused by another request.
903 
904 		Most handlers should be able to call this; retaining memory is the exception in any cgi program, but since I can't prove it from inside the library, it plays it safe and lets the GC manage it unless you opt into this behavior. All cgi.d functions will duplicate strings if needed (e.g. session ids from cookies) so unless you're doing something yourself, this should be ok.
905 
906 		History:
907 			Added
908 	+/
909 	public void recycleMemory() {
910 
911 	}
912 	+/
913 
914 
915 	/++
916 		Cgi provides a per-request memory pool
917 
918 	+/
919 	void[] allocateMemory(size_t nBytes) {
920 
921 	}
922 
923 	/// ditto
924 	void[] reallocateMemory(void[] old, size_t nBytes) {
925 
926 	}
927 
928 	/// ditto
929 	void freeMemory(void[] memory) {
930 
931 	}
932 	+/
933 
934 
935 /*
936 	import core.runtime;
937 	auto args = Runtime.args();
938 
939 	we can call the app a few ways:
940 
941 	1) set up the environment variables and call the app (manually simulating CGI)
942 	2) simulate a call automatically:
943 		./app method 'uri'
944 
945 		for example:
946 			./app get /path?arg arg2=something
947 
948 	  Anything on the uri is treated as query string etc
949 
950 	  on get method, further args are appended to the query string (encoded automatically)
951 	  on post method, further args are done as post
952 
953 
954 	  @name means import from file "name". if name == -, it uses stdin
955 	  (so info=@- means set info to the value of stdin)
956 
957 
958 	  Other arguments include:
959 	  	--cookie name=value (these are all concated together)
960 		--header 'X-Something: cool'
961 		--referrer 'something'
962 		--port 80
963 		--remote-address some.ip.address.here
964 		--https yes
965 		--user-agent 'something'
966 		--userpass 'user:pass'
967 		--authorization 'Basic base64encoded_user:pass'
968 		--accept 'content' // FIXME: better example
969 		--last-event-id 'something'
970 		--host 'something.com'
971 		--session name=value (these are added to a mock session, changes to the session are printed out as dummy response headers)
972 
973 	  Non-simulation arguments:
974 	  	--port xxx listening port for non-cgi things (valid for the cgi interfaces)
975 		--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`.
976 
977 */
978 
979 	/** Initializes it with command line arguments (for easy testing) */
980 	this(string[] args, void delegate(const(ubyte)[]) _rawDataOutput = null) {
981 		rawDataOutput = _rawDataOutput;
982 		// these are all set locally so the loop works
983 		// without triggering errors in dmd 2.064
984 		// we go ahead and set them at the end of it to the this version
985 		int port;
986 		string referrer;
987 		string remoteAddress;
988 		string userAgent;
989 		string authorization;
990 		string origin;
991 		string accept;
992 		string lastEventId;
993 		bool https;
994 		string host;
995 		RequestMethod requestMethod;
996 		string requestUri;
997 		string pathInfo;
998 		string queryString;
999 
1000 		bool lookingForMethod;
1001 		bool lookingForUri;
1002 		string nextArgIs;
1003 
1004 		string _cookie;
1005 		string _queryString;
1006 		string[][string] _post;
1007 		string[string] _headers;
1008 
1009 		string[] breakUp(string s) {
1010 			string k, v;
1011 			auto idx = s.indexOf("=");
1012 			if(idx == -1) {
1013 				k = s;
1014 			} else {
1015 				k = s[0 .. idx];
1016 				v = s[idx + 1 .. $];
1017 			}
1018 
1019 			return [k, v];
1020 		}
1021 
1022 		lookingForMethod = true;
1023 
1024 		scriptName = args[0];
1025 		scriptFileName = args[0];
1026 
1027 		environmentVariables = cast(const) environment.toAA;
1028 
1029 		foreach(arg; args[1 .. $]) {
1030 			if(arg.startsWith("--")) {
1031 				nextArgIs = arg[2 .. $];
1032 			} else if(nextArgIs.length) {
1033 				if (nextArgIs == "cookie") {
1034 					auto info = breakUp(arg);
1035 					if(_cookie.length)
1036 						_cookie ~= "; ";
1037 					_cookie ~= encodeUriComponent(info[0]) ~ "=" ~ encodeUriComponent(info[1]);
1038 				}
1039 				if (nextArgIs == "session") {
1040 					auto info = breakUp(arg);
1041 					_commandLineSession[info[0]] = info[1];
1042 				}
1043 
1044 				else if (nextArgIs == "port") {
1045 					port = to!int(arg);
1046 				}
1047 				else if (nextArgIs == "referrer") {
1048 					referrer = arg;
1049 				}
1050 				else if (nextArgIs == "remote-address") {
1051 					remoteAddress = arg;
1052 				}
1053 				else if (nextArgIs == "user-agent") {
1054 					userAgent = arg;
1055 				}
1056 				else if (nextArgIs == "authorization") {
1057 					authorization = arg;
1058 				}
1059 				else if (nextArgIs == "userpass") {
1060 					authorization = "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (arg)).idup;
1061 				}
1062 				else if (nextArgIs == "origin") {
1063 					origin = arg;
1064 				}
1065 				else if (nextArgIs == "accept") {
1066 					accept = arg;
1067 				}
1068 				else if (nextArgIs == "last-event-id") {
1069 					lastEventId = arg;
1070 				}
1071 				else if (nextArgIs == "https") {
1072 					if(arg == "yes")
1073 						https = true;
1074 				}
1075 				else if (nextArgIs == "header") {
1076 					string thing, other;
1077 					auto idx = arg.indexOf(":");
1078 					if(idx == -1)
1079 						throw new Exception("need a colon in a http header");
1080 					thing = arg[0 .. idx];
1081 					other = arg[idx + 1.. $];
1082 					_headers[thing.strip.toLower()] = other.strip;
1083 				}
1084 				else if (nextArgIs == "host") {
1085 					host = arg;
1086 				}
1087 				// else
1088 				// skip, we don't know it but that's ok, it might be used elsewhere so no error
1089 
1090 				nextArgIs = null;
1091 			} else if(lookingForMethod) {
1092 				lookingForMethod = false;
1093 				lookingForUri = true;
1094 
1095 				if(arg.asLowerCase().equal("commandline"))
1096 					requestMethod = RequestMethod.CommandLine;
1097 				else
1098 					requestMethod = to!RequestMethod(arg.toUpper());
1099 			} else if(lookingForUri) {
1100 				lookingForUri = false;
1101 
1102 				requestUri = arg;
1103 
1104 				auto idx = arg.indexOf("?");
1105 				if(idx == -1)
1106 					pathInfo = arg;
1107 				else {
1108 					pathInfo = arg[0 .. idx];
1109 					_queryString = arg[idx + 1 .. $];
1110 				}
1111 			} else {
1112 				// it is an argument of some sort
1113 				if(requestMethod == Cgi.RequestMethod.POST || requestMethod == Cgi.RequestMethod.PATCH || requestMethod == Cgi.RequestMethod.PUT || requestMethod == Cgi.RequestMethod.CommandLine) {
1114 					auto parts = breakUp(arg);
1115 					_post[parts[0]] ~= parts[1];
1116 					allPostNamesInOrder ~= parts[0];
1117 					allPostValuesInOrder ~= parts[1];
1118 				} else {
1119 					if(_queryString.length)
1120 						_queryString ~= "&";
1121 					auto parts = breakUp(arg);
1122 					_queryString ~= encodeUriComponent(parts[0]) ~ "=" ~ encodeUriComponent(parts[1]);
1123 				}
1124 			}
1125 		}
1126 
1127 		acceptsGzip = false;
1128 		keepAliveRequested = false;
1129 		requestHeaders = cast(immutable) _headers;
1130 
1131 		cookie = _cookie;
1132 		cookiesArray =  getCookieArray();
1133 		cookies = keepLastOf(cookiesArray);
1134 
1135 		queryString = _queryString;
1136 		getArray = cast(immutable) decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
1137 		get = keepLastOf(getArray);
1138 
1139 		postArray = cast(immutable) _post;
1140 		post = keepLastOf(_post);
1141 
1142 		// FIXME
1143 		filesArray = null;
1144 		files = null;
1145 
1146 		isCalledWithCommandLineArguments = true;
1147 
1148 		this.port = port;
1149 		this.referrer = referrer;
1150 		this.remoteAddress = remoteAddress;
1151 		this.userAgent = userAgent;
1152 		this.authorization = authorization;
1153 		this.origin = origin;
1154 		this.accept = accept;
1155 		this.lastEventId = lastEventId;
1156 		this.https = https;
1157 		this.host = host;
1158 		this.requestMethod = requestMethod;
1159 		this.requestUri = requestUri;
1160 		this.pathInfo = pathInfo;
1161 		this.queryString = queryString;
1162 		this.postBody = null;
1163 		this.requestContentType = null;
1164 	}
1165 
1166 	private {
1167 		string[] allPostNamesInOrder;
1168 		string[] allPostValuesInOrder;
1169 		string[] allGetNamesInOrder;
1170 		string[] allGetValuesInOrder;
1171 	}
1172 
1173 	CgiConnectionHandle getOutputFileHandle() {
1174 		return _outputFileHandle;
1175 	}
1176 
1177 	CgiConnectionHandle _outputFileHandle = INVALID_CGI_CONNECTION_HANDLE;
1178 
1179 	/** Initializes it using a CGI or CGI-like interface */
1180 	this(long maxContentLength = defaultMaxContentLength,
1181 		// use this to override the environment variable listing
1182 		in string[string] env = null,
1183 		// and this should return a chunk of data. return empty when done
1184 		const(ubyte)[] delegate() readdata = null,
1185 		// finally, use this to do custom output if needed
1186 		void delegate(const(ubyte)[]) _rawDataOutput = null,
1187 		// to flush teh custom output
1188 		void delegate() _flush = null
1189 		)
1190 	{
1191 
1192 		// these are all set locally so the loop works
1193 		// without triggering errors in dmd 2.064
1194 		// we go ahead and set them at the end of it to the this version
1195 		int port;
1196 		string referrer;
1197 		string remoteAddress;
1198 		string userAgent;
1199 		string authorization;
1200 		string origin;
1201 		string accept;
1202 		string lastEventId;
1203 		bool https;
1204 		string host;
1205 		RequestMethod requestMethod;
1206 		string requestUri;
1207 		string pathInfo;
1208 		string queryString;
1209 
1210 
1211 
1212 		isCalledWithCommandLineArguments = false;
1213 		rawDataOutput = _rawDataOutput;
1214 		flushDelegate = _flush;
1215 		auto getenv = delegate string(string var) {
1216 			if(env is null)
1217 				return std.process.environment.get(var);
1218 			auto e = var in env;
1219 			if(e is null)
1220 				return null;
1221 			return *e;
1222 		};
1223 
1224 		environmentVariables = env is null ?
1225 			cast(const) environment.toAA :
1226 			env;
1227 
1228 		// fetching all the request headers
1229 		string[string] requestHeadersHere;
1230 		foreach(k, v; env is null ? cast(const) environment.toAA() : env) {
1231 			if(k.startsWith("HTTP_")) {
1232 				requestHeadersHere[replace(k["HTTP_".length .. $].toLower(), "_", "-")] = v;
1233 			}
1234 		}
1235 
1236 		this.requestHeaders = assumeUnique(requestHeadersHere);
1237 
1238 		requestUri = getenv("REQUEST_URI");
1239 
1240 		cookie = getenv("HTTP_COOKIE");
1241 		cookiesArray = getCookieArray();
1242 		cookies = keepLastOf(cookiesArray);
1243 
1244 		referrer = getenv("HTTP_REFERER");
1245 		userAgent = getenv("HTTP_USER_AGENT");
1246 		remoteAddress = getenv("REMOTE_ADDR");
1247 		host = getenv("HTTP_HOST");
1248 		pathInfo = getenv("PATH_INFO");
1249 
1250 		queryString = getenv("QUERY_STRING");
1251 		scriptName = getenv("SCRIPT_NAME");
1252 		{
1253 			import core.runtime;
1254 			auto sfn = getenv("SCRIPT_FILENAME");
1255 			scriptFileName = sfn.length ? sfn : (Runtime.args.length ? Runtime.args[0] : null);
1256 		}
1257 
1258 		bool iis = false;
1259 
1260 		// Because IIS doesn't pass requestUri, we simulate it here if it's empty.
1261 		if(requestUri.length == 0) {
1262 			// IIS sometimes includes the script name as part of the path info - we don't want that
1263 			if(pathInfo.length >= scriptName.length && (pathInfo[0 .. scriptName.length] == scriptName))
1264 				pathInfo = pathInfo[scriptName.length .. $];
1265 
1266 			requestUri = scriptName ~ pathInfo ~ (queryString.length ? ("?" ~ queryString) : "");
1267 
1268 			iis = true; // FIXME HACK - used in byChunk below - see bugzilla 6339
1269 
1270 			// FIXME: this works for apache and iis... but what about others?
1271 		}
1272 
1273 
1274 		auto ugh = decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
1275 		getArray = assumeUnique(ugh);
1276 		get = keepLastOf(getArray);
1277 
1278 
1279 		// NOTE: on shitpache, you need to specifically forward this
1280 		authorization = getenv("HTTP_AUTHORIZATION");
1281 		// this is a hack because Apache is a shitload of fuck and
1282 		// refuses to send the real header to us. Compatible
1283 		// programs should send both the standard and X- versions
1284 
1285 		// NOTE: if you have access to .htaccess or httpd.conf, you can make this
1286 		// unnecessary with mod_rewrite, so it is commented
1287 
1288 		//if(authorization.length == 0) // if the std is there, use it
1289 		//	authorization = getenv("HTTP_X_AUTHORIZATION");
1290 
1291 		// the REDIRECT_HTTPS check is here because with an Apache hack, the port can become wrong
1292 		if(getenv("SERVER_PORT").length && getenv("REDIRECT_HTTPS") != "on")
1293 			port = to!int(getenv("SERVER_PORT"));
1294 		else
1295 			port = 0; // this was probably called from the command line
1296 
1297 		auto ae = getenv("HTTP_ACCEPT_ENCODING");
1298 		if(ae.length && ae.indexOf("gzip") != -1)
1299 			acceptsGzip = true;
1300 
1301 		accept = getenv("HTTP_ACCEPT");
1302 		lastEventId = getenv("HTTP_LAST_EVENT_ID");
1303 
1304 		auto ka = getenv("HTTP_CONNECTION");
1305 		if(ka.length && ka.asLowerCase().canFind("keep-alive"))
1306 			keepAliveRequested = true;
1307 
1308 		auto or = getenv("HTTP_ORIGIN");
1309 			origin = or;
1310 
1311 		auto rm = getenv("REQUEST_METHOD");
1312 		if(rm.length)
1313 			requestMethod = to!RequestMethod(getenv("REQUEST_METHOD"));
1314 		else
1315 			requestMethod = RequestMethod.CommandLine;
1316 
1317 						// 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.
1318 		https = (getenv("HTTPS") == "on" || getenv("REDIRECT_HTTPS") == "on");
1319 
1320 		// FIXME: DOCUMENT_ROOT?
1321 
1322 		// FIXME: what about PUT?
1323 		if(requestMethod == RequestMethod.POST || requestMethod == Cgi.RequestMethod.PATCH || requestMethod == Cgi.RequestMethod.PUT || requestMethod == Cgi.RequestMethod.CommandLine) {
1324 			version(preserveData) // a hack to make forwarding simpler
1325 				immutable(ubyte)[] data;
1326 			size_t amountReceived = 0;
1327 			auto contentType = getenv("CONTENT_TYPE");
1328 
1329 			// FIXME: is this ever not going to be set? I guess it depends
1330 			// on if the server de-chunks and buffers... seems like it has potential
1331 			// to be slow if they did that. The spec says it is always there though.
1332 			// And it has worked reliably for me all year in the live environment,
1333 			// but some servers might be different.
1334 			auto cls = getenv("CONTENT_LENGTH");
1335 			auto contentLength = to!size_t(cls.length ? cls : "0");
1336 
1337 			immutable originalContentLength = contentLength;
1338 			if(contentLength) {
1339 				if(maxContentLength > 0 && contentLength > maxContentLength) {
1340 					setResponseStatus("413 Request entity too large");
1341 					write("You tried to upload a file that is too large.");
1342 					close();
1343 					throw new Exception("POST too large");
1344 				}
1345 				prepareForIncomingDataChunks(contentType, contentLength);
1346 
1347 
1348 				int processChunk(in ubyte[] chunk) {
1349 					if(chunk.length > contentLength) {
1350 						handleIncomingDataChunk(chunk[0..contentLength]);
1351 						amountReceived += contentLength;
1352 						contentLength = 0;
1353 						return 1;
1354 					} else {
1355 						handleIncomingDataChunk(chunk);
1356 						contentLength -= chunk.length;
1357 						amountReceived += chunk.length;
1358 					}
1359 					if(contentLength == 0)
1360 						return 1;
1361 
1362 					onRequestBodyDataReceived(amountReceived, originalContentLength);
1363 					return 0;
1364 				}
1365 
1366 
1367 				if(readdata is null) {
1368 					foreach(ubyte[] chunk; stdin.byChunk(iis ? contentLength : 4096))
1369 						if(processChunk(chunk))
1370 							break;
1371 				} else {
1372 					// we have a custom data source..
1373 					auto chunk = readdata();
1374 					while(chunk.length) {
1375 						if(processChunk(chunk))
1376 							break;
1377 						chunk = readdata();
1378 					}
1379 				}
1380 
1381 				onRequestBodyDataReceived(amountReceived, originalContentLength);
1382 				postArray = assumeUnique(pps._post);
1383 				filesArray = assumeUnique(pps._files);
1384 				files = keepLastOf(filesArray);
1385 				post = keepLastOf(postArray);
1386 				this.postBody = pps.postBody;
1387 				this.requestContentType = contentType;
1388 				cleanUpPostDataState();
1389 			}
1390 
1391 			version(preserveData)
1392 				originalPostData = data;
1393 		}
1394 		// fixme: remote_user script name
1395 
1396 
1397 		this.port = port;
1398 		this.referrer = referrer;
1399 		this.remoteAddress = remoteAddress;
1400 		this.userAgent = userAgent;
1401 		this.authorization = authorization;
1402 		this.origin = origin;
1403 		this.accept = accept;
1404 		this.lastEventId = lastEventId;
1405 		this.https = https;
1406 		this.host = host;
1407 		this.requestMethod = requestMethod;
1408 		this.requestUri = requestUri;
1409 		this.pathInfo = pathInfo;
1410 		this.queryString = queryString;
1411 	}
1412 
1413 	/// Cleans up any temporary files. Do not use the object
1414 	/// after calling this.
1415 	///
1416 	/// NOTE: it is called automatically by GenericMain
1417 	// FIXME: this should be called if the constructor fails too, if it has created some garbage...
1418 	void dispose() {
1419 		foreach(file; files) {
1420 			if(!file.contentInMemory)
1421 				if(std.file.exists(file.contentFilename))
1422 					std.file.remove(file.contentFilename);
1423 		}
1424 	}
1425 
1426 	private {
1427 		struct PostParserState {
1428 			string contentType;
1429 			string boundary;
1430 			string localBoundary; // the ones used at the end or something lol
1431 			bool isMultipart;
1432 			bool needsSavedBody;
1433 
1434 			ulong expectedLength;
1435 			ulong contentConsumed;
1436 			immutable(ubyte)[] buffer;
1437 
1438 			// multipart parsing state
1439 			int whatDoWeWant;
1440 			bool weHaveAPart;
1441 			string[] thisOnesHeaders;
1442 			immutable(ubyte)[] thisOnesData;
1443 
1444 			string postBody;
1445 
1446 			UploadedFile piece;
1447 			bool isFile = false;
1448 
1449 			size_t memoryCommitted;
1450 
1451 			// do NOT keep mutable references to these anywhere!
1452 			// I assume they are unique in the constructor once we're all done getting data.
1453 			string[][string] _post;
1454 			UploadedFile[][string] _files;
1455 		}
1456 
1457 		PostParserState pps;
1458 	}
1459 
1460 	/// This represents a file the user uploaded via a POST request.
1461 	static struct UploadedFile {
1462 		/// If you want to create one of these structs for yourself from some data,
1463 		/// use this function.
1464 		static UploadedFile fromData(immutable(void)[] data, string name = null) {
1465 			Cgi.UploadedFile f;
1466 			f.filename = name;
1467 			f.content = cast(immutable(ubyte)[]) data;
1468 			f.contentInMemory = true;
1469 			return f;
1470 		}
1471 
1472 		string name; 		/// The name of the form element.
1473 		string filename; 	/// The filename the user set.
1474 		string contentType; 	/// The MIME type the user's browser reported. (Not reliable.)
1475 
1476 		/**
1477 			For small files, cgi.d will buffer the uploaded file in memory, and make it
1478 			directly accessible to you through the content member. I find this very convenient
1479 			and somewhat efficient, since it can avoid hitting the disk entirely. (I
1480 			often want to inspect and modify the file anyway!)
1481 
1482 			I find the file is very large, it is undesirable to eat that much memory just
1483 			for a file buffer. In those cases, if you pass a large enough value for maxContentLength
1484 			to the constructor so they are accepted, cgi.d will write the content to a temporary
1485 			file that you can re-read later.
1486 
1487 			You can override this behavior by subclassing Cgi and overriding the protected
1488 			handlePostChunk method. Note that the object is not initialized when you
1489 			write that method - the http headers are available, but the cgi.post method
1490 			is not. You may parse the file as it streams in using this method.
1491 
1492 
1493 			Anyway, if the file is small enough to be in memory, contentInMemory will be
1494 			set to true, and the content is available in the content member.
1495 
1496 			If not, contentInMemory will be set to false, and the content saved in a file,
1497 			whose name will be available in the contentFilename member.
1498 
1499 
1500 			Tip: if you know you are always dealing with small files, and want the convenience
1501 			of ignoring this member, construct Cgi with a small maxContentLength. Then, if
1502 			a large file comes in, it simply throws an exception (and HTTP error response)
1503 			instead of trying to handle it.
1504 
1505 			The default value of maxContentLength in the constructor is for small files.
1506 		*/
1507 		bool contentInMemory = true; // the default ought to always be true
1508 		immutable(ubyte)[] content; /// The actual content of the file, if contentInMemory == true
1509 		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.
1510 
1511 		///
1512 		ulong fileSize() const {
1513 			if(contentInMemory)
1514 				return content.length;
1515 			import std.file;
1516 			return std.file.getSize(contentFilename);
1517 
1518 		}
1519 
1520 		///
1521 		void writeToFile(string filenameToSaveTo) const {
1522 			import std.file;
1523 			if(contentInMemory)
1524 				std.file.write(filenameToSaveTo, content);
1525 			else
1526 				std.file.rename(contentFilename, filenameToSaveTo);
1527 		}
1528 	}
1529 
1530 	// given a content type and length, decide what we're going to do with the data..
1531 	protected void prepareForIncomingDataChunks(string contentType, ulong contentLength) {
1532 		pps.expectedLength = contentLength;
1533 
1534 		auto terminator = contentType.indexOf(";");
1535 		if(terminator == -1)
1536 			terminator = contentType.length;
1537 
1538 		pps.contentType = contentType[0 .. terminator];
1539 		auto b = contentType[terminator .. $];
1540 		if(b.length) {
1541 			auto idx = b.indexOf("boundary=");
1542 			if(idx != -1) {
1543 				pps.boundary = b[idx + "boundary=".length .. $];
1544 				pps.localBoundary = "\r\n--" ~ pps.boundary;
1545 			}
1546 		}
1547 
1548 		// while a content type SHOULD be sent according to the RFC, it is
1549 		// not required. We're told we SHOULD guess by looking at the content
1550 		// but it seems to me that this only happens when it is urlencoded.
1551 		if(pps.contentType == "application/x-www-form-urlencoded" || pps.contentType == "") {
1552 			pps.isMultipart = false;
1553 			pps.needsSavedBody = false;
1554 		} else if(pps.contentType == "multipart/form-data") {
1555 			pps.isMultipart = true;
1556 			enforce(pps.boundary.length, "no boundary");
1557 		} else if(pps.contentType == "text/xml") { // FIXME: could this be special and load the post params
1558 			// save the body so the application can handle it
1559 			pps.isMultipart = false;
1560 			pps.needsSavedBody = true;
1561 		} else if(pps.contentType == "application/json") { // FIXME: this could prolly try to load post params too
1562 			// save the body so the application can handle it
1563 			pps.needsSavedBody = true;
1564 			pps.isMultipart = false;
1565 		} else {
1566 			// the rest is 100% handled by the application. just save the body and send it to them
1567 			pps.needsSavedBody = true;
1568 			pps.isMultipart = false;
1569 		}
1570 	}
1571 
1572 	// handles streaming POST data. If you handle some other content type, you should
1573 	// override this. If the data isn't the content type you want, you ought to call
1574 	// super.handleIncomingDataChunk so regular forms and files still work.
1575 
1576 	// FIXME: I do some copying in here that I'm pretty sure is unnecessary, and the
1577 	// file stuff I'm sure is inefficient. But, my guess is the real bottleneck is network
1578 	// input anyway, so I'm not going to get too worked up about it right now.
1579 	protected void handleIncomingDataChunk(const(ubyte)[] chunk) {
1580 		if(chunk.length == 0)
1581 			return;
1582 		assert(chunk.length <= 32 * 1024 * 1024); // we use chunk size as a memory constraint thing, so
1583 							// if we're passed big chunks, it might throw unnecessarily.
1584 							// just pass it smaller chunks at a time.
1585 		if(pps.isMultipart) {
1586 			// multipart/form-data
1587 
1588 
1589 			// FIXME: this might want to be factored out and factorized
1590 			// need to make sure the stream hooks actually work.
1591 			void pieceHasNewContent() {
1592 				// we just grew the piece's buffer. Do we have to switch to file backing?
1593 				if(pps.piece.contentInMemory) {
1594 					if(pps.piece.content.length <= 10 * 1024 * 1024)
1595 						// meh, I'm ok with it.
1596 						return;
1597 					else {
1598 						// this is too big.
1599 						if(!pps.isFile)
1600 							throw new Exception("Request entity too large"); // a variable this big is kinda ridiculous, just reject it.
1601 						else {
1602 							// a file this large is probably acceptable though... let's use a backing file.
1603 							pps.piece.contentInMemory = false;
1604 							// FIXME: say... how do we intend to delete these things? cgi.dispose perhaps.
1605 
1606 							int count = 0;
1607 							pps.piece.contentFilename = getTempDirectory() ~ "arsd_cgi_uploaded_file_" ~ to!string(getUtcTime()) ~ "-" ~ to!string(count);
1608 							// odds are this loop will never be entered, but we want it just in case.
1609 							while(std.file.exists(pps.piece.contentFilename)) {
1610 								count++;
1611 								pps.piece.contentFilename = getTempDirectory() ~ "arsd_cgi_uploaded_file_" ~ to!string(getUtcTime()) ~ "-" ~ to!string(count);
1612 							}
1613 							// I hope this creates the file pretty quickly, or the loop might be useless...
1614 							// FIXME: maybe I should write some kind of custom transaction here.
1615 							std.file.write(pps.piece.contentFilename, pps.piece.content);
1616 
1617 							pps.piece.content = null;
1618 						}
1619 					}
1620 				} else {
1621 					// it's already in a file, so just append it to what we have
1622 					if(pps.piece.content.length) {
1623 						// FIXME: this is surely very inefficient... we'll be calling this by 4kb chunk...
1624 						std.file.append(pps.piece.contentFilename, pps.piece.content);
1625 						pps.piece.content = null;
1626 					}
1627 				}
1628 			}
1629 
1630 
1631 			void commitPart() {
1632 				if(!pps.weHaveAPart)
1633 					return;
1634 
1635 				pieceHasNewContent(); // be sure the new content is handled every time
1636 
1637 				if(pps.isFile) {
1638 					// I'm not sure if other environments put files in post or not...
1639 					// I used to not do it, but I think I should, since it is there...
1640 					pps._post[pps.piece.name] ~= pps.piece.filename;
1641 					pps._files[pps.piece.name] ~= pps.piece;
1642 
1643 					allPostNamesInOrder ~= pps.piece.name;
1644 					allPostValuesInOrder ~= pps.piece.filename;
1645 				} else {
1646 					pps._post[pps.piece.name] ~= cast(string) pps.piece.content;
1647 
1648 					allPostNamesInOrder ~= pps.piece.name;
1649 					allPostValuesInOrder ~= cast(string) pps.piece.content;
1650 				}
1651 
1652 				/*
1653 				stderr.writeln("RECEIVED: ", pps.piece.name, "=",
1654 					pps.piece.content.length < 1000
1655 					?
1656 					to!string(pps.piece.content)
1657 					:
1658 					"too long");
1659 				*/
1660 
1661 				// FIXME: the limit here
1662 				pps.memoryCommitted += pps.piece.content.length;
1663 
1664 				pps.weHaveAPart = false;
1665 				pps.whatDoWeWant = 1;
1666 				pps.thisOnesHeaders = null;
1667 				pps.thisOnesData = null;
1668 
1669 				pps.piece = UploadedFile.init;
1670 				pps.isFile = false;
1671 			}
1672 
1673 			void acceptChunk() {
1674 				pps.buffer ~= chunk;
1675 				chunk = null; // we've consumed it into the buffer, so keeping it just brings confusion
1676 			}
1677 
1678 			immutable(ubyte)[] consume(size_t howMuch) {
1679 				pps.contentConsumed += howMuch;
1680 				auto ret = pps.buffer[0 .. howMuch];
1681 				pps.buffer = pps.buffer[howMuch .. $];
1682 				return ret;
1683 			}
1684 
1685 			dataConsumptionLoop: do {
1686 			switch(pps.whatDoWeWant) {
1687 				default: assert(0);
1688 				case 0:
1689 					acceptChunk();
1690 					// the format begins with two extra leading dashes, then we should be at the boundary
1691 					if(pps.buffer.length < 2)
1692 						return;
1693 					assert(pps.buffer[0] == '-', "no leading dash");
1694 					consume(1);
1695 					assert(pps.buffer[0] == '-', "no second leading dash");
1696 					consume(1);
1697 
1698 					pps.whatDoWeWant = 1;
1699 					goto case 1;
1700 				/* fallthrough */
1701 				case 1: // looking for headers
1702 					// here, we should be lined up right at the boundary, which is followed by a \r\n
1703 
1704 					// want to keep the buffer under control in case we're under attack
1705 					//stderr.writeln("here once");
1706 					//if(pps.buffer.length + chunk.length > 70 * 1024) // they should be < 1 kb really....
1707 					//	throw new Exception("wtf is up with the huge mime part headers");
1708 
1709 					acceptChunk();
1710 
1711 					if(pps.buffer.length < pps.boundary.length)
1712 						return; // not enough data, since there should always be a boundary here at least
1713 
1714 					if(pps.contentConsumed + pps.boundary.length + 6 == pps.expectedLength) {
1715 						assert(pps.buffer.length == pps.boundary.length + 4 + 2); // --, --, and \r\n
1716 						// we *should* be at the end here!
1717 						assert(pps.buffer[0] == '-');
1718 						consume(1);
1719 						assert(pps.buffer[0] == '-');
1720 						consume(1);
1721 
1722 						// the message is terminated by --BOUNDARY--\r\n (after a \r\n leading to the boundary)
1723 						assert(pps.buffer[0 .. pps.boundary.length] == cast(const(ubyte[])) pps.boundary,
1724 							"not lined up on boundary " ~ pps.boundary);
1725 						consume(pps.boundary.length);
1726 
1727 						assert(pps.buffer[0] == '-');
1728 						consume(1);
1729 						assert(pps.buffer[0] == '-');
1730 						consume(1);
1731 
1732 						assert(pps.buffer[0] == '\r');
1733 						consume(1);
1734 						assert(pps.buffer[0] == '\n');
1735 						consume(1);
1736 
1737 						assert(pps.buffer.length == 0);
1738 						assert(pps.contentConsumed == pps.expectedLength);
1739 						break dataConsumptionLoop; // we're done!
1740 					} else {
1741 						// we're not done yet. We should be lined up on a boundary.
1742 
1743 						// But, we want to ensure the headers are here before we consume anything!
1744 						auto headerEndLocation = locationOf(pps.buffer, "\r\n\r\n");
1745 						if(headerEndLocation == -1)
1746 							return; // they *should* all be here, so we can handle them all at once.
1747 
1748 						assert(pps.buffer[0 .. pps.boundary.length] == cast(const(ubyte[])) pps.boundary,
1749 							"not lined up on boundary " ~ pps.boundary);
1750 
1751 						consume(pps.boundary.length);
1752 						// the boundary is always followed by a \r\n
1753 						assert(pps.buffer[0] == '\r');
1754 						consume(1);
1755 						assert(pps.buffer[0] == '\n');
1756 						consume(1);
1757 					}
1758 
1759 					// re-running since by consuming the boundary, we invalidate the old index.
1760 					auto headerEndLocation = locationOf(pps.buffer, "\r\n\r\n");
1761 					assert(headerEndLocation >= 0, "no header");
1762 					auto thisOnesHeaders = pps.buffer[0..headerEndLocation];
1763 
1764 					consume(headerEndLocation + 4); // The +4 is the \r\n\r\n that caps it off
1765 
1766 					pps.thisOnesHeaders = split(cast(string) thisOnesHeaders, "\r\n");
1767 
1768 					// now we'll parse the headers
1769 					foreach(h; pps.thisOnesHeaders) {
1770 						auto p = h.indexOf(":");
1771 						assert(p != -1, "no colon in header, got " ~ to!string(pps.thisOnesHeaders));
1772 						string hn = h[0..p];
1773 						string hv = h[p+2..$];
1774 
1775 						switch(hn.toLower) {
1776 							default: assert(0);
1777 							case "content-disposition":
1778 								auto info = hv.split("; ");
1779 								foreach(i; info[1..$]) { // skipping the form-data
1780 									auto o = i.split("="); // FIXME
1781 									string pn = o[0];
1782 									string pv = o[1][1..$-1];
1783 
1784 									if(pn == "name") {
1785 										pps.piece.name = pv;
1786 									} else if (pn == "filename") {
1787 										pps.piece.filename = pv;
1788 										pps.isFile = true;
1789 									}
1790 								}
1791 							break;
1792 							case "content-type":
1793 								pps.piece.contentType = hv;
1794 							break;
1795 						}
1796 					}
1797 
1798 					pps.whatDoWeWant++; // move to the next step - the data
1799 				break;
1800 				case 2:
1801 					// when we get here, pps.buffer should contain our first chunk of data
1802 
1803 					if(pps.buffer.length + chunk.length > 8 * 1024 * 1024) // we might buffer quite a bit but not much
1804 						throw new Exception("wtf is up with the huge mime part buffer");
1805 
1806 					acceptChunk();
1807 
1808 					// so the trick is, we want to process all the data up to the boundary,
1809 					// but what if the chunk's end cuts the boundary off? If we're unsure, we
1810 					// want to wait for the next chunk. We start by looking for the whole boundary
1811 					// in the buffer somewhere.
1812 
1813 					auto boundaryLocation = locationOf(pps.buffer, pps.localBoundary);
1814 					// assert(boundaryLocation != -1, "should have seen "~to!string(cast(ubyte[]) pps.localBoundary)~" in " ~ to!string(pps.buffer));
1815 					if(boundaryLocation != -1) {
1816 						// this is easy - we can see it in it's entirety!
1817 
1818 						pps.piece.content ~= consume(boundaryLocation);
1819 
1820 						assert(pps.buffer[0] == '\r');
1821 						consume(1);
1822 						assert(pps.buffer[0] == '\n');
1823 						consume(1);
1824 						assert(pps.buffer[0] == '-');
1825 						consume(1);
1826 						assert(pps.buffer[0] == '-');
1827 						consume(1);
1828 						// the boundary here is always preceded by \r\n--, which is why we used localBoundary instead of boundary to locate it. Cut that off.
1829 						pps.weHaveAPart = true;
1830 						pps.whatDoWeWant = 1; // back to getting headers for the next part
1831 
1832 						commitPart(); // we're done here
1833 					} else {
1834 						// we can't see the whole thing, but what if there's a partial boundary?
1835 
1836 						enforce(pps.localBoundary.length < 128); // the boundary ought to be less than a line...
1837 						assert(pps.localBoundary.length > 1); // should already be sane but just in case
1838 						bool potentialBoundaryFound = false;
1839 
1840 						boundaryCheck: for(int a = 1; a < pps.localBoundary.length; a++) {
1841 							// we grow the boundary a bit each time. If we think it looks the
1842 							// same, better pull another chunk to be sure it's not the end.
1843 							// Starting small because exiting the loop early is desirable, since
1844 							// we're not keeping any ambiguity and 1 / 256 chance of exiting is
1845 							// the best we can do.
1846 							if(a > pps.buffer.length)
1847 								break; // FIXME: is this right?
1848 							assert(a <= pps.buffer.length);
1849 							assert(a > 0);
1850 							if(std.algorithm.endsWith(pps.buffer, pps.localBoundary[0 .. a])) {
1851 								// ok, there *might* be a boundary here, so let's
1852 								// not treat the end as data yet. The rest is good to
1853 								// use though, since if there was a boundary there, we'd
1854 								// have handled it up above after locationOf.
1855 
1856 								pps.piece.content ~= pps.buffer[0 .. $ - a];
1857 								consume(pps.buffer.length - a);
1858 								pieceHasNewContent();
1859 								potentialBoundaryFound = true;
1860 								break boundaryCheck;
1861 							}
1862 						}
1863 
1864 						if(!potentialBoundaryFound) {
1865 							// we can consume the whole thing
1866 							pps.piece.content ~= pps.buffer;
1867 							pieceHasNewContent();
1868 							consume(pps.buffer.length);
1869 						} else {
1870 							// we found a possible boundary, but there was
1871 							// insufficient data to be sure.
1872 							assert(pps.buffer == cast(const(ubyte[])) pps.localBoundary[0 .. pps.buffer.length]);
1873 
1874 							return; // wait for the next chunk.
1875 						}
1876 					}
1877 			}
1878 			} while(pps.buffer.length);
1879 
1880 			// btw all boundaries except the first should have a \r\n before them
1881 		} else {
1882 			// application/x-www-form-urlencoded and application/json
1883 
1884 				// not using maxContentLength because that might be cranked up to allow
1885 				// large file uploads. We can handle them, but a huge post[] isn't any good.
1886 			if(pps.buffer.length + chunk.length > 8 * 1024 * 1024) // surely this is plenty big enough
1887 				throw new Exception("wtf is up with such a gigantic form submission????");
1888 
1889 			pps.buffer ~= chunk;
1890 
1891 			// simple handling, but it works... until someone bombs us with gigabytes of crap at least...
1892 			if(pps.buffer.length == pps.expectedLength) {
1893 				if(pps.needsSavedBody)
1894 					pps.postBody = cast(string) pps.buffer;
1895 				else
1896 					pps._post = decodeVariables(cast(string) pps.buffer, "&", &allPostNamesInOrder, &allPostValuesInOrder);
1897 				version(preserveData)
1898 					originalPostData = pps.buffer;
1899 			} else {
1900 				// just for debugging
1901 			}
1902 		}
1903 	}
1904 
1905 	protected void cleanUpPostDataState() {
1906 		pps = PostParserState.init;
1907 	}
1908 
1909 	/// you can override this function to somehow react
1910 	/// to an upload in progress.
1911 	///
1912 	/// Take note that parts of the CGI object is not yet
1913 	/// initialized! Stuff from HTTP headers, including get[], is usable.
1914 	/// But, none of post[] is usable, and you cannot write here. That's
1915 	/// why this method is const - mutating the object won't do much anyway.
1916 	///
1917 	/// My idea here was so you can output a progress bar or
1918 	/// something to a cooperative client (see arsd.rtud for a potential helper)
1919 	///
1920 	/// The default is to do nothing. Subclass cgi and use the
1921 	/// CustomCgiMain mixin to do something here.
1922 	void onRequestBodyDataReceived(size_t receivedSoFar, size_t totalExpected) const {
1923 		// This space intentionally left blank.
1924 	}
1925 
1926 	/// Initializes the cgi from completely raw HTTP data. The ir must have a Socket source.
1927 	/// *closeConnection will be set to true if you should close the connection after handling this request
1928 	this(BufferedInputRange ir, bool* closeConnection) {
1929 		isCalledWithCommandLineArguments = false;
1930 		import al = std.algorithm;
1931 
1932 		immutable(ubyte)[] data;
1933 
1934 		void rdo(const(ubyte)[] d) {
1935 		//import std.stdio; writeln(d);
1936 			sendAll(ir.source, d);
1937 		}
1938 
1939 		auto ira = ir.source.remoteAddress();
1940 		auto irLocalAddress = ir.source.localAddress();
1941 
1942 		ushort port = 80;
1943 		if(auto ia = cast(InternetAddress) irLocalAddress) {
1944 			port = ia.port;
1945 		} else if(auto ia = cast(Internet6Address) irLocalAddress) {
1946 			port = ia.port;
1947 		}
1948 
1949 		// that check for UnixAddress is to work around a Phobos bug
1950 		// see: https://github.com/dlang/phobos/pull/7383
1951 		// but this might be more useful anyway tbh for this case
1952 		version(Posix)
1953 		this(ir, ira is null ? null : cast(UnixAddress) ira ? "unix:" : ira.toString(), port, 0, false, &rdo, null, closeConnection);
1954 		else
1955 		this(ir, ira is null ? null : ira.toString(), port, 0, false, &rdo, null, closeConnection);
1956 	}
1957 
1958 	/**
1959 		Initializes it from raw HTTP request data. GenericMain uses this when you compile with -version=embedded_httpd.
1960 
1961 		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
1962 
1963 		Params:
1964 			inputData = the incoming data, including headers and other raw http data.
1965 				When the constructor exits, it will leave this range exactly at the start of
1966 				the next request on the connection (if there is one).
1967 
1968 			address = the IP address of the remote user
1969 			_port = the port number of the connection
1970 			pathInfoStarts = the offset into the path component of the http header where the SCRIPT_NAME ends and the PATH_INFO begins.
1971 			_https = if this connection is encrypted (note that the input data must not actually be encrypted)
1972 			_rawDataOutput = delegate to accept response data. It should write to the socket or whatever; Cgi does all the needed processing to speak http.
1973 			_flush = if _rawDataOutput buffers, this delegate should flush the buffer down the wire
1974 			closeConnection = if the request asks to close the connection, *closeConnection == true.
1975 	*/
1976 	this(
1977 		BufferedInputRange inputData,
1978 //		string[] headers, immutable(ubyte)[] data,
1979 		string address, ushort _port,
1980 		int pathInfoStarts = 0, // use this if you know the script name, like if this is in a folder in a bigger web environment
1981 		bool _https = false,
1982 		void delegate(const(ubyte)[]) _rawDataOutput = null,
1983 		void delegate() _flush = null,
1984 		// this pointer tells if the connection is supposed to be closed after we handle this
1985 		bool* closeConnection = null)
1986 	{
1987 		// these are all set locally so the loop works
1988 		// without triggering errors in dmd 2.064
1989 		// we go ahead and set them at the end of it to the this version
1990 		int port;
1991 		string referrer;
1992 		string remoteAddress;
1993 		string userAgent;
1994 		string authorization;
1995 		string origin;
1996 		string accept;
1997 		string lastEventId;
1998 		bool https;
1999 		string host;
2000 		RequestMethod requestMethod;
2001 		string requestUri;
2002 		string pathInfo;
2003 		string queryString;
2004 		string scriptName;
2005 		string[string] get;
2006 		string[][string] getArray;
2007 		bool keepAliveRequested;
2008 		bool acceptsGzip;
2009 		string cookie;
2010 
2011 
2012 
2013 		environmentVariables = cast(const) environment.toAA;
2014 
2015 		idlol = inputData;
2016 
2017 		isCalledWithCommandLineArguments = false;
2018 
2019 		https = _https;
2020 		port = _port;
2021 
2022 		rawDataOutput = _rawDataOutput;
2023 		flushDelegate = _flush;
2024 		nph = true;
2025 
2026 		remoteAddress = address;
2027 
2028 		// streaming parser
2029 		import al = std.algorithm;
2030 
2031 			// FIXME: tis cast is technically wrong, but Phobos deprecated al.indexOf... for some reason.
2032 		auto idx = indexOf(cast(string) inputData.front(), "\r\n\r\n");
2033 		while(idx == -1) {
2034 			inputData.popFront(0);
2035 			idx = indexOf(cast(string) inputData.front(), "\r\n\r\n");
2036 		}
2037 
2038 		assert(idx != -1);
2039 
2040 
2041 		string contentType = "";
2042 		string[string] requestHeadersHere;
2043 
2044 		size_t contentLength;
2045 
2046 		bool isChunked;
2047 
2048 		{
2049 			import core.runtime;
2050 			scriptFileName = Runtime.args.length ? Runtime.args[0] : null;
2051 		}
2052 
2053 
2054 		int headerNumber = 0;
2055 		foreach(line; al.splitter(inputData.front()[0 .. idx], "\r\n"))
2056 		if(line.length) {
2057 			headerNumber++;
2058 			auto header = cast(string) line.idup;
2059 			if(headerNumber == 1) {
2060 				// request line
2061 				auto parts = al.splitter(header, " ");
2062 				if(parts.front == "PRI") {
2063 					// this is an HTTP/2.0 line - "PRI * HTTP/2.0" - which indicates their payload will follow
2064 					// we're going to immediately refuse this, im not interested in implementing http2 (it is unlikely
2065 					// to bring me benefit)
2066 					throw new HttpVersionNotSupportedException();
2067 				}
2068 				requestMethod = to!RequestMethod(parts.front);
2069 				parts.popFront();
2070 				requestUri = parts.front;
2071 
2072 				// FIXME:  the requestUri could be an absolute path!!! should I rename it or something?
2073 				scriptName = requestUri[0 .. pathInfoStarts];
2074 
2075 				auto question = requestUri.indexOf("?");
2076 				if(question == -1) {
2077 					queryString = "";
2078 					// FIXME: double check, this might be wrong since it could be url encoded
2079 					pathInfo = requestUri[pathInfoStarts..$];
2080 				} else {
2081 					queryString = requestUri[question+1..$];
2082 					pathInfo = requestUri[pathInfoStarts..question];
2083 				}
2084 
2085 				auto ugh = decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
2086 				getArray = cast(string[][string]) assumeUnique(ugh);
2087 
2088 				if(header.indexOf("HTTP/1.0") != -1) {
2089 					http10 = true;
2090 					autoBuffer = true;
2091 					if(closeConnection) {
2092 						// on http 1.0, close is assumed (unlike http/1.1 where we assume keep alive)
2093 						*closeConnection = true;
2094 					}
2095 				}
2096 			} else {
2097 				// other header
2098 				auto colon = header.indexOf(":");
2099 				if(colon == -1)
2100 					throw new Exception("HTTP headers should have a colon!");
2101 				string name = header[0..colon].toLower;
2102 				string value = header[colon+2..$]; // skip the colon and the space
2103 
2104 				requestHeadersHere[name] = value;
2105 
2106 				if (name == "accept") {
2107 					accept = value;
2108 				}
2109 				else if (name == "origin") {
2110 					origin = value;
2111 				}
2112 				else if (name == "connection") {
2113 					if(value == "close" && closeConnection)
2114 						*closeConnection = true;
2115 					if(value.asLowerCase().canFind("keep-alive")) {
2116 						keepAliveRequested = true;
2117 
2118 						// on http 1.0, the connection is closed by default,
2119 						// but not if they request keep-alive. then we don't close
2120 						// anymore - undoing the set above
2121 						if(http10 && closeConnection) {
2122 							*closeConnection = false;
2123 						}
2124 					}
2125 				}
2126 				else if (name == "transfer-encoding") {
2127 					if(value == "chunked")
2128 						isChunked = true;
2129 				}
2130 				else if (name == "last-event-id") {
2131 					lastEventId = value;
2132 				}
2133 				else if (name == "authorization") {
2134 					authorization = value;
2135 				}
2136 				else if (name == "content-type") {
2137 					contentType = value;
2138 				}
2139 				else if (name == "content-length") {
2140 					contentLength = to!size_t(value);
2141 				}
2142 				else if (name == "x-forwarded-for") {
2143 					remoteAddress = value;
2144 				}
2145 				else if (name == "x-forwarded-host" || name == "host") {
2146 					if(name != "host" || host is null)
2147 						host = value;
2148 				}
2149 				// FIXME: https://tools.ietf.org/html/rfc7239
2150 				else if (name == "accept-encoding") {
2151 					if(value.indexOf("gzip") != -1)
2152 						acceptsGzip = true;
2153 				}
2154 				else if (name == "user-agent") {
2155 					userAgent = value;
2156 				}
2157 				else if (name == "referer") {
2158 					referrer = value;
2159 				}
2160 				else if (name == "cookie") {
2161 					cookie ~= value;
2162 				} else if(name == "expect") {
2163 					if(value == "100-continue") {
2164 						// FIXME we should probably give user code a chance
2165 						// to process and reject but that needs to be virtual,
2166 						// perhaps part of the CGI redesign.
2167 
2168 						// FIXME: if size is > max content length it should
2169 						// also fail at this point.
2170 						_rawDataOutput(cast(ubyte[]) "HTTP/1.1 100 Continue\r\n\r\n");
2171 
2172 						// FIXME: let the user write out 103 early hints too
2173 					}
2174 				}
2175 				// else
2176 				// ignore it
2177 
2178 			}
2179 		}
2180 
2181 		inputData.consume(idx + 4);
2182 		// done
2183 
2184 		requestHeaders = assumeUnique(requestHeadersHere);
2185 
2186 		ByChunkRange dataByChunk;
2187 
2188 		// reading Content-Length type data
2189 		// We need to read up the data we have, and write it out as a chunk.
2190 		if(!isChunked) {
2191 			dataByChunk = byChunk(inputData, contentLength);
2192 		} else {
2193 			// chunked requests happen, but not every day. Since we need to know
2194 			// the content length (for now, maybe that should change), we'll buffer
2195 			// the whole thing here instead of parse streaming. (I think this is what Apache does anyway in cgi modes)
2196 			auto data = dechunk(inputData);
2197 
2198 			// set the range here
2199 			dataByChunk = byChunk(data);
2200 			contentLength = data.length;
2201 		}
2202 
2203 		assert(dataByChunk !is null);
2204 
2205 		if(contentLength) {
2206 			prepareForIncomingDataChunks(contentType, contentLength);
2207 			foreach(dataChunk; dataByChunk) {
2208 				handleIncomingDataChunk(dataChunk);
2209 			}
2210 			postArray = assumeUnique(pps._post);
2211 			filesArray = assumeUnique(pps._files);
2212 			files = keepLastOf(filesArray);
2213 			post = keepLastOf(postArray);
2214 			postBody = pps.postBody;
2215 			this.requestContentType = contentType;
2216 
2217 			cleanUpPostDataState();
2218 		}
2219 
2220 		this.port = port;
2221 		this.referrer = referrer;
2222 		this.remoteAddress = remoteAddress;
2223 		this.userAgent = userAgent;
2224 		this.authorization = authorization;
2225 		this.origin = origin;
2226 		this.accept = accept;
2227 		this.lastEventId = lastEventId;
2228 		this.https = https;
2229 		this.host = host;
2230 		this.requestMethod = requestMethod;
2231 		this.requestUri = requestUri;
2232 		this.pathInfo = pathInfo;
2233 		this.queryString = queryString;
2234 
2235 		this.scriptName = scriptName;
2236 		this.get = keepLastOf(getArray);
2237 		this.getArray = cast(immutable) getArray;
2238 		this.keepAliveRequested = keepAliveRequested;
2239 		this.acceptsGzip = acceptsGzip;
2240 		this.cookie = cookie;
2241 
2242 		cookiesArray = getCookieArray();
2243 		cookies = keepLastOf(cookiesArray);
2244 
2245 	}
2246 	BufferedInputRange idlol;
2247 
2248 	private immutable(string[string]) keepLastOf(in string[][string] arr) {
2249 		string[string] ca;
2250 		foreach(k, v; arr)
2251 			ca[k] = v[$-1];
2252 
2253 		return assumeUnique(ca);
2254 	}
2255 
2256 	// FIXME duplication
2257 	private immutable(UploadedFile[string]) keepLastOf(in UploadedFile[][string] arr) {
2258 		UploadedFile[string] ca;
2259 		foreach(k, v; arr)
2260 			ca[k] = v[$-1];
2261 
2262 		return assumeUnique(ca);
2263 	}
2264 
2265 
2266 	private immutable(string[][string]) getCookieArray() {
2267 		auto forTheLoveOfGod = decodeVariables(cookie, "; ");
2268 		return assumeUnique(forTheLoveOfGod);
2269 	}
2270 
2271 	/++
2272 		Very simple method to require a basic auth username and password.
2273 		If the http request doesn't include the required credentials, it throws a
2274 		HTTP 401 error, and an exception to cancel your handler. Do NOT catch the
2275 		`AuthorizationRequiredException` exception thrown by this if you want the
2276 		http basic auth prompt to work for the user!
2277 
2278 		Note: basic auth does not provide great security, especially over unencrypted HTTP;
2279 		the user's credentials are sent in plain text on every request.
2280 
2281 		If you are using Apache, the HTTP_AUTHORIZATION variable may not be sent to the
2282 		application. Either use Apache's built in methods for basic authentication, or add
2283 		something along these lines to your server configuration:
2284 
2285 		     ```
2286 		     RewriteEngine On
2287 		     RewriteCond %{HTTP:Authorization} ^(.*)
2288 		     RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
2289 		     ```
2290 
2291 		To ensure the necessary data is available to cgi.d.
2292 	+/
2293 	void requireBasicAuth(string user, string pass, string message = null, string file = __FILE__, size_t line = __LINE__) {
2294 		if(authorization != "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (user ~ ":" ~ pass))) {
2295 			throw new AuthorizationRequiredException("Basic", message, file, line);
2296 		}
2297 	}
2298 
2299 	/// Very simple caching controls - setCache(false) means it will never be cached. Good for rapidly updated or sensitive sites.
2300 	/// setCache(true) means it will always be cached for as long as possible. Best for static content.
2301 	/// Use setResponseExpires and updateResponseExpires for more control
2302 	void setCache(bool allowCaching) {
2303 		noCache = !allowCaching;
2304 	}
2305 
2306 	/// Set to true and use cgi.write(data, true); to send a gzipped response to browsers
2307 	/// who can accept it
2308 	bool gzipResponse;
2309 
2310 	immutable bool acceptsGzip;
2311 	immutable bool keepAliveRequested;
2312 
2313 	/// Set to true if and only if this was initialized with command line arguments
2314 	immutable bool isCalledWithCommandLineArguments;
2315 
2316 	/// This gets a full url for the current request, including port, protocol, host, path, and query
2317 	string getCurrentCompleteUri() const {
2318 		ushort defaultPort = https ? 443 : 80;
2319 
2320 		string uri = "http";
2321 		if(https)
2322 			uri ~= "s";
2323 		uri ~= "://";
2324 		uri ~= host;
2325 		/+ // the host has the port so p sure this never needed, cgi on apache and embedded http all do the right hting now
2326 		version(none)
2327 		if(!(!port || port == defaultPort)) {
2328 			uri ~= ":";
2329 			uri ~= to!string(port);
2330 		}
2331 		+/
2332 		uri ~= requestUri;
2333 		return uri;
2334 	}
2335 
2336 	/// You can override this if your site base url isn't the same as the script name
2337 	string logicalScriptName() const {
2338 		return scriptName;
2339 	}
2340 
2341 	/++
2342 		Sets the HTTP status of the response. For example, "404 File Not Found" or "500 Internal Server Error".
2343 		It assumes "200 OK", and automatically changes to "302 Found" if you call setResponseLocation().
2344 		Note setResponseStatus() must be called *before* you write() any data to the output.
2345 
2346 		History:
2347 			The `int` overload was added on January 11, 2021.
2348 	+/
2349 	void setResponseStatus(string status) {
2350 		assert(!outputtedResponseData);
2351 		responseStatus = status;
2352 	}
2353 	/// ditto
2354 	void setResponseStatus(int statusCode) {
2355 		setResponseStatus(getHttpCodeText(statusCode));
2356 	}
2357 	private string responseStatus = null;
2358 
2359 	/// Returns true if it is still possible to output headers
2360 	bool canOutputHeaders() {
2361 		return !isClosed && !outputtedResponseData;
2362 	}
2363 
2364 	/// Sets the location header, which the browser will redirect the user to automatically.
2365 	/// Note setResponseLocation() must be called *before* you write() any data to the output.
2366 	/// The optional important argument is used if it's a default suggestion rather than something to insist upon.
2367 	void setResponseLocation(string uri, bool important = true, string status = null) {
2368 		if(!important && isCurrentResponseLocationImportant)
2369 			return; // important redirects always override unimportant ones
2370 
2371 		if(uri is null) {
2372 			responseStatus = "200 OK";
2373 			responseLocation = null;
2374 			isCurrentResponseLocationImportant = important;
2375 			return; // this just cancels the redirect
2376 		}
2377 
2378 		assert(!outputtedResponseData);
2379 		if(status is null)
2380 			responseStatus = "302 Found";
2381 		else
2382 			responseStatus = status;
2383 
2384 		responseLocation = uri.strip;
2385 		isCurrentResponseLocationImportant = important;
2386 	}
2387 	protected string responseLocation = null;
2388 	private bool isCurrentResponseLocationImportant = false;
2389 
2390 	/// Sets the Expires: http header. See also: updateResponseExpires, setPublicCaching
2391 	/// The parameter is in unix_timestamp * 1000. Try setResponseExpires(getUTCtime() + SOME AMOUNT) for normal use.
2392 	/// Note: the when parameter is different than setCookie's expire parameter.
2393 	void setResponseExpires(long when, bool isPublic = false) {
2394 		responseExpires = when;
2395 		setCache(true); // need to enable caching so the date has meaning
2396 
2397 		responseIsPublic = isPublic;
2398 		responseExpiresRelative = false;
2399 	}
2400 
2401 	/// Sets a cache-control max-age header for whenFromNow, in seconds.
2402 	void setResponseExpiresRelative(int whenFromNow, bool isPublic = false) {
2403 		responseExpires = whenFromNow;
2404 		setCache(true); // need to enable caching so the date has meaning
2405 
2406 		responseIsPublic = isPublic;
2407 		responseExpiresRelative = true;
2408 	}
2409 	private long responseExpires = long.min;
2410 	private bool responseIsPublic = false;
2411 	private bool responseExpiresRelative = false;
2412 
2413 	/// This is like setResponseExpires, but it can be called multiple times. The setting most in the past is the one kept.
2414 	/// If you have multiple functions, they all might call updateResponseExpires about their own return value. The program
2415 	/// output as a whole is as cacheable as the least cachable part in the chain.
2416 
2417 	/// 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.
2418 	/// Conversely, setting here overrides setCache(true), since any expiration date is in the past of infinity.
2419 	void updateResponseExpires(long when, bool isPublic) {
2420 		if(responseExpires == long.min)
2421 			setResponseExpires(when, isPublic);
2422 		else if(when < responseExpires)
2423 			setResponseExpires(when, responseIsPublic && isPublic); // if any part of it is private, it all is
2424 	}
2425 
2426 	/*
2427 	/// Set to true if you want the result to be cached publically - that is, is the content shared?
2428 	/// Should generally be false if the user is logged in. It assumes private cache only.
2429 	/// setCache(true) also turns on public caching, and setCache(false) sets to private.
2430 	void setPublicCaching(bool allowPublicCaches) {
2431 		publicCaching = allowPublicCaches;
2432 	}
2433 	private bool publicCaching = false;
2434 	*/
2435 
2436 	/++
2437 		History:
2438 			Added January 11, 2021
2439 	+/
2440 	enum SameSitePolicy {
2441 		Lax,
2442 		Strict,
2443 		None
2444 	}
2445 
2446 	/++
2447 		Sets an HTTP cookie, automatically encoding the data to the correct string.
2448 		expiresIn is how many milliseconds in the future the cookie will expire.
2449 		TIP: to make a cookie accessible from subdomains, set the domain to .yourdomain.com.
2450 		Note setCookie() must be called *before* you write() any data to the output.
2451 
2452 		History:
2453 			Parameter `sameSitePolicy` was added on January 11, 2021.
2454 	+/
2455 	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) {
2456 		assert(!outputtedResponseData);
2457 		string cookie = encodeUriComponent(name) ~ "=";
2458 		cookie ~= encodeUriComponent(data);
2459 		if(path !is null)
2460 			cookie ~= "; path=" ~ path;
2461 		// FIXME: should I just be using max-age here? (also in cache below)
2462 		if(expiresIn != 0)
2463 			cookie ~= "; expires=" ~ printDate(cast(DateTime) Clock.currTime(UTC()) + dur!"msecs"(expiresIn));
2464 		if(domain !is null)
2465 			cookie ~= "; domain=" ~ domain;
2466 		if(secure == true)
2467 			cookie ~= "; Secure";
2468 		if(httpOnly == true )
2469 			cookie ~= "; HttpOnly";
2470 		final switch(sameSitePolicy) {
2471 			case SameSitePolicy.Lax:
2472 				cookie ~= "; SameSite=Lax";
2473 			break;
2474 			case SameSitePolicy.Strict:
2475 				cookie ~= "; SameSite=Strict";
2476 			break;
2477 			case SameSitePolicy.None:
2478 				cookie ~= "; SameSite=None";
2479 				assert(secure); // cookie spec requires this now, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
2480 			break;
2481 		}
2482 
2483 		if(auto idx = name in cookieIndexes) {
2484 			responseCookies[*idx] = cookie;
2485 		} else {
2486 			cookieIndexes[name] = responseCookies.length;
2487 			responseCookies ~= cookie;
2488 		}
2489 	}
2490 	private string[] responseCookies;
2491 	private size_t[string] cookieIndexes;
2492 
2493 	/// Clears a previously set cookie with the given name, path, and domain.
2494 	void clearCookie(string name, string path = null, string domain = null) {
2495 		assert(!outputtedResponseData);
2496 		setCookie(name, "", 1, path, domain);
2497 	}
2498 
2499 	/// Sets the content type of the response, for example "text/html" (the default) for HTML, or "image/png" for a PNG image
2500 	void setResponseContentType(string ct) {
2501 		assert(!outputtedResponseData);
2502 		responseContentType = ct;
2503 	}
2504 	private string responseContentType = null;
2505 
2506 	/// Adds a custom header. It should be the name: value, but without any line terminator.
2507 	/// For example: header("X-My-Header: Some value");
2508 	/// Note you should use the specialized functions in this object if possible to avoid
2509 	/// duplicates in the output.
2510 	void header(string h) {
2511 		customHeaders ~= h;
2512 	}
2513 
2514 	/++
2515 		I named the original function `header` after PHP, but this pattern more fits
2516 		the rest of the Cgi object.
2517 
2518 		Either name are allowed.
2519 
2520 		History:
2521 			Alias added June 17, 2022.
2522 	+/
2523 	alias setResponseHeader = header;
2524 
2525 	private string[] customHeaders;
2526 	private bool websocketMode;
2527 
2528 	void flushHeaders(const(void)[] t, bool isAll = false) {
2529 		StackBuffer buffer = StackBuffer(0);
2530 
2531 		prepHeaders(t, isAll, &buffer);
2532 
2533 		if(rawDataOutput !is null)
2534 			rawDataOutput(cast(const(ubyte)[]) buffer.get());
2535 		else {
2536 			stdout.rawWrite(buffer.get());
2537 		}
2538 	}
2539 
2540 	private void prepHeaders(const(void)[] t, bool isAll, StackBuffer* buffer) {
2541 		string terminator = "\n";
2542 		if(rawDataOutput !is null)
2543 			terminator = "\r\n";
2544 
2545 		if(responseStatus !is null) {
2546 			if(nph) {
2547 				if(http10)
2548 					buffer.add("HTTP/1.0 ", responseStatus, terminator);
2549 				else
2550 					buffer.add("HTTP/1.1 ", responseStatus, terminator);
2551 			} else
2552 				buffer.add("Status: ", responseStatus, terminator);
2553 		} else if (nph) {
2554 			if(http10)
2555 				buffer.add("HTTP/1.0 200 OK", terminator);
2556 			else
2557 				buffer.add("HTTP/1.1 200 OK", terminator);
2558 		}
2559 
2560 		if(websocketMode)
2561 			goto websocket;
2562 
2563 		if(nph) { // we're responsible for setting the date too according to http 1.1
2564 			char[29] db = void;
2565 			printDateToBuffer(cast(DateTime) Clock.currTime(UTC()), db[]);
2566 			buffer.add("Date: ", db[], terminator);
2567 		}
2568 
2569 		// FIXME: what if the user wants to set his own content-length?
2570 		// The custom header function can do it, so maybe that's best.
2571 		// Or we could reuse the isAll param.
2572 		if(responseLocation !is null) {
2573 			buffer.add("Location: ", responseLocation, terminator);
2574 		}
2575 		if(!noCache && responseExpires != long.min) { // an explicit expiration date is set
2576 			if(responseExpiresRelative) {
2577 				buffer.add("Cache-Control: ", responseIsPublic ? "public" : "private", ", max-age=");
2578 				buffer.add(responseExpires);
2579 				buffer.add(", no-cache=\"set-cookie, set-cookie2\"", terminator);
2580 			} else {
2581 				auto expires = SysTime(unixTimeToStdTime(cast(int)(responseExpires / 1000)), UTC());
2582 				char[29] db = void;
2583 				printDateToBuffer(cast(DateTime) expires, db[]);
2584 				buffer.add("Expires: ", db[], terminator);
2585 				// FIXME: assuming everything is private unless you use nocache - generally right for dynamic pages, but not necessarily
2586 				buffer.add("Cache-Control: ", (responseIsPublic ? "public" : "private"), ", no-cache=\"set-cookie, set-cookie2\"");
2587 				buffer.add(terminator);
2588 			}
2589 		}
2590 		if(responseCookies !is null && responseCookies.length > 0) {
2591 			foreach(c; responseCookies)
2592 				buffer.add("Set-Cookie: ", c, terminator);
2593 		}
2594 		if(noCache) { // we specifically do not want caching (this is actually the default)
2595 			buffer.add("Cache-Control: private, no-cache=\"set-cookie\"", terminator);
2596 			buffer.add("Expires: 0", terminator);
2597 			buffer.add("Pragma: no-cache", terminator);
2598 		} else {
2599 			if(responseExpires == long.min) { // caching was enabled, but without a date set - that means assume cache forever
2600 				buffer.add("Cache-Control: public", terminator);
2601 				buffer.add("Expires: Tue, 31 Dec 2030 14:00:00 GMT", terminator); // FIXME: should not be more than one year in the future
2602 			}
2603 		}
2604 		if(responseContentType !is null) {
2605 			buffer.add("Content-Type: ", responseContentType, terminator);
2606 		} else
2607 			buffer.add("Content-Type: text/html; charset=utf-8", terminator);
2608 
2609 		if(gzipResponse && acceptsGzip && isAll) { // FIXME: isAll really shouldn't be necessary
2610 			buffer.add("Content-Encoding: gzip", terminator);
2611 		}
2612 
2613 
2614 		if(!isAll) {
2615 			if(nph && !http10) {
2616 				buffer.add("Transfer-Encoding: chunked", terminator);
2617 				responseChunked = true;
2618 			}
2619 		} else {
2620 			buffer.add("Content-Length: ");
2621 			buffer.add(t.length);
2622 			buffer.add(terminator);
2623 			if(nph && keepAliveRequested) {
2624 				buffer.add("Connection: Keep-Alive", terminator);
2625 			}
2626 		}
2627 
2628 		websocket:
2629 
2630 		foreach(hd; customHeaders)
2631 			buffer.add(hd, terminator);
2632 
2633 		// FIXME: what about duplicated headers?
2634 
2635 		// end of header indicator
2636 		buffer.add(terminator);
2637 
2638 		outputtedResponseData = true;
2639 	}
2640 
2641 	/// Writes the data to the output, flushing headers if they have not yet been sent.
2642 	void write(const(void)[] t, bool isAll = false, bool maybeAutoClose = true) {
2643 		assert(!closed, "Output has already been closed");
2644 
2645 		StackBuffer buffer = StackBuffer(0);
2646 
2647 		if(gzipResponse && acceptsGzip && isAll) { // FIXME: isAll really shouldn't be necessary
2648 			// actually gzip the data here
2649 
2650 			auto c = new Compress(HeaderFormat.gzip); // want gzip
2651 
2652 			auto data = c.compress(t);
2653 			data ~= c.flush();
2654 
2655 			// std.file.write("/tmp/last-item", data);
2656 
2657 			t = data;
2658 		}
2659 
2660 		if(!outputtedResponseData && (!autoBuffer || isAll)) {
2661 			prepHeaders(t, isAll, &buffer);
2662 		}
2663 
2664 		if(requestMethod != RequestMethod.HEAD && t.length > 0) {
2665 			if (autoBuffer && !isAll) {
2666 				outputBuffer ~= cast(ubyte[]) t;
2667 			}
2668 			if(!autoBuffer || isAll) {
2669 				if(rawDataOutput !is null)
2670 					if(nph && responseChunked) {
2671 						//rawDataOutput(makeChunk(cast(const(ubyte)[]) t));
2672 						// we're making the chunk here instead of in a function
2673 						// to avoid unneeded gc pressure
2674 						buffer.add(toHex(t.length));
2675 						buffer.add("\r\n");
2676 						buffer.add(cast(char[]) t, "\r\n");
2677 					} else {
2678 						buffer.add(cast(char[]) t);
2679 					}
2680 				else
2681 					buffer.add(cast(char[]) t);
2682 			}
2683 		}
2684 
2685 		if(rawDataOutput !is null)
2686 			rawDataOutput(cast(const(ubyte)[]) buffer.get());
2687 		else
2688 			stdout.rawWrite(buffer.get());
2689 
2690 		if(maybeAutoClose && isAll)
2691 			close(); // if you say it is all, that means we're definitely done
2692 				// maybeAutoClose can be false though to avoid this (important if you call from inside close()!
2693 	}
2694 
2695 	/++
2696 		Convenience method to set content type to json and write the string as the complete response.
2697 
2698 		History:
2699 			Added January 16, 2020
2700 	+/
2701 	void writeJson(string json) {
2702 		this.setResponseContentType("application/json");
2703 		this.write(json, true);
2704 	}
2705 
2706 	/// Flushes the pending buffer, leaving the connection open so you can send more.
2707 	void flush() {
2708 		if(rawDataOutput is null)
2709 			stdout.flush();
2710 		else if(flushDelegate !is null)
2711 			flushDelegate();
2712 	}
2713 
2714 	version(autoBuffer)
2715 		bool autoBuffer = true;
2716 	else
2717 		bool autoBuffer = false;
2718 	ubyte[] outputBuffer;
2719 
2720 	/// Flushes the buffers to the network, signifying that you are done.
2721 	/// You should always call this explicitly when you are done outputting data.
2722 	void close() {
2723 		if(closed)
2724 			return; // don't double close
2725 
2726 		if(!outputtedResponseData)
2727 			write("", true, false);
2728 
2729 		// writing auto buffered data
2730 		if(requestMethod != RequestMethod.HEAD && autoBuffer) {
2731 			if(!nph)
2732 				stdout.rawWrite(outputBuffer);
2733 			else
2734 				write(outputBuffer, true, false); // tell it this is everything
2735 		}
2736 
2737 		// closing the last chunk...
2738 		if(nph && rawDataOutput !is null && responseChunked)
2739 			rawDataOutput(cast(const(ubyte)[]) "0\r\n\r\n");
2740 
2741 		if(flushDelegate)
2742 			flushDelegate();
2743 
2744 		closed = true;
2745 	}
2746 
2747 	// Closes without doing anything, shouldn't be used often
2748 	void rawClose() {
2749 		closed = true;
2750 	}
2751 
2752 	/++
2753 		Gets a request variable as a specific type, or the default value of it isn't there
2754 		or isn't convertible to the request type.
2755 
2756 		Checks both GET and POST variables, preferring the POST variable, if available.
2757 
2758 		A nice trick is using the default value to choose the type:
2759 
2760 		---
2761 			/*
2762 				The return value will match the type of the default.
2763 				Here, I gave 10 as a default, so the return value will
2764 				be an int.
2765 
2766 				If the user-supplied value cannot be converted to the
2767 				requested type, you will get the default value back.
2768 			*/
2769 			int a = cgi.request("number", 10);
2770 
2771 			if(cgi.get["number"] == "11")
2772 				assert(a == 11); // conversion succeeds
2773 
2774 			if("number" !in cgi.get)
2775 				assert(a == 10); // no value means you can't convert - give the default
2776 
2777 			if(cgi.get["number"] == "twelve")
2778 				assert(a == 10); // conversion from string to int would fail, so we get the default
2779 		---
2780 
2781 		You can use an enum as an easy whitelist, too:
2782 
2783 		---
2784 			enum Operations {
2785 				add, remove, query
2786 			}
2787 
2788 			auto op = cgi.request("op", Operations.query);
2789 
2790 			if(cgi.get["op"] == "add")
2791 				assert(op == Operations.add);
2792 			if(cgi.get["op"] == "remove")
2793 				assert(op == Operations.remove);
2794 			if(cgi.get["op"] == "query")
2795 				assert(op == Operations.query);
2796 
2797 			if(cgi.get["op"] == "random string")
2798 				assert(op == Operations.query); // the value can't be converted to the enum, so we get the default
2799 		---
2800 	+/
2801 	T request(T = string)(in string name, in T def = T.init) const nothrow {
2802 		try {
2803 			return
2804 				(name in post) ? to!T(post[name]) :
2805 				(name in get)  ? to!T(get[name]) :
2806 				def;
2807 		} catch(Exception e) { return def; }
2808 	}
2809 
2810 	/// Is the output already closed?
2811 	bool isClosed() const {
2812 		return closed;
2813 	}
2814 
2815 	private SessionObject commandLineSessionObject;
2816 
2817 	/++
2818 		Gets a session object associated with the `cgi` request. You can use different type throughout your application.
2819 	+/
2820 	Session!Data getSessionObject(Data)() {
2821 		if(testInProcess !is null) {
2822 			// test mode
2823 			auto obj = testInProcess.getSessionOverride(typeid(typeof(return)));
2824 			if(obj !is null)
2825 				return cast(typeof(return)) obj;
2826 			else {
2827 				auto o = new MockSession!Data();
2828 				testInProcess.setSessionOverride(typeid(typeof(return)), o);
2829 				return o;
2830 			}
2831 		} else {
2832 			// FIXME: the changes are not printed out at the end!
2833 			if(_commandLineSession !is null) {
2834 				if(commandLineSessionObject is null) {
2835 					auto clso = new MockSession!Data();
2836 					commandLineSessionObject = clso;
2837 
2838 
2839 					foreach(memberName; __traits(allMembers, Data)) {
2840 						if(auto str = memberName in _commandLineSession)
2841 							__traits(getMember, clso.store_, memberName) = to!(typeof(__traits(getMember, Data, memberName)))(*str);
2842 					}
2843 				}
2844 
2845 				return cast(typeof(return)) commandLineSessionObject;
2846 			}
2847 
2848 			// normal operation
2849 			return new BasicDataServerSession!Data(this);
2850 		}
2851 	}
2852 
2853 	// if it is in test mode; triggers mock sessions. Used by CgiTester
2854 	version(with_breaking_cgi_features)
2855 	private CgiTester testInProcess;
2856 
2857 	/* Hooks for redirecting input and output */
2858 	private void delegate(const(ubyte)[]) rawDataOutput = null;
2859 	private void delegate() flushDelegate = null;
2860 
2861 	/* This info is used when handling a more raw HTTP protocol */
2862 	private bool nph;
2863 	private bool http10;
2864 	private bool closed;
2865 	private bool responseChunked = false;
2866 
2867 	version(preserveData) // note: this can eat lots of memory; don't use unless you're sure you need it.
2868 	immutable(ubyte)[] originalPostData;
2869 
2870 	/++
2871 		This holds the posted body data if it has not been parsed into [post] and [postArray].
2872 
2873 		It is intended to be used for JSON and XML request content types, but also may be used
2874 		for other content types your application can handle. But it will NOT be populated
2875 		for content types application/x-www-form-urlencoded or multipart/form-data, since those are
2876 		parsed into the post and postArray members.
2877 
2878 		Remember that anything beyond your `maxContentLength` param when setting up [GenericMain], etc.,
2879 		will be discarded to the client with an error. This helps keep this array from being exploded in size
2880 		and consuming all your server's memory (though it may still be possible to eat excess ram from a concurrent
2881 		client in certain build modes.)
2882 
2883 		History:
2884 			Added January 5, 2021
2885 			Documented February 21, 2023 (dub v11.0)
2886 	+/
2887 	public immutable string postBody;
2888 	alias postJson = postBody; // old name
2889 
2890 	/++
2891 		The content type header of the request. The [postBody] member may hold the actual data (see [postBody] for details).
2892 
2893 		History:
2894 			Added January 26, 2024 (dub v11.4)
2895 	+/
2896 	public immutable string requestContentType;
2897 
2898 	/* Internal state flags */
2899 	private bool outputtedResponseData;
2900 	private bool noCache = true;
2901 
2902 	const(string[string]) environmentVariables;
2903 
2904 	/** What follows is data gotten from the HTTP request. It is all fully immutable,
2905 	    partially because it logically is (your code doesn't change what the user requested...)
2906 	    and partially because I hate how bad programs in PHP change those superglobals to do
2907 	    all kinds of hard to follow ugliness. I don't want that to ever happen in D.
2908 
2909 	    For some of these, you'll want to refer to the http or cgi specs for more details.
2910 	*/
2911 	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.
2912 
2913 	immutable(char[]) host; 	/// The hostname in the request. If one program serves multiple domains, you can use this to differentiate between them.
2914 	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.
2915 	immutable(char[]) userAgent; 	/// The browser's user-agent string. Can be used to identify the browser.
2916 	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".
2917 	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".
2918 	immutable(char[]) scriptFileName;   /// The physical filename of your script
2919 	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.
2920 	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.)
2921 	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.
2922 
2923 	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.
2924 	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.)
2925 	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.
2926 	/** 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.
2927 
2928 	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.
2929 	*/
2930 	immutable(char[]) referrer;
2931 	immutable(char[]) requestUri; 	/// The full url if the current request, excluding the protocol and host. requestUri == scriptName ~ pathInfo ~ (queryString.length ? "?" ~ queryString : "");
2932 
2933 	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.)
2934 
2935 	immutable bool https; 	/// Was the request encrypted via https?
2936 	immutable int port; 	/// On what TCP port number did the server receive the request?
2937 
2938 	/** Here come the parsed request variables - the things that come close to PHP's _GET, _POST, etc. superglobals in content. */
2939 
2940 	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.
2941 	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.
2942 	immutable(string[string]) cookies; /// Separates out the cookie header into individual name/value pairs (which is how you set them!)
2943 
2944 	/// added later
2945 	alias query = get;
2946 
2947 	/**
2948 		Represents user uploaded files.
2949 
2950 		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.
2951 	*/
2952 	immutable(UploadedFile[][string]) filesArray;
2953 	immutable(UploadedFile[string]) files;
2954 
2955 	/// 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.
2956 	/// the order of the arrays is the order the data arrives
2957 	immutable(string[][string]) getArray; /// like get, but an array of values per name
2958 	immutable(string[][string]) postArray; /// ditto for post
2959 	immutable(string[][string]) cookiesArray; /// ditto for cookies
2960 
2961 	private string[string] _commandLineSession;
2962 
2963 	// convenience function for appending to a uri without extra ?
2964 	// matches the name and effect of javascript's location.search property
2965 	string search() const {
2966 		if(queryString.length)
2967 			return "?" ~ queryString;
2968 		return "";
2969 	}
2970 
2971 	// FIXME: what about multiple files with the same name?
2972   private:
2973 	//RequestMethod _requestMethod;
2974 }
2975 
2976 /// use this for testing or other isolated things when you want it to be no-ops
2977 Cgi dummyCgi(Cgi.RequestMethod method = Cgi.RequestMethod.GET, string url = null, in ubyte[] data = null, void delegate(const(ubyte)[]) outputSink = null) {
2978 	// we want to ignore, not use stdout
2979 	if(outputSink is null)
2980 		outputSink = delegate void(const(ubyte)[]) { };
2981 
2982 	string[string] env;
2983 	env["REQUEST_METHOD"] = to!string(method);
2984 	env["CONTENT_LENGTH"] = to!string(data.length);
2985 
2986 	auto cgi = new Cgi(
2987 		0,
2988 		env,
2989 		{ return data; },
2990 		outputSink,
2991 		null);
2992 
2993 	return cgi;
2994 }
2995 
2996 /++
2997 	A helper test class for request handler unittests.
2998 +/
2999 version(with_breaking_cgi_features)
3000 class CgiTester {
3001 	private {
3002 		SessionObject[TypeInfo] mockSessions;
3003 		SessionObject getSessionOverride(TypeInfo ti) {
3004 			if(auto o = ti in mockSessions)
3005 				return *o;
3006 			else
3007 				return null;
3008 		}
3009 		void setSessionOverride(TypeInfo ti, SessionObject so) {
3010 			mockSessions[ti] = so;
3011 		}
3012 	}
3013 
3014 	/++
3015 		Gets (and creates if necessary) a mock session object for this test. Note
3016 		it will be the same one used for any test operations through this CgiTester instance.
3017 	+/
3018 	Session!Data getSessionObject(Data)() {
3019 		auto obj = getSessionOverride(typeid(typeof(return)));
3020 		if(obj !is null)
3021 			return cast(typeof(return)) obj;
3022 		else {
3023 			auto o = new MockSession!Data();
3024 			setSessionOverride(typeid(typeof(return)), o);
3025 			return o;
3026 		}
3027 	}
3028 
3029 	/++
3030 		Pass a reference to your request handler when creating the tester.
3031 	+/
3032 	this(void function(Cgi) requestHandler) {
3033 		this.requestHandler = requestHandler;
3034 	}
3035 
3036 	/++
3037 		You can check response information with these methods after you call the request handler.
3038 	+/
3039 	struct Response {
3040 		int code;
3041 		string[string] headers;
3042 		string responseText;
3043 		ubyte[] responseBody;
3044 	}
3045 
3046 	/++
3047 		Executes a test request on your request handler, and returns the response.
3048 
3049 		Params:
3050 			url = The URL to test. Should be an absolute path, but excluding domain. e.g. `"/test"`.
3051 			args = additional arguments. Same format as cgi's command line handler.
3052 	+/
3053 	Response GET(string url, string[] args = null) {
3054 		return executeTest("GET", url, args);
3055 	}
3056 	/// ditto
3057 	Response POST(string url, string[] args = null) {
3058 		return executeTest("POST", url, args);
3059 	}
3060 
3061 	/// ditto
3062 	Response executeTest(string method, string url, string[] args) {
3063 		ubyte[] outputtedRawData;
3064 		void outputSink(const(ubyte)[] data) {
3065 			outputtedRawData ~= data;
3066 		}
3067 		auto cgi = new Cgi(["test", method, url] ~ args, &outputSink);
3068 		cgi.testInProcess = this;
3069 		scope(exit) cgi.dispose();
3070 
3071 		requestHandler(cgi);
3072 
3073 		cgi.close();
3074 
3075 		Response response;
3076 
3077 		if(outputtedRawData.length) {
3078 			enum LINE = "\r\n";
3079 
3080 			auto idx = outputtedRawData.locationOf(LINE ~ LINE);
3081 			assert(idx != -1, to!string(outputtedRawData));
3082 			auto headers = cast(string) outputtedRawData[0 .. idx];
3083 			response.code = 200;
3084 			while(headers.length) {
3085 				auto i = headers.locationOf(LINE);
3086 				if(i == -1) i = cast(int) headers.length;
3087 
3088 				auto header = headers[0 .. i];
3089 
3090 				auto c = header.locationOf(":");
3091 				if(c != -1) {
3092 					auto name = header[0 .. c];
3093 					auto value = header[c + 2 ..$];
3094 
3095 					if(name == "Status")
3096 						response.code = value[0 .. value.locationOf(" ")].to!int;
3097 
3098 					response.headers[name] = value;
3099 				} else {
3100 					assert(0);
3101 				}
3102 
3103 				if(i != headers.length)
3104 					i += 2;
3105 				headers = headers[i .. $];
3106 			}
3107 			response.responseBody = outputtedRawData[idx + 4 .. $];
3108 			response.responseText = cast(string) response.responseBody;
3109 		}
3110 
3111 		return response;
3112 	}
3113 
3114 	private void function(Cgi) requestHandler;
3115 }
3116 
3117 
3118 // should this be a separate module? Probably, but that's a hassle.
3119 
3120 /// Makes a data:// uri that can be used as links in most newer browsers (IE8+).
3121 string makeDataUrl(string mimeType, in void[] data) {
3122 	auto data64 = Base64.encode(cast(const(ubyte[])) data);
3123 	return "data:" ~ mimeType ~ ";base64," ~ assumeUnique(data64);
3124 }
3125 
3126 // FIXME: I don't think this class correctly decodes/encodes the individual parts
3127 /// Represents a url that can be broken down or built up through properties
3128 struct Uri {
3129 	alias toString this; // blargh idk a url really is a string, but should it be implicit?
3130 
3131 	// scheme//userinfo@host:port/path?query#fragment
3132 
3133 	string scheme; /// e.g. "http" in "http://example.com/"
3134 	string userinfo; /// the username (and possibly a password) in the uri
3135 	string host; /// the domain name
3136 	int port; /// port number, if given. Will be zero if a port was not explicitly given
3137 	string path; /// e.g. "/folder/file.html" in "http://example.com/folder/file.html"
3138 	string query; /// the stuff after the ? in a uri
3139 	string fragment; /// the stuff after the # in a uri.
3140 
3141 	// 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
3142 	// the decode ones need to keep different names anyway because we can't overload on return values...
3143 	static string encode(string s) { return encodeUriComponent(s); }
3144 	static string encode(string[string] s) { return encodeVariables(s); }
3145 	static string encode(string[][string] s) { return encodeVariables(s); }
3146 
3147 	/// Breaks down a uri string to its components
3148 	this(string uri) {
3149 		reparse(uri);
3150 	}
3151 
3152 	private void reparse(string uri) {
3153 		// from RFC 3986
3154 		// the ctRegex triples the compile time and makes ugly errors for no real benefit
3155 		// it was a nice experiment but just not worth it.
3156 		// enum ctr = ctRegex!r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?";
3157 		/*
3158 			Captures:
3159 				0 = whole url
3160 				1 = scheme, with :
3161 				2 = scheme, no :
3162 				3 = authority, with //
3163 				4 = authority, no //
3164 				5 = path
3165 				6 = query string, with ?
3166 				7 = query string, no ?
3167 				8 = anchor, with #
3168 				9 = anchor, no #
3169 		*/
3170 		// Yikes, even regular, non-CT regex is also unacceptably slow to compile. 1.9s on my computer!
3171 		// instead, I will DIY and cut that down to 0.6s on the same computer.
3172 		/*
3173 
3174 				Note that authority is
3175 					user:password@domain:port
3176 				where the user:password@ part is optional, and the :port is optional.
3177 
3178 				Regex translation:
3179 
3180 				Scheme cannot have :, /, ?, or # in it, and must have one or more chars and end in a :. It is optional, but must be first.
3181 				Authority must start with //, but cannot have any other /, ?, or # in it. It is optional.
3182 				Path cannot have any ? or # in it. It is optional.
3183 				Query must start with ? and must not have # in it. It is optional.
3184 				Anchor must start with # and can have anything else in it to end of string. It is optional.
3185 		*/
3186 
3187 		this = Uri.init; // reset all state
3188 
3189 		// empty uri = nothing special
3190 		if(uri.length == 0) {
3191 			return;
3192 		}
3193 
3194 		size_t idx;
3195 
3196 		scheme_loop: foreach(char c; uri[idx .. $]) {
3197 			switch(c) {
3198 				case ':':
3199 				case '/':
3200 				case '?':
3201 				case '#':
3202 					break scheme_loop;
3203 				default:
3204 			}
3205 			idx++;
3206 		}
3207 
3208 		if(idx == 0 && uri[idx] == ':') {
3209 			// this is actually a path! we skip way ahead
3210 			goto path_loop;
3211 		}
3212 
3213 		if(idx == uri.length) {
3214 			// the whole thing is a path, apparently
3215 			path = uri;
3216 			return;
3217 		}
3218 
3219 		if(idx > 0 && uri[idx] == ':') {
3220 			scheme = uri[0 .. idx];
3221 			idx++;
3222 		} else {
3223 			// we need to rewind; it found a / but no :, so the whole thing is prolly a path...
3224 			idx = 0;
3225 		}
3226 
3227 		if(idx + 2 < uri.length && uri[idx .. idx + 2] == "//") {
3228 			// we have an authority....
3229 			idx += 2;
3230 
3231 			auto authority_start = idx;
3232 			authority_loop: foreach(char c; uri[idx .. $]) {
3233 				switch(c) {
3234 					case '/':
3235 					case '?':
3236 					case '#':
3237 						break authority_loop;
3238 					default:
3239 				}
3240 				idx++;
3241 			}
3242 
3243 			auto authority = uri[authority_start .. idx];
3244 
3245 			auto idx2 = authority.indexOf("@");
3246 			if(idx2 != -1) {
3247 				userinfo = authority[0 .. idx2];
3248 				authority = authority[idx2 + 1 .. $];
3249 			}
3250 
3251 			if(authority.length && authority[0] == '[') {
3252 				// ipv6 address special casing
3253 				idx2 = authority.indexOf(']');
3254 				if(idx2 != -1) {
3255 					auto end = authority[idx2 + 1 .. $];
3256 					if(end.length && end[0] == ':')
3257 						idx2 = idx2 + 1;
3258 					else
3259 						idx2 = -1;
3260 				}
3261 			} else {
3262 				idx2 = authority.indexOf(":");
3263 			}
3264 
3265 			if(idx2 == -1) {
3266 				port = 0; // 0 means not specified; we should use the default for the scheme
3267 				host = authority;
3268 			} else {
3269 				host = authority[0 .. idx2];
3270 				if(idx2 + 1 < authority.length)
3271 					port = to!int(authority[idx2 + 1 .. $]);
3272 				else
3273 					port = 0;
3274 			}
3275 		}
3276 
3277 		path_loop:
3278 		auto path_start = idx;
3279 
3280 		foreach(char c; uri[idx .. $]) {
3281 			if(c == '?' || c == '#')
3282 				break;
3283 			idx++;
3284 		}
3285 
3286 		path = uri[path_start .. idx];
3287 
3288 		if(idx == uri.length)
3289 			return; // nothing more to examine...
3290 
3291 		if(uri[idx] == '?') {
3292 			idx++;
3293 			auto query_start = idx;
3294 			foreach(char c; uri[idx .. $]) {
3295 				if(c == '#')
3296 					break;
3297 				idx++;
3298 			}
3299 			query = uri[query_start .. idx];
3300 		}
3301 
3302 		if(idx < uri.length && uri[idx] == '#') {
3303 			idx++;
3304 			fragment = uri[idx .. $];
3305 		}
3306 
3307 		// uriInvalidated = false;
3308 	}
3309 
3310 	private string rebuildUri() const {
3311 		string ret;
3312 		if(scheme.length)
3313 			ret ~= scheme ~ ":";
3314 		if(userinfo.length || host.length)
3315 			ret ~= "//";
3316 		if(userinfo.length)
3317 			ret ~= userinfo ~ "@";
3318 		if(host.length)
3319 			ret ~= host;
3320 		if(port)
3321 			ret ~= ":" ~ to!string(port);
3322 
3323 		ret ~= path;
3324 
3325 		if(query.length)
3326 			ret ~= "?" ~ query;
3327 
3328 		if(fragment.length)
3329 			ret ~= "#" ~ fragment;
3330 
3331 		// uri = ret;
3332 		// uriInvalidated = false;
3333 		return ret;
3334 	}
3335 
3336 	/// Converts the broken down parts back into a complete string
3337 	string toString() const {
3338 		// if(uriInvalidated)
3339 			return rebuildUri();
3340 	}
3341 
3342 	/// Returns a new absolute Uri given a base. It treats this one as
3343 	/// relative where possible, but absolute if not. (If protocol, domain, or
3344 	/// other info is not set, the new one inherits it from the base.)
3345 	///
3346 	/// Browsers use a function like this to figure out links in html.
3347 	Uri basedOn(in Uri baseUrl) const {
3348 		Uri n = this; // copies
3349 		if(n.scheme == "data")
3350 			return n;
3351 		// n.uriInvalidated = true; // make sure we regenerate...
3352 
3353 		// userinfo is not inherited... is this wrong?
3354 
3355 		// if anything is given in the existing url, we don't use the base anymore.
3356 		if(n.scheme.empty) {
3357 			n.scheme = baseUrl.scheme;
3358 			if(n.host.empty) {
3359 				n.host = baseUrl.host;
3360 				if(n.port == 0) {
3361 					n.port = baseUrl.port;
3362 					if(n.path.length > 0 && n.path[0] != '/') {
3363 						auto b = baseUrl.path[0 .. baseUrl.path.lastIndexOf("/") + 1];
3364 						if(b.length == 0)
3365 							b = "/";
3366 						n.path = b ~ n.path;
3367 					} else if(n.path.length == 0) {
3368 						n.path = baseUrl.path;
3369 					}
3370 				}
3371 			}
3372 		}
3373 
3374 		n.removeDots();
3375 
3376 		return n;
3377 	}
3378 
3379 	void removeDots() {
3380 		auto parts = this.path.split("/");
3381 		string[] toKeep;
3382 		foreach(part; parts) {
3383 			if(part == ".") {
3384 				continue;
3385 			} else if(part == "..") {
3386 				//if(toKeep.length > 1)
3387 					toKeep = toKeep[0 .. $-1];
3388 				//else
3389 					//toKeep = [""];
3390 				continue;
3391 			} else {
3392 				//if(toKeep.length && toKeep[$-1].length == 0 && part.length == 0)
3393 					//continue; // skip a `//` situation
3394 				toKeep ~= part;
3395 			}
3396 		}
3397 
3398 		auto path = toKeep.join("/");
3399 		if(path.length && path[0] != '/')
3400 			path = "/" ~ path;
3401 
3402 		this.path = path;
3403 	}
3404 
3405 	unittest {
3406 		auto uri = Uri("test.html");
3407 		assert(uri.path == "test.html");
3408 		uri = Uri("path/1/lol");
3409 		assert(uri.path == "path/1/lol");
3410 		uri = Uri("http://me@example.com");
3411 		assert(uri.scheme == "http");
3412 		assert(uri.userinfo == "me");
3413 		assert(uri.host == "example.com");
3414 		uri = Uri("http://example.com/#a");
3415 		assert(uri.scheme == "http");
3416 		assert(uri.host == "example.com");
3417 		assert(uri.fragment == "a");
3418 		uri = Uri("#foo");
3419 		assert(uri.fragment == "foo");
3420 		uri = Uri("?lol");
3421 		assert(uri.query == "lol");
3422 		uri = Uri("#foo?lol");
3423 		assert(uri.fragment == "foo?lol");
3424 		uri = Uri("?lol#foo");
3425 		assert(uri.fragment == "foo");
3426 		assert(uri.query == "lol");
3427 
3428 		uri = Uri("http://127.0.0.1/");
3429 		assert(uri.host == "127.0.0.1");
3430 		assert(uri.port == 0);
3431 
3432 		uri = Uri("http://127.0.0.1:123/");
3433 		assert(uri.host == "127.0.0.1");
3434 		assert(uri.port == 123);
3435 
3436 		uri = Uri("http://[ff:ff::0]/");
3437 		assert(uri.host == "[ff:ff::0]");
3438 
3439 		uri = Uri("http://[ff:ff::0]:123/");
3440 		assert(uri.host == "[ff:ff::0]");
3441 		assert(uri.port == 123);
3442 	}
3443 
3444 	// This can sometimes be a big pain in the butt for me, so lots of copy/paste here to cover
3445 	// the possibilities.
3446 	unittest {
3447 		auto url = Uri("cool.html"); // checking relative links
3448 
3449 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/what/cool.html");
3450 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/what/cool.html");
3451 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/what/cool.html");
3452 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/cool.html");
3453 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/cool.html");
3454 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/what/cool.html");
3455 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/what/cool.html");
3456 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/what/cool.html");
3457 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/cool.html");
3458 
3459 		url = Uri("/something/cool.html"); // same server, different path
3460 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/something/cool.html");
3461 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/something/cool.html");
3462 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/something/cool.html");
3463 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/something/cool.html");
3464 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/something/cool.html");
3465 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/something/cool.html");
3466 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/something/cool.html");
3467 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/something/cool.html");
3468 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/something/cool.html");
3469 
3470 		url = Uri("?query=answer"); // same path. server, protocol, and port, just different query string and fragment
3471 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/what/test.html?query=answer");
3472 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/what/test.html?query=answer");
3473 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/what/?query=answer");
3474 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/?query=answer");
3475 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com?query=answer");
3476 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/what/test.html?query=answer");
3477 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/what/test.html?query=answer");
3478 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/what/test.html?query=answer");
3479 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com?query=answer");
3480 
3481 		url = Uri("/test/bar");
3482 		assert(Uri("./").basedOn(url) == "/test/", Uri("./").basedOn(url));
3483 		assert(Uri("../").basedOn(url) == "/");
3484 
3485 		url = Uri("http://example.com/");
3486 		assert(Uri("../foo").basedOn(url) == "http://example.com/foo");
3487 
3488 		//auto uriBefore = url;
3489 		url = Uri("#anchor"); // everything should remain the same except the anchor
3490 		//uriBefore.anchor = "anchor");
3491 		//assert(url == uriBefore);
3492 
3493 		url = Uri("//example.com"); // same protocol, but different server. the path here should be blank.
3494 
3495 		url = Uri("//example.com/example.html"); // same protocol, but different server and path
3496 
3497 		url = Uri("http://example.com/test.html"); // completely absolute link should never be modified
3498 
3499 		url = Uri("http://example.com"); // completely absolute link should never be modified, even if it has no path
3500 
3501 		// FIXME: add something for port too
3502 	}
3503 
3504 	// these are like javascript's location.search and location.hash
3505 	string search() const {
3506 		return query.length ? ("?" ~ query) : "";
3507 	}
3508 	string hash() const {
3509 		return fragment.length ? ("#" ~ fragment) : "";
3510 	}
3511 }
3512 
3513 
3514 /*
3515 	for session, see web.d
3516 */
3517 
3518 /// breaks down a url encoded string
3519 string[][string] decodeVariables(string data, string separator = "&", string[]* namesInOrder = null, string[]* valuesInOrder = null) {
3520 	auto vars = data.split(separator);
3521 	string[][string] _get;
3522 	foreach(var; vars) {
3523 		auto equal = var.indexOf("=");
3524 		string name;
3525 		string value;
3526 		if(equal == -1) {
3527 			name = decodeUriComponent(var);
3528 			value = "";
3529 		} else {
3530 			//_get[decodeUriComponent(var[0..equal])] ~= decodeUriComponent(var[equal + 1 .. $].replace("+", " "));
3531 			// stupid + -> space conversion.
3532 			name = decodeUriComponent(var[0..equal].replace("+", " "));
3533 			value = decodeUriComponent(var[equal + 1 .. $].replace("+", " "));
3534 		}
3535 
3536 		_get[name] ~= value;
3537 		if(namesInOrder)
3538 			(*namesInOrder) ~= name;
3539 		if(valuesInOrder)
3540 			(*valuesInOrder) ~= value;
3541 	}
3542 	return _get;
3543 }
3544 
3545 /// breaks down a url encoded string, but only returns the last value of any array
3546 string[string] decodeVariablesSingle(string data) {
3547 	string[string] va;
3548 	auto varArray = decodeVariables(data);
3549 	foreach(k, v; varArray)
3550 		va[k] = v[$-1];
3551 
3552 	return va;
3553 }
3554 
3555 /// url encodes the whole string
3556 string encodeVariables(in string[string] data) {
3557 	string ret;
3558 
3559 	bool outputted = false;
3560 	foreach(k, v; data) {
3561 		if(outputted)
3562 			ret ~= "&";
3563 		else
3564 			outputted = true;
3565 
3566 		ret ~= encodeUriComponent(k) ~ "=" ~ encodeUriComponent(v);
3567 	}
3568 
3569 	return ret;
3570 }
3571 
3572 /// url encodes a whole string
3573 string encodeVariables(in string[][string] data) {
3574 	string ret;
3575 
3576 	bool outputted = false;
3577 	foreach(k, arr; data) {
3578 		foreach(v; arr) {
3579 			if(outputted)
3580 				ret ~= "&";
3581 			else
3582 				outputted = true;
3583 			ret ~= encodeUriComponent(k) ~ "=" ~ encodeUriComponent(v);
3584 		}
3585 	}
3586 
3587 	return ret;
3588 }
3589 
3590 /// Encodes all but the explicitly unreserved characters per rfc 3986
3591 /// Alphanumeric and -_.~ are the only ones left unencoded
3592 /// name is borrowed from php
3593 string rawurlencode(in char[] data) {
3594 	string ret;
3595 	ret.reserve(data.length * 2);
3596 	foreach(char c; data) {
3597 		if(
3598 			(c >= 'a' && c <= 'z') ||
3599 			(c >= 'A' && c <= 'Z') ||
3600 			(c >= '0' && c <= '9') ||
3601 			c == '-' || c == '_' || c == '.' || c == '~')
3602 		{
3603 			ret ~= c;
3604 		} else {
3605 			ret ~= '%';
3606 			// since we iterate on char, this should give us the octets of the full utf8 string
3607 			ret ~= toHexUpper(c);
3608 		}
3609 	}
3610 
3611 	return ret;
3612 }
3613 
3614 
3615 // http helper functions
3616 
3617 // for chunked responses (which embedded http does whenever possible)
3618 version(none) // this is moved up above to avoid making a copy of the data
3619 const(ubyte)[] makeChunk(const(ubyte)[] data) {
3620 	const(ubyte)[] ret;
3621 
3622 	ret = cast(const(ubyte)[]) toHex(data.length);
3623 	ret ~= cast(const(ubyte)[]) "\r\n";
3624 	ret ~= data;
3625 	ret ~= cast(const(ubyte)[]) "\r\n";
3626 
3627 	return ret;
3628 }
3629 
3630 string toHex(long num) {
3631 	string ret;
3632 	while(num) {
3633 		int v = num % 16;
3634 		num /= 16;
3635 		char d = cast(char) ((v < 10) ? v + '0' : (v-10) + 'a');
3636 		ret ~= d;
3637 	}
3638 
3639 	return to!string(array(ret.retro));
3640 }
3641 
3642 string toHexUpper(long num) {
3643 	string ret;
3644 	while(num) {
3645 		int v = num % 16;
3646 		num /= 16;
3647 		char d = cast(char) ((v < 10) ? v + '0' : (v-10) + 'A');
3648 		ret ~= d;
3649 	}
3650 
3651 	if(ret.length == 1)
3652 		ret ~= "0"; // url encoding requires two digits and that's what this function is used for...
3653 
3654 	return to!string(array(ret.retro));
3655 }
3656 
3657 
3658 // the generic mixins
3659 
3660 /++
3661 	Use this instead of writing your own main
3662 
3663 	It ultimately calls [cgiMainImpl] which creates a [RequestServer] for you.
3664 +/
3665 mixin template GenericMain(alias fun, long maxContentLength = defaultMaxContentLength) {
3666 	mixin CustomCgiMain!(Cgi, fun, maxContentLength);
3667 }
3668 
3669 /++
3670 	Boilerplate mixin for a main function that uses the [dispatcher] function.
3671 
3672 	You can send `typeof(null)` as the `Presenter` argument to use a generic one.
3673 
3674 	History:
3675 		Added July 9, 2021
3676 +/
3677 mixin template DispatcherMain(Presenter, DispatcherArgs...) {
3678 	/// forwards to [CustomCgiDispatcherMain] with default args
3679 	mixin CustomCgiDispatcherMain!(Cgi, defaultMaxContentLength, Presenter, DispatcherArgs);
3680 }
3681 
3682 /// ditto
3683 mixin template DispatcherMain(DispatcherArgs...) if(!is(DispatcherArgs[0] : WebPresenter!T, T)) {
3684 	class GenericPresenter : WebPresenter!GenericPresenter {}
3685 	mixin DispatcherMain!(GenericPresenter, DispatcherArgs);
3686 }
3687 
3688 /++
3689 	Allows for a generic [DispatcherMain] with custom arguments. Note you can use [defaultMaxContentLength] as the second argument if you like.
3690 
3691 	History:
3692 		Added May 13, 2023 (dub v11.0)
3693 +/
3694 mixin template CustomCgiDispatcherMain(CustomCgi, size_t maxContentLength, Presenter, DispatcherArgs...) {
3695 	/++
3696 		Handler to the generated presenter you can use from your objects, etc.
3697 	+/
3698 	Presenter activePresenter;
3699 
3700 	/++
3701 		Request handler that creates the presenter then forwards to the [dispatcher] function.
3702 		Renders 404 if the dispatcher did not handle the request.
3703 
3704 		Will automatically serve the presenter.style and presenter.script as "style.css" and "script.js"
3705 	+/
3706 	void handler(Cgi cgi) {
3707 		auto presenter = new Presenter;
3708 		activePresenter = presenter;
3709 		scope(exit) activePresenter = null;
3710 
3711 		if(cgi.pathInfo.length == 0) {
3712 			cgi.setResponseLocation(cgi.scriptName ~ "/");
3713 			return;
3714 		}
3715 
3716 		if(cgi.dispatcher!DispatcherArgs(presenter))
3717 			return;
3718 
3719 		switch(cgi.pathInfo) {
3720 			case "/style.css":
3721 				cgi.setCache(true);
3722 				cgi.setResponseContentType("text/css");
3723 				cgi.write(presenter.style(), true);
3724 			break;
3725 			case "/script.js":
3726 				cgi.setCache(true);
3727 				cgi.setResponseContentType("application/javascript");
3728 				cgi.write(presenter.script(), true);
3729 			break;
3730 			default:
3731 				presenter.renderBasicError(cgi, 404);
3732 		}
3733 	}
3734 	mixin CustomCgiMain!(CustomCgi, handler, maxContentLength);
3735 }
3736 
3737 /// ditto
3738 mixin template CustomCgiDispatcherMain(CustomCgi, size_t maxContentLength, DispatcherArgs...) if(!is(DispatcherArgs[0] : WebPresenter!T, T)) {
3739 	class GenericPresenter : WebPresenter!GenericPresenter {}
3740 	mixin CustomCgiDispatcherMain!(CustomCgi, maxContentLength, GenericPresenter, DispatcherArgs);
3741 
3742 }
3743 
3744 private string simpleHtmlEncode(string s) {
3745 	return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />\n");
3746 }
3747 
3748 string messageFromException(Throwable t) {
3749 	string message;
3750 	if(t !is null) {
3751 		debug message = t.toString();
3752 		else  message = "An unexpected error has occurred.";
3753 	} else {
3754 		message = "Unknown error";
3755 	}
3756 	return message;
3757 }
3758 
3759 string plainHttpError(bool isCgi, string type, Throwable t) {
3760 	auto message = messageFromException(t);
3761 	message = simpleHtmlEncode(message);
3762 
3763 	return format("%s %s\r\nContent-Length: %s\r\nConnection: close\r\n\r\n%s",
3764 		isCgi ? "Status:" : "HTTP/1.1",
3765 		type, message.length, message);
3766 }
3767 
3768 // returns true if we were able to recover reasonably
3769 bool handleException(Cgi cgi, Throwable t) {
3770 	if(cgi.isClosed) {
3771 		// if the channel has been explicitly closed, we can't handle it here
3772 		return true;
3773 	}
3774 
3775 	if(cgi.outputtedResponseData) {
3776 		// the headers are sent, but the channel is open... since it closes if all was sent, we can append an error message here.
3777 		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.
3778 	} else {
3779 		// no headers are sent, we can send a full blown error and recover
3780 		cgi.setCache(false);
3781 		cgi.setResponseContentType("text/html");
3782 		cgi.setResponseLocation(null); // cancel the redirect
3783 		cgi.setResponseStatus("500 Internal Server Error");
3784 		cgi.write(simpleHtmlEncode(messageFromException(t)));
3785 		cgi.close();
3786 		return true;
3787 	}
3788 }
3789 
3790 bool isCgiRequestMethod(string s) {
3791 	s = s.toUpper();
3792 	if(s == "COMMANDLINE")
3793 		return true;
3794 	foreach(member; __traits(allMembers, Cgi.RequestMethod))
3795 		if(s == member)
3796 			return true;
3797 	return false;
3798 }
3799 
3800 /// If you want to use a subclass of Cgi with generic main, use this mixin.
3801 mixin template CustomCgiMain(CustomCgi, alias fun, long maxContentLength = defaultMaxContentLength) if(is(CustomCgi : Cgi)) {
3802 	// kinda hacky - the T... is passed to Cgi's constructor in standard cgi mode, and ignored elsewhere
3803 	void main(string[] args) {
3804 		cgiMainImpl!(fun, CustomCgi, maxContentLength)(args);
3805 	}
3806 }
3807 
3808 version(embedded_httpd_processes)
3809 	__gshared int processPoolSize = 8;
3810 
3811 // Returns true if run. You should exit the program after that.
3812 bool tryAddonServers(string[] args) {
3813 	if(args.length > 1) {
3814 		// run the special separate processes if needed
3815 		switch(args[1]) {
3816 			case "--websocket-server":
3817 				version(with_addon_servers)
3818 					websocketServers[args[2]](args[3 .. $]);
3819 				else
3820 					printf("Add-on servers not compiled in.\n");
3821 				return true;
3822 			case "--websocket-servers":
3823 				import core.demangle;
3824 				version(with_addon_servers_connections)
3825 				foreach(k, v; websocketServers)
3826 					writeln(k, "\t", demangle(k));
3827 				return true;
3828 			case "--session-server":
3829 				version(with_addon_servers)
3830 					runSessionServer();
3831 				else
3832 					printf("Add-on servers not compiled in.\n");
3833 				return true;
3834 			case "--event-server":
3835 				version(with_addon_servers)
3836 					runEventServer();
3837 				else
3838 					printf("Add-on servers not compiled in.\n");
3839 				return true;
3840 			case "--timer-server":
3841 				version(with_addon_servers)
3842 					runTimerServer();
3843 				else
3844 					printf("Add-on servers not compiled in.\n");
3845 				return true;
3846 			case "--timed-jobs":
3847 				import core.demangle;
3848 				version(with_addon_servers_connections)
3849 				foreach(k, v; scheduledJobHandlers)
3850 					writeln(k, "\t", demangle(k));
3851 				return true;
3852 			case "--timed-job":
3853 				scheduledJobHandlers[args[2]](args[3 .. $]);
3854 				return true;
3855 			default:
3856 				// intentionally blank - do nothing and carry on to run normally
3857 		}
3858 	}
3859 	return false;
3860 }
3861 
3862 /// Tries to simulate a request from the command line. Returns true if it does, false if it didn't find the args.
3863 bool trySimulatedRequest(alias fun, CustomCgi = Cgi)(string[] args) if(is(CustomCgi : Cgi)) {
3864 	// we support command line thing for easy testing everywhere
3865 	// it needs to be called ./app method uri [other args...]
3866 	if(args.length >= 3 && isCgiRequestMethod(args[1])) {
3867 		Cgi cgi = new CustomCgi(args);
3868 		scope(exit) cgi.dispose();
3869 		try {
3870 			fun(cgi);
3871 			cgi.close();
3872 		} catch(AuthorizationRequiredException are) {
3873 			cgi.setResponseStatus("401 Authorization Required");
3874 			cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
3875 			cgi.close();
3876 		}
3877 		writeln(); // just to put a blank line before the prompt cuz it annoys me
3878 		// FIXME: put in some footers to show what changes happened in the session
3879 		// could make the MockSession be some kind of ReflectableSessionObject or something
3880 		return true;
3881 	}
3882 	return false;
3883 }
3884 
3885 /++
3886 	A server control and configuration struct, as a potential alternative to calling [GenericMain] or [cgiMainImpl]. See the source of [cgiMainImpl] for a complete, up-to-date, example of how it is used internally.
3887 
3888 	As of version 11 (released August 2023), you can also make things like this:
3889 
3890 	---
3891 		// listens on both a unix domain socket called `foo` and on the loopback interfaces port 8080
3892 		RequestServer server = RequestServer(["http://unix:foo", "http://localhost:8080"]);
3893 
3894 		// can also:
3895 		// RequestServer server = RequestServer(0); // listen on an OS-provided port on all interfaces
3896 
3897 		// NOT IMPLEMENTED YET
3898 		// server.initialize(); // explicit initialization will populate any "any port" things and throw if a bind failed
3899 
3900 		foreach(listenSpec; server.listenSpecs) {
3901 			// you can check what it actually bound to here and see your assigned ports
3902 		}
3903 
3904 		// NOT IMPLEMENTED YET
3905 		// server.start!handler(); // starts and runs in the arsd.core event loop
3906 
3907 		server.serve!handler(); // blocks the thread until the server exits
3908 	---
3909 
3910 	History:
3911 		Added Sept 26, 2020 (release version 8.5).
3912 
3913 		The `listenSpec` member was added July 31, 2023.
3914 +/
3915 struct RequestServer {
3916 	/++
3917 		Sets the host and port the server will listen on. This is semi-deprecated; the new (as of July 31, 2023) [listenSpec] parameter obsoletes these. You cannot use both together; the listeningHost and listeningPort are ONLY used if listenSpec is null.
3918 	+/
3919 	string listeningHost = defaultListeningHost();
3920 	/// ditto
3921 	ushort listeningPort = defaultListeningPort();
3922 
3923 	static struct ListenSpec {
3924 		enum Protocol {
3925 			http,
3926 			https,
3927 			scgi
3928 		}
3929 		Protocol protocol;
3930 
3931 		enum AddressType {
3932 			ip,
3933 			unix,
3934 			abstract_
3935 		}
3936 		AddressType addressType;
3937 
3938 		string address;
3939 		ushort port;
3940 	}
3941 
3942 	/++
3943 		The array of addresses you want to listen on. The format looks like a url but has a few differences.
3944 
3945 		This ONLY works on embedded_httpd_threads, embedded_httpd_hybrid, and scgi builds at this time.
3946 
3947 		`http://localhost:8080`
3948 
3949 		`http://unix:filename/here`
3950 
3951 		`scgi://abstract:/name/here`
3952 
3953 		`http://[::1]:4444`
3954 
3955 		Note that IPv6 addresses must be enclosed in brackets. If you want to listen on an interface called `unix` or `abstract`, contact me, that is not supported but I could add some kind of escape mechanism.
3956 
3957 		If you leave off the protocol, it assumes the default based on compile flags. If you only give a number, it is assumed to be a port on any tcp interface.
3958 
3959 		`localhost:8080` serves the default protocol.
3960 
3961 		`8080` or `:8080` assumes default protocol on localhost.
3962 
3963 		The protocols can be `http:`, `https:`, and `scgi:`. Original `cgi` is not supported with this, since it is transactional with a single process.
3964 
3965 		Valid hosts are an IPv4 address (with a mandatory port), an IPv6 address (with a mandatory port), just a port alone, `unix:/path/to/unix/socket` (which may be a relative path without a leading slash), or `abstract:/path/to/linux/abstract/namespace`.
3966 
3967 		`http://unix:foo` will serve http over the unix domain socket named `foo` in the current working directory.
3968 
3969 		$(PITFALL
3970 			If you set this to anything non-null (including a non-null, zero-length array) any `listenSpec` entries, [listeningHost] and [listeningPort] are ignored.
3971 		)
3972 
3973 		Bugs:
3974 			The implementation currently ignores the protocol spec in favor of the default compiled in option.
3975 
3976 		History:
3977 			Added July 31, 2023 (dub v11.0)
3978 	+/
3979 	string[] listenSpec;
3980 
3981 	/++
3982 		Uses a fork() call, if available, to provide additional crash resiliency and possibly improved performance. On the
3983 		other hand, if you fork, you must not assume any memory is shared between requests (you shouldn't be anyway though! But
3984 		if you have to, you probably want to set this to false and use an explicit threaded server with [serveEmbeddedHttp]) and
3985 		[stop] may not work as well.
3986 
3987 		History:
3988 			Added August 12, 2022  (dub v10.9). Previously, this was only configurable through the `-version=cgi_no_fork`
3989 			argument to dmd. That version still defines the value of `cgi_use_fork_default`, used to initialize this, for
3990 			compatibility.
3991 	+/
3992 	bool useFork = cgi_use_fork_default;
3993 
3994 	/++
3995 		Determines the number of worker threads to spawn per process, for server modes that use worker threads. 0 will use a
3996 		default based on the number of cpus modified by the server mode.
3997 
3998 		History:
3999 			Added August 12, 2022 (dub v10.9)
4000 	+/
4001 	int numberOfThreads = 0;
4002 
4003 	/++
4004 		Creates a server configured to listen to multiple URLs.
4005 
4006 		History:
4007 			Added July 31, 2023 (dub v11.0)
4008 	+/
4009 	this(string[] listenTo) {
4010 		this.listenSpec = listenTo;
4011 	}
4012 
4013 	/// Creates a server object configured to listen on a single host and port.
4014 	this(string defaultHost, ushort defaultPort) {
4015 		this.listeningHost = defaultHost;
4016 		this.listeningPort = defaultPort;
4017 	}
4018 
4019 	/// ditto
4020 	this(ushort defaultPort) {
4021 		listeningPort = defaultPort;
4022 	}
4023 
4024 	/++
4025 		Reads the command line arguments into the values here.
4026 
4027 		Possible arguments are `--listen` (can appear multiple times), `--listening-host`, `--listening-port` (or `--port`), `--uid`, and `--gid`.
4028 
4029 		Please note you cannot combine `--listen` with `--listening-host` or `--listening-port` / `--port`. Use one or the other style.
4030 	+/
4031 	void configureFromCommandLine(string[] args) {
4032 		bool portOrHostFound = false;
4033 
4034 		bool foundPort = false;
4035 		bool foundHost = false;
4036 		bool foundUid = false;
4037 		bool foundGid = false;
4038 		bool foundListen = false;
4039 		foreach(arg; args) {
4040 			if(foundPort) {
4041 				listeningPort = to!ushort(arg);
4042 				portOrHostFound = true;
4043 				foundPort = false;
4044 				continue;
4045 			}
4046 			if(foundHost) {
4047 				listeningHost = arg;
4048 				portOrHostFound = true;
4049 				foundHost = false;
4050 				continue;
4051 			}
4052 			if(foundUid) {
4053 				privilegesDropToUid = to!uid_t(arg);
4054 				foundUid = false;
4055 				continue;
4056 			}
4057 			if(foundGid) {
4058 				privilegesDropToGid = to!gid_t(arg);
4059 				foundGid = false;
4060 				continue;
4061 			}
4062 			if(foundListen) {
4063 				this.listenSpec ~= arg;
4064 				foundListen = false;
4065 				continue;
4066 			}
4067 			if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host")
4068 				foundHost = true;
4069 			else if(arg == "--port" || arg == "-p" || arg == "/port" || arg == "--listening-port")
4070 				foundPort = true;
4071 			else if(arg == "--uid")
4072 				foundUid = true;
4073 			else if(arg == "--gid")
4074 				foundGid = true;
4075 			else if(arg == "--listen")
4076 				foundListen = true;
4077 		}
4078 
4079 		if(portOrHostFound && listenSpec.length) {
4080 			throw new Exception("You passed both a --listening-host or --listening-port and a --listen argument. You should fix your script to ONLY use --listen arguments.");
4081 		}
4082 	}
4083 
4084 	version(Windows) {
4085 		private alias uid_t = int;
4086 		private alias gid_t = int;
4087 	}
4088 
4089 	/// user (uid) to drop privileges to
4090 	/// 0 … do nothing
4091 	uid_t privilegesDropToUid = 0;
4092 	/// group (gid) to drop privileges to
4093 	/// 0 … do nothing
4094 	gid_t privilegesDropToGid = 0;
4095 
4096 	private void dropPrivileges() {
4097 		version(Posix) {
4098 			import core.sys.posix.unistd;
4099 
4100 			if (privilegesDropToGid != 0 && setgid(privilegesDropToGid) != 0)
4101 				throw new Exception("Dropping privileges via setgid() failed.");
4102 
4103 			if (privilegesDropToUid != 0 && setuid(privilegesDropToUid) != 0)
4104 				throw new Exception("Dropping privileges via setuid() failed.");
4105 		}
4106 		else {
4107 			// FIXME: Windows?
4108 			//pragma(msg, "Dropping privileges is not implemented for this platform");
4109 		}
4110 
4111 		// done, set zero
4112 		privilegesDropToGid = 0;
4113 		privilegesDropToUid = 0;
4114 	}
4115 
4116 	/++
4117 		Serves a single HTTP request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders
4118 
4119 		History:
4120 			Added Oct 10, 2020.
4121 		Example:
4122 
4123 		---
4124 		import arsd.cgi;
4125 		void main() {
4126 			RequestServer server = RequestServer("127.0.0.1", 6789);
4127 			string oauthCode;
4128 			string oauthScope;
4129 			server.serveHttpOnce!((cgi) {
4130 				oauthCode = cgi.request("code");
4131 				oauthScope = cgi.request("scope");
4132 				cgi.write("Thank you, please return to the application.");
4133 			});
4134 			// use the code and scope given
4135 		}
4136 		---
4137 	+/
4138 	void serveHttpOnce(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4139 		import std.socket;
4140 
4141 		bool tcp;
4142 		void delegate() cleanup;
4143 		auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1, &dropPrivileges);
4144 		auto connection = socket.accept();
4145 		doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection);
4146 
4147 		if(cleanup)
4148 			cleanup();
4149 	}
4150 
4151 	/++
4152 		Starts serving requests according to the current configuration.
4153 	+/
4154 	void serve(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4155 		version(netman_httpd) {
4156 			// Obsolete!
4157 
4158 			import arsd.httpd;
4159 			// what about forwarding the other constructor args?
4160 			// this probably needs a whole redoing...
4161 			serveHttp!CustomCgi(&fun, listeningPort);//5005);
4162 			return;
4163 		} else
4164 		version(embedded_httpd_processes) {
4165 			serveEmbeddedHttpdProcesses!(fun, CustomCgi)(this);
4166 		} else
4167 		version(embedded_httpd_threads) {
4168 			serveEmbeddedHttp!(fun, CustomCgi, maxContentLength)();
4169 		} else
4170 		version(scgi) {
4171 			serveScgi!(fun, CustomCgi, maxContentLength)();
4172 		} else
4173 		version(fastcgi) {
4174 			serveFastCgi!(fun, CustomCgi, maxContentLength)(this);
4175 		} else
4176 		version(stdio_http) {
4177 			serveSingleHttpConnectionOnStdio!(fun, CustomCgi, maxContentLength)();
4178 		} else
4179 		version(plain_cgi) {
4180 			handleCgiRequest!(fun, CustomCgi, maxContentLength)();
4181 		} else {
4182 			if(this.listenSpec.length) {
4183 				// FIXME: what about heterogeneous listen specs?
4184 				if(this.listenSpec[0].startsWith("scgi:"))
4185 					serveScgi!(fun, CustomCgi, maxContentLength)();
4186 				else
4187 					serveEmbeddedHttp!(fun, CustomCgi, maxContentLength)();
4188 			} else {
4189 				import std.process;
4190 				if("REQUEST_METHOD" in environment) {
4191 					// GATEWAY_INTERFACE must be set according to the spec for it to be a cgi request
4192 					// REQUEST_METHOD must also be set
4193 					handleCgiRequest!(fun, CustomCgi, maxContentLength)();
4194 				} else {
4195 					import std.stdio;
4196 					writeln("To start a local-only http server, use `thisprogram --listen http://localhost:PORT_NUMBER`");
4197 					writeln("To start a externally-accessible http server, use `thisprogram --listen http://:PORT_NUMBER`");
4198 					writeln("To start a scgi server, use `thisprogram --listen scgi://localhost:PORT_NUMBER`");
4199 					writeln("To test a request on the command line, use `thisprogram REQUEST /path arg=value`");
4200 					writeln("Or copy this program to your web server's cgi-bin folder to run it that way.");
4201 					writeln("If you need FastCGI, recompile this program with -version=fastcgi");
4202 					writeln();
4203 					writeln("Learn more at https://opendlang.org/library/arsd.cgi.html#Command-line-interface");
4204 				}
4205 			}
4206 		}
4207 	}
4208 
4209 	/++
4210 		Runs the embedded HTTP thread server specifically, regardless of which build configuration you have.
4211 
4212 		If you want the forking worker process server, you do need to compile with the embedded_httpd_processes config though.
4213 	+/
4214 	void serveEmbeddedHttp(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(ThisFor!fun _this) {
4215 		globalStopFlag = false;
4216 		static if(__traits(isStaticFunction, fun))
4217 			alias funToUse = fun;
4218 		else
4219 			void funToUse(CustomCgi cgi) {
4220 				static if(__VERSION__ > 2097)
4221 					__traits(child, _this, fun)(cgi);
4222 				else static assert(0, "Not implemented in your compiler version!");
4223 			}
4224 		auto manager = this.listenSpec is null ?
4225 			new ListeningConnectionManager(listeningHost, listeningPort, &doThreadHttpConnection!(CustomCgi, funToUse), null, useFork, numberOfThreads) :
4226 			new ListeningConnectionManager(this.listenSpec, &doThreadHttpConnection!(CustomCgi, funToUse), null, useFork, numberOfThreads);
4227 		manager.listen();
4228 	}
4229 
4230 	/++
4231 		Runs the embedded SCGI server specifically, regardless of which build configuration you have.
4232 	+/
4233 	void serveScgi(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4234 		globalStopFlag = false;
4235 		auto manager = this.listenSpec is null ?
4236 			new ListeningConnectionManager(listeningHost, listeningPort, &doThreadScgiConnection!(CustomCgi, fun, maxContentLength), null, useFork, numberOfThreads) :
4237 			new ListeningConnectionManager(this.listenSpec, &doThreadScgiConnection!(CustomCgi, fun, maxContentLength), null, useFork, numberOfThreads);
4238 		manager.listen();
4239 	}
4240 
4241 	/++
4242 		Serves a single "connection", but the connection is spoken on stdin and stdout instead of on a socket.
4243 
4244 		Intended for cases like working from systemd, like discussed here: [https://forum.dlang.org/post/avmkfdiitirnrenzljwc@forum.dlang.org]
4245 
4246 		History:
4247 			Added May 29, 2021
4248 	+/
4249 	void serveSingleHttpConnectionOnStdio(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4250 		doThreadHttpConnectionGuts!(CustomCgi, fun, true)(new FakeSocketForStdin());
4251 	}
4252 
4253 	/++
4254 		The [stop] function sets a flag that request handlers can (and should) check periodically. If a handler doesn't
4255 		respond to this flag, the library will force the issue. This determines when and how the issue will be forced.
4256 	+/
4257 	enum ForceStop {
4258 		/++
4259 			Stops accepting new requests, but lets ones already in the queue start and complete before exiting.
4260 		+/
4261 		afterQueuedRequestsComplete,
4262 		/++
4263 			Finishes requests already started their handlers, but drops any others in the queue. Streaming handlers
4264 			should cooperate and exit gracefully, but if they don't, it will continue waiting for them.
4265 		+/
4266 		afterCurrentRequestsComplete,
4267 		/++
4268 			Partial response writes will throw an exception, cancelling any streaming response, but complete
4269 			writes will continue to process. Request handlers that respect the stop token will also gracefully cancel.
4270 		+/
4271 		cancelStreamingRequestsEarly,
4272 		/++
4273 			All writes will throw.
4274 		+/
4275 		cancelAllRequestsEarly,
4276 		/++
4277 			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).
4278 		+/
4279 		forciblyTerminate,
4280 	}
4281 
4282 	version(embedded_httpd_processes) {} else
4283 	/++
4284 		Stops serving after the current requests are completed.
4285 
4286 		Bugs:
4287 			Not implemented on version=embedded_httpd_processes, version=fastcgi on any system, or embedded_httpd on Windows (it does work on embedded_httpd_hybrid
4288 			on Windows however). Only partially implemented on non-Linux posix systems.
4289 
4290 			You might also try SIGINT perhaps.
4291 
4292 			The stopPriority is not yet fully implemented.
4293 	+/
4294 	static void stop(ForceStop stopPriority = ForceStop.afterCurrentRequestsComplete) {
4295 		globalStopFlag = true;
4296 
4297 		version(Posix) {
4298 			if(cancelfd > 0) {
4299 				ulong a = 1;
4300 				core.sys.posix.unistd.write(cancelfd, &a, a.sizeof);
4301 			}
4302 		}
4303 		version(Windows) {
4304 			if(iocp) {
4305 				foreach(i; 0 .. 16) // FIXME
4306 				PostQueuedCompletionStatus(iocp, 0, cast(ULONG_PTR) null, null);
4307 			}
4308 		}
4309 	}
4310 }
4311 
4312 class AuthorizationRequiredException : Exception {
4313 	string type;
4314 	string realm;
4315 	this(string type, string realm, string file, size_t line) {
4316 		this.type = type;
4317 		this.realm = realm;
4318 
4319 		super("Authorization Required", file, line);
4320 	}
4321 }
4322 
4323 private alias AliasSeq(T...) = T;
4324 
4325 version(with_breaking_cgi_features)
4326 mixin(q{
4327 	template ThisFor(alias t) {
4328 		static if(__traits(isStaticFunction, t)) {
4329 			alias ThisFor = AliasSeq!();
4330 		} else {
4331 			alias ThisFor = __traits(parent, t);
4332 		}
4333 	}
4334 });
4335 else
4336 	alias ThisFor(alias t) = AliasSeq!();
4337 
4338 private __gshared bool globalStopFlag = false;
4339 
4340 version(embedded_httpd_processes)
4341 void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer params) {
4342 	import core.sys.posix.unistd;
4343 	import core.sys.posix.sys.socket;
4344 	import core.sys.posix.netinet.in_;
4345 	//import std.c.linux.socket;
4346 
4347 	int sock = socket(AF_INET, SOCK_STREAM, 0);
4348 	if(sock == -1)
4349 		throw new Exception("socket");
4350 
4351 	cloexec(sock);
4352 
4353 	{
4354 
4355 		sockaddr_in addr;
4356 		addr.sin_family = AF_INET;
4357 		addr.sin_port = htons(params.listeningPort);
4358 		auto lh = params.listeningHost;
4359 		if(lh.length) {
4360 			if(inet_pton(AF_INET, lh.toStringz(), &addr.sin_addr.s_addr) != 1)
4361 				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.");
4362 		} else
4363 			addr.sin_addr.s_addr = INADDR_ANY;
4364 
4365 		// HACKISH
4366 		int on = 1;
4367 		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, on.sizeof);
4368 		// end hack
4369 
4370 
4371 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
4372 			close(sock);
4373 			throw new Exception("bind");
4374 		}
4375 
4376 		// FIXME: if this queue is full, it will just ignore it
4377 		// and wait for the client to retransmit it. This is an
4378 		// obnoxious timeout condition there.
4379 		if(sock.listen(128) == -1) {
4380 			close(sock);
4381 			throw new Exception("listen");
4382 		}
4383 		params.dropPrivileges();
4384 	}
4385 
4386 	version(embedded_httpd_processes_accept_after_fork) {} else {
4387 		int pipeReadFd;
4388 		int pipeWriteFd;
4389 
4390 		{
4391 			int[2] pipeFd;
4392 			if(socketpair(AF_UNIX, SOCK_DGRAM, 0, pipeFd)) {
4393 				import core.stdc.errno;
4394 				throw new Exception("pipe failed " ~ to!string(errno));
4395 			}
4396 
4397 			pipeReadFd = pipeFd[0];
4398 			pipeWriteFd = pipeFd[1];
4399 		}
4400 	}
4401 
4402 
4403 	int processCount;
4404 	pid_t newPid;
4405 	reopen:
4406 	while(processCount < processPoolSize) {
4407 		newPid = fork();
4408 		if(newPid == 0) {
4409 			// start serving on the socket
4410 			//ubyte[4096] backingBuffer;
4411 			for(;;) {
4412 				bool closeConnection;
4413 				uint i;
4414 				sockaddr addr;
4415 				i = addr.sizeof;
4416 				version(embedded_httpd_processes_accept_after_fork) {
4417 					int s = accept(sock, &addr, &i);
4418 					int opt = 1;
4419 					import core.sys.posix.netinet.tcp;
4420 					// the Cgi class does internal buffering, so disabling this
4421 					// helps with latency in many cases...
4422 					setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
4423 					cloexec(s);
4424 				} else {
4425 					int s;
4426 					auto readret = read_fd(pipeReadFd, &s, s.sizeof, &s);
4427 					if(readret != s.sizeof) {
4428 						import core.stdc.errno;
4429 						throw new Exception("pipe read failed " ~ to!string(errno));
4430 					}
4431 
4432 					//writeln("process ", getpid(), " got socket ", s);
4433 				}
4434 
4435 				try {
4436 
4437 					if(s == -1)
4438 						throw new Exception("accept");
4439 
4440 					scope(failure) close(s);
4441 					//ubyte[__traits(classInstanceSize, BufferedInputRange)] bufferedRangeContainer;
4442 					auto ir = new BufferedInputRange(s);
4443 					//auto ir = emplace!BufferedInputRange(bufferedRangeContainer, s, backingBuffer);
4444 
4445 					while(!ir.empty) {
4446 						//ubyte[__traits(classInstanceSize, CustomCgi)] cgiContainer;
4447 
4448 						Cgi cgi;
4449 						try {
4450 							cgi = new CustomCgi(ir, &closeConnection);
4451 							cgi._outputFileHandle = cast(CgiConnectionHandle) s;
4452 							// 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.
4453 							if(processPoolSize <= 1)
4454 								closeConnection = true;
4455 							//cgi = emplace!CustomCgi(cgiContainer, ir, &closeConnection);
4456 						} catch(HttpVersionNotSupportedException he) {
4457 							sendAll(ir.source, plainHttpError(false, "505 HTTP Version Not Supported", he));
4458 							closeConnection = true;
4459 							break;
4460 						} catch(Throwable t) {
4461 							// a construction error is either bad code or bad request; bad request is what it should be since this is bug free :P
4462 							// anyway let's kill the connection
4463 							version(CRuntime_Musl) {
4464 								// LockingTextWriter fails here
4465 								// so working around it
4466 								auto estr = t.toString();
4467 								stderr.rawWrite(estr);
4468 								stderr.rawWrite("\n");
4469 							} else
4470 								stderr.writeln(t.toString());
4471 							sendAll(ir.source, plainHttpError(false, "400 Bad Request", t));
4472 							closeConnection = true;
4473 							break;
4474 						}
4475 						assert(cgi !is null);
4476 						scope(exit)
4477 							cgi.dispose();
4478 
4479 						try {
4480 							fun(cgi);
4481 							cgi.close();
4482 							if(cgi.websocketMode)
4483 								closeConnection = true;
4484 
4485 						} catch(AuthorizationRequiredException are) {
4486 							cgi.setResponseStatus("401 Authorization Required");
4487 							cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
4488 							cgi.close();
4489 						} catch(ConnectionException ce) {
4490 							closeConnection = true;
4491 						} catch(Throwable t) {
4492 							// a processing error can be recovered from
4493 							version(CRuntime_Musl) {
4494 								// LockingTextWriter fails here
4495 								// so working around it
4496 								auto estr = t.toString();
4497 								stderr.rawWrite(estr);
4498 							} else {
4499 								stderr.writeln(t.toString);
4500 							}
4501 							if(!handleException(cgi, t))
4502 								closeConnection = true;
4503 						}
4504 
4505 						if(closeConnection) {
4506 							ir.source.close();
4507 							break;
4508 						} else {
4509 							if(!ir.empty)
4510 								ir.popFront(); // get the next
4511 							else if(ir.sourceClosed) {
4512 								ir.source.close();
4513 							}
4514 						}
4515 					}
4516 
4517 					ir.source.close();
4518 				} catch(Throwable t) {
4519 					version(CRuntime_Musl) {} else
4520 						debug writeln(t);
4521 					// most likely cause is a timeout
4522 				}
4523 			}
4524 		} else if(newPid < 0) {
4525 			throw new Exception("fork failed");
4526 		} else {
4527 			processCount++;
4528 		}
4529 	}
4530 
4531 	// the parent should wait for its children...
4532 	if(newPid) {
4533 		import core.sys.posix.sys.wait;
4534 
4535 		version(embedded_httpd_processes_accept_after_fork) {} else {
4536 			import core.sys.posix.sys.select;
4537 			int[] fdQueue;
4538 			while(true) {
4539 				// writeln("select call");
4540 				int nfds = pipeWriteFd;
4541 				if(sock > pipeWriteFd)
4542 					nfds = sock;
4543 				nfds += 1;
4544 				fd_set read_fds;
4545 				fd_set write_fds;
4546 				FD_ZERO(&read_fds);
4547 				FD_ZERO(&write_fds);
4548 				FD_SET(sock, &read_fds);
4549 				if(fdQueue.length)
4550 					FD_SET(pipeWriteFd, &write_fds);
4551 				auto ret = select(nfds, &read_fds, &write_fds, null, null);
4552 				if(ret == -1) {
4553 					import core.stdc.errno;
4554 					if(errno == EINTR)
4555 						goto try_wait;
4556 					else
4557 						throw new Exception("wtf select");
4558 				}
4559 
4560 				int s = -1;
4561 				if(FD_ISSET(sock, &read_fds)) {
4562 					uint i;
4563 					sockaddr addr;
4564 					i = addr.sizeof;
4565 					s = accept(sock, &addr, &i);
4566 					cloexec(s);
4567 					import core.sys.posix.netinet.tcp;
4568 					int opt = 1;
4569 					setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
4570 				}
4571 
4572 				if(FD_ISSET(pipeWriteFd, &write_fds)) {
4573 					if(s == -1 && fdQueue.length) {
4574 						s = fdQueue[0];
4575 						fdQueue = fdQueue[1 .. $]; // FIXME reuse buffer
4576 					}
4577 					write_fd(pipeWriteFd, &s, s.sizeof, s);
4578 					close(s); // we are done with it, let the other process take ownership
4579 				} else
4580 					fdQueue ~= s;
4581 			}
4582 		}
4583 
4584 		try_wait:
4585 
4586 		int status;
4587 		while(-1 != wait(&status)) {
4588 			version(CRuntime_Musl) {} else {
4589 				import std.stdio; writeln("Process died ", status);
4590 			}
4591 			processCount--;
4592 			goto reopen;
4593 		}
4594 		close(sock);
4595 	}
4596 }
4597 
4598 version(fastcgi)
4599 void serveFastCgi(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(RequestServer params) {
4600 	//         SetHandler fcgid-script
4601 	FCGX_Stream* input, output, error;
4602 	FCGX_ParamArray env;
4603 
4604 
4605 
4606 	const(ubyte)[] getFcgiChunk() {
4607 		const(ubyte)[] ret;
4608 		while(FCGX_HasSeenEOF(input) != -1)
4609 			ret ~= cast(ubyte) FCGX_GetChar(input);
4610 		return ret;
4611 	}
4612 
4613 	void writeFcgi(const(ubyte)[] data) {
4614 		FCGX_PutStr(data.ptr, data.length, output);
4615 	}
4616 
4617 	void doARequest() {
4618 		string[string] fcgienv;
4619 
4620 		for(auto e = env; e !is null && *e !is null; e++) {
4621 			string cur = to!string(*e);
4622 			auto idx = cur.indexOf("=");
4623 			string name, value;
4624 			if(idx == -1)
4625 				name = cur;
4626 			else {
4627 				name = cur[0 .. idx];
4628 				value = cur[idx + 1 .. $];
4629 			}
4630 
4631 			fcgienv[name] = value;
4632 		}
4633 
4634 		void flushFcgi() {
4635 			FCGX_FFlush(output);
4636 		}
4637 
4638 		Cgi cgi;
4639 		try {
4640 			cgi = new CustomCgi(maxContentLength, fcgienv, &getFcgiChunk, &writeFcgi, &flushFcgi);
4641 		} catch(Throwable t) {
4642 			FCGX_PutStr(cast(ubyte*) t.msg.ptr, t.msg.length, error);
4643 			writeFcgi(cast(const(ubyte)[]) plainHttpError(true, "400 Bad Request", t));
4644 			return; //continue;
4645 		}
4646 		assert(cgi !is null);
4647 		scope(exit) cgi.dispose();
4648 		try {
4649 			fun(cgi);
4650 			cgi.close();
4651 		} catch(AuthorizationRequiredException are) {
4652 			cgi.setResponseStatus("401 Authorization Required");
4653 			cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
4654 			cgi.close();
4655 		} catch(Throwable t) {
4656 			// log it to the error stream
4657 			FCGX_PutStr(cast(ubyte*) t.msg.ptr, t.msg.length, error);
4658 			// handle it for the user, if we can
4659 			if(!handleException(cgi, t))
4660 				return; // continue;
4661 		}
4662 	}
4663 
4664 	auto lp = params.listeningPort;
4665 	auto host = params.listeningHost;
4666 
4667 	FCGX_Request request;
4668 	if(lp || !host.empty) {
4669 		// if a listening port was specified on the command line, we want to spawn ourself
4670 		// (needed for nginx without spawn-fcgi, e.g. on Windows)
4671 		FCGX_Init();
4672 
4673 		int sock;
4674 
4675 		if(host.startsWith("unix:")) {
4676 			sock = FCGX_OpenSocket(toStringz(params.listeningHost["unix:".length .. $]), 12);
4677 		} else if(host.startsWith("abstract:")) {
4678 			sock = FCGX_OpenSocket(toStringz("\0" ~ params.listeningHost["abstract:".length .. $]), 12);
4679 		} else {
4680 			sock = FCGX_OpenSocket(toStringz(params.listeningHost ~ ":" ~ to!string(lp)), 12);
4681 		}
4682 
4683 		if(sock < 0)
4684 			throw new Exception("Couldn't listen on the port");
4685 		FCGX_InitRequest(&request, sock, 0);
4686 		while(FCGX_Accept_r(&request) >= 0) {
4687 			input = request.inStream;
4688 			output = request.outStream;
4689 			error = request.errStream;
4690 			env = request.envp;
4691 			doARequest();
4692 		}
4693 	} else {
4694 		// otherwise, assume the httpd is doing it (the case for Apache, IIS, and Lighttpd)
4695 		// using the version with a global variable since we are separate processes anyway
4696 		while(FCGX_Accept(&input, &output, &error, &env) >= 0) {
4697 			doARequest();
4698 		}
4699 	}
4700 }
4701 
4702 /// Returns the default listening port for the current cgi configuration. 8085 for embedded httpd, 4000 for scgi, irrelevant for others.
4703 ushort defaultListeningPort() @safe {
4704 	version(netman_httpd)
4705 		return 8080;
4706 	else version(embedded_httpd_processes)
4707 		return 8085;
4708 	else version(embedded_httpd_threads)
4709 		return 8085;
4710 	else version(scgi)
4711 		return 4000;
4712 	else
4713 		return 0;
4714 }
4715 
4716 /// 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.
4717 string defaultListeningHost() @safe {
4718 	version(netman_httpd)
4719 		return null;
4720 	else version(embedded_httpd_processes)
4721 		return null;
4722 	else version(embedded_httpd_threads)
4723 		return null;
4724 	else version(scgi)
4725 		return "127.0.0.1";
4726 	else
4727 		return null;
4728 
4729 }
4730 
4731 /++
4732 	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`.
4733 
4734 	Please note that this may spawn other helper processes that will call `main` again. It does this currently for the timer server and event source server (and the quasi-deprecated web socket server).
4735 
4736 	Params:
4737 		fun = Your request handler
4738 		CustomCgi = a subclass of Cgi, if you wise to customize it further
4739 		maxContentLength = max POST size you want to allow
4740 		args = command-line arguments
4741 
4742 	History:
4743 		Documented Sept 26, 2020.
4744 +/
4745 void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(string[] args) if(is(CustomCgi : Cgi)) {
4746 	if(tryAddonServers(args))
4747 		return;
4748 
4749 	if(trySimulatedRequest!(fun, CustomCgi)(args))
4750 		return;
4751 
4752 	RequestServer server;
4753 	// you can change the port here if you like
4754 	// server.listeningPort = 9000;
4755 
4756 	// then call this to let the command line args override your default
4757 	server.configureFromCommandLine(args);
4758 
4759 	// and serve the request(s).
4760 	server.serve!(fun, CustomCgi, maxContentLength)();
4761 }
4762 
4763 //version(plain_cgi)
4764 void handleCgiRequest(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4765 	// standard CGI is the default version
4766 
4767 
4768 	// Set stdin to binary mode if necessary to avoid mangled newlines
4769 	// the fact that stdin is global means this could be trouble but standard cgi request
4770 	// handling is one per process anyway so it shouldn't actually be threaded here or anything.
4771 	version(Windows) {
4772 		version(Win64)
4773 		_setmode(std.stdio.stdin.fileno(), 0x8000);
4774 		else
4775 		setmode(std.stdio.stdin.fileno(), 0x8000);
4776 	}
4777 
4778 	Cgi cgi;
4779 	try {
4780 		cgi = new CustomCgi(maxContentLength);
4781 		version(Posix)
4782 			cgi._outputFileHandle = cast(CgiConnectionHandle) 1; // stdout
4783 		else version(Windows)
4784 			cgi._outputFileHandle = cast(CgiConnectionHandle) GetStdHandle(STD_OUTPUT_HANDLE);
4785 		else static assert(0);
4786 	} catch(Throwable t) {
4787 		version(CRuntime_Musl) {
4788 			// LockingTextWriter fails here
4789 			// so working around it
4790 			auto s = t.toString();
4791 			stderr.rawWrite(s);
4792 			stdout.rawWrite(plainHttpError(true, "400 Bad Request", t));
4793 		} else {
4794 			stderr.writeln(t.msg);
4795 			// the real http server will probably handle this;
4796 			// most likely, this is a bug in Cgi. But, oh well.
4797 			stdout.write(plainHttpError(true, "400 Bad Request", t));
4798 		}
4799 		return;
4800 	}
4801 	assert(cgi !is null);
4802 	scope(exit) cgi.dispose();
4803 
4804 	try {
4805 		fun(cgi);
4806 		cgi.close();
4807 	} catch(AuthorizationRequiredException are) {
4808 		cgi.setResponseStatus("401 Authorization Required");
4809 		cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
4810 		cgi.close();
4811 	} catch (Throwable t) {
4812 		version(CRuntime_Musl) {
4813 			// LockingTextWriter fails here
4814 			// so working around it
4815 			auto s = t.msg;
4816 			stderr.rawWrite(s);
4817 		} else {
4818 			stderr.writeln(t.msg);
4819 		}
4820 		if(!handleException(cgi, t))
4821 			return;
4822 	}
4823 }
4824 
4825 private __gshared int cancelfd = -1;
4826 
4827 /+
4828 	The event loop for embedded_httpd_threads will prolly fiber dispatch
4829 	cgi constructors too, so slow posts will not monopolize a worker thread.
4830 
4831 	May want to provide the worker task system just need to ensure all the fibers
4832 	has a big enough stack for real work... would also ideally like to reuse them.
4833 
4834 
4835 	So prolly bir would switch it to nonblocking. If it would block, it epoll
4836 	registers one shot with this existing fiber to take it over.
4837 
4838 		new connection comes in. it picks a fiber off the free list,
4839 		or if there is none, it creates a new one. this fiber handles
4840 		this connection the whole time.
4841 
4842 		epoll triggers the fiber when something comes in. it is called by
4843 		a random worker thread, it might change at any time. at least during
4844 		the constructor. maybe into the main body it will stay tied to a thread
4845 		just so TLS stuff doesn't randomly change in the middle. but I could
4846 		specify if you yield all bets are off.
4847 
4848 		when the request is finished, if there's more data buffered, it just
4849 		keeps going. if there is no more data buffered, it epoll ctls to
4850 		get triggered when more data comes in. all one shot.
4851 
4852 		when a connection is closed, the fiber returns and is then reset
4853 		and added to the free list. if the free list is full, the fiber is
4854 		just freed, this means it will balloon to a certain size but not generally
4855 		grow beyond that unless the activity keeps going.
4856 
4857 		256 KB stack i thnk per fiber. 4,000 active fibers per gigabyte of memory.
4858 
4859 	So the fiber has its own magic methods to read and write. if they would block, it registers
4860 	for epoll and yields. when it returns, it read/writes and then returns back normal control.
4861 
4862 	basically you issue the command and it tells you when it is done
4863 
4864 	it needs to DEL the epoll thing when it is closed. add it when opened. mod it when anther thing issued
4865 
4866 +/
4867 
4868 /++
4869 	The stack size when a fiber is created. You can set this from your main or from a shared static constructor
4870 	to optimize your memory use if you know you don't need this much space. Be careful though, some functions use
4871 	more stack space than you realize and a recursive function (including ones like in dom.d) can easily grow fast!
4872 
4873 	History:
4874 		Added July 10, 2021. Previously, it used the druntime default of 16 KB.
4875 +/
4876 version(cgi_use_fiber)
4877 __gshared size_t fiberStackSize = 4096 * 100;
4878 
4879 version(cgi_use_fiber)
4880 class CgiFiber : Fiber {
4881 	private void function(Socket) f_handler;
4882 	private void f_handler_dg(Socket s) { // to avoid extra allocation w/ function
4883 		f_handler(s);
4884 	}
4885 	this(void function(Socket) handler) {
4886 		this.f_handler = handler;
4887 		this(&f_handler_dg);
4888 	}
4889 
4890 	this(void delegate(Socket) handler) {
4891 		this.handler = handler;
4892 		super(&run, fiberStackSize);
4893 	}
4894 
4895 	Socket connection;
4896 	void delegate(Socket) handler;
4897 
4898 	void run() {
4899 		handler(connection);
4900 	}
4901 
4902 	void delegate() postYield;
4903 
4904 	private void setPostYield(scope void delegate() py) @nogc {
4905 		postYield = cast(void delegate()) py;
4906 	}
4907 
4908 	void proceed() {
4909 		try {
4910 			call();
4911 			auto py = postYield;
4912 			postYield = null;
4913 			if(py !is null)
4914 				py();
4915 		} catch(Exception e) {
4916 			if(connection)
4917 				connection.close();
4918 			goto terminate;
4919 		}
4920 
4921 		if(state == State.TERM) {
4922 			terminate:
4923 			import core.memory;
4924 			GC.removeRoot(cast(void*) this);
4925 		}
4926 	}
4927 }
4928 
4929 version(cgi_use_fiber)
4930 version(Windows) {
4931 
4932 extern(Windows) private {
4933 
4934 	import core.sys.windows.mswsock;
4935 
4936 	alias GROUP=uint;
4937 	alias LPWSAPROTOCOL_INFOW = void*;
4938 	SOCKET WSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo, GROUP g, DWORD dwFlags);
4939 	alias WSASend = arsd.core.WSASend;
4940 	alias WSARecv = arsd.core.WSARecv;
4941 	alias WSABUF = arsd.core.WSABUF;
4942 
4943 	/+
4944 	int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
4945 	int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
4946 
4947 	struct WSABUF {
4948 		ULONG len;
4949 		CHAR  *buf;
4950 	}
4951 	+/
4952 	alias LPWSABUF = WSABUF*;
4953 
4954 	alias WSAOVERLAPPED = OVERLAPPED;
4955 	alias LPWSAOVERLAPPED = LPOVERLAPPED;
4956 	/+
4957 
4958 	alias LPFN_ACCEPTEX =
4959 		BOOL
4960 		function(
4961 				SOCKET sListenSocket,
4962 				SOCKET sAcceptSocket,
4963 				//_Out_writes_bytes_(dwReceiveDataLength+dwLocalAddressLength+dwRemoteAddressLength) PVOID lpOutputBuffer,
4964 				void* lpOutputBuffer,
4965 				WORD dwReceiveDataLength,
4966 				WORD dwLocalAddressLength,
4967 				WORD dwRemoteAddressLength,
4968 				LPDWORD lpdwBytesReceived,
4969 				LPOVERLAPPED lpOverlapped
4970 			);
4971 
4972 	enum WSAID_ACCEPTEX = GUID([0xb5367df1,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]]);
4973 	+/
4974 
4975 	enum WSAID_GETACCEPTEXSOCKADDRS = GUID(0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]);
4976 }
4977 
4978 private class PseudoblockingOverlappedSocket : Socket {
4979 	SOCKET handle;
4980 
4981 	CgiFiber fiber;
4982 
4983 	this(AddressFamily af, SocketType st) {
4984 		auto handle = WSASocketW(af, st, 0, null, 0, 1 /*WSA_FLAG_OVERLAPPED*/);
4985 		if(!handle)
4986 			throw new Exception("WSASocketW");
4987 		this.handle = handle;
4988 
4989 		iocp = CreateIoCompletionPort(cast(HANDLE) handle, iocp, cast(ULONG_PTR) cast(void*) this, 0);
4990 
4991 		if(iocp is null) {
4992 			writeln(GetLastError());
4993 			throw new Exception("CreateIoCompletionPort");
4994 		}
4995 
4996 		super(cast(socket_t) handle, af);
4997 	}
4998 	this() pure nothrow @trusted { assert(0); }
4999 
5000 	override void blocking(bool) {} // meaningless to us, just ignore it.
5001 
5002 	protected override Socket accepting() pure nothrow {
5003 		assert(0);
5004 	}
5005 
5006 	bool addressesParsed;
5007 	Address la;
5008 	Address ra;
5009 
5010 	private void populateAddresses() {
5011 		if(addressesParsed)
5012 			return;
5013 		addressesParsed = true;
5014 
5015 		int lalen, ralen;
5016 
5017 		sockaddr_in* la;
5018 		sockaddr_in* ra;
5019 
5020 		lpfnGetAcceptExSockaddrs(
5021 			scratchBuffer.ptr,
5022 			0, // same as in the AcceptEx call!
5023 			sockaddr_in.sizeof + 16,
5024 			sockaddr_in.sizeof + 16,
5025 			cast(sockaddr**) &la,
5026 			&lalen,
5027 			cast(sockaddr**) &ra,
5028 			&ralen
5029 		);
5030 
5031 		if(la)
5032 			this.la = new InternetAddress(*la);
5033 		if(ra)
5034 			this.ra = new InternetAddress(*ra);
5035 
5036 	}
5037 
5038 	override @property @trusted Address localAddress() {
5039 		populateAddresses();
5040 		return la;
5041 	}
5042 	override @property @trusted Address remoteAddress() {
5043 		populateAddresses();
5044 		return ra;
5045 	}
5046 
5047 	PseudoblockingOverlappedSocket accepted;
5048 
5049 	__gshared static LPFN_ACCEPTEX lpfnAcceptEx;
5050 	__gshared static typeof(&GetAcceptExSockaddrs) lpfnGetAcceptExSockaddrs;
5051 
5052 	override Socket accept() @trusted {
5053 		__gshared static LPFN_ACCEPTEX lpfnAcceptEx;
5054 
5055 		if(lpfnAcceptEx is null) {
5056 			DWORD dwBytes;
5057 			GUID GuidAcceptEx = WSAID_ACCEPTEX;
5058 
5059 			auto iResult = WSAIoctl(handle, 0xc8000006 /*SIO_GET_EXTENSION_FUNCTION_POINTER*/,
5060 					&GuidAcceptEx, GuidAcceptEx.sizeof,
5061 					&lpfnAcceptEx, lpfnAcceptEx.sizeof,
5062 					&dwBytes, null, null);
5063 
5064 			GuidAcceptEx = WSAID_GETACCEPTEXSOCKADDRS;
5065 			iResult = WSAIoctl(handle, 0xc8000006 /*SIO_GET_EXTENSION_FUNCTION_POINTER*/,
5066 					&GuidAcceptEx, GuidAcceptEx.sizeof,
5067 					&lpfnGetAcceptExSockaddrs, lpfnGetAcceptExSockaddrs.sizeof,
5068 					&dwBytes, null, null);
5069 
5070 		}
5071 
5072 		auto pfa = new PseudoblockingOverlappedSocket(AddressFamily.INET, SocketType.STREAM);
5073 		accepted = pfa;
5074 
5075 		SOCKET pendingForAccept = pfa.handle;
5076 		DWORD ignored;
5077 
5078 		auto ret = lpfnAcceptEx(handle,
5079 			pendingForAccept,
5080 			// buffer to receive up front
5081 			pfa.scratchBuffer.ptr,
5082 			0,
5083 			// size of local and remote addresses. normally + 16.
5084 			sockaddr_in.sizeof + 16,
5085 			sockaddr_in.sizeof + 16,
5086 			&ignored, // bytes would be given through the iocp instead but im not even requesting the thing
5087 			&overlapped
5088 		);
5089 
5090 		return pfa;
5091 	}
5092 
5093 	override void connect(Address to) { assert(0); }
5094 
5095 	DWORD lastAnswer;
5096 	ubyte[1024] scratchBuffer;
5097 	static assert(scratchBuffer.length > sockaddr_in.sizeof * 2 + 32);
5098 
5099 	WSABUF[1] buffer;
5100 	OVERLAPPED overlapped;
5101 	override ptrdiff_t send(scope const(void)[] buf, SocketFlags flags) @trusted {
5102 		overlapped = overlapped.init;
5103 		buffer[0].len = cast(DWORD) buf.length;
5104 		buffer[0].buf = cast(ubyte*) buf.ptr;
5105 		fiber.setPostYield( () {
5106 			if(!WSASend(handle, buffer.ptr, cast(DWORD) buffer.length, null, 0, &overlapped, null)) {
5107 				if(GetLastError() != 997) {
5108 					//throw new Exception("WSASend fail");
5109 				}
5110 			}
5111 		});
5112 
5113 		Fiber.yield();
5114 		return lastAnswer;
5115 	}
5116 	override ptrdiff_t receive(scope void[] buf, SocketFlags flags) @trusted {
5117 		overlapped = overlapped.init;
5118 		buffer[0].len = cast(DWORD) buf.length;
5119 		buffer[0].buf = cast(ubyte*) buf.ptr;
5120 
5121 		DWORD flags2 = 0;
5122 
5123 		fiber.setPostYield(() {
5124 			if(!WSARecv(handle, buffer.ptr, cast(DWORD) buffer.length, null, &flags2 /* flags */, &overlapped, null)) {
5125 				if(GetLastError() != 997) {
5126 					//writeln("WSARecv ", WSAGetLastError());
5127 					//throw new Exception("WSARecv fail");
5128 				}
5129 			}
5130 		});
5131 
5132 		Fiber.yield();
5133 		return lastAnswer;
5134 	}
5135 
5136 	// I might go back and implement these for udp things.
5137 	override ptrdiff_t receiveFrom(scope void[] buf, SocketFlags flags, ref Address from) @trusted {
5138 		assert(0);
5139 	}
5140 	override ptrdiff_t receiveFrom(scope void[] buf, SocketFlags flags) @trusted {
5141 		assert(0);
5142 	}
5143 	override ptrdiff_t sendTo(scope const(void)[] buf, SocketFlags flags, Address to) @trusted {
5144 		assert(0);
5145 	}
5146 	override ptrdiff_t sendTo(scope const(void)[] buf, SocketFlags flags) @trusted {
5147 		assert(0);
5148 	}
5149 
5150 	// lol overload sets
5151 	alias send = typeof(super).send;
5152 	alias receive = typeof(super).receive;
5153 	alias sendTo = typeof(super).sendTo;
5154 	alias receiveFrom = typeof(super).receiveFrom;
5155 
5156 }
5157 }
5158 
5159 void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
5160 	assert(connection !is null);
5161 	version(cgi_use_fiber) {
5162 		auto fiber = new CgiFiber(&doThreadHttpConnectionGuts!(CustomCgi, fun));
5163 
5164 		version(Windows) {
5165 			(cast(PseudoblockingOverlappedSocket) connection).fiber = fiber;
5166 		}
5167 
5168 		import core.memory;
5169 		GC.addRoot(cast(void*) fiber);
5170 		fiber.connection = connection;
5171 		fiber.proceed();
5172 	} else {
5173 		doThreadHttpConnectionGuts!(CustomCgi, fun)(connection);
5174 	}
5175 }
5176 
5177 /+
5178 
5179 /+
5180 	The represents a recyclable per-task arena allocator. The default is to let the GC manage the whole block as a large array, meaning if a reference into it is escaped, it waste memory but is not dangerous. If you don't escape any references to it and don't do anything special, the GC collects it.
5181 
5182 	But, if you call `cgi.recyclable = true`, the memory is retained for the next request on the thread. If a reference is escaped, it is the user's problem; it can be modified (and break the `immutable` guarantees!) and thus be memory unsafe. They're taking responsibility for doing it right when they call `escape`. But if they do it right and opt into recycling, the memory is all reused to give a potential boost without requiring the GC's involvement.
5183 
5184 	What if one request used an abnormally large amount of memory though? Will recycling it keep that pinned forever? No, that's why it keeps track of some stats. If a working set was significantly above average and not fully utilized for a while, it will just let the GC have it again despite your suggestion to recycle it.
5185 
5186 	Be warned that growing the memory block may release the old, smaller block for garbage collection. If you retained references to it, it may not be collectable and lead to some unnecessary memory growth. It is probably best to try to keep the things sized in a continuous block that doesn't have to grow often.
5187 
5188 	Internally, it is broken up into a few blocks:
5189 		* the request block. This holds the incoming request and associated data (parsed headers, variables, etc).
5190 		* the scannable block. this holds pointers arrays, classes, etc. associated with this request, so named because the GC scans it.
5191 		* the response block. This holds the output buffer.
5192 
5193 	And I may add more later if I decide to open this up to outside user code.
5194 
5195 	The scannable block is separate to limit the amount of work the GC has to do; no point asking it to scan that which need not be scanned.
5196 
5197 	The request and response blocks are separated because they will have different typical sizes, with the request likely being less predictable. Being able to release one to the GC while recycling the other might help, and having them grow independently (if needed) may also prevent some pain.
5198 
5199 	All of this are internal implementation details subject to change at any time without notice. It is valid for my recycle method to do absolutely nothing; the GC also eventually recycles memory!
5200 
5201 	Each active task can have its own recyclable memory object. When you recycle it, it is added to a thread-local freelist. If the list is excessively large, entries maybe discarded at random and left for the GC to prevent a temporary burst of activity from leading to a permanent waste of memory.
5202 +/
5203 struct RecyclableMemory {
5204 	private ubyte[] inputBuffer;
5205 	private ubyte[] processedRequestBlock;
5206 	private void[] scannableBlock;
5207 	private ubyte[] outputBuffer;
5208 
5209 	RecyclableMemory* next;
5210 }
5211 
5212 /++
5213 	This emulates the D associative array interface with a different internal implementation.
5214 
5215 	string s = cgi.get["foo"]; // just does cgi.getArray[x][$-1];
5216 	string[] arr = cgi.getArray["foo"];
5217 
5218 	"foo" in cgi.get
5219 
5220 	foreach(k, v; cgi.get)
5221 
5222 	cgi.get.toAA // for compatibility
5223 
5224 	// and this can urldecode lazily tbh... in-place even, since %xx is always longer than a single char thing it turns into...
5225 		... but how does it mark that it has already been processed in-place? it'd have to just add it to the index then.
5226 
5227 	deprecated alias toAA this;
5228 +/
5229 struct VariableCollection {
5230 	private VariableArrayCollection* vac;
5231 
5232 	const(char[]) opIndex(scope const char[] key) {
5233 		return (*vac)[key][$-1];
5234 	}
5235 
5236 	const(char[]*) opBinaryRight(string op : "in")(scope const char[] key) {
5237 		return key in (*vac);
5238 	}
5239 
5240 	int opApply(int delegate(scope const(char)[] key, scope const(char)[] value) dg) {
5241 		foreach(k, v; *vac) {
5242 			if(auto res = dg(k, v[$-1]))
5243 				return res;
5244 		}
5245 		return 0;
5246 	}
5247 
5248 	immutable(string[string]) toAA() {
5249 		string[string] aa;
5250 		foreach(k, v; *vac)
5251 			aa[k.idup] = v[$-1].idup;
5252 		return aa;
5253 	}
5254 
5255 	deprecated alias toAA this;
5256 }
5257 
5258 struct VariableArrayCollection {
5259 	/+
5260 		This needs the actual implementation of looking it up. As it pulls data, it should
5261 		decode and index for later.
5262 
5263 		The index will go into a block attached to the cgi object and it should prolly be sorted
5264 		something like
5265 
5266 		[count of names]
5267 		[slice to name][count of values][slice to value, decoded in-place, ...]
5268 		...
5269 	+/
5270 	private Cgi cgi;
5271 
5272 	const(char[][]) opIndex(scope const char[] key) {
5273 		return null;
5274 	}
5275 
5276 	const(char[][]*) opBinaryRight(string op : "in")(scope const char[] key) {
5277 		return null;
5278 	}
5279 
5280 	// int opApply(int delegate(scope const(char)[] key, scope const(char)[][] value) dg)
5281 
5282 	immutable(string[string]) toAA() {
5283 		return null;
5284 	}
5285 
5286 	deprecated alias toAA this;
5287 
5288 }
5289 
5290 struct HeaderCollection {
5291 
5292 }
5293 +/
5294 
5295 void doThreadHttpConnectionGuts(CustomCgi, alias fun, bool alwaysCloseConnection = false)(Socket connection) {
5296 	scope(failure) {
5297 		// catch all for other errors
5298 		try {
5299 			sendAll(connection, plainHttpError(false, "500 Internal Server Error", null));
5300 			connection.close();
5301 		} catch(Exception e) {} // swallow it, we're aborting anyway.
5302 	}
5303 
5304 	bool closeConnection = alwaysCloseConnection;
5305 
5306 	/+
5307 	ubyte[4096] inputBuffer = void;
5308 	ubyte[__traits(classInstanceSize, BufferedInputRange)] birBuffer = void;
5309 	ubyte[__traits(classInstanceSize, CustomCgi)] cgiBuffer = void;
5310 
5311 	birBuffer[] = cast(ubyte[]) typeid(BufferedInputRange).initializer()[];
5312 	BufferedInputRange ir = cast(BufferedInputRange) cast(void*) birBuffer.ptr;
5313 	ir.__ctor(connection, inputBuffer[], true);
5314 	+/
5315 
5316 	auto ir = new BufferedInputRange(connection);
5317 
5318 	while(!ir.empty) {
5319 
5320 		if(ir.view.length == 0) {
5321 			ir.popFront();
5322 			if(ir.sourceClosed) {
5323 				connection.close();
5324 				closeConnection = true;
5325 				break;
5326 			}
5327 		}
5328 
5329 		Cgi cgi;
5330 		try {
5331 			cgi = new CustomCgi(ir, &closeConnection);
5332 			// There's a bunch of these casts around because the type matches up with
5333 			// the -version=.... specifiers, just you can also create a RequestServer
5334 			// and instantiate the things where the types don't match up. It isn't exactly
5335 			// correct but I also don't care rn. Might FIXME and either remove it later or something.
5336 			cgi._outputFileHandle = cast(CgiConnectionHandle) connection.handle;
5337 		} catch(ConnectionClosedException ce) {
5338 			closeConnection = true;
5339 			break;
5340 		} catch(ConnectionException ce) {
5341 			// broken pipe or something, just abort the connection
5342 			closeConnection = true;
5343 			break;
5344 		} catch(HttpVersionNotSupportedException ve) {
5345 			sendAll(connection, plainHttpError(false, "505 HTTP Version Not Supported", ve));
5346 			closeConnection = true;
5347 			break;
5348 		} catch(Throwable t) {
5349 			// a construction error is either bad code or bad request; bad request is what it should be since this is bug free :P
5350 			// anyway let's kill the connection
5351 			version(CRuntime_Musl) {
5352 				stderr.rawWrite(t.toString());
5353 				stderr.rawWrite("\n");
5354 			} else {
5355 				stderr.writeln(t.toString());
5356 			}
5357 			sendAll(connection, plainHttpError(false, "400 Bad Request", t));
5358 			closeConnection = true;
5359 			break;
5360 		}
5361 		assert(cgi !is null);
5362 		scope(exit)
5363 			cgi.dispose();
5364 
5365 		try {
5366 			fun(cgi);
5367 			cgi.close();
5368 			if(cgi.websocketMode)
5369 				closeConnection = true;
5370 		} catch(AuthorizationRequiredException are) {
5371 			cgi.setResponseStatus("401 Authorization Required");
5372 			cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
5373 			cgi.close();
5374 		} catch(ConnectionException ce) {
5375 			// broken pipe or something, just abort the connection
5376 			closeConnection = true;
5377 		} catch(ConnectionClosedException ce) {
5378 			// broken pipe or something, just abort the connection
5379 			closeConnection = true;
5380 		} catch(Throwable t) {
5381 			// a processing error can be recovered from
5382 			version(CRuntime_Musl) {} else
5383 			stderr.writeln(t.toString);
5384 			if(!handleException(cgi, t))
5385 				closeConnection = true;
5386 		}
5387 
5388 		if(globalStopFlag)
5389 			closeConnection = true;
5390 
5391 		if(closeConnection || alwaysCloseConnection) {
5392 			connection.shutdown(SocketShutdown.BOTH);
5393 			connection.close();
5394 			ir.dispose();
5395 			closeConnection = false; // don't reclose after loop
5396 			break;
5397 		} else {
5398 			if(ir.front.length) {
5399 				ir.popFront(); // we can't just discard the buffer, so get the next bit and keep chugging along
5400 			} else if(ir.sourceClosed) {
5401 				ir.source.shutdown(SocketShutdown.BOTH);
5402 				ir.source.close();
5403 				ir.dispose();
5404 				closeConnection = false;
5405 			} else {
5406 				continue;
5407 				// break; // this was for a keepalive experiment
5408 			}
5409 		}
5410 	}
5411 
5412 	if(closeConnection) {
5413 		connection.shutdown(SocketShutdown.BOTH);
5414 		connection.close();
5415 		ir.dispose();
5416 	}
5417 
5418 	// I am otherwise NOT closing it here because the parent thread might still be able to make use of the keep-alive connection!
5419 }
5420 
5421 void doThreadScgiConnection(CustomCgi, alias fun, long maxContentLength)(Socket connection) {
5422 	// and now we can buffer
5423 	scope(failure)
5424 		connection.close();
5425 
5426 	import al = std.algorithm;
5427 
5428 	size_t size;
5429 
5430 	string[string] headers;
5431 
5432 	auto range = new BufferedInputRange(connection);
5433 	more_data:
5434 	auto chunk = range.front();
5435 	// waiting for colon for header length
5436 	auto idx = indexOf(cast(string) chunk, ':');
5437 	if(idx == -1) {
5438 		try {
5439 			range.popFront();
5440 		} catch(Exception e) {
5441 			// it is just closed, no big deal
5442 			connection.close();
5443 			return;
5444 		}
5445 		goto more_data;
5446 	}
5447 
5448 	size = to!size_t(cast(string) chunk[0 .. idx]);
5449 	chunk = range.consume(idx + 1);
5450 	// reading headers
5451 	if(chunk.length < size)
5452 		range.popFront(0, size + 1);
5453 	// we are now guaranteed to have enough
5454 	chunk = range.front();
5455 	assert(chunk.length > size);
5456 
5457 	idx = 0;
5458 	string key;
5459 	string value;
5460 	foreach(part; al.splitter(chunk, '\0')) {
5461 		if(idx & 1) { // odd is value
5462 			value = cast(string)(part.idup);
5463 			headers[key] = value; // commit
5464 		} else
5465 			key = cast(string)(part.idup);
5466 		idx++;
5467 	}
5468 
5469 	enforce(chunk[size] == ','); // the terminator
5470 
5471 	range.consume(size + 1);
5472 	// reading data
5473 	// this will be done by Cgi
5474 
5475 	const(ubyte)[] getScgiChunk() {
5476 		// we are already primed
5477 		auto data = range.front();
5478 		if(data.length == 0 && !range.sourceClosed) {
5479 			range.popFront(0);
5480 			data = range.front();
5481 		} else if (range.sourceClosed)
5482 			range.source.close();
5483 
5484 		return data;
5485 	}
5486 
5487 	void writeScgi(const(ubyte)[] data) {
5488 		sendAll(connection, data);
5489 	}
5490 
5491 	void flushScgi() {
5492 		// I don't *think* I have to do anything....
5493 	}
5494 
5495 	Cgi cgi;
5496 	try {
5497 		cgi = new CustomCgi(maxContentLength, headers, &getScgiChunk, &writeScgi, &flushScgi);
5498 		cgi._outputFileHandle = cast(CgiConnectionHandle) connection.handle;
5499 	} catch(Throwable t) {
5500 		sendAll(connection, plainHttpError(true, "400 Bad Request", t));
5501 		connection.close();
5502 		return; // this connection is dead
5503 	}
5504 	assert(cgi !is null);
5505 	scope(exit) cgi.dispose();
5506 	try {
5507 		fun(cgi);
5508 		cgi.close();
5509 		connection.close();
5510 
5511 	} catch(AuthorizationRequiredException are) {
5512 		cgi.setResponseStatus("401 Authorization Required");
5513 		cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
5514 		cgi.close();
5515 	} catch(Throwable t) {
5516 		// no std err
5517 		if(!handleException(cgi, t)) {
5518 			connection.close();
5519 			return;
5520 		} else {
5521 			connection.close();
5522 			return;
5523 		}
5524 	}
5525 }
5526 
5527 string printDate(DateTime date) {
5528 	char[29] buffer = void;
5529 	printDateToBuffer(date, buffer[]);
5530 	return buffer.idup;
5531 }
5532 
5533 int printDateToBuffer(DateTime date, char[] buffer) @nogc {
5534 	assert(buffer.length >= 29);
5535 	// 29 static length ?
5536 
5537 	static immutable daysOfWeek = [
5538 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
5539 	];
5540 
5541 	static immutable months = [
5542 		null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
5543 	];
5544 
5545 	buffer[0 .. 3] = daysOfWeek[date.dayOfWeek];
5546 	buffer[3 .. 5] = ", ";
5547 	buffer[5] = date.day / 10 + '0';
5548 	buffer[6] = date.day % 10 + '0';
5549 	buffer[7] = ' ';
5550 	buffer[8 .. 11] = months[date.month];
5551 	buffer[11] = ' ';
5552 	auto y = date.year;
5553 	buffer[12] = cast(char) (y / 1000 + '0'); y %= 1000;
5554 	buffer[13] = cast(char) (y / 100 + '0'); y %= 100;
5555 	buffer[14] = cast(char) (y / 10 + '0'); y %= 10;
5556 	buffer[15] = cast(char) (y + '0');
5557 	buffer[16] = ' ';
5558 	buffer[17] = date.hour / 10 + '0';
5559 	buffer[18] = date.hour % 10 + '0';
5560 	buffer[19] = ':';
5561 	buffer[20] = date.minute / 10 + '0';
5562 	buffer[21] = date.minute % 10 + '0';
5563 	buffer[22] = ':';
5564 	buffer[23] = date.second / 10 + '0';
5565 	buffer[24] = date.second % 10 + '0';
5566 	buffer[25 .. $] = " GMT";
5567 
5568 	return 29;
5569 }
5570 
5571 
5572 // Referencing this gigantic typeid seems to remind the compiler
5573 // to actually put the symbol in the object file. I guess the immutable
5574 // assoc array array isn't actually included in druntime
5575 void hackAroundLinkerError() {
5576       stdout.rawWrite(typeid(const(immutable(char)[][])[immutable(char)[]]).toString());
5577       stdout.rawWrite(typeid(immutable(char)[][][immutable(char)[]]).toString());
5578       stdout.rawWrite(typeid(Cgi.UploadedFile[immutable(char)[]]).toString());
5579       stdout.rawWrite(typeid(Cgi.UploadedFile[][immutable(char)[]]).toString());
5580       stdout.rawWrite(typeid(immutable(Cgi.UploadedFile)[immutable(char)[]]).toString());
5581       stdout.rawWrite(typeid(immutable(Cgi.UploadedFile[])[immutable(char)[]]).toString());
5582       stdout.rawWrite(typeid(immutable(char[])[immutable(char)[]]).toString());
5583       // this is getting kinda ridiculous btw. Moving assoc arrays
5584       // to the library is the pain that keeps on coming.
5585 
5586       // eh this broke the build on the work server
5587       // stdout.rawWrite(typeid(immutable(char)[][immutable(string[])]));
5588       stdout.rawWrite(typeid(immutable(string[])[immutable(char)[]]).toString());
5589 }
5590 
5591 
5592 
5593 
5594 
5595 version(fastcgi) {
5596 	pragma(lib, "fcgi");
5597 
5598 	static if(size_t.sizeof == 8) // 64 bit
5599 		alias long c_int;
5600 	else
5601 		alias int c_int;
5602 
5603 	extern(C) {
5604 		struct FCGX_Stream {
5605 			ubyte* rdNext;
5606 			ubyte* wrNext;
5607 			ubyte* stop;
5608 			ubyte* stopUnget;
5609 			c_int isReader;
5610 			c_int isClosed;
5611 			c_int wasFCloseCalled;
5612 			c_int FCGI_errno;
5613 			void* function(FCGX_Stream* stream) fillBuffProc;
5614 			void* function(FCGX_Stream* stream, c_int doClose) emptyBuffProc;
5615 			void* data;
5616 		}
5617 
5618 		// note: this is meant to be opaque, so don't access it directly
5619 		struct FCGX_Request {
5620 			int requestId;
5621 			int role;
5622 			FCGX_Stream* inStream;
5623 			FCGX_Stream* outStream;
5624 			FCGX_Stream* errStream;
5625 			char** envp;
5626 			void* paramsPtr;
5627 			int ipcFd;
5628 			int isBeginProcessed;
5629 			int keepConnection;
5630 			int appStatus;
5631 			int nWriters;
5632 			int flags;
5633 			int listen_sock;
5634 		}
5635 
5636 		int FCGX_InitRequest(FCGX_Request *request, int sock, int flags);
5637 		void FCGX_Init();
5638 
5639 		int FCGX_Accept_r(FCGX_Request *request);
5640 
5641 
5642 		alias char** FCGX_ParamArray;
5643 
5644 		c_int FCGX_Accept(FCGX_Stream** stdin, FCGX_Stream** stdout, FCGX_Stream** stderr, FCGX_ParamArray* envp);
5645 		c_int FCGX_GetChar(FCGX_Stream* stream);
5646 		c_int FCGX_PutStr(const ubyte* str, c_int n, FCGX_Stream* stream);
5647 		int FCGX_HasSeenEOF(FCGX_Stream* stream);
5648 		c_int FCGX_FFlush(FCGX_Stream *stream);
5649 
5650 		int FCGX_OpenSocket(const char*, int);
5651 	}
5652 }
5653 
5654 
5655 /* This might go int a separate module eventually. It is a network input helper class. */
5656 
5657 import std.socket;
5658 
5659 version(cgi_use_fiber) {
5660 	import core.thread;
5661 
5662 	version(linux) {
5663 		import core.sys.linux.epoll;
5664 
5665 		int epfd = -1; // thread local because EPOLLEXCLUSIVE works much better this way... weirdly.
5666 	} else version(Windows) {
5667 		// declaring the iocp thing below...
5668 	} else static assert(0, "The hybrid fiber server is not implemented on your OS.");
5669 }
5670 
5671 version(Windows)
5672 	__gshared HANDLE iocp;
5673 
5674 version(cgi_use_fiber) {
5675 	version(linux)
5676 	private enum WakeupEvent {
5677 		Read = EPOLLIN,
5678 		Write = EPOLLOUT
5679 	}
5680 	else version(Windows)
5681 	private enum WakeupEvent {
5682 		Read, Write
5683 	}
5684 	else static assert(0);
5685 }
5686 
5687 version(cgi_use_fiber)
5688 private void registerEventWakeup(bool* registered, Socket source, WakeupEvent e) @nogc {
5689 
5690 	// static cast since I know what i have in here and don't want to pay for dynamic cast
5691 	auto f = cast(CgiFiber) cast(void*) Fiber.getThis();
5692 
5693 	version(linux) {
5694 		f.setPostYield = () {
5695 			if(*registered) {
5696 				// rearm
5697 				epoll_event evt;
5698 				evt.events = e | EPOLLONESHOT;
5699 				evt.data.ptr = cast(void*) f;
5700 				if(epoll_ctl(epfd, EPOLL_CTL_MOD, source.handle, &evt) == -1)
5701 					throw new Exception("epoll_ctl");
5702 			} else {
5703 				// initial registration
5704 				*registered = true ;
5705 				int fd = source.handle;
5706 				epoll_event evt;
5707 				evt.events = e | EPOLLONESHOT;
5708 				evt.data.ptr = cast(void*) f;
5709 				if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt) == -1)
5710 					throw new Exception("epoll_ctl");
5711 			}
5712 		};
5713 
5714 		Fiber.yield();
5715 
5716 		f.setPostYield(null);
5717 	} else version(Windows) {
5718 		Fiber.yield();
5719 	}
5720 	else static assert(0);
5721 }
5722 
5723 version(cgi_use_fiber)
5724 void unregisterSource(Socket s) {
5725 	version(linux) {
5726 		epoll_event evt;
5727 		epoll_ctl(epfd, EPOLL_CTL_DEL, s.handle(), &evt);
5728 	} else version(Windows) {
5729 		// intentionally blank
5730 	}
5731 	else static assert(0);
5732 }
5733 
5734 // it is a class primarily for reference semantics
5735 // I might change this interface
5736 /// This is NOT ACTUALLY an input range! It is too different. Historical mistake kinda.
5737 class BufferedInputRange {
5738 	version(Posix)
5739 	this(int source, ubyte[] buffer = null) {
5740 		this(new Socket(cast(socket_t) source, AddressFamily.INET), buffer);
5741 	}
5742 
5743 	this(Socket source, ubyte[] buffer = null, bool allowGrowth = true) {
5744 		// if they connect but never send stuff to us, we don't want it wasting the process
5745 		// so setting a time out
5746 		version(cgi_use_fiber)
5747 			source.blocking = false;
5748 		else
5749 			source.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(3));
5750 
5751 		this.source = source;
5752 		if(buffer is null) {
5753 			underlyingBuffer = new ubyte[4096];
5754 			this.allowGrowth = true;
5755 		} else {
5756 			underlyingBuffer = buffer;
5757 			this.allowGrowth = allowGrowth;
5758 		}
5759 
5760 		assert(underlyingBuffer.length);
5761 
5762 		// we assume view.ptr is always inside underlyingBuffer
5763 		view = underlyingBuffer[0 .. 0];
5764 
5765 		popFront(); // prime
5766 	}
5767 
5768 	version(cgi_use_fiber) {
5769 		bool registered;
5770 	}
5771 
5772 	void dispose() {
5773 		version(cgi_use_fiber) {
5774 			if(registered)
5775 				unregisterSource(source);
5776 		}
5777 	}
5778 
5779 	/**
5780 		A slight difference from regular ranges is you can give it the maximum
5781 		number of bytes to consume.
5782 
5783 		IMPORTANT NOTE: the default is to consume nothing, so if you don't call
5784 		consume() yourself and use a regular foreach, it will infinitely loop!
5785 
5786 		The default is to do what a normal range does, and consume the whole buffer
5787 		and wait for additional input.
5788 
5789 		You can also specify 0, to append to the buffer, or any other number
5790 		to remove the front n bytes and wait for more.
5791 	*/
5792 	void popFront(size_t maxBytesToConsume = 0 /*size_t.max*/, size_t minBytesToSettleFor = 0, bool skipConsume = false) {
5793 		if(sourceClosed)
5794 			throw new ConnectionClosedException("can't get any more data from a closed source");
5795 		if(!skipConsume)
5796 			consume(maxBytesToConsume);
5797 
5798 		// we might have to grow the buffer
5799 		if(minBytesToSettleFor > underlyingBuffer.length || view.length == underlyingBuffer.length) {
5800 			if(allowGrowth) {
5801 			//import std.stdio; writeln("growth");
5802 				auto viewStart = view.ptr - underlyingBuffer.ptr;
5803 				size_t growth = 4096;
5804 				// make sure we have enough for what we're being asked for
5805 				if(minBytesToSettleFor > 0 && minBytesToSettleFor - underlyingBuffer.length > growth)
5806 					growth = minBytesToSettleFor - underlyingBuffer.length;
5807 				//import std.stdio; writeln(underlyingBuffer.length, " ", viewStart, " ", view.length, " ", growth,  " ", minBytesToSettleFor, " ", minBytesToSettleFor - underlyingBuffer.length);
5808 				underlyingBuffer.length += growth;
5809 				view = underlyingBuffer[viewStart .. view.length];
5810 			} else
5811 				throw new Exception("No room left in the buffer");
5812 		}
5813 
5814 		do {
5815 			auto freeSpace = underlyingBuffer[view.ptr - underlyingBuffer.ptr + view.length .. $];
5816 			try_again:
5817 			auto ret = source.receive(freeSpace);
5818 			if(ret == Socket.ERROR) {
5819 				if(wouldHaveBlocked()) {
5820 					version(cgi_use_fiber) {
5821 						registerEventWakeup(&registered, source, WakeupEvent.Read);
5822 						goto try_again;
5823 					} else {
5824 						// gonna treat a timeout here as a close
5825 						sourceClosed = true;
5826 						return;
5827 					}
5828 				}
5829 				version(Posix) {
5830 					import core.stdc.errno;
5831 					if(errno == EINTR || errno == EAGAIN) {
5832 						goto try_again;
5833 					}
5834 					if(errno == ECONNRESET) {
5835 						sourceClosed = true;
5836 						return;
5837 					}
5838 				}
5839 				throw new Exception(lastSocketError); // FIXME
5840 			}
5841 			if(ret == 0) {
5842 				sourceClosed = true;
5843 				return;
5844 			}
5845 
5846 			//import std.stdio; writeln(view.ptr); writeln(underlyingBuffer.ptr); writeln(view.length, " ", ret, " = ", view.length + ret);
5847 			view = underlyingBuffer[view.ptr - underlyingBuffer.ptr .. view.length + ret];
5848 			//import std.stdio; writeln(cast(string) view);
5849 		} while(view.length < minBytesToSettleFor);
5850 	}
5851 
5852 	/// Removes n bytes from the front of the buffer, and returns the new buffer slice.
5853 	/// You might want to idup the data you are consuming if you store it, since it may
5854 	/// be overwritten on the new popFront.
5855 	///
5856 	/// You do not need to call this if you always want to wait for more data when you
5857 	/// consume some.
5858 	ubyte[] consume(size_t bytes) {
5859 		//import std.stdio; writeln("consuime ", bytes, "/", view.length);
5860 		view = view[bytes > $ ? $ : bytes .. $];
5861 		if(view.length == 0) {
5862 			view = underlyingBuffer[0 .. 0]; // go ahead and reuse the beginning
5863 			/*
5864 			writeln("HERE");
5865 			popFront(0, 0, true); // try to load more if we can, checks if the source is closed
5866 			writeln(cast(string)front);
5867 			writeln("DONE");
5868 			*/
5869 		}
5870 		return front;
5871 	}
5872 
5873 	bool empty() {
5874 		return sourceClosed && view.length == 0;
5875 	}
5876 
5877 	ubyte[] front() {
5878 		return view;
5879 	}
5880 
5881 	@system invariant() {
5882 		assert(view.ptr >= underlyingBuffer.ptr);
5883 		// it should never be equal, since if that happens view ought to be empty, and thus reusing the buffer
5884 		assert(view.ptr < underlyingBuffer.ptr + underlyingBuffer.length);
5885 	}
5886 
5887 	ubyte[] underlyingBuffer;
5888 	bool allowGrowth;
5889 	ubyte[] view;
5890 	Socket source;
5891 	bool sourceClosed;
5892 }
5893 
5894 private class FakeSocketForStdin : Socket {
5895 	import std.stdio;
5896 
5897 	this() {
5898 
5899 	}
5900 
5901 	private bool closed;
5902 
5903 	override ptrdiff_t receive(scope void[] buffer, std.socket.SocketFlags) @trusted {
5904 		if(closed)
5905 			throw new Exception("Closed");
5906 		return stdin.rawRead(buffer).length;
5907 	}
5908 
5909 	override ptrdiff_t send(const scope void[] buffer, std.socket.SocketFlags) @trusted {
5910 		if(closed)
5911 			throw new Exception("Closed");
5912 		stdout.rawWrite(buffer);
5913 		return buffer.length;
5914 	}
5915 
5916 	override void close() @trusted scope {
5917 		(cast(void delegate() @nogc nothrow) &realClose)();
5918 	}
5919 
5920 	override void shutdown(SocketShutdown s) {
5921 		// FIXME
5922 	}
5923 
5924 	override void setOption(SocketOptionLevel, SocketOption, scope void[]) {}
5925 	override void setOption(SocketOptionLevel, SocketOption, Duration) {}
5926 
5927 	override @property @trusted Address remoteAddress() { return null; }
5928 	override @property @trusted Address localAddress() { return null; }
5929 
5930 	void realClose() {
5931 		closed = true;
5932 		try {
5933 			stdin.close();
5934 			stdout.close();
5935 		} catch(Exception e) {
5936 
5937 		}
5938 	}
5939 }
5940 
5941 import core.sync.semaphore;
5942 import core.atomic;
5943 
5944 /**
5945 	To use this thing:
5946 
5947 	---
5948 	void handler(Socket s) { do something... }
5949 	auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler, &delegateThatDropsPrivileges);
5950 	manager.listen();
5951 	---
5952 
5953 	The 4th parameter is optional.
5954 
5955 	I suggest you use BufferedInputRange(connection) to handle the input. As a packet
5956 	comes in, you will get control. You can just continue; though to fetch more.
5957 
5958 
5959 	FIXME: should I offer an event based async thing like netman did too? Yeah, probably.
5960 */
5961 class ListeningConnectionManager {
5962 	Semaphore semaphore;
5963 	Socket[256] queue;
5964 	shared(ubyte) nextIndexFront;
5965 	ubyte nextIndexBack;
5966 	shared(int) queueLength;
5967 
5968 	Socket acceptCancelable() {
5969 		version(Posix) {
5970 			import core.sys.posix.sys.select;
5971 			fd_set read_fds;
5972 			FD_ZERO(&read_fds);
5973 			int max = 0;
5974 			foreach(listener; listeners) {
5975 				FD_SET(listener.handle, &read_fds);
5976 				if(listener.handle > max)
5977 					max = listener.handle;
5978 			}
5979 			if(cancelfd != -1) {
5980 				FD_SET(cancelfd, &read_fds);
5981 				if(cancelfd > max)
5982 					max = cancelfd;
5983 			}
5984 			auto ret = select(max + 1, &read_fds, null, null, null);
5985 			if(ret == -1) {
5986 				import core.stdc.errno;
5987 				if(errno == EINTR)
5988 					return null;
5989 				else
5990 					throw new Exception("wtf select");
5991 			}
5992 
5993 			if(cancelfd != -1 && FD_ISSET(cancelfd, &read_fds)) {
5994 				return null;
5995 			}
5996 
5997 			foreach(listener; listeners) {
5998 				if(FD_ISSET(listener.handle, &read_fds))
5999 					return listener.accept();
6000 			}
6001 
6002 			return null;
6003 		} else {
6004 
6005 			auto check = new SocketSet();
6006 
6007 			keep_looping:
6008 			check.reset();
6009 			foreach(listener; listeners)
6010 				check.add(listener);
6011 
6012 			// just to check the stop flag on a kinda busy loop. i hate this FIXME
6013 			auto got = Socket.select(check, null, null, 3.seconds);
6014 			if(got > 0)
6015 				foreach(listener; listeners)
6016 					if(check.isSet(listener))
6017 						return listener.accept();
6018 			if(globalStopFlag)
6019 				return null;
6020 			else
6021 				goto keep_looping;
6022 		}
6023 	}
6024 
6025 	int defaultNumberOfThreads() {
6026 		import std.parallelism;
6027 		version(cgi_use_fiber) {
6028 			return totalCPUs * 2 + 1; // still chance some will be pointlessly blocked anyway
6029 		} else {
6030 			// I times 4 here because there's a good chance some will be blocked on i/o.
6031 			return totalCPUs * 4;
6032 		}
6033 
6034 	}
6035 
6036 	void listen() {
6037 		shared(int) loopBroken;
6038 
6039 		version(Posix) {
6040 			import core.sys.posix.signal;
6041 			signal(SIGPIPE, SIG_IGN);
6042 		}
6043 
6044 		version(linux) {
6045 			if(cancelfd == -1)
6046 				cancelfd = eventfd(0, 0);
6047 		}
6048 
6049 		version(cgi_no_threads) {
6050 			// NEVER USE THIS
6051 			// it exists only for debugging and other special occasions
6052 
6053 			// the thread mode is faster and less likely to stall the whole
6054 			// thing when a request is slow
6055 			while(!loopBroken && !globalStopFlag) {
6056 				auto sn = acceptCancelable();
6057 				if(sn is null) continue;
6058 				cloexec(sn);
6059 				try {
6060 					handler(sn);
6061 				} catch(Exception e) {
6062 					// 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)
6063 					sn.close();
6064 				}
6065 			}
6066 		} else {
6067 
6068 			if(useFork) {
6069 				version(linux) {
6070 					//asm { int 3; }
6071 					fork();
6072 				}
6073 			}
6074 
6075 			version(cgi_use_fiber) {
6076 
6077 				version(Windows) {
6078 					// please note these are overlapped sockets! so the accept just kicks things off
6079 					foreach(listener; listeners)
6080 						listener.accept();
6081 				}
6082 
6083 				WorkerThread[] threads = new WorkerThread[](numberOfThreads);
6084 				foreach(i, ref thread; threads) {
6085 					thread = new WorkerThread(this, handler, cast(int) i);
6086 					thread.start();
6087 				}
6088 
6089 				bool fiber_crash_check() {
6090 					bool hasAnyRunning;
6091 					foreach(thread; threads) {
6092 						if(!thread.isRunning) {
6093 							thread.join();
6094 						} else hasAnyRunning = true;
6095 					}
6096 
6097 					return (!hasAnyRunning);
6098 				}
6099 
6100 
6101 				while(!globalStopFlag) {
6102 					Thread.sleep(1.seconds);
6103 					if(fiber_crash_check())
6104 						break;
6105 				}
6106 
6107 			} else {
6108 				semaphore = new Semaphore();
6109 
6110 				ConnectionThread[] threads = new ConnectionThread[](numberOfThreads);
6111 				foreach(i, ref thread; threads) {
6112 					thread = new ConnectionThread(this, handler, cast(int) i);
6113 					thread.start();
6114 				}
6115 
6116 				while(!loopBroken && !globalStopFlag) {
6117 					Socket sn;
6118 
6119 					bool crash_check() {
6120 						bool hasAnyRunning;
6121 						foreach(thread; threads) {
6122 							if(!thread.isRunning) {
6123 								thread.join();
6124 							} else hasAnyRunning = true;
6125 						}
6126 
6127 						return (!hasAnyRunning);
6128 					}
6129 
6130 
6131 					void accept_new_connection() {
6132 						sn = acceptCancelable();
6133 						if(sn is null) return;
6134 						cloexec(sn);
6135 						if(tcp) {
6136 							// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
6137 							// on the socket because we do some buffering internally. I think this helps,
6138 							// certainly does for small requests, and I think it does for larger ones too
6139 							sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
6140 
6141 							sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
6142 						}
6143 					}
6144 
6145 					void existing_connection_new_data() {
6146 						// wait until a slot opens up
6147 						// int waited = 0;
6148 						while(queueLength >= queue.length) {
6149 							Thread.sleep(1.msecs);
6150 							// waited ++;
6151 						}
6152 						// if(waited) {import std.stdio; writeln(waited);}
6153 						synchronized(this) {
6154 							queue[nextIndexBack] = sn;
6155 							nextIndexBack++;
6156 							atomicOp!"+="(queueLength, 1);
6157 						}
6158 						semaphore.notify();
6159 					}
6160 
6161 
6162 					accept_new_connection();
6163 					if(sn !is null)
6164 						existing_connection_new_data();
6165 					else if(sn is null && globalStopFlag) {
6166 						foreach(thread; threads) {
6167 							semaphore.notify();
6168 						}
6169 						Thread.sleep(50.msecs);
6170 					}
6171 
6172 					if(crash_check())
6173 						break;
6174 				}
6175 			}
6176 
6177 			// FIXME: i typically stop this with ctrl+c which never
6178 			// actually gets here. i need to do a sigint handler.
6179 			if(cleanup)
6180 				cleanup();
6181 		}
6182 	}
6183 
6184 	//version(linux)
6185 		//int epoll_fd;
6186 
6187 	bool tcp;
6188 	void delegate() cleanup;
6189 
6190 	private void function(Socket) fhandler;
6191 	private void dg_handler(Socket s) {
6192 		fhandler(s);
6193 	}
6194 
6195 
6196 	this(string[] listenSpec, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6197 		fhandler = handler;
6198 		this(listenSpec, &dg_handler, dropPrivs, useFork, numberOfThreads);
6199 	}
6200 	this(string[] listenSpec, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6201 		string[] host;
6202 		ushort[] port;
6203 
6204 		foreach(spec; listenSpec) {
6205 			/+
6206 				The format:
6207 
6208 					protocol://
6209 					address_spec
6210 
6211 				Protocol is optional. Must be http, https, scgi, or fastcgi.
6212 
6213 				address_spec is either:
6214 					ipv4 address : port
6215 					[ipv6 address] : port
6216 					unix:filename
6217 					abstract:name
6218 					port <which is tcp but on any interface>
6219 			+/
6220 
6221 			string protocol;
6222 			string address_spec;
6223 
6224 			auto protocolIdx = spec.indexOf("://");
6225 			if(protocolIdx != -1) {
6226 				protocol = spec[0 .. protocolIdx];
6227 				address_spec = spec[protocolIdx + "://".length .. $];
6228 			} else {
6229 				address_spec = spec;
6230 			}
6231 
6232 			if(address_spec.startsWith("unix:") || address_spec.startsWith("abstract:")) {
6233 				host ~= address_spec;
6234 				port ~= 0;
6235 			} else {
6236 				auto idx = address_spec.lastIndexOf(":");
6237 				if(idx == -1) {
6238 					host ~= null;
6239 				} else {
6240 					auto as = address_spec[0 .. idx];
6241 					if(as.length >= 3 && as[0] == '[' && as[$-1] == ']')
6242 						as = as[1 .. $-1];
6243 					host ~= as;
6244 				}
6245 				port ~= address_spec[idx + 1 .. $].to!ushort;
6246 			}
6247 
6248 		}
6249 
6250 		this(host, port, handler, dropPrivs, useFork, numberOfThreads);
6251 	}
6252 
6253 	this(string host, ushort port, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6254 		this([host], [port], handler, dropPrivs, useFork, numberOfThreads);
6255 	}
6256 	this(string host, ushort port, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6257 		this([host], [port], handler, dropPrivs, useFork, numberOfThreads);
6258 	}
6259 
6260 	this(string[] host, ushort[] port, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6261 		fhandler = handler;
6262 		this(host, port, &dg_handler, dropPrivs, useFork, numberOfThreads);
6263 	}
6264 
6265 	this(string[] host, ushort[] port, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6266 		assert(host.length == port.length);
6267 
6268 		this.handler = handler;
6269 		this.useFork = useFork;
6270 		this.numberOfThreads = numberOfThreads ? numberOfThreads : defaultNumberOfThreads();
6271 
6272 		listeners.reserve(host.length);
6273 
6274 		foreach(i; 0 .. host.length)
6275 			if(host[i] == "localhost") {
6276 				listeners ~= startListening("127.0.0.1", port[i], tcp, cleanup, 128, dropPrivs);
6277 				listeners ~= startListening("::1", port[i], tcp, cleanup, 128, dropPrivs);
6278 			} else {
6279 				listeners ~= startListening(host[i], port[i], tcp, cleanup, 128, dropPrivs);
6280 			}
6281 
6282 		version(cgi_use_fiber)
6283 		if(useFork) {
6284 			foreach(listener; listeners)
6285 				listener.blocking = false;
6286 		}
6287 
6288 		// this is the UI control thread and thus gets more priority
6289 		Thread.getThis.priority = Thread.PRIORITY_MAX;
6290 	}
6291 
6292 	Socket[] listeners;
6293 	void delegate(Socket) handler;
6294 
6295 	immutable bool useFork;
6296 	int numberOfThreads;
6297 }
6298 
6299 Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue, void delegate() dropPrivs) {
6300 	Socket listener;
6301 	if(host.startsWith("unix:")) {
6302 		version(Posix) {
6303 			listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
6304 			cloexec(listener);
6305 			string filename = host["unix:".length .. $].idup;
6306 			listener.bind(new UnixAddress(filename));
6307 			cleanup = delegate() {
6308 				listener.close();
6309 				import std.file;
6310 				remove(filename);
6311 			};
6312 			tcp = false;
6313 		} else {
6314 			throw new Exception("unix sockets not supported on this system");
6315 		}
6316 	} else if(host.startsWith("abstract:")) {
6317 		version(linux) {
6318 			listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
6319 			cloexec(listener);
6320 			string filename = "\0" ~ host["abstract:".length .. $];
6321 			import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
6322 			listener.bind(new UnixAddress(filename));
6323 			tcp = false;
6324 		} else {
6325 			throw new Exception("abstract unix sockets not supported on this system");
6326 		}
6327 	} else {
6328 		auto address = host.length ? parseAddress(host, port) : new InternetAddress(port);
6329 		version(cgi_use_fiber) {
6330 			version(Windows)
6331 				listener = new PseudoblockingOverlappedSocket(AddressFamily.INET, SocketType.STREAM);
6332 			else
6333 				listener = new Socket(address.addressFamily, SocketType.STREAM);
6334 		} else {
6335 			listener = new Socket(address.addressFamily, SocketType.STREAM);
6336 		}
6337 		cloexec(listener);
6338 		listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
6339 		if(address.addressFamily == AddressFamily.INET6)
6340 			listener.setOption(SocketOptionLevel.IPV6, SocketOption.IPV6_V6ONLY, true);
6341 		listener.bind(address);
6342 		cleanup = delegate() {
6343 			listener.close();
6344 		};
6345 		tcp = true;
6346 	}
6347 
6348 	listener.listen(backQueue);
6349 
6350 	if (dropPrivs !is null) // can be null, backwards compatibility
6351 		dropPrivs();
6352 
6353 	return listener;
6354 }
6355 
6356 // 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.
6357 void sendAll(Socket s, const(void)[] data, string file = __FILE__, size_t line = __LINE__) {
6358 	if(data.length == 0) return;
6359 	ptrdiff_t amount;
6360 	//import std.stdio; writeln("***",cast(string) data,"///");
6361 	do {
6362 		amount = s.send(data);
6363 		if(amount == Socket.ERROR) {
6364 			version(cgi_use_fiber) {
6365 				if(wouldHaveBlocked()) {
6366 					bool registered = true;
6367 					registerEventWakeup(&registered, s, WakeupEvent.Write);
6368 					continue;
6369 				}
6370 			}
6371 			throw new ConnectionException(s, lastSocketError, file, line);
6372 		}
6373 		assert(amount > 0);
6374 
6375 		data = data[amount .. $];
6376 	} while(data.length);
6377 }
6378 
6379 class ConnectionException : Exception {
6380 	Socket socket;
6381 	this(Socket s, string msg, string file = __FILE__, size_t line = __LINE__) {
6382 		this.socket = s;
6383 		super(msg, file, line);
6384 	}
6385 }
6386 
6387 class HttpVersionNotSupportedException : Exception {
6388 	this(string file = __FILE__, size_t line = __LINE__) {
6389 		super("HTTP Version Not Supported", file, line);
6390 	}
6391 }
6392 
6393 alias void delegate(Socket) CMT;
6394 
6395 import core.thread;
6396 /+
6397 	cgi.d now uses a hybrid of event i/o and threads at the top level.
6398 
6399 	Top level thread is responsible for accepting sockets and selecting on them.
6400 
6401 	It then indicates to a child that a request is pending, and any random worker
6402 	thread that is free handles it. It goes into blocking mode and handles that
6403 	http request to completion.
6404 
6405 	At that point, it goes back into the waiting queue.
6406 
6407 
6408 	This concept is only implemented on Linux. On all other systems, it still
6409 	uses the worker threads and semaphores (which is perfectly fine for a lot of
6410 	things! Just having a great number of keep-alive connections will break that.)
6411 
6412 
6413 	So the algorithm is:
6414 
6415 	select(accept, event, pending)
6416 		if accept -> send socket to free thread, if any. if not, add socket to queue
6417 		if event -> send the signaling thread a socket from the queue, if not, mark it free
6418 			- event might block until it can be *written* to. it is a fifo sending socket fds!
6419 
6420 	A worker only does one http request at a time, then signals its availability back to the boss.
6421 
6422 	The socket the worker was just doing should be added to the one-off epoll read. If it is closed,
6423 	great, we can get rid of it. Otherwise, it is considered `pending`. The *kernel* manages that; the
6424 	actual FD will not be kept out here.
6425 
6426 	So:
6427 		queue = sockets we know are ready to read now, but no worker thread is available
6428 		idle list = worker threads not doing anything else. they signal back and forth
6429 
6430 	the workers all read off the event fd. This is the semaphore wait
6431 
6432 	the boss waits on accept or other sockets read events (one off! and level triggered). If anything happens wrt ready read,
6433 	it puts it in the queue and writes to the event fd.
6434 
6435 	The child could put the socket back in the epoll thing itself.
6436 
6437 	The child needs to be able to gracefully handle being given a socket that just closed with no work.
6438 +/
6439 class ConnectionThread : Thread {
6440 	this(ListeningConnectionManager lcm, CMT dg, int myThreadNumber) {
6441 		this.lcm = lcm;
6442 		this.dg = dg;
6443 		this.myThreadNumber = myThreadNumber;
6444 		super(&run);
6445 	}
6446 
6447 	void run() {
6448 		while(true) {
6449 			// so if there's a bunch of idle keep-alive connections, it can
6450 			// consume all the worker threads... just sitting there.
6451 			lcm.semaphore.wait();
6452 			if(globalStopFlag)
6453 				return;
6454 			Socket socket;
6455 			synchronized(lcm) {
6456 				auto idx = lcm.nextIndexFront;
6457 				socket = lcm.queue[idx];
6458 				lcm.queue[idx] = null;
6459 				atomicOp!"+="(lcm.nextIndexFront, 1);
6460 				atomicOp!"-="(lcm.queueLength, 1);
6461 			}
6462 			try {
6463 			//import std.stdio; writeln(myThreadNumber, " taking it");
6464 				dg(socket);
6465 				/+
6466 				if(socket.isAlive) {
6467 					// process it more later
6468 					version(linux) {
6469 						import core.sys.linux.epoll;
6470 						epoll_event ev;
6471 						ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
6472 						ev.data.fd = socket.handle;
6473 						import std.stdio; writeln("adding");
6474 						if(epoll_ctl(lcm.epoll_fd, EPOLL_CTL_ADD, socket.handle, &ev) == -1) {
6475 							if(errno == EEXIST) {
6476 								ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
6477 								ev.data.fd = socket.handle;
6478 								if(epoll_ctl(lcm.epoll_fd, EPOLL_CTL_MOD, socket.handle, &ev) == -1)
6479 									throw new Exception("epoll_ctl " ~ to!string(errno));
6480 							} else
6481 								throw new Exception("epoll_ctl " ~ to!string(errno));
6482 						}
6483 						//import std.stdio; writeln("keep alive");
6484 						// writing to this private member is to prevent the GC from closing my precious socket when I'm trying to use it later
6485 						__traits(getMember, socket, "sock") = cast(socket_t) -1;
6486 					} else {
6487 						continue; // hope it times out in a reasonable amount of time...
6488 					}
6489 				}
6490 				+/
6491 			} catch(ConnectionClosedException e) {
6492 				// can just ignore this, it is fairly normal
6493 				socket.close();
6494 			} catch(Throwable e) {
6495 				import std.stdio; stderr.rawWrite(e.toString); stderr.rawWrite("\n");
6496 				socket.close();
6497 			}
6498 		}
6499 	}
6500 
6501 	ListeningConnectionManager lcm;
6502 	CMT dg;
6503 	int myThreadNumber;
6504 }
6505 
6506 version(cgi_use_fiber)
6507 class WorkerThread : Thread {
6508 	this(ListeningConnectionManager lcm, CMT dg, int myThreadNumber) {
6509 		this.lcm = lcm;
6510 		this.dg = dg;
6511 		this.myThreadNumber = myThreadNumber;
6512 		super(&run);
6513 	}
6514 
6515 	version(Windows)
6516 	void run() {
6517 		auto timeout = INFINITE;
6518 		PseudoblockingOverlappedSocket key;
6519 		OVERLAPPED* overlapped;
6520 		DWORD bytes;
6521 		while(!globalStopFlag && GetQueuedCompletionStatus(iocp, &bytes, cast(PULONG_PTR) &key, &overlapped, timeout)) {
6522 			if(key is null)
6523 				continue;
6524 			key.lastAnswer = bytes;
6525 			if(key.fiber) {
6526 				key.fiber.proceed();
6527 			} else {
6528 				// we have a new connection, issue the first receive on it and issue the next accept
6529 
6530 				auto sn = key.accepted;
6531 
6532 				key.accept();
6533 
6534 				cloexec(sn);
6535 				if(lcm.tcp) {
6536 					// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
6537 					// on the socket because we do some buffering internally. I think this helps,
6538 					// certainly does for small requests, and I think it does for larger ones too
6539 					sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
6540 
6541 					sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
6542 				}
6543 
6544 				dg(sn);
6545 			}
6546 		}
6547 		//SleepEx(INFINITE, TRUE);
6548 	}
6549 
6550 	version(linux)
6551 	void run() {
6552 
6553 		import core.sys.linux.epoll;
6554 		epfd = epoll_create1(EPOLL_CLOEXEC);
6555 		if(epfd == -1)
6556 			throw new Exception("epoll_create1 " ~ to!string(errno));
6557 		scope(exit) {
6558 			import core.sys.posix.unistd;
6559 			close(epfd);
6560 		}
6561 
6562 		{
6563 			epoll_event ev;
6564 			ev.events = EPOLLIN;
6565 			ev.data.fd = cancelfd;
6566 			epoll_ctl(epfd, EPOLL_CTL_ADD, cancelfd, &ev);
6567 		}
6568 
6569 		foreach(listener; lcm.listeners) {
6570 			epoll_event ev;
6571 			ev.events = EPOLLIN | EPOLLEXCLUSIVE; // EPOLLEXCLUSIVE is only available on kernels since like 2017 but that's prolly good enough.
6572 			ev.data.fd = listener.handle;
6573 			if(epoll_ctl(epfd, EPOLL_CTL_ADD, listener.handle, &ev) == -1)
6574 				throw new Exception("epoll_ctl " ~ to!string(errno));
6575 		}
6576 
6577 
6578 
6579 		while(!globalStopFlag) {
6580 			Socket sn;
6581 
6582 			epoll_event[64] events;
6583 			auto nfds = epoll_wait(epfd, events.ptr, events.length, -1);
6584 			if(nfds == -1) {
6585 				if(errno == EINTR)
6586 					continue;
6587 				throw new Exception("epoll_wait " ~ to!string(errno));
6588 			}
6589 
6590 			outer: foreach(idx; 0 .. nfds) {
6591 				auto flags = events[idx].events;
6592 
6593 				if(cast(size_t) events[idx].data.ptr == cast(size_t) cancelfd) {
6594 					globalStopFlag = true;
6595 					//import std.stdio; writeln("exit heard");
6596 					break;
6597 				} else {
6598 					foreach(listener; lcm.listeners) {
6599 						if(cast(size_t) events[idx].data.ptr == cast(size_t) listener.handle) {
6600 							//import std.stdio; writeln(myThreadNumber, " woken up ", flags);
6601 							// this try/catch is because it is set to non-blocking mode
6602 							// and Phobos' stupid api throws an exception instead of returning
6603 							// if it would block. Why would it block? because a forked process
6604 							// might have beat us to it, but the wakeup event thundered our herds.
6605 								try
6606 								sn = listener.accept(); // don't need to do the acceptCancelable here since the epoll checks it better
6607 								catch(SocketAcceptException e) { continue outer; }
6608 
6609 							cloexec(sn);
6610 							if(lcm.tcp) {
6611 								// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
6612 								// on the socket because we do some buffering internally. I think this helps,
6613 								// certainly does for small requests, and I think it does for larger ones too
6614 								sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
6615 
6616 								sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
6617 							}
6618 
6619 							dg(sn);
6620 							continue outer;
6621 						} else {
6622 							// writeln(events[idx].data.ptr);
6623 						}
6624 					}
6625 
6626 					if(cast(size_t) events[idx].data.ptr < 1024) {
6627 						throw arsd.core.ArsdException!"this doesn't look like a fiber pointer... "(cast(size_t) events[idx].data.ptr);
6628 					}
6629 					auto fiber = cast(CgiFiber) events[idx].data.ptr;
6630 					fiber.proceed();
6631 				}
6632 			}
6633 		}
6634 	}
6635 
6636 	ListeningConnectionManager lcm;
6637 	CMT dg;
6638 	int myThreadNumber;
6639 }
6640 
6641 
6642 /* Done with network helper */
6643 
6644 /* Helpers for doing temporary files. Used both here and in web.d */
6645 
6646 version(Windows) {
6647 	import core.sys.windows.windows;
6648 	extern(Windows) DWORD GetTempPathW(DWORD, LPWSTR);
6649 	alias GetTempPathW GetTempPath;
6650 }
6651 
6652 version(Posix) {
6653 	static import linux = core.sys.posix.unistd;
6654 }
6655 
6656 string getTempDirectory() {
6657 	string path;
6658 	version(Windows) {
6659 		wchar[1024] buffer;
6660 		auto len = GetTempPath(1024, buffer.ptr);
6661 		if(len == 0)
6662 			throw new Exception("couldn't find a temporary path");
6663 
6664 		auto b = buffer[0 .. len];
6665 
6666 		path = to!string(b);
6667 	} else
6668 		path = "/tmp/";
6669 
6670 	return path;
6671 }
6672 
6673 
6674 // I like std.date. These functions help keep my old code and data working with phobos changing.
6675 
6676 long sysTimeToDTime(in SysTime sysTime) {
6677     return convert!("hnsecs", "msecs")(sysTime.stdTime - 621355968000000000L);
6678 }
6679 
6680 long dateTimeToDTime(in DateTime dt) {
6681 	return sysTimeToDTime(cast(SysTime) dt);
6682 }
6683 
6684 long getUtcTime() { // renamed primarily to avoid conflict with std.date itself
6685 	return sysTimeToDTime(Clock.currTime(UTC()));
6686 }
6687 
6688 // NOTE: new SimpleTimeZone(minutes); can perhaps work with the getTimezoneOffset() JS trick
6689 SysTime dTimeToSysTime(long dTime, immutable TimeZone tz = null) {
6690 	immutable hnsecs = convert!("msecs", "hnsecs")(dTime) + 621355968000000000L;
6691 	return SysTime(hnsecs, tz);
6692 }
6693 
6694 
6695 
6696 // this is a helper to read HTTP transfer-encoding: chunked responses
6697 immutable(ubyte[]) dechunk(BufferedInputRange ir) {
6698 	immutable(ubyte)[] ret;
6699 
6700 	another_chunk:
6701 	// If here, we are at the beginning of a chunk.
6702 	auto a = ir.front();
6703 	int chunkSize;
6704 	int loc = locationOf(a, "\r\n");
6705 	while(loc == -1) {
6706 		ir.popFront();
6707 		a = ir.front();
6708 		loc = locationOf(a, "\r\n");
6709 	}
6710 
6711 	string hex;
6712 	hex = "";
6713 	for(int i = 0; i < loc; i++) {
6714 		char c = a[i];
6715 		if(c >= 'A' && c <= 'Z')
6716 			c += 0x20;
6717 		if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
6718 			hex ~= c;
6719 		} else {
6720 			break;
6721 		}
6722 	}
6723 
6724 	assert(hex.length);
6725 
6726 	int power = 1;
6727 	int size = 0;
6728 	foreach(cc1; retro(hex)) {
6729 		dchar cc = cc1;
6730 		if(cc >= 'a' && cc <= 'z')
6731 			cc -= 0x20;
6732 		int val = 0;
6733 		if(cc >= '0' && cc <= '9')
6734 			val = cc - '0';
6735 		else
6736 			val = cc - 'A' + 10;
6737 
6738 		size += power * val;
6739 		power *= 16;
6740 	}
6741 
6742 	chunkSize = size;
6743 	assert(size >= 0);
6744 
6745 	if(loc + 2 > a.length) {
6746 		ir.popFront(0, a.length + loc + 2);
6747 		a = ir.front();
6748 	}
6749 
6750 	a = ir.consume(loc + 2);
6751 
6752 	if(chunkSize == 0) { // we're done with the response
6753 		// if we got here, will change must be true....
6754 		more_footers:
6755 		loc = locationOf(a, "\r\n");
6756 		if(loc == -1) {
6757 			ir.popFront();
6758 			a = ir.front;
6759 			goto more_footers;
6760 		} else {
6761 			assert(loc == 0);
6762 			ir.consume(loc + 2);
6763 			goto finish;
6764 		}
6765 	} else {
6766 		// if we got here, will change must be true....
6767 		if(a.length < chunkSize + 2) {
6768 			ir.popFront(0, chunkSize + 2);
6769 			a = ir.front();
6770 		}
6771 
6772 		ret ~= (a[0..chunkSize]);
6773 
6774 		if(!(a.length > chunkSize + 2)) {
6775 			ir.popFront(0, chunkSize + 2);
6776 			a = ir.front();
6777 		}
6778 		assert(a[chunkSize] == 13);
6779 		assert(a[chunkSize+1] == 10);
6780 		a = ir.consume(chunkSize + 2);
6781 		chunkSize = 0;
6782 		goto another_chunk;
6783 	}
6784 
6785 	finish:
6786 	return ret;
6787 }
6788 
6789 // I want to be able to get data from multiple sources the same way...
6790 interface ByChunkRange {
6791 	bool empty();
6792 	void popFront();
6793 	const(ubyte)[] front();
6794 }
6795 
6796 ByChunkRange byChunk(const(ubyte)[] data) {
6797 	return new class ByChunkRange {
6798 		override bool empty() {
6799 			return !data.length;
6800 		}
6801 
6802 		override void popFront() {
6803 			if(data.length > 4096)
6804 				data = data[4096 .. $];
6805 			else
6806 				data = null;
6807 		}
6808 
6809 		override const(ubyte)[] front() {
6810 			return data[0 .. $ > 4096 ? 4096 : $];
6811 		}
6812 	};
6813 }
6814 
6815 ByChunkRange byChunk(BufferedInputRange ir, size_t atMost) {
6816 	const(ubyte)[] f;
6817 
6818 	f = ir.front;
6819 	if(f.length > atMost)
6820 		f = f[0 .. atMost];
6821 
6822 	return new class ByChunkRange {
6823 		override bool empty() {
6824 			return atMost == 0;
6825 		}
6826 
6827 		override const(ubyte)[] front() {
6828 			return f;
6829 		}
6830 
6831 		override void popFront() {
6832 			ir.consume(f.length);
6833 			atMost -= f.length;
6834 			auto a = ir.front();
6835 
6836 			if(a.length <= atMost) {
6837 				f = a;
6838 				atMost -= a.length;
6839 				a = ir.consume(a.length);
6840 				if(atMost != 0)
6841 					ir.popFront();
6842 				if(f.length == 0) {
6843 					f = ir.front();
6844 				}
6845 			} else {
6846 				// we actually have *more* here than we need....
6847 				f = a[0..atMost];
6848 				atMost = 0;
6849 				ir.consume(atMost);
6850 			}
6851 		}
6852 	};
6853 }
6854 
6855 version(cgi_with_websocket) {
6856 	// http://tools.ietf.org/html/rfc6455
6857 
6858 	/++
6859 		WEBSOCKET SUPPORT:
6860 
6861 		Full example:
6862 		---
6863 			import arsd.cgi;
6864 
6865 			void websocketEcho(Cgi cgi) {
6866 				if(cgi.websocketRequested()) {
6867 					if(cgi.origin != "http://arsdnet.net")
6868 						throw new Exception("bad origin");
6869 					auto websocket = cgi.acceptWebsocket();
6870 
6871 					websocket.send("hello");
6872 					websocket.send(" world!");
6873 
6874 					auto msg = websocket.recv();
6875 					while(msg.opcode != WebSocketOpcode.close) {
6876 						if(msg.opcode == WebSocketOpcode.text) {
6877 							websocket.send(msg.textData);
6878 						} else if(msg.opcode == WebSocketOpcode.binary) {
6879 							websocket.send(msg.data);
6880 						}
6881 
6882 						msg = websocket.recv();
6883 					}
6884 
6885 					websocket.close();
6886 				} else {
6887 					cgi.write("You are loading the websocket endpoint in a browser instead of a websocket client. Use a websocket client on this url instead.\n", true);
6888 				}
6889 			}
6890 
6891 			mixin GenericMain!websocketEcho;
6892 		---
6893 	+/
6894 
6895 	class WebSocket {
6896 		Cgi cgi;
6897 
6898 		private bool isClient = false;
6899 
6900 		private this(Cgi cgi) {
6901 			this.cgi = cgi;
6902 
6903 			Socket socket = cgi.idlol.source;
6904 			socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"minutes"(5));
6905 		}
6906 
6907 		// returns true if data available, false if it timed out
6908 		bool recvAvailable(Duration timeout = dur!"msecs"(0)) {
6909 			if(!waitForNextMessageWouldBlock())
6910 				return true;
6911 			if(isDataPending(timeout))
6912 				return true; // this is kinda a lie.
6913 
6914 			return false;
6915 		}
6916 
6917 		public bool lowLevelReceive() {
6918 			auto bfr = cgi.idlol;
6919 			top:
6920 			auto got = bfr.front;
6921 			if(got.length) {
6922 				if(receiveBuffer.length < receiveBufferUsedLength + got.length)
6923 					receiveBuffer.length += receiveBufferUsedLength + got.length;
6924 
6925 				receiveBuffer[receiveBufferUsedLength .. receiveBufferUsedLength + got.length] = got[];
6926 				receiveBufferUsedLength += got.length;
6927 				bfr.consume(got.length);
6928 
6929 				return true;
6930 			}
6931 
6932 			if(bfr.sourceClosed) {
6933 				return false;
6934 			}
6935 
6936 			bfr.popFront(0);
6937 			if(bfr.sourceClosed) {
6938 				return false;
6939 			}
6940 			goto top;
6941 		}
6942 
6943 
6944 		bool isDataPending(Duration timeout = 0.seconds) {
6945 			Socket socket = cgi.idlol.source;
6946 
6947 			auto check = new SocketSet();
6948 			check.add(socket);
6949 
6950 			auto got = Socket.select(check, null, null, timeout);
6951 			if(got > 0)
6952 				return true;
6953 			return false;
6954 		}
6955 
6956 		// note: this blocks
6957 		WebSocketFrame recv() {
6958 			return waitForNextMessage();
6959 		}
6960 
6961 
6962 
6963 
6964 		private void llclose() {
6965 			cgi.close();
6966 		}
6967 
6968 		private void llsend(ubyte[] data) {
6969 			cgi.write(data);
6970 			cgi.flush();
6971 		}
6972 
6973 		void unregisterActiveSocket(WebSocket) {}
6974 
6975 		/* copy/paste section { */
6976 
6977 		private int readyState_;
6978 		private ubyte[] receiveBuffer;
6979 		private size_t receiveBufferUsedLength;
6980 
6981 		private Config config;
6982 
6983 		enum CONNECTING = 0; /// Socket has been created. The connection is not yet open.
6984 		enum OPEN = 1; /// The connection is open and ready to communicate.
6985 		enum CLOSING = 2; /// The connection is in the process of closing.
6986 		enum CLOSED = 3; /// The connection is closed or couldn't be opened.
6987 
6988 		/++
6989 
6990 		+/
6991 		/// Group: foundational
6992 		static struct Config {
6993 			/++
6994 				These control the size of the receive buffer.
6995 
6996 				It starts at the initial size, will temporarily
6997 				balloon up to the maximum size, and will reuse
6998 				a buffer up to the likely size.
6999 
7000 				Anything larger than the maximum size will cause
7001 				the connection to be aborted and an exception thrown.
7002 				This is to protect you against a peer trying to
7003 				exhaust your memory, while keeping the user-level
7004 				processing simple.
7005 			+/
7006 			size_t initialReceiveBufferSize = 4096;
7007 			size_t likelyReceiveBufferSize = 4096; /// ditto
7008 			size_t maximumReceiveBufferSize = 10 * 1024 * 1024; /// ditto
7009 
7010 			/++
7011 				Maximum combined size of a message.
7012 			+/
7013 			size_t maximumMessageSize = 10 * 1024 * 1024;
7014 
7015 			string[string] cookies; /// Cookies to send with the initial request. cookies[name] = value;
7016 			string origin; /// Origin URL to send with the handshake, if desired.
7017 			string protocol; /// the protocol header, if desired.
7018 
7019 			/++
7020 				Additional headers to put in the HTTP request. These should be formatted `Name: value`, like for example:
7021 
7022 				---
7023 				Config config;
7024 				config.additionalHeaders ~= "Authorization: Bearer your_auth_token_here";
7025 				---
7026 
7027 				History:
7028 					Added February 19, 2021 (included in dub version 9.2)
7029 			+/
7030 			string[] additionalHeaders;
7031 
7032 			/++
7033 				Amount of time (in msecs) of idleness after which to send an automatic ping
7034 
7035 				Please note how this interacts with [timeoutFromInactivity] - a ping counts as activity that
7036 				keeps the socket alive.
7037 			+/
7038 			int pingFrequency = 5000;
7039 
7040 			/++
7041 				Amount of time to disconnect when there's no activity. Note that automatic pings will keep the connection alive; this timeout only occurs if there's absolutely nothing, including no responses to websocket ping frames. Since the default [pingFrequency] is only seconds, this one minute should never elapse unless the connection is actually dead.
7042 
7043 				The one thing to keep in mind is if your program is busy and doesn't check input, it might consider this a time out since there's no activity. The reason is that your program was busy rather than a connection failure, but it doesn't care. You should avoid long processing periods anyway though!
7044 
7045 				History:
7046 					Added March 31, 2021 (included in dub version 9.4)
7047 			+/
7048 			Duration timeoutFromInactivity = 1.minutes;
7049 
7050 			/++
7051 				For https connections, if this is `true`, it will fail to connect if the TLS certificate can not be
7052 				verified. Setting this to `false` will skip this check and allow the connection to continue anyway.
7053 
7054 				History:
7055 					Added April 5, 2022 (dub v10.8)
7056 
7057 					Prior to this, it always used the global (but undocumented) `defaultVerifyPeer` setting, and sometimes
7058 					even if it was true, it would skip the verification. Now, it always respects this local setting.
7059 			+/
7060 			bool verifyPeer = true;
7061 		}
7062 
7063 		/++
7064 			Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED].
7065 		+/
7066 		int readyState() {
7067 			return readyState_;
7068 		}
7069 
7070 		/++
7071 			Closes the connection, sending a graceful teardown message to the other side.
7072 
7073 			Code 1000 is the normal closure code.
7074 
7075 			History:
7076 				The default `code` was changed to 1000 on January 9, 2023. Previously it was 0,
7077 				but also ignored anyway.
7078 		+/
7079 		/// Group: foundational
7080 		void close(int code = 1000, string reason = null)
7081 			//in (reason.length < 123)
7082 			in { assert(reason.length < 123); } do
7083 		{
7084 			if(readyState_ != OPEN)
7085 				return; // it cool, we done
7086 			WebSocketFrame wss;
7087 			wss.fin = true;
7088 			wss.masked = this.isClient;
7089 			wss.opcode = WebSocketOpcode.close;
7090 			wss.data = [ubyte((code >> 8) & 0xff), ubyte(code & 0xff)] ~ cast(ubyte[]) reason.dup;
7091 			wss.send(&llsend);
7092 
7093 			readyState_ = CLOSING;
7094 
7095 			closeCalled = true;
7096 
7097 			llclose();
7098 		}
7099 
7100 		private bool closeCalled;
7101 
7102 		/++
7103 			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.
7104 		+/
7105 		/// Group: foundational
7106 		void ping(in ubyte[] data = null) {
7107 			WebSocketFrame wss;
7108 			wss.fin = true;
7109 			wss.masked = this.isClient;
7110 			wss.opcode = WebSocketOpcode.ping;
7111 			if(data !is null) wss.data = data.dup;
7112 			wss.send(&llsend);
7113 		}
7114 
7115 		/++
7116 			Sends a pong message to the server. This is normally done automatically in response to pings.
7117 		+/
7118 		/// Group: foundational
7119 		void pong(in ubyte[] data = null) {
7120 			WebSocketFrame wss;
7121 			wss.fin = true;
7122 			wss.masked = this.isClient;
7123 			wss.opcode = WebSocketOpcode.pong;
7124 			if(data !is null) wss.data = data.dup;
7125 			wss.send(&llsend);
7126 		}
7127 
7128 		/++
7129 			Sends a text message through the websocket.
7130 		+/
7131 		/// Group: foundational
7132 		void send(in char[] textData) {
7133 			WebSocketFrame wss;
7134 			wss.fin = true;
7135 			wss.masked = this.isClient;
7136 			wss.opcode = WebSocketOpcode.text;
7137 			wss.data = cast(ubyte[]) textData.dup;
7138 			wss.send(&llsend);
7139 		}
7140 
7141 		/++
7142 			Sends a binary message through the websocket.
7143 		+/
7144 		/// Group: foundational
7145 		void send(in ubyte[] binaryData) {
7146 			WebSocketFrame wss;
7147 			wss.masked = this.isClient;
7148 			wss.fin = true;
7149 			wss.opcode = WebSocketOpcode.binary;
7150 			wss.data = cast(ubyte[]) binaryData.dup;
7151 			wss.send(&llsend);
7152 		}
7153 
7154 		/++
7155 			Waits for and returns the next complete message on the socket.
7156 
7157 			Note that the onmessage function is still called, right before
7158 			this returns.
7159 		+/
7160 		/// Group: blocking_api
7161 		public WebSocketFrame waitForNextMessage() {
7162 			do {
7163 				auto m = processOnce();
7164 				if(m.populated)
7165 					return m;
7166 			} while(lowLevelReceive());
7167 
7168 			throw new ConnectionClosedException("Websocket receive timed out");
7169 			//return WebSocketFrame.init; // FIXME? maybe.
7170 		}
7171 
7172 		/++
7173 			Tells if [waitForNextMessage] would block.
7174 		+/
7175 		/// Group: blocking_api
7176 		public bool waitForNextMessageWouldBlock() {
7177 			checkAgain:
7178 			if(isMessageBuffered())
7179 				return false;
7180 			if(!isDataPending())
7181 				return true;
7182 
7183 			while(isDataPending()) {
7184 				if(lowLevelReceive() == false)
7185 					throw new ConnectionClosedException("Connection closed in middle of message");
7186 			}
7187 
7188 			goto checkAgain;
7189 		}
7190 
7191 		/++
7192 			Is there a message in the buffer already?
7193 			If `true`, [waitForNextMessage] is guaranteed to return immediately.
7194 			If `false`, check [isDataPending] as the next step.
7195 		+/
7196 		/// Group: blocking_api
7197 		public bool isMessageBuffered() {
7198 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
7199 			auto s = d;
7200 			if(d.length) {
7201 				auto orig = d;
7202 				auto m = WebSocketFrame.read(d);
7203 				// that's how it indicates that it needs more data
7204 				if(d !is orig)
7205 					return true;
7206 			}
7207 
7208 			return false;
7209 		}
7210 
7211 		private ubyte continuingType;
7212 		private ubyte[] continuingData;
7213 		//private size_t continuingDataLength;
7214 
7215 		private WebSocketFrame processOnce() {
7216 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
7217 			auto s = d;
7218 			// FIXME: handle continuation frames more efficiently. it should really just reuse the receive buffer.
7219 			WebSocketFrame m;
7220 			if(d.length) {
7221 				auto orig = d;
7222 				m = WebSocketFrame.read(d);
7223 				// that's how it indicates that it needs more data
7224 				if(d is orig)
7225 					return WebSocketFrame.init;
7226 				m.unmaskInPlace();
7227 				switch(m.opcode) {
7228 					case WebSocketOpcode.continuation:
7229 						if(continuingData.length + m.data.length > config.maximumMessageSize)
7230 							throw new Exception("message size exceeded");
7231 
7232 						continuingData ~= m.data;
7233 						if(m.fin) {
7234 							if(ontextmessage)
7235 								ontextmessage(cast(char[]) continuingData);
7236 							if(onbinarymessage)
7237 								onbinarymessage(continuingData);
7238 
7239 							continuingData = null;
7240 						}
7241 					break;
7242 					case WebSocketOpcode.text:
7243 						if(m.fin) {
7244 							if(ontextmessage)
7245 								ontextmessage(m.textData);
7246 						} else {
7247 							continuingType = m.opcode;
7248 							//continuingDataLength = 0;
7249 							continuingData = null;
7250 							continuingData ~= m.data;
7251 						}
7252 					break;
7253 					case WebSocketOpcode.binary:
7254 						if(m.fin) {
7255 							if(onbinarymessage)
7256 								onbinarymessage(m.data);
7257 						} else {
7258 							continuingType = m.opcode;
7259 							//continuingDataLength = 0;
7260 							continuingData = null;
7261 							continuingData ~= m.data;
7262 						}
7263 					break;
7264 					case WebSocketOpcode.close:
7265 
7266 						//import std.stdio; writeln("closed ", cast(string) m.data);
7267 
7268 						ushort code = CloseEvent.StandardCloseCodes.noStatusCodePresent;
7269 						const(char)[] reason;
7270 
7271 						if(m.data.length >= 2) {
7272 							code = (m.data[0] << 8) | m.data[1];
7273 							reason = (cast(char[]) m.data[2 .. $]);
7274 						}
7275 
7276 						if(onclose)
7277 							onclose(CloseEvent(code, reason, true));
7278 
7279 						// if we receive one and haven't sent one back we're supposed to echo it back and close.
7280 						if(!closeCalled)
7281 							close(code, reason.idup);
7282 
7283 						readyState_ = CLOSED;
7284 
7285 						unregisterActiveSocket(this);
7286 					break;
7287 					case WebSocketOpcode.ping:
7288 						// import std.stdio; writeln("ping received ", m.data);
7289 						pong(m.data);
7290 					break;
7291 					case WebSocketOpcode.pong:
7292 						// import std.stdio; writeln("pong received ", m.data);
7293 						// just really references it is still alive, nbd.
7294 					break;
7295 					default: // ignore though i could and perhaps should throw too
7296 				}
7297 			}
7298 
7299 			if(d.length) {
7300 				m.data = m.data.dup();
7301 			}
7302 
7303 			import core.stdc.string;
7304 			memmove(receiveBuffer.ptr, d.ptr, d.length);
7305 			receiveBufferUsedLength = d.length;
7306 
7307 			return m;
7308 		}
7309 
7310 		private void autoprocess() {
7311 			// FIXME
7312 			do {
7313 				processOnce();
7314 			} while(lowLevelReceive());
7315 		}
7316 
7317 		/++
7318 			Arguments for the close event. The `code` and `reason` are provided from the close message on the websocket, if they are present. The spec says code 1000 indicates a normal, default reason close, but reserves the code range from 3000-5000 for future definition; the 3000s can be registered with IANA and the 4000's are application private use. The `reason` should be user readable, but not displayed to the end user. `wasClean` is true if the server actually sent a close event, false if it just disconnected.
7319 
7320 			$(PITFALL
7321 				The `reason` argument references a temporary buffer and there's no guarantee it will remain valid once your callback returns. It may be freed and will very likely be overwritten. If you want to keep the reason beyond the callback, make sure you `.idup` it.
7322 			)
7323 
7324 			History:
7325 				Added March 19, 2023 (dub v11.0).
7326 		+/
7327 		static struct CloseEvent {
7328 			ushort code;
7329 			const(char)[] reason;
7330 			bool wasClean;
7331 
7332 			string extendedErrorInformationUnstable;
7333 
7334 			/++
7335 				See https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 for details.
7336 			+/
7337 			enum StandardCloseCodes {
7338 				purposeFulfilled = 1000,
7339 				goingAway = 1001,
7340 				protocolError = 1002,
7341 				unacceptableData = 1003, // e.g. got text message when you can only handle binary
7342 				Reserved = 1004,
7343 				noStatusCodePresent = 1005, // not set by endpoint.
7344 				abnormalClosure = 1006, // not set by endpoint. closed without a Close control. FIXME: maybe keep a copy of errno around for these
7345 				inconsistentData = 1007, // e.g. utf8 validation failed
7346 				genericPolicyViolation = 1008,
7347 				messageTooBig = 1009,
7348 				clientRequiredExtensionMissing = 1010, // only the client should send this
7349 				unnexpectedCondition = 1011,
7350 				unverifiedCertificate = 1015, // not set by client
7351 			}
7352 		}
7353 
7354 		/++
7355 			The `CloseEvent` you get references a temporary buffer that may be overwritten after your handler returns. If you want to keep it or the `event.reason` member, remember to `.idup` it.
7356 
7357 			History:
7358 				The `CloseEvent` was changed to a [arsd.core.FlexibleDelegate] on March 19, 2023 (dub v11.0). Before that, `onclose` was a public member of type `void delegate()`. This change means setters still work with or without the [CloseEvent] argument.
7359 
7360 				Your onclose method is now also called on abnormal terminations. Check the `wasClean` member of the `CloseEvent` to know if it came from a close frame or other cause.
7361 		+/
7362 		arsd.core.FlexibleDelegate!(void delegate(CloseEvent event)) onclose;
7363 		void delegate() onerror; ///
7364 		void delegate(in char[]) ontextmessage; ///
7365 		void delegate(in ubyte[]) onbinarymessage; ///
7366 		void delegate() onopen; ///
7367 
7368 		/++
7369 
7370 		+/
7371 		/// Group: browser_api
7372 		void onmessage(void delegate(in char[]) dg) {
7373 			ontextmessage = dg;
7374 		}
7375 
7376 		/// ditto
7377 		void onmessage(void delegate(in ubyte[]) dg) {
7378 			onbinarymessage = dg;
7379 		}
7380 
7381 	/* } end copy/paste */
7382 
7383 
7384 
7385 	}
7386 
7387 	/++
7388 		Returns true if the request headers are asking for a websocket upgrade.
7389 
7390 		If this returns true, and you want to accept it, call [acceptWebsocket].
7391 	+/
7392 	bool websocketRequested(Cgi cgi) {
7393 		return
7394 			"sec-websocket-key" in cgi.requestHeaders
7395 			&&
7396 			"connection" in cgi.requestHeaders &&
7397 				cgi.requestHeaders["connection"].asLowerCase().canFind("upgrade")
7398 			&&
7399 			"upgrade" in cgi.requestHeaders &&
7400 				cgi.requestHeaders["upgrade"].asLowerCase().equal("websocket")
7401 			;
7402 	}
7403 
7404 	/++
7405 		If [websocketRequested], you can call this to accept it and upgrade the connection. It returns the new [WebSocket] object you use for future communication on this connection; the `cgi` object should no longer be used.
7406 	+/
7407 	WebSocket acceptWebsocket(Cgi cgi) {
7408 		assert(!cgi.closed);
7409 		assert(!cgi.outputtedResponseData);
7410 		cgi.setResponseStatus("101 Switching Protocols");
7411 		cgi.header("Upgrade: WebSocket");
7412 		cgi.header("Connection: upgrade");
7413 
7414 		string key = cgi.requestHeaders["sec-websocket-key"];
7415 		key ~= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // the defined guid from the websocket spec
7416 
7417 		import std.digest.sha;
7418 		auto hash = sha1Of(key);
7419 		auto accept = Base64.encode(hash);
7420 
7421 		cgi.header(("Sec-WebSocket-Accept: " ~ accept).idup);
7422 
7423 		cgi.websocketMode = true;
7424 		cgi.write("");
7425 
7426 		cgi.flush();
7427 
7428 		auto ws = new WebSocket(cgi);
7429 		ws.readyState_ = WebSocket.OPEN;
7430 		return ws;
7431 	}
7432 
7433 	// FIXME get websocket to work on other modes, not just embedded_httpd
7434 
7435 	/* copy/paste in http2.d { */
7436 	enum WebSocketOpcode : ubyte {
7437 		continuation = 0,
7438 		text = 1,
7439 		binary = 2,
7440 		// 3, 4, 5, 6, 7 RESERVED
7441 		close = 8,
7442 		ping = 9,
7443 		pong = 10,
7444 		// 11,12,13,14,15 RESERVED
7445 	}
7446 
7447 	public struct WebSocketFrame {
7448 		private bool populated;
7449 		bool fin;
7450 		bool rsv1;
7451 		bool rsv2;
7452 		bool rsv3;
7453 		WebSocketOpcode opcode; // 4 bits
7454 		bool masked;
7455 		ubyte lengthIndicator; // don't set this when building one to send
7456 		ulong realLength; // don't use when sending
7457 		ubyte[4] maskingKey; // don't set this when sending
7458 		ubyte[] data;
7459 
7460 		static WebSocketFrame simpleMessage(WebSocketOpcode opcode, void[] data) {
7461 			WebSocketFrame msg;
7462 			msg.fin = true;
7463 			msg.opcode = opcode;
7464 			msg.data = cast(ubyte[]) data.dup;
7465 
7466 			return msg;
7467 		}
7468 
7469 		private void send(scope void delegate(ubyte[]) llsend) {
7470 			ubyte[64] headerScratch;
7471 			int headerScratchPos = 0;
7472 
7473 			realLength = data.length;
7474 
7475 			{
7476 				ubyte b1;
7477 				b1 |= cast(ubyte) opcode;
7478 				b1 |= rsv3 ? (1 << 4) : 0;
7479 				b1 |= rsv2 ? (1 << 5) : 0;
7480 				b1 |= rsv1 ? (1 << 6) : 0;
7481 				b1 |= fin  ? (1 << 7) : 0;
7482 
7483 				headerScratch[0] = b1;
7484 				headerScratchPos++;
7485 			}
7486 
7487 			{
7488 				headerScratchPos++; // we'll set header[1] at the end of this
7489 				auto rlc = realLength;
7490 				ubyte b2;
7491 				b2 |= masked ? (1 << 7) : 0;
7492 
7493 				assert(headerScratchPos == 2);
7494 
7495 				if(realLength > 65535) {
7496 					// use 64 bit length
7497 					b2 |= 0x7f;
7498 
7499 					// FIXME: double check endinaness
7500 					foreach(i; 0 .. 8) {
7501 						headerScratch[2 + 7 - i] = rlc & 0x0ff;
7502 						rlc >>>= 8;
7503 					}
7504 
7505 					headerScratchPos += 8;
7506 				} else if(realLength > 125) {
7507 					// use 16 bit length
7508 					b2 |= 0x7e;
7509 
7510 					// FIXME: double check endinaness
7511 					foreach(i; 0 .. 2) {
7512 						headerScratch[2 + 1 - i] = rlc & 0x0ff;
7513 						rlc >>>= 8;
7514 					}
7515 
7516 					headerScratchPos += 2;
7517 				} else {
7518 					// use 7 bit length
7519 					b2 |= realLength & 0b_0111_1111;
7520 				}
7521 
7522 				headerScratch[1] = b2;
7523 			}
7524 
7525 			//assert(!masked, "masking key not properly implemented");
7526 			if(masked) {
7527 				// FIXME: randomize this
7528 				headerScratch[headerScratchPos .. headerScratchPos + 4] = maskingKey[];
7529 				headerScratchPos += 4;
7530 
7531 				// we'll just mask it in place...
7532 				int keyIdx = 0;
7533 				foreach(i; 0 .. data.length) {
7534 					data[i] = data[i] ^ maskingKey[keyIdx];
7535 					if(keyIdx == 3)
7536 						keyIdx = 0;
7537 					else
7538 						keyIdx++;
7539 				}
7540 			}
7541 
7542 			//writeln("SENDING ", headerScratch[0 .. headerScratchPos], data);
7543 			llsend(headerScratch[0 .. headerScratchPos]);
7544 			llsend(data);
7545 		}
7546 
7547 		static WebSocketFrame read(ref ubyte[] d) {
7548 			WebSocketFrame msg;
7549 
7550 			auto orig = d;
7551 
7552 			WebSocketFrame needsMoreData() {
7553 				d = orig;
7554 				return WebSocketFrame.init;
7555 			}
7556 
7557 			if(d.length < 2)
7558 				return needsMoreData();
7559 
7560 			ubyte b = d[0];
7561 
7562 			msg.populated = true;
7563 
7564 			msg.opcode = cast(WebSocketOpcode) (b & 0x0f);
7565 			b >>= 4;
7566 			msg.rsv3 = b & 0x01;
7567 			b >>= 1;
7568 			msg.rsv2 = b & 0x01;
7569 			b >>= 1;
7570 			msg.rsv1 = b & 0x01;
7571 			b >>= 1;
7572 			msg.fin = b & 0x01;
7573 
7574 			b = d[1];
7575 			msg.masked = (b & 0b1000_0000) ? true : false;
7576 			msg.lengthIndicator = b & 0b0111_1111;
7577 
7578 			d = d[2 .. $];
7579 
7580 			if(msg.lengthIndicator == 0x7e) {
7581 				// 16 bit length
7582 				msg.realLength = 0;
7583 
7584 				if(d.length < 2) return needsMoreData();
7585 
7586 				foreach(i; 0 .. 2) {
7587 					msg.realLength |= d[0] << ((1-i) * 8);
7588 					d = d[1 .. $];
7589 				}
7590 			} else if(msg.lengthIndicator == 0x7f) {
7591 				// 64 bit length
7592 				msg.realLength = 0;
7593 
7594 				if(d.length < 8) return needsMoreData();
7595 
7596 				foreach(i; 0 .. 8) {
7597 					msg.realLength |= ulong(d[0]) << ((7-i) * 8);
7598 					d = d[1 .. $];
7599 				}
7600 			} else {
7601 				// 7 bit length
7602 				msg.realLength = msg.lengthIndicator;
7603 			}
7604 
7605 			if(msg.masked) {
7606 
7607 				if(d.length < 4) return needsMoreData();
7608 
7609 				msg.maskingKey = d[0 .. 4];
7610 				d = d[4 .. $];
7611 			}
7612 
7613 			if(msg.realLength > d.length) {
7614 				return needsMoreData();
7615 			}
7616 
7617 			msg.data = d[0 .. cast(size_t) msg.realLength];
7618 			d = d[cast(size_t) msg.realLength .. $];
7619 
7620 			return msg;
7621 		}
7622 
7623 		void unmaskInPlace() {
7624 			if(this.masked) {
7625 				int keyIdx = 0;
7626 				foreach(i; 0 .. this.data.length) {
7627 					this.data[i] = this.data[i] ^ this.maskingKey[keyIdx];
7628 					if(keyIdx == 3)
7629 						keyIdx = 0;
7630 					else
7631 						keyIdx++;
7632 				}
7633 			}
7634 		}
7635 
7636 		char[] textData() {
7637 			return cast(char[]) data;
7638 		}
7639 	}
7640 	/* } */
7641 }
7642 
7643 
7644 version(Windows)
7645 {
7646     version(CRuntime_DigitalMars)
7647     {
7648         extern(C) int setmode(int, int) nothrow @nogc;
7649     }
7650     else version(CRuntime_Microsoft)
7651     {
7652         extern(C) int _setmode(int, int) nothrow @nogc;
7653         alias setmode = _setmode;
7654     }
7655     else static assert(0);
7656 }
7657 
7658 version(Posix) {
7659 	import core.sys.posix.unistd;
7660 	version(CRuntime_Musl) {} else {
7661 		private extern(C) int posix_spawn(pid_t*, const char*, void*, void*, const char**, const char**);
7662 	}
7663 }
7664 
7665 
7666 // FIXME: these aren't quite public yet.
7667 //private:
7668 
7669 // template for laziness
7670 void startAddonServer()(string arg) {
7671 	version(OSX) {
7672 		assert(0, "Not implemented");
7673 	} else version(linux) {
7674 		import core.sys.posix.unistd;
7675 		pid_t pid;
7676 		const(char)*[16] args;
7677 		args[0] = "ARSD_CGI_ADDON_SERVER";
7678 		args[1] = arg.ptr;
7679 		posix_spawn(&pid, "/proc/self/exe",
7680 			null,
7681 			null,
7682 			args.ptr,
7683 			null // env
7684 		);
7685 	} else version(Windows) {
7686 		wchar[2048] filename;
7687 		auto len = GetModuleFileNameW(null, filename.ptr, cast(DWORD) filename.length);
7688 		if(len == 0 || len == filename.length)
7689 			throw new Exception("could not get process name to start helper server");
7690 
7691 		STARTUPINFOW startupInfo;
7692 		startupInfo.cb = cast(DWORD) startupInfo.sizeof;
7693 		PROCESS_INFORMATION processInfo;
7694 
7695 		import std.utf;
7696 
7697 		// I *MIGHT* need to run it as a new job or a service...
7698 		auto ret = CreateProcessW(
7699 			filename.ptr,
7700 			toUTF16z(arg),
7701 			null, // process attributes
7702 			null, // thread attributes
7703 			false, // inherit handles
7704 			0, // creation flags
7705 			null, // environment
7706 			null, // working directory
7707 			&startupInfo,
7708 			&processInfo
7709 		);
7710 
7711 		if(!ret)
7712 			throw new Exception("create process failed");
7713 
7714 		// when done with those, if we set them
7715 		/*
7716 		CloseHandle(hStdInput);
7717 		CloseHandle(hStdOutput);
7718 		CloseHandle(hStdError);
7719 		*/
7720 
7721 	} else static assert(0, "Websocket server not implemented on this system yet (email me, i can prolly do it if you need it)");
7722 }
7723 
7724 // template for laziness
7725 /*
7726 	The websocket server is a single-process, single-thread, event
7727 	I/O thing. It is passed websockets from other CGI processes
7728 	and is then responsible for handling their messages and responses.
7729 	Note that the CGI process is responsible for websocket setup,
7730 	including authentication, etc.
7731 
7732 	It also gets data sent to it by other processes and is responsible
7733 	for distributing that, as necessary.
7734 */
7735 void runWebsocketServer()() {
7736 	assert(0, "not implemented");
7737 }
7738 
7739 void sendToWebsocketServer(WebSocket ws, string group) {
7740 	assert(0, "not implemented");
7741 }
7742 
7743 void sendToWebsocketServer(string content, string group) {
7744 	assert(0, "not implemented");
7745 }
7746 
7747 
7748 void runEventServer()() {
7749 	runAddonServer("/tmp/arsd_cgi_event_server", new EventSourceServerImplementation());
7750 }
7751 
7752 void runTimerServer()() {
7753 	runAddonServer("/tmp/arsd_scheduled_job_server", new ScheduledJobServerImplementation());
7754 }
7755 
7756 version(Posix) {
7757 	alias LocalServerConnectionHandle = int;
7758 	alias CgiConnectionHandle = int;
7759 	alias SocketConnectionHandle = int;
7760 
7761 	enum INVALID_CGI_CONNECTION_HANDLE = -1;
7762 } else version(Windows) {
7763 	alias LocalServerConnectionHandle = HANDLE;
7764 	version(embedded_httpd_threads) {
7765 		alias CgiConnectionHandle = SOCKET;
7766 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
7767 	} else version(fastcgi) {
7768 		alias CgiConnectionHandle = void*; // Doesn't actually work! But I don't want compile to fail pointlessly at this point.
7769 		enum INVALID_CGI_CONNECTION_HANDLE = null;
7770 	} else version(scgi) {
7771 		alias CgiConnectionHandle = SOCKET;
7772 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
7773 	} else { /* version(plain_cgi) */
7774 		alias CgiConnectionHandle = HANDLE;
7775 		enum INVALID_CGI_CONNECTION_HANDLE = null;
7776 	}
7777 	alias SocketConnectionHandle = SOCKET;
7778 }
7779 
7780 version(with_addon_servers_connections)
7781 LocalServerConnectionHandle openLocalServerConnection()(string name, string arg) {
7782 	version(Posix) {
7783 		import core.sys.posix.unistd;
7784 		import core.sys.posix.sys.un;
7785 
7786 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
7787 		if(sock == -1)
7788 			throw new Exception("socket " ~ to!string(errno));
7789 
7790 		scope(failure)
7791 			close(sock);
7792 
7793 		cloexec(sock);
7794 
7795 		// add-on server processes are assumed to be local, and thus will
7796 		// use unix domain sockets. Besides, I want to pass sockets to them,
7797 		// so it basically must be local (except for the session server, but meh).
7798 		sockaddr_un addr;
7799 		addr.sun_family = AF_UNIX;
7800 		version(linux) {
7801 			// on linux, we will use the abstract namespace
7802 			addr.sun_path[0] = 0;
7803 			addr.sun_path[1 .. name.length + 1] = cast(typeof(addr.sun_path[])) name[];
7804 		} else {
7805 			// but otherwise, just use a file cuz we must.
7806 			addr.sun_path[0 .. name.length] = cast(typeof(addr.sun_path[])) name[];
7807 		}
7808 
7809 		bool alreadyTried;
7810 
7811 		try_again:
7812 
7813 		if(connect(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
7814 			if(!alreadyTried && errno == ECONNREFUSED) {
7815 				// try auto-spawning the server, then attempt connection again
7816 				startAddonServer(arg);
7817 				import core.thread;
7818 				Thread.sleep(50.msecs);
7819 				alreadyTried = true;
7820 				goto try_again;
7821 			} else
7822 				throw new Exception("connect " ~ to!string(errno));
7823 		}
7824 
7825 		return sock;
7826 	} else version(Windows) {
7827 		return null; // FIXME
7828 	}
7829 }
7830 
7831 version(with_addon_servers_connections)
7832 void closeLocalServerConnection(LocalServerConnectionHandle handle) {
7833 	version(Posix) {
7834 		import core.sys.posix.unistd;
7835 		close(handle);
7836 	} else version(Windows)
7837 		CloseHandle(handle);
7838 }
7839 
7840 void runSessionServer()() {
7841 	runAddonServer("/tmp/arsd_session_server", new BasicDataServerImplementation());
7842 }
7843 
7844 import core.stdc.errno;
7845 
7846 struct IoOp {
7847 	@disable this();
7848 	@disable this(this);
7849 
7850 	/*
7851 		So we want to be able to eventually handle generic sockets too.
7852 	*/
7853 
7854 	enum Read = 1;
7855 	enum Write = 2;
7856 	enum Accept = 3;
7857 	enum ReadSocketHandle = 4;
7858 
7859 	// Your handler may be called in a different thread than the one that initiated the IO request!
7860 	// It is also possible to have multiple io requests being called simultaneously. Use proper thread safety caution.
7861 	private bool delegate(IoOp*, int) handler; // returns true if you are done and want it to be closed
7862 	private void delegate(IoOp*) closeHandler;
7863 	private void delegate(IoOp*) completeHandler;
7864 	private int internalFd;
7865 	private int operation;
7866 	private int bufferLengthAllocated;
7867 	private int bufferLengthUsed;
7868 	private ubyte[1] internalBuffer; // it can be overallocated!
7869 
7870 	ubyte[] allocatedBuffer() return {
7871 		return internalBuffer.ptr[0 .. bufferLengthAllocated];
7872 	}
7873 
7874 	ubyte[] usedBuffer() return {
7875 		return allocatedBuffer[0 .. bufferLengthUsed];
7876 	}
7877 
7878 	void reset() {
7879 		bufferLengthUsed = 0;
7880 	}
7881 
7882 	int fd() {
7883 		return internalFd;
7884 	}
7885 }
7886 
7887 IoOp* allocateIoOp(int fd, int operation, int bufferSize, bool delegate(IoOp*, int) handler) {
7888 	import core.stdc.stdlib;
7889 
7890 	auto ptr = calloc(IoOp.sizeof + bufferSize, 1);
7891 	if(ptr is null)
7892 		assert(0); // out of memory!
7893 
7894 	auto op = cast(IoOp*) ptr;
7895 
7896 	op.handler = handler;
7897 	op.internalFd = fd;
7898 	op.operation = operation;
7899 	op.bufferLengthAllocated = bufferSize;
7900 	op.bufferLengthUsed = 0;
7901 
7902 	import core.memory;
7903 
7904 	GC.addRoot(ptr);
7905 
7906 	return op;
7907 }
7908 
7909 void freeIoOp(ref IoOp* ptr) {
7910 
7911 	import core.memory;
7912 	GC.removeRoot(ptr);
7913 
7914 	import core.stdc.stdlib;
7915 	free(ptr);
7916 	ptr = null;
7917 }
7918 
7919 version(Posix)
7920 version(with_addon_servers_connections)
7921 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
7922 
7923 	//import std.stdio : writeln; writeln(cast(string) data);
7924 
7925 	import core.sys.posix.unistd;
7926 
7927 	auto ret = write(connection, data.ptr, data.length);
7928 	if(ret != data.length) {
7929 		if(ret == 0 || (ret == -1 && (errno == EPIPE || errno == ETIMEDOUT))) {
7930 			// the file is closed, remove it
7931 			eis.fileClosed(connection);
7932 		} else
7933 			throw new Exception("alas " ~ to!string(ret) ~ " " ~ to!string(errno)); // FIXME
7934 	}
7935 }
7936 version(Windows)
7937 version(with_addon_servers_connections)
7938 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
7939 	// FIXME
7940 }
7941 
7942 bool isInvalidHandle(CgiConnectionHandle h) {
7943 	return h == INVALID_CGI_CONNECTION_HANDLE;
7944 }
7945 
7946 /+
7947 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsarecv
7948 https://support.microsoft.com/en-gb/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode
7949 https://stackoverflow.com/questions/18018489/should-i-use-iocps-or-overlapped-wsasend-receive
7950 https://docs.microsoft.com/en-us/windows/desktop/fileio/i-o-completion-ports
7951 https://docs.microsoft.com/en-us/windows/desktop/fileio/createiocompletionport
7952 https://docs.microsoft.com/en-us/windows/desktop/api/mswsock/nf-mswsock-acceptex
7953 https://docs.microsoft.com/en-us/windows/desktop/Sync/waitable-timer-objects
7954 https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-setwaitabletimer
7955 https://docs.microsoft.com/en-us/windows/desktop/Sync/using-a-waitable-timer-with-an-asynchronous-procedure-call
7956 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsagetoverlappedresult
7957 
7958 +/
7959 
7960 /++
7961 	You can customize your server by subclassing the appropriate server. Then, register your
7962 	subclass at compile time with the [registerEventIoServer] template, or implement your own
7963 	main function and call it yourself.
7964 
7965 	$(TIP If you make your subclass a `final class`, there is a slight performance improvement.)
7966 +/
7967 version(with_addon_servers_connections)
7968 interface EventIoServer {
7969 	bool handleLocalConnectionData(IoOp* op, int receivedFd);
7970 	void handleLocalConnectionClose(IoOp* op);
7971 	void handleLocalConnectionComplete(IoOp* op);
7972 	void wait_timeout();
7973 	void fileClosed(int fd);
7974 
7975 	void epoll_fd(int fd);
7976 }
7977 
7978 // the sink should buffer it
7979 private void serialize(T)(scope void delegate(scope ubyte[]) sink, T t) {
7980 	static if(is(T == struct)) {
7981 		foreach(member; __traits(allMembers, T))
7982 			serialize(sink, __traits(getMember, t, member));
7983 	} else static if(is(T : int)) {
7984 		// no need to think of endianness just because this is only used
7985 		// for local, same-machine stuff anyway. thanks private lol
7986 		sink((cast(ubyte*) &t)[0 .. t.sizeof]);
7987 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
7988 		// these are common enough to optimize
7989 		int len = cast(int) t.length; // want length consistent size tho, in case 32 bit program sends to 64 bit server, etc.
7990 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7991 		sink(cast(ubyte[]) t[]);
7992 	} else static if(is(T : A[], A)) {
7993 		// generic array is less optimal but still prolly ok
7994 		int len = cast(int) t.length;
7995 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7996 		foreach(item; t)
7997 			serialize(sink, item);
7998 	} else static assert(0, T.stringof);
7999 }
8000 
8001 // all may be stack buffers, so use cautio
8002 private void deserialize(T)(scope ubyte[] delegate(int sz) get, scope void delegate(T) dg) {
8003 	static if(is(T == struct)) {
8004 		T t;
8005 		foreach(member; __traits(allMembers, T))
8006 			deserialize!(typeof(__traits(getMember, T, member)))(get, (mbr) { __traits(getMember, t, member) = mbr; });
8007 		dg(t);
8008 	} else static if(is(T : int)) {
8009 		// no need to think of endianness just because this is only used
8010 		// for local, same-machine stuff anyway. thanks private lol
8011 		T t;
8012 		auto data = get(t.sizeof);
8013 		t = (cast(T[]) data)[0];
8014 		dg(t);
8015 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
8016 		// these are common enough to optimize
8017 		int len;
8018 		auto data = get(len.sizeof);
8019 		len = (cast(int[]) data)[0];
8020 
8021 		/*
8022 		typeof(T[0])[2000] stackBuffer;
8023 		T buffer;
8024 
8025 		if(len < stackBuffer.length)
8026 			buffer = stackBuffer[0 .. len];
8027 		else
8028 			buffer = new T(len);
8029 
8030 		data = get(len * typeof(T[0]).sizeof);
8031 		*/
8032 
8033 		T t = cast(T) get(len * cast(int) typeof(T.init[0]).sizeof);
8034 
8035 		dg(t);
8036 	} else static if(is(T == E[], E)) {
8037 		T t;
8038 		int len;
8039 		auto data = get(len.sizeof);
8040 		len = (cast(int[]) data)[0];
8041 		t.length = len;
8042 		foreach(ref e; t) {
8043 			deserialize!E(get, (ele) { e = ele; });
8044 		}
8045 		dg(t);
8046 	} else static assert(0, T.stringof);
8047 }
8048 
8049 unittest {
8050 	serialize((ubyte[] b) {
8051 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 1); });
8052 	}, 1);
8053 	serialize((ubyte[] b) {
8054 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 56674); });
8055 	}, 56674);
8056 	ubyte[1000] buffer;
8057 	int bufferPoint;
8058 	void add(scope ubyte[] b) {
8059 		buffer[bufferPoint ..  bufferPoint + b.length] = b[];
8060 		bufferPoint += b.length;
8061 	}
8062 	ubyte[] get(int sz) {
8063 		auto b = buffer[bufferPoint .. bufferPoint + sz];
8064 		bufferPoint += sz;
8065 		return b;
8066 	}
8067 	serialize(&add, "test here");
8068 	bufferPoint = 0;
8069 	deserialize!string(&get, (t) { assert(t == "test here"); });
8070 	bufferPoint = 0;
8071 
8072 	struct Foo {
8073 		int a;
8074 		ubyte c;
8075 		string d;
8076 	}
8077 	serialize(&add, Foo(403, 37, "amazing"));
8078 	bufferPoint = 0;
8079 	deserialize!Foo(&get, (t) {
8080 		assert(t.a == 403);
8081 		assert(t.c == 37);
8082 		assert(t.d == "amazing");
8083 	});
8084 	bufferPoint = 0;
8085 }
8086 
8087 /*
8088 	Here's the way the RPC interface works:
8089 
8090 	You define the interface that lists the functions you can call on the remote process.
8091 	The interface may also have static methods for convenience. These forward to a singleton
8092 	instance of an auto-generated class, which actually sends the args over the pipe.
8093 
8094 	An impl class actually implements it. A receiving server deserializes down the pipe and
8095 	calls methods on the class.
8096 
8097 	I went with the interface to get some nice compiler checking and documentation stuff.
8098 
8099 	I could have skipped the interface and just implemented it all from the server class definition
8100 	itself, but then the usage may call the method instead of rpcing it; I just like having the user
8101 	interface and the implementation separate so you aren't tempted to `new impl` to call the methods.
8102 
8103 
8104 	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.
8105 
8106 	Realistically though the bodies would just be
8107 		connection.call(this.mangleof, args...) sooooo.
8108 
8109 	FIXME: overloads aren't supported
8110 */
8111 
8112 /// Base for storing sessions in an array. Exists primarily for internal purposes and you should generally not use this.
8113 interface SessionObject {}
8114 
8115 private immutable void delegate(string[])[string] scheduledJobHandlers;
8116 private immutable void delegate(string[])[string] websocketServers;
8117 
8118 version(with_breaking_cgi_features)
8119 mixin(q{
8120 
8121 mixin template ImplementRpcClientInterface(T, string serverPath, string cmdArg) {
8122 	static import std.traits;
8123 
8124 	// 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.
8125 	static foreach(idx, member; __traits(derivedMembers, T)) {
8126 	static if(__traits(isVirtualMethod, __traits(getMember, T, member)))
8127 		mixin( q{
8128 		std.traits.ReturnType!(__traits(getMember, T, member))
8129 		} ~ member ~ q{(std.traits.Parameters!(__traits(getMember, T, member)) params)
8130 		{
8131 			SerializationBuffer buffer;
8132 			auto i = cast(ushort) idx;
8133 			serialize(&buffer.sink, i);
8134 			serialize(&buffer.sink, __traits(getMember, T, member).mangleof);
8135 			foreach(param; params)
8136 				serialize(&buffer.sink, param);
8137 
8138 			auto sendable = buffer.sendable;
8139 
8140 			version(Posix) {{
8141 				auto ret = send(connectionHandle, sendable.ptr, sendable.length, 0);
8142 
8143 				if(ret == -1) {
8144 					throw new Exception("send returned -1, errno: " ~ to!string(errno));
8145 				} else if(ret == 0) {
8146 					throw new Exception("Connection to addon server lost");
8147 				} if(ret < sendable.length)
8148 					throw new Exception("Send failed to send all");
8149 				assert(ret == sendable.length);
8150 			}} // FIXME Windows impl
8151 
8152 			static if(!is(typeof(return) == void)) {
8153 				// there is a return value; we need to wait for it too
8154 				version(Posix) {
8155 					ubyte[3000] revBuffer;
8156 					auto ret = recv(connectionHandle, revBuffer.ptr, revBuffer.length, 0);
8157 					auto got = revBuffer[0 .. ret];
8158 
8159 					int dataLocation;
8160 					ubyte[] grab(int sz) {
8161 						auto dataLocation1 = dataLocation;
8162 						dataLocation += sz;
8163 						return got[dataLocation1 .. dataLocation];
8164 					}
8165 
8166 					typeof(return) retu;
8167 					deserialize!(typeof(return))(&grab, (a) { retu = a; });
8168 					return retu;
8169 				} else {
8170 					// FIXME Windows impl
8171 					return typeof(return).init;
8172 				}
8173 
8174 			}
8175 		}});
8176 	}
8177 
8178 	private static typeof(this) singletonInstance;
8179 	private LocalServerConnectionHandle connectionHandle;
8180 
8181 	static typeof(this) connection() {
8182 		if(singletonInstance is null) {
8183 			singletonInstance = new typeof(this)();
8184 			singletonInstance.connect();
8185 		}
8186 		return singletonInstance;
8187 	}
8188 
8189 	void connect() {
8190 		connectionHandle = openLocalServerConnection(serverPath, cmdArg);
8191 	}
8192 
8193 	void disconnect() {
8194 		closeLocalServerConnection(connectionHandle);
8195 	}
8196 }
8197 
8198 void dispatchRpcServer(Interface, Class)(Class this_, ubyte[] data, int fd) if(is(Class : Interface)) {
8199 	ushort calledIdx;
8200 	string calledFunction;
8201 
8202 	int dataLocation;
8203 	ubyte[] grab(int sz) {
8204 		if(sz == 0) assert(0);
8205 		auto d = data[dataLocation .. dataLocation + sz];
8206 		dataLocation += sz;
8207 		return d;
8208 	}
8209 
8210 	again:
8211 
8212 	deserialize!ushort(&grab, (a) { calledIdx = a; });
8213 	deserialize!string(&grab, (a) { calledFunction = a; });
8214 
8215 	import std.traits;
8216 
8217 	sw: switch(calledIdx) {
8218 		foreach(idx, memberName; __traits(derivedMembers, Interface))
8219 		static if(__traits(isVirtualMethod, __traits(getMember, Interface, memberName))) {
8220 			case idx:
8221 				assert(calledFunction == __traits(getMember, Interface, memberName).mangleof);
8222 
8223 				Parameters!(__traits(getMember, Interface, memberName)) params;
8224 				foreach(ref param; params)
8225 					deserialize!(typeof(param))(&grab, (a) { param = a; });
8226 
8227 				static if(is(ReturnType!(__traits(getMember, Interface, memberName)) == void)) {
8228 					__traits(getMember, this_, memberName)(params);
8229 				} else {
8230 					auto ret = __traits(getMember, this_, memberName)(params);
8231 					SerializationBuffer buffer;
8232 					serialize(&buffer.sink, ret);
8233 
8234 					auto sendable = buffer.sendable;
8235 
8236 					version(Posix) {
8237 						auto r = send(fd, sendable.ptr, sendable.length, 0);
8238 						if(r == -1) {
8239 							throw new Exception("send returned -1, errno: " ~ to!string(errno));
8240 						} else if(r == 0) {
8241 							throw new Exception("Connection to addon client lost");
8242 						} if(r < sendable.length)
8243 							throw new Exception("Send failed to send all");
8244 
8245 					} // FIXME Windows impl
8246 				}
8247 			break sw;
8248 		}
8249 		default: assert(0);
8250 	}
8251 
8252 	if(dataLocation != data.length)
8253 		goto again;
8254 }
8255 
8256 
8257 private struct SerializationBuffer {
8258 	ubyte[2048] bufferBacking;
8259 	int bufferLocation;
8260 	void sink(scope ubyte[] data) {
8261 		bufferBacking[bufferLocation .. bufferLocation + data.length] = data[];
8262 		bufferLocation += data.length;
8263 	}
8264 
8265 	ubyte[] sendable() return {
8266 		return bufferBacking[0 .. bufferLocation];
8267 	}
8268 }
8269 
8270 /*
8271 	FIXME:
8272 		add a version command line arg
8273 		version data in the library
8274 		management gui as external program
8275 
8276 		at server with event_fd for each run
8277 		use .mangleof in the at function name
8278 
8279 		i think the at server will have to:
8280 			pipe args to the child
8281 			collect child output for logging
8282 			get child return value for logging
8283 
8284 			on windows timers work differently. idk how to best combine with the io stuff.
8285 
8286 			will have to have dump and restore too, so i can restart without losing stuff.
8287 */
8288 
8289 /++
8290 	A convenience object for talking to the [BasicDataServer] from a higher level.
8291 	See: [Cgi.getSessionObject].
8292 
8293 	You pass it a `Data` struct describing the data you want saved in the session.
8294 	Then, this class will generate getter and setter properties that allow access
8295 	to that data.
8296 
8297 	Note that each load and store will be done as-accessed; it doesn't front-load
8298 	mutable data nor does it batch updates out of fear of read-modify-write race
8299 	conditions. (In fact, right now it does this for everything, but in the future,
8300 	I might batch load `immutable` members of the Data struct.)
8301 
8302 	At some point in the future, I might also let it do different backends, like
8303 	a client-side cookie store too, but idk.
8304 
8305 	Note that the plain-old-data members of your `Data` struct are wrapped by this
8306 	interface via a static foreach to make property functions.
8307 
8308 	See_Also: [MockSession]
8309 +/
8310 interface Session(Data) : SessionObject {
8311 	@property string sessionId() const;
8312 
8313 	/++
8314 		Starts a new session. Note that a session is also
8315 		implicitly started as soon as you write data to it,
8316 		so if you need to alter these parameters from their
8317 		defaults, be sure to explicitly call this BEFORE doing
8318 		any writes to session data.
8319 
8320 		Params:
8321 			idleLifetime = How long, in seconds, the session
8322 			should remain in memory when not being read from
8323 			or written to. The default is one day.
8324 
8325 			NOT IMPLEMENTED
8326 
8327 			useExtendedLifetimeCookie = The session ID is always
8328 			stored in a HTTP cookie, and by default, that cookie
8329 			is discarded when the user closes their browser.
8330 
8331 			But if you set this to true, it will use a non-perishable
8332 			cookie for the given idleLifetime.
8333 
8334 			NOT IMPLEMENTED
8335 	+/
8336 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false);
8337 
8338 	/++
8339 		Regenerates the session ID and updates the associated
8340 		cookie.
8341 
8342 		This is also your chance to change immutable data
8343 		(not yet implemented).
8344 	+/
8345 	void regenerateId();
8346 
8347 	/++
8348 		Terminates this session, deleting all saved data.
8349 	+/
8350 	void terminate();
8351 
8352 	/++
8353 		Plain-old-data members of your `Data` struct are wrapped here via
8354 		the property getters and setters.
8355 
8356 		If the member is a non-string array, it returns a magical array proxy
8357 		object which allows for atomic appends and replaces via overloaded operators.
8358 		You can slice this to get a range representing a $(B const) view of the array.
8359 		This is to protect you against read-modify-write race conditions.
8360 	+/
8361 	static foreach(memberName; __traits(allMembers, Data))
8362 		static if(is(typeof(__traits(getMember, Data, memberName))))
8363 		mixin(q{
8364 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout;
8365 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value);
8366 		});
8367 
8368 }
8369 
8370 /++
8371 	An implementation of [Session] that works on real cgi connections utilizing the
8372 	[BasicDataServer].
8373 
8374 	As opposed to a [MockSession] which is made for testing purposes.
8375 
8376 	You will not construct one of these directly. See [Cgi.getSessionObject] instead.
8377 +/
8378 class BasicDataServerSession(Data) : Session!Data {
8379 	private Cgi cgi;
8380 	private string sessionId_;
8381 
8382 	public @property string sessionId() const {
8383 		return sessionId_;
8384 	}
8385 
8386 	protected @property string sessionId(string s) {
8387 		return this.sessionId_ = s;
8388 	}
8389 
8390 	private this(Cgi cgi) {
8391 		this.cgi = cgi;
8392 		if(auto ptr = "sessionId" in cgi.cookies)
8393 			sessionId = (*ptr).length ? *ptr : null;
8394 	}
8395 
8396 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {
8397 		assert(sessionId is null);
8398 
8399 		// FIXME: what if there is a session ID cookie, but no corresponding session on the server?
8400 
8401 		import std.random, std.conv;
8402 		sessionId = to!string(uniform(1, long.max));
8403 
8404 		BasicDataServer.connection.createSession(sessionId, idleLifetime);
8405 		setCookie();
8406 	}
8407 
8408 	protected void setCookie() {
8409 		cgi.setCookie(
8410 			"sessionId", sessionId,
8411 			0 /* expiration */,
8412 			"/" /* path */,
8413 			null /* domain */,
8414 			true /* http only */,
8415 			cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
8416 	}
8417 
8418 	void regenerateId() {
8419 		if(sessionId is null) {
8420 			start();
8421 			return;
8422 		}
8423 		import std.random, std.conv;
8424 		auto oldSessionId = sessionId;
8425 		sessionId = to!string(uniform(1, long.max));
8426 		BasicDataServer.connection.renameSession(oldSessionId, sessionId);
8427 		setCookie();
8428 	}
8429 
8430 	void terminate() {
8431 		BasicDataServer.connection.destroySession(sessionId);
8432 		sessionId = null;
8433 		setCookie();
8434 	}
8435 
8436 	static foreach(memberName; __traits(allMembers, Data))
8437 		static if(is(typeof(__traits(getMember, Data, memberName))))
8438 		mixin(q{
8439 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
8440 				if(sessionId is null)
8441 					return typeof(return).init;
8442 
8443 				import std.traits;
8444 				auto v = BasicDataServer.connection.getSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName);
8445 				if(v.length == 0)
8446 					return typeof(return).init;
8447 				import std.conv;
8448 				// why this cast? to doesn't like being given an inout argument. so need to do it without that, then
8449 				// we need to return it and that needed the cast. It should be fine since we basically respect constness..
8450 				// basically. Assuming the session is POD this should be fine.
8451 				return cast(typeof(return)) to!(typeof(__traits(getMember, Data, memberName)))(v);
8452 			}
8453 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
8454 				if(sessionId is null)
8455 					start();
8456 				import std.conv;
8457 				import std.traits;
8458 				BasicDataServer.connection.setSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName, to!string(value));
8459 				return value;
8460 			}
8461 		});
8462 }
8463 
8464 /++
8465 	A mock object that works like the real session, but doesn't actually interact with any actual database or http connection.
8466 	Simply stores the data in its instance members.
8467 +/
8468 class MockSession(Data) : Session!Data {
8469 	pure {
8470 		@property string sessionId() const { return "mock"; }
8471 		void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {}
8472 		void regenerateId() {}
8473 		void terminate() {}
8474 
8475 		private Data store_;
8476 
8477 		static foreach(memberName; __traits(allMembers, Data))
8478 			static if(is(typeof(__traits(getMember, Data, memberName))))
8479 			mixin(q{
8480 				@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
8481 					return __traits(getMember, store_, memberName);
8482 				}
8483 				@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
8484 					return __traits(getMember, store_, memberName) = value;
8485 				}
8486 			});
8487 	}
8488 }
8489 
8490 /++
8491 	Direct interface to the basic data add-on server. You can
8492 	typically use [Cgi.getSessionObject] as a more convenient interface.
8493 +/
8494 version(with_addon_servers_connections)
8495 interface BasicDataServer {
8496 	///
8497 	void createSession(string sessionId, int lifetime);
8498 	///
8499 	void renewSession(string sessionId, int lifetime);
8500 	///
8501 	void destroySession(string sessionId);
8502 	///
8503 	void renameSession(string oldSessionId, string newSessionId);
8504 
8505 	///
8506 	void setSessionData(string sessionId, string dataKey, string dataValue);
8507 	///
8508 	string getSessionData(string sessionId, string dataKey);
8509 
8510 	///
8511 	static BasicDataServerConnection connection() {
8512 		return BasicDataServerConnection.connection();
8513 	}
8514 }
8515 
8516 version(with_addon_servers_connections)
8517 class BasicDataServerConnection : BasicDataServer {
8518 	mixin ImplementRpcClientInterface!(BasicDataServer, "/tmp/arsd_session_server", "--session-server");
8519 }
8520 
8521 version(with_addon_servers)
8522 final class BasicDataServerImplementation : BasicDataServer, EventIoServer {
8523 
8524 	void createSession(string sessionId, int lifetime) {
8525 		sessions[sessionId.idup] = Session(lifetime);
8526 	}
8527 	void destroySession(string sessionId) {
8528 		sessions.remove(sessionId);
8529 	}
8530 	void renewSession(string sessionId, int lifetime) {
8531 		sessions[sessionId].lifetime = lifetime;
8532 	}
8533 	void renameSession(string oldSessionId, string newSessionId) {
8534 		sessions[newSessionId.idup] = sessions[oldSessionId];
8535 		sessions.remove(oldSessionId);
8536 	}
8537 	void setSessionData(string sessionId, string dataKey, string dataValue) {
8538 		if(sessionId !in sessions)
8539 			createSession(sessionId, 3600); // FIXME?
8540 		sessions[sessionId].values[dataKey.idup] = dataValue.idup;
8541 	}
8542 	string getSessionData(string sessionId, string dataKey) {
8543 		if(auto session = sessionId in sessions) {
8544 			if(auto data = dataKey in (*session).values)
8545 				return *data;
8546 			else
8547 				return null; // no such data
8548 
8549 		} else {
8550 			return null; // no session
8551 		}
8552 	}
8553 
8554 
8555 	protected:
8556 
8557 	struct Session {
8558 		int lifetime;
8559 
8560 		string[string] values;
8561 	}
8562 
8563 	Session[string] sessions;
8564 
8565 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8566 		auto data = op.usedBuffer;
8567 		dispatchRpcServer!BasicDataServer(this, data, op.fd);
8568 		return false;
8569 	}
8570 
8571 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
8572 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
8573 	void wait_timeout() {}
8574 	void fileClosed(int fd) {} // stateless so irrelevant
8575 	void epoll_fd(int fd) {}
8576 }
8577 
8578 /++
8579 	See [schedule] to make one of these. You then call one of the methods here to set it up:
8580 
8581 	---
8582 		schedule!fn(args).at(DateTime(2019, 8, 7, 12, 00, 00)); // run the function at August 7, 2019, 12 noon UTC
8583 		schedule!fn(args).delay(6.seconds); // run it after waiting 6 seconds
8584 		schedule!fn(args).asap(); // run it in the background as soon as the event loop gets around to it
8585 	---
8586 +/
8587 version(with_addon_servers_connections)
8588 struct ScheduledJobHelper {
8589 	private string func;
8590 	private string[] args;
8591 	private bool consumed;
8592 
8593 	private this(string func, string[] args) {
8594 		this.func = func;
8595 		this.args = args;
8596 	}
8597 
8598 	~this() {
8599 		assert(consumed);
8600 	}
8601 
8602 	/++
8603 		Schedules the job to be run at the given time.
8604 	+/
8605 	void at(DateTime when, immutable TimeZone timezone = UTC()) {
8606 		consumed = true;
8607 
8608 		auto conn = ScheduledJobServerConnection.connection;
8609 		import std.file;
8610 		auto st = SysTime(when, timezone);
8611 		auto jobId = conn.scheduleJob(1, cast(int) st.toUnixTime(), thisExePath, func, args);
8612 	}
8613 
8614 	/++
8615 		Schedules the job to run at least after the specified delay.
8616 	+/
8617 	void delay(Duration delay) {
8618 		consumed = true;
8619 
8620 		auto conn = ScheduledJobServerConnection.connection;
8621 		import std.file;
8622 		auto jobId = conn.scheduleJob(0, cast(int) delay.total!"seconds", thisExePath, func, args);
8623 	}
8624 
8625 	/++
8626 		Runs the job in the background ASAP.
8627 
8628 		$(NOTE It may run in a background thread. Don't segfault!)
8629 	+/
8630 	void asap() {
8631 		consumed = true;
8632 
8633 		auto conn = ScheduledJobServerConnection.connection;
8634 		import std.file;
8635 		auto jobId = conn.scheduleJob(0, 1, thisExePath, func, args);
8636 	}
8637 
8638 	/+
8639 	/++
8640 		Schedules the job to recur on the given pattern.
8641 	+/
8642 	void recur(string spec) {
8643 
8644 	}
8645 	+/
8646 }
8647 
8648 /++
8649 	First step to schedule a job on the scheduled job server.
8650 
8651 	The scheduled job needs to be a top-level function that doesn't read any
8652 	variables from outside its arguments because it may be run in a new process,
8653 	without any context existing later.
8654 
8655 	You MUST set details on the returned object to actually do anything!
8656 +/
8657 template schedule(alias fn, T...) if(is(typeof(fn) == function)) {
8658 	///
8659 	ScheduledJobHelper schedule(T args) {
8660 		// this isn't meant to ever be called, but instead just to
8661 		// get the compiler to type check the arguments passed for us
8662 		auto sample = delegate() {
8663 			fn(args);
8664 		};
8665 		string[] sargs;
8666 		foreach(arg; args)
8667 			sargs ~= to!string(arg);
8668 		return ScheduledJobHelper(fn.mangleof, sargs);
8669 	}
8670 
8671 	shared static this() {
8672 		scheduledJobHandlers[fn.mangleof] = delegate(string[] sargs) {
8673 			import std.traits;
8674 			Parameters!fn args;
8675 			foreach(idx, ref arg; args)
8676 				arg = to!(typeof(arg))(sargs[idx]);
8677 			fn(args);
8678 		};
8679 	}
8680 }
8681 
8682 ///
8683 interface ScheduledJobServer {
8684 	/// Use the [schedule] function for a higher-level interface.
8685 	int scheduleJob(int whenIs, int when, string executable, string func, string[] args);
8686 	///
8687 	void cancelJob(int jobId);
8688 }
8689 
8690 version(with_addon_servers_connections)
8691 class ScheduledJobServerConnection : ScheduledJobServer {
8692 	mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server", "--timer-server");
8693 }
8694 
8695 version(with_addon_servers)
8696 final class ScheduledJobServerImplementation : ScheduledJobServer, EventIoServer {
8697 	// FIXME: we need to handle SIGCHLD in this somehow
8698 	// whenIs is 0 for relative, 1 for absolute
8699 	protected int scheduleJob(int whenIs, int when, string executable, string func, string[] args) {
8700 		auto nj = nextJobId;
8701 		nextJobId++;
8702 
8703 		version(linux) {
8704 			import core.sys.linux.timerfd;
8705 			import core.sys.linux.epoll;
8706 			import core.sys.posix.unistd;
8707 
8708 
8709 			auto fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
8710 			if(fd == -1)
8711 				throw new Exception("fd timer create failed");
8712 
8713 			foreach(ref arg; args)
8714 				arg = arg.idup;
8715 			auto job = Job(executable.idup, func.idup, .dup(args), fd, nj);
8716 
8717 			itimerspec value;
8718 			value.it_value.tv_sec = when;
8719 			value.it_value.tv_nsec = 0;
8720 
8721 			value.it_interval.tv_sec = 0;
8722 			value.it_interval.tv_nsec = 0;
8723 
8724 			if(timerfd_settime(fd, whenIs == 1 ? TFD_TIMER_ABSTIME : 0, &value, null) == -1)
8725 				throw new Exception("couldn't set fd timer");
8726 
8727 			auto op = allocateIoOp(fd, IoOp.Read, 16, (IoOp* op, int fd) {
8728 				jobs.remove(nj);
8729 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, null);
8730 				close(fd);
8731 
8732 
8733 				spawnProcess([job.executable, "--timed-job", job.func] ~ job.args);
8734 
8735 				return true;
8736 			});
8737 			scope(failure)
8738 				freeIoOp(op);
8739 
8740 			epoll_event ev;
8741 			ev.events = EPOLLIN | EPOLLET;
8742 			ev.data.ptr = op;
8743 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1)
8744 				throw new Exception("epoll_ctl " ~ to!string(errno));
8745 
8746 			jobs[nj] = job;
8747 			return nj;
8748 		} else assert(0);
8749 	}
8750 
8751 	protected void cancelJob(int jobId) {
8752 		version(linux) {
8753 			auto job = jobId in jobs;
8754 			if(job is null)
8755 				return;
8756 
8757 			jobs.remove(jobId);
8758 
8759 			version(linux) {
8760 				import core.sys.linux.timerfd;
8761 				import core.sys.linux.epoll;
8762 				import core.sys.posix.unistd;
8763 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, job.timerfd, null);
8764 				close(job.timerfd);
8765 			}
8766 		}
8767 		jobs.remove(jobId);
8768 	}
8769 
8770 	int nextJobId = 1;
8771 	static struct Job {
8772 		string executable;
8773 		string func;
8774 		string[] args;
8775 		int timerfd;
8776 		int id;
8777 	}
8778 	Job[int] jobs;
8779 
8780 
8781 	// event io server methods below
8782 
8783 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8784 		auto data = op.usedBuffer;
8785 		dispatchRpcServer!ScheduledJobServer(this, data, op.fd);
8786 		return false;
8787 	}
8788 
8789 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
8790 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
8791 	void wait_timeout() {}
8792 	void fileClosed(int fd) {} // stateless so irrelevant
8793 
8794 	int epoll_fd_;
8795 	void epoll_fd(int fd) {this.epoll_fd_ = fd; }
8796 	int epoll_fd() { return epoll_fd_; }
8797 }
8798 
8799 /++
8800 	History:
8801 		Added January 6, 2019
8802 +/
8803 version(with_addon_servers_connections)
8804 interface EventSourceServer {
8805 	/++
8806 		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.
8807 
8808 		See_Also:
8809 			[sendEvent]
8810 
8811 		Bugs:
8812 			Not implemented on Windows!
8813 
8814 		History:
8815 			Officially stabilised on November 23, 2023 (dub v11.4). It actually worked pretty well in its original design.
8816 	+/
8817 	public static void adoptConnection(Cgi cgi, in char[] eventUrl) {
8818 		/*
8819 			If lastEventId is missing or empty, you just get new events as they come.
8820 
8821 			If it is set from something else, it sends all since then (that are still alive)
8822 			down the pipe immediately.
8823 
8824 			The reason it can come from the header is that's what the standard defines for
8825 			browser reconnects. The reason it can come from a query string is just convenience
8826 			in catching up in a user-defined manner.
8827 
8828 			The reason the header overrides the query string is if the browser tries to reconnect,
8829 			it will send the header AND the query (it reconnects to the same url), so we just
8830 			want to do the restart thing.
8831 
8832 			Note that if you ask for "0" as the lastEventId, it will get ALL still living events.
8833 		*/
8834 		string lastEventId = cgi.lastEventId;
8835 		if(lastEventId.length == 0 && "lastEventId" in cgi.get)
8836 			lastEventId = cgi.get["lastEventId"];
8837 
8838 		cgi.setResponseContentType("text/event-stream");
8839 		cgi.write(":\n", false); // to initialize the chunking and send headers before keeping the fd for later
8840 		cgi.flush();
8841 
8842 		cgi.closed = true;
8843 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
8844 		scope(exit)
8845 			closeLocalServerConnection(s);
8846 
8847 		version(fastcgi)
8848 			throw new Exception("sending fcgi connections not supported");
8849 		else {
8850 			auto fd = cgi.getOutputFileHandle();
8851 			if(isInvalidHandle(fd))
8852 				throw new Exception("bad fd from cgi!");
8853 
8854 			EventSourceServerImplementation.SendableEventConnection sec;
8855 			sec.populate(cgi.responseChunked, eventUrl, lastEventId);
8856 
8857 			version(Posix) {
8858 				auto res = write_fd(s, cast(void*) &sec, sec.sizeof, fd);
8859 				assert(res == sec.sizeof);
8860 			} else version(Windows) {
8861 				// FIXME
8862 			}
8863 		}
8864 	}
8865 
8866 	/++
8867 		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.
8868 
8869 		Params:
8870 			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.
8871 			event = the event type string, which is used in the Javascript addEventListener API on EventSource
8872 			data = the event data. Available in JS as `event.data`.
8873 			lifetime = the amount of time to keep this event for replaying on the event server.
8874 
8875 		Bugs:
8876 			Not implemented on Windows!
8877 
8878 		History:
8879 			Officially stabilised on November 23, 2023 (dub v11.4). It actually worked pretty well in its original design.
8880 	+/
8881 	public static void sendEvent(string url, string event, string data, int lifetime) {
8882 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
8883 		scope(exit)
8884 			closeLocalServerConnection(s);
8885 
8886 		EventSourceServerImplementation.SendableEvent sev;
8887 		sev.populate(url, event, data, lifetime);
8888 
8889 		version(Posix) {
8890 			auto ret = send(s, &sev, sev.sizeof, 0);
8891 			assert(ret == sev.sizeof);
8892 		} else version(Windows) {
8893 			// FIXME
8894 		}
8895 	}
8896 
8897 	/++
8898 		Messages sent to `url` will also be sent to anyone listening on `forwardUrl`.
8899 
8900 		See_Also: [disconnect]
8901 	+/
8902 	void connect(string url, string forwardUrl);
8903 
8904 	/++
8905 		Disconnects `forwardUrl` from `url`
8906 
8907 		See_Also: [connect]
8908 	+/
8909 	void disconnect(string url, string forwardUrl);
8910 }
8911 
8912 ///
8913 version(with_addon_servers)
8914 final class EventSourceServerImplementation : EventSourceServer, EventIoServer {
8915 
8916 	protected:
8917 
8918 	void connect(string url, string forwardUrl) {
8919 		pipes[url] ~= forwardUrl;
8920 	}
8921 	void disconnect(string url, string forwardUrl) {
8922 		auto t = url in pipes;
8923 		if(t is null)
8924 			return;
8925 		foreach(idx, n; (*t))
8926 			if(n == forwardUrl) {
8927 				(*t)[idx] = (*t)[$-1];
8928 				(*t) = (*t)[0 .. $-1];
8929 				break;
8930 			}
8931 	}
8932 
8933 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8934 		if(receivedFd != -1) {
8935 			//writeln("GOT FD ", receivedFd, " -- ", op.usedBuffer);
8936 
8937 			//core.sys.posix.unistd.write(receivedFd, "hello".ptr, 5);
8938 
8939 			SendableEventConnection* got = cast(SendableEventConnection*) op.usedBuffer.ptr;
8940 
8941 			auto url = got.url.idup;
8942 			eventConnectionsByUrl[url] ~= EventConnection(receivedFd, got.responseChunked > 0 ? true : false);
8943 
8944 			// FIXME: catch up on past messages here
8945 		} else {
8946 			auto data = op.usedBuffer;
8947 			auto event = cast(SendableEvent*) data.ptr;
8948 
8949 			if(event.magic == 0xdeadbeef) {
8950 				handleInputEvent(event);
8951 
8952 				if(event.url in pipes)
8953 				foreach(pipe; pipes[event.url]) {
8954 					event.url = pipe;
8955 					handleInputEvent(event);
8956 				}
8957 			} else {
8958 				dispatchRpcServer!EventSourceServer(this, data, op.fd);
8959 			}
8960 		}
8961 		return false;
8962 	}
8963 	void handleLocalConnectionClose(IoOp* op) {
8964 		fileClosed(op.fd);
8965 	}
8966 	void handleLocalConnectionComplete(IoOp* op) {}
8967 
8968 	void wait_timeout() {
8969 		// just keeping alive
8970 		foreach(url, connections; eventConnectionsByUrl)
8971 		foreach(connection; connections)
8972 			if(connection.needsChunking)
8973 				nonBlockingWrite(this, connection.fd, "1b\r\nevent: keepalive\ndata: ok\n\n\r\n");
8974 			else
8975 				nonBlockingWrite(this, connection.fd, "event: keepalive\ndata: ok\n\n\r\n");
8976 	}
8977 
8978 	void fileClosed(int fd) {
8979 		outer: foreach(url, ref connections; eventConnectionsByUrl) {
8980 			foreach(idx, conn; connections) {
8981 				if(fd == conn.fd) {
8982 					connections[idx] = connections[$-1];
8983 					connections = connections[0 .. $ - 1];
8984 					continue outer;
8985 				}
8986 			}
8987 		}
8988 	}
8989 
8990 	void epoll_fd(int fd) {}
8991 
8992 
8993 	private:
8994 
8995 
8996 	struct SendableEventConnection {
8997 		ubyte responseChunked;
8998 
8999 		int urlLength;
9000 		char[256] urlBuffer = 0;
9001 
9002 		int lastEventIdLength;
9003 		char[32] lastEventIdBuffer = 0;
9004 
9005 		char[] url() return {
9006 			return urlBuffer[0 .. urlLength];
9007 		}
9008 		void url(in char[] u) {
9009 			urlBuffer[0 .. u.length] = u[];
9010 			urlLength = cast(int) u.length;
9011 		}
9012 		char[] lastEventId() return {
9013 			return lastEventIdBuffer[0 .. lastEventIdLength];
9014 		}
9015 		void populate(bool responseChunked, in char[] url, in char[] lastEventId)
9016 		in {
9017 			assert(url.length < this.urlBuffer.length);
9018 			assert(lastEventId.length < this.lastEventIdBuffer.length);
9019 		}
9020 		do {
9021 			this.responseChunked = responseChunked ? 1 : 0;
9022 			this.urlLength = cast(int) url.length;
9023 			this.lastEventIdLength = cast(int) lastEventId.length;
9024 
9025 			this.urlBuffer[0 .. url.length] = url[];
9026 			this.lastEventIdBuffer[0 .. lastEventId.length] = lastEventId[];
9027 		}
9028 	}
9029 
9030 	struct SendableEvent {
9031 		int magic = 0xdeadbeef;
9032 		int urlLength;
9033 		char[256] urlBuffer = 0;
9034 		int typeLength;
9035 		char[32] typeBuffer = 0;
9036 		int messageLength;
9037 		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.
9038 		int _lifetime;
9039 
9040 		char[] message() return {
9041 			return messageBuffer[0 .. messageLength];
9042 		}
9043 		char[] type() return {
9044 			return typeBuffer[0 .. typeLength];
9045 		}
9046 		char[] url() return {
9047 			return urlBuffer[0 .. urlLength];
9048 		}
9049 		void url(in char[] u) {
9050 			urlBuffer[0 .. u.length] = u[];
9051 			urlLength = cast(int) u.length;
9052 		}
9053 		int lifetime() {
9054 			return _lifetime;
9055 		}
9056 
9057 		///
9058 		void populate(string url, string type, string message, int lifetime)
9059 		in {
9060 			assert(url.length < this.urlBuffer.length);
9061 			assert(type.length < this.typeBuffer.length);
9062 			assert(message.length < this.messageBuffer.length);
9063 		}
9064 		do {
9065 			this.urlLength = cast(int) url.length;
9066 			this.typeLength = cast(int) type.length;
9067 			this.messageLength = cast(int) message.length;
9068 			this._lifetime = lifetime;
9069 
9070 			this.urlBuffer[0 .. url.length] = url[];
9071 			this.typeBuffer[0 .. type.length] = type[];
9072 			this.messageBuffer[0 .. message.length] = message[];
9073 		}
9074 	}
9075 
9076 	struct EventConnection {
9077 		int fd;
9078 		bool needsChunking;
9079 	}
9080 
9081 	private EventConnection[][string] eventConnectionsByUrl;
9082 	private string[][string] pipes;
9083 
9084 	private void handleInputEvent(scope SendableEvent* event) {
9085 		static int eventId;
9086 
9087 		static struct StoredEvent {
9088 			int id;
9089 			string type;
9090 			string message;
9091 			int lifetimeRemaining;
9092 		}
9093 
9094 		StoredEvent[][string] byUrl;
9095 
9096 		int thisId = ++eventId;
9097 
9098 		if(event.lifetime)
9099 			byUrl[event.url.idup] ~= StoredEvent(thisId, event.type.idup, event.message.idup, event.lifetime);
9100 
9101 		auto connectionsPtr = event.url in eventConnectionsByUrl;
9102 		EventConnection[] connections;
9103 		if(connectionsPtr is null)
9104 			return;
9105 		else
9106 			connections = *connectionsPtr;
9107 
9108 		char[4096] buffer;
9109 		char[] formattedMessage;
9110 
9111 		void append(const char[] a) {
9112 			// the 6's here are to leave room for a HTTP chunk header, if it proves necessary
9113 			buffer[6 + formattedMessage.length .. 6 + formattedMessage.length + a.length] = a[];
9114 			formattedMessage = buffer[6 .. 6 + formattedMessage.length + a.length];
9115 		}
9116 
9117 		import std.algorithm.iteration;
9118 
9119 		if(connections.length) {
9120 			append("id: ");
9121 			append(to!string(thisId));
9122 			append("\n");
9123 
9124 			append("event: ");
9125 			append(event.type);
9126 			append("\n");
9127 
9128 			foreach(line; event.message.splitter("\n")) {
9129 				append("data: ");
9130 				append(line);
9131 				append("\n");
9132 			}
9133 
9134 			append("\n");
9135 		}
9136 
9137 		// chunk it for HTTP!
9138 		auto len = toHex(formattedMessage.length);
9139 		buffer[4 .. 6] = "\r\n"[];
9140 		buffer[4 - len.length .. 4] = len[];
9141 		buffer[6 + formattedMessage.length] = '\r';
9142 		buffer[6 + formattedMessage.length + 1] = '\n';
9143 
9144 		auto chunkedMessage = buffer[4 - len.length .. 6 + formattedMessage.length +2];
9145 		// done
9146 
9147 		// FIXME: send back requests when needed
9148 		// FIXME: send a single ":\n" every 15 seconds to keep alive
9149 
9150 		foreach(connection; connections) {
9151 			if(connection.needsChunking) {
9152 				nonBlockingWrite(this, connection.fd, chunkedMessage);
9153 			} else {
9154 				nonBlockingWrite(this, connection.fd, formattedMessage);
9155 			}
9156 		}
9157 	}
9158 }
9159 
9160 void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoServer)) {
9161 	version(Posix) {
9162 
9163 		import core.sys.posix.unistd;
9164 		import core.sys.posix.fcntl;
9165 		import core.sys.posix.sys.un;
9166 
9167 		import core.sys.posix.signal;
9168 		signal(SIGPIPE, SIG_IGN);
9169 
9170 		static extern(C) void sigchldhandler(int) {
9171 			int status;
9172 			import w = core.sys.posix.sys.wait;
9173 			w.wait(&status);
9174 		}
9175 		signal(SIGCHLD, &sigchldhandler);
9176 
9177 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
9178 		if(sock == -1)
9179 			throw new Exception("socket " ~ to!string(errno));
9180 
9181 		scope(failure)
9182 			close(sock);
9183 
9184 		cloexec(sock);
9185 
9186 		// add-on server processes are assumed to be local, and thus will
9187 		// use unix domain sockets. Besides, I want to pass sockets to them,
9188 		// so it basically must be local (except for the session server, but meh).
9189 		sockaddr_un addr;
9190 		addr.sun_family = AF_UNIX;
9191 		version(linux) {
9192 			// on linux, we will use the abstract namespace
9193 			addr.sun_path[0] = 0;
9194 			addr.sun_path[1 .. localListenerName.length + 1] = cast(typeof(addr.sun_path[])) localListenerName[];
9195 		} else {
9196 			// but otherwise, just use a file cuz we must.
9197 			addr.sun_path[0 .. localListenerName.length] = cast(typeof(addr.sun_path[])) localListenerName[];
9198 		}
9199 
9200 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1)
9201 			throw new Exception("bind " ~ to!string(errno));
9202 
9203 		if(listen(sock, 128) == -1)
9204 			throw new Exception("listen " ~ to!string(errno));
9205 
9206 		makeNonBlocking(sock);
9207 
9208 		version(linux) {
9209 			import core.sys.linux.epoll;
9210 			auto epoll_fd = epoll_create1(EPOLL_CLOEXEC);
9211 			if(epoll_fd == -1)
9212 				throw new Exception("epoll_create1 " ~ to!string(errno));
9213 			scope(failure)
9214 				close(epoll_fd);
9215 		} else {
9216 			import core.sys.posix.poll;
9217 		}
9218 
9219 		version(linux)
9220 		eis.epoll_fd = epoll_fd;
9221 
9222 		auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
9223 		scope(exit)
9224 			freeIoOp(acceptOp);
9225 
9226 		version(linux) {
9227 			epoll_event ev;
9228 			ev.events = EPOLLIN | EPOLLET;
9229 			ev.data.ptr = acceptOp;
9230 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev) == -1)
9231 				throw new Exception("epoll_ctl " ~ to!string(errno));
9232 
9233 			epoll_event[64] events;
9234 		} else {
9235 			pollfd[] pollfds;
9236 			IoOp*[int] ioops;
9237 			pollfds ~= pollfd(sock, POLLIN);
9238 			ioops[sock] = acceptOp;
9239 		}
9240 
9241 		import core.time : MonoTime, seconds;
9242 
9243 		MonoTime timeout = MonoTime.currTime + 15.seconds;
9244 
9245 		while(true) {
9246 
9247 			// FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently
9248 
9249 			int timeout_milliseconds = 0; //  -1; // infinite
9250 
9251 			timeout_milliseconds = cast(int) (timeout - MonoTime.currTime).total!"msecs";
9252 			if(timeout_milliseconds < 0)
9253 				timeout_milliseconds = 0;
9254 
9255 			//writeln("waiting for ", name);
9256 
9257 			version(linux) {
9258 				auto nfds = epoll_wait(epoll_fd, events.ptr, events.length, timeout_milliseconds);
9259 				if(nfds == -1) {
9260 					if(errno == EINTR)
9261 						continue;
9262 					throw new Exception("epoll_wait " ~ to!string(errno));
9263 				}
9264 			} else {
9265 				int nfds = poll(pollfds.ptr, cast(int) pollfds.length, timeout_milliseconds);
9266 				size_t lastIdx = 0;
9267 			}
9268 
9269 			if(nfds == 0) {
9270 				eis.wait_timeout();
9271 				timeout += 15.seconds;
9272 			}
9273 
9274 			foreach(idx; 0 .. nfds) {
9275 				version(linux) {
9276 					auto flags = events[idx].events;
9277 					auto ioop = cast(IoOp*) events[idx].data.ptr;
9278 				} else {
9279 					IoOp* ioop;
9280 					foreach(tidx, thing; pollfds[lastIdx .. $]) {
9281 						if(thing.revents) {
9282 							ioop = ioops[thing.fd];
9283 							lastIdx += tidx + 1;
9284 							break;
9285 						}
9286 					}
9287 				}
9288 
9289 				//writeln(flags, " ", ioop.fd);
9290 
9291 				void newConnection() {
9292 					// on edge triggering, it is important that we get it all
9293 					while(true) {
9294 						auto size = cast(socklen_t) addr.sizeof;
9295 						auto ns = accept(sock, cast(sockaddr*) &addr, &size);
9296 						if(ns == -1) {
9297 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9298 								// all done, got it all
9299 								break;
9300 							}
9301 							throw new Exception("accept " ~ to!string(errno));
9302 						}
9303 						cloexec(ns);
9304 
9305 						makeNonBlocking(ns);
9306 						auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096 * 4, &eis.handleLocalConnectionData);
9307 						niop.closeHandler = &eis.handleLocalConnectionClose;
9308 						niop.completeHandler = &eis.handleLocalConnectionComplete;
9309 						scope(failure) freeIoOp(niop);
9310 
9311 						version(linux) {
9312 							epoll_event nev;
9313 							nev.events = EPOLLIN | EPOLLET;
9314 							nev.data.ptr = niop;
9315 							if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ns, &nev) == -1)
9316 								throw new Exception("epoll_ctl " ~ to!string(errno));
9317 						} else {
9318 							bool found = false;
9319 							foreach(ref pfd; pollfds) {
9320 								if(pfd.fd < 0) {
9321 									pfd.fd = ns;
9322 									found = true;
9323 								}
9324 							}
9325 							if(!found)
9326 								pollfds ~= pollfd(ns, POLLIN);
9327 							ioops[ns] = niop;
9328 						}
9329 					}
9330 				}
9331 
9332 				bool newConnectionCondition() {
9333 					version(linux)
9334 						return ioop.fd == sock && (flags & EPOLLIN);
9335 					else
9336 						return pollfds[idx].fd == sock && (pollfds[idx].revents & POLLIN);
9337 				}
9338 
9339 				if(newConnectionCondition()) {
9340 					newConnection();
9341 				} else if(ioop.operation == IoOp.ReadSocketHandle) {
9342 					while(true) {
9343 						int in_fd;
9344 						auto got = read_fd(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, &in_fd);
9345 						if(got == -1) {
9346 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9347 								// all done, got it all
9348 								if(ioop.completeHandler)
9349 									ioop.completeHandler(ioop);
9350 								break;
9351 							}
9352 							throw new Exception("recv " ~ to!string(errno));
9353 						}
9354 
9355 						if(got == 0) {
9356 							if(ioop.closeHandler) {
9357 								ioop.closeHandler(ioop);
9358 								version(linux) {} // nothing needed
9359 								else {
9360 									foreach(ref pfd; pollfds) {
9361 										if(pfd.fd == ioop.fd)
9362 											pfd.fd = -1;
9363 									}
9364 								}
9365 							}
9366 							close(ioop.fd);
9367 							freeIoOp(ioop);
9368 							break;
9369 						}
9370 
9371 						ioop.bufferLengthUsed = cast(int) got;
9372 						ioop.handler(ioop, in_fd);
9373 					}
9374 				} else if(ioop.operation == IoOp.Read) {
9375 					while(true) {
9376 						auto got = read(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length);
9377 						if(got == -1) {
9378 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9379 								// all done, got it all
9380 								if(ioop.completeHandler)
9381 									ioop.completeHandler(ioop);
9382 								break;
9383 							}
9384 							throw new Exception("recv " ~ to!string(ioop.fd) ~ " errno " ~ to!string(errno));
9385 						}
9386 
9387 						if(got == 0) {
9388 							if(ioop.closeHandler)
9389 								ioop.closeHandler(ioop);
9390 							close(ioop.fd);
9391 							freeIoOp(ioop);
9392 							break;
9393 						}
9394 
9395 						ioop.bufferLengthUsed = cast(int) got;
9396 						if(ioop.handler(ioop, ioop.fd)) {
9397 							close(ioop.fd);
9398 							freeIoOp(ioop);
9399 							break;
9400 						}
9401 					}
9402 				}
9403 
9404 				// EPOLLHUP?
9405 			}
9406 		}
9407 	} else version(Windows) {
9408 
9409 		// set up a named pipe
9410 		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx
9411 		// https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsaduplicatesocketw
9412 		// https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getnamedpipeserverprocessid
9413 
9414 	} else static assert(0);
9415 }
9416 
9417 
9418 version(with_sendfd)
9419 // copied from the web and ported from C
9420 // see https://stackoverflow.com/questions/2358684/can-i-share-a-file-descriptor-to-another-process-on-linux-or-are-they-local-to-t
9421 ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd) {
9422 	msghdr msg;
9423 	iovec[1] iov;
9424 
9425 	version(OSX) {
9426 		//msg.msg_accrights = cast(cattr_t) &sendfd;
9427 		//msg.msg_accrightslen = int.sizeof;
9428 	} else version(Android) {
9429 	} else {
9430 		union ControlUnion {
9431 			cmsghdr cm;
9432 			char[CMSG_SPACE(int.sizeof)] control;
9433 		}
9434 
9435 		ControlUnion control_un;
9436 		cmsghdr* cmptr;
9437 
9438 		msg.msg_control = control_un.control.ptr;
9439 		msg.msg_controllen = control_un.control.length;
9440 
9441 		cmptr = CMSG_FIRSTHDR(&msg);
9442 		cmptr.cmsg_len = CMSG_LEN(int.sizeof);
9443 		cmptr.cmsg_level = SOL_SOCKET;
9444 		cmptr.cmsg_type = SCM_RIGHTS;
9445 		*(cast(int *) CMSG_DATA(cmptr)) = sendfd;
9446 	}
9447 
9448 	msg.msg_name = null;
9449 	msg.msg_namelen = 0;
9450 
9451 	iov[0].iov_base = ptr;
9452 	iov[0].iov_len = nbytes;
9453 	msg.msg_iov = iov.ptr;
9454 	msg.msg_iovlen = 1;
9455 
9456 	return sendmsg(fd, &msg, 0);
9457 }
9458 
9459 version(with_sendfd)
9460 // copied from the web and ported from C
9461 ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
9462 	msghdr msg;
9463 	iovec[1] iov;
9464 	ssize_t n;
9465 	int newfd;
9466 
9467 	version(OSX) {
9468 		//msg.msg_accrights = cast(cattr_t) recvfd;
9469 		//msg.msg_accrightslen = int.sizeof;
9470 	} else version(Android) {
9471 	} else {
9472 		union ControlUnion {
9473 			cmsghdr cm;
9474 			char[CMSG_SPACE(int.sizeof)] control;
9475 		}
9476 		ControlUnion control_un;
9477 		cmsghdr* cmptr;
9478 
9479 		msg.msg_control = control_un.control.ptr;
9480 		msg.msg_controllen = control_un.control.length;
9481 	}
9482 
9483 	msg.msg_name = null;
9484 	msg.msg_namelen = 0;
9485 
9486 	iov[0].iov_base = ptr;
9487 	iov[0].iov_len = nbytes;
9488 	msg.msg_iov = iov.ptr;
9489 	msg.msg_iovlen = 1;
9490 
9491 	if ( (n = recvmsg(fd, &msg, 0)) <= 0)
9492 		return n;
9493 
9494 	version(OSX) {
9495 		//if(msg.msg_accrightslen != int.sizeof)
9496 			//*recvfd = -1;
9497 	} else version(Android) {
9498 	} else {
9499 		if ( (cmptr = CMSG_FIRSTHDR(&msg)) != null &&
9500 				cmptr.cmsg_len == CMSG_LEN(int.sizeof)) {
9501 			if (cmptr.cmsg_level != SOL_SOCKET)
9502 				throw new Exception("control level != SOL_SOCKET");
9503 			if (cmptr.cmsg_type != SCM_RIGHTS)
9504 				throw new Exception("control type != SCM_RIGHTS");
9505 			*recvfd = *(cast(int *) CMSG_DATA(cmptr));
9506 		} else
9507 			*recvfd = -1;       /* descriptor was not passed */
9508 	}
9509 
9510 	return n;
9511 }
9512 /* end read_fd */
9513 
9514 
9515 /*
9516 	Event source stuff
9517 
9518 	The api is:
9519 
9520 	sendEvent(string url, string type, string data, int timeout = 60*10);
9521 
9522 	attachEventListener(string url, int fd, lastId)
9523 
9524 
9525 	It just sends to all attached listeners, and stores it until the timeout
9526 	for replaying via lastEventId.
9527 */
9528 
9529 /*
9530 	Session process stuff
9531 
9532 	it stores it all. the cgi object has a session object that can grab it
9533 
9534 	session may be done in the same process if possible, there is a version
9535 	switch to choose if you want to override.
9536 */
9537 
9538 struct DispatcherDefinition(alias dispatchHandler, DispatcherDetails = typeof(null)) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
9539 	alias handler = dispatchHandler;
9540 	string urlPrefix;
9541 	bool rejectFurther;
9542 	immutable(DispatcherDetails) details;
9543 }
9544 
9545 private string urlify(string name) pure {
9546 	return beautify(name, '-', true);
9547 }
9548 
9549 private string beautify(string name, char space = ' ', bool allLowerCase = false) pure {
9550 	if(name == "id")
9551 		return allLowerCase ? name : "ID";
9552 
9553 	char[160] buffer;
9554 	int bufferIndex = 0;
9555 	bool shouldCap = true;
9556 	bool shouldSpace;
9557 	bool lastWasCap;
9558 	foreach(idx, char ch; name) {
9559 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
9560 
9561 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
9562 			if(lastWasCap) {
9563 				// two caps in a row, don't change. Prolly acronym.
9564 			} else {
9565 				if(idx)
9566 					shouldSpace = true; // new word, add space
9567 			}
9568 
9569 			lastWasCap = true;
9570 		} else {
9571 			lastWasCap = false;
9572 		}
9573 
9574 		if(shouldSpace) {
9575 			buffer[bufferIndex++] = space;
9576 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
9577 			shouldSpace = false;
9578 		}
9579 		if(shouldCap) {
9580 			if(ch >= 'a' && ch <= 'z')
9581 				ch -= 32;
9582 			shouldCap = false;
9583 		}
9584 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
9585 			ch += 32;
9586 		buffer[bufferIndex++] = ch;
9587 	}
9588 	return buffer[0 .. bufferIndex].idup;
9589 }
9590 
9591 /*
9592 string urlFor(alias func)() {
9593 	return __traits(identifier, func);
9594 }
9595 */
9596 
9597 /++
9598 	UDA: The name displayed to the user in auto-generated HTML.
9599 
9600 	Default is `beautify(identifier)`.
9601 +/
9602 struct DisplayName {
9603 	string name;
9604 }
9605 
9606 /++
9607 	UDA: The name used in the URL or web parameter.
9608 
9609 	Default is `urlify(identifier)` for functions and `identifier` for parameters and data members.
9610 +/
9611 struct UrlName {
9612 	string name;
9613 }
9614 
9615 /++
9616 	UDA: default format to respond for this method
9617 +/
9618 struct DefaultFormat { string value; }
9619 
9620 class MissingArgumentException : Exception {
9621 	string functionName;
9622 	string argumentName;
9623 	string argumentType;
9624 
9625 	this(string functionName, string argumentName, string argumentType, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
9626 		this.functionName = functionName;
9627 		this.argumentName = argumentName;
9628 		this.argumentType = argumentType;
9629 
9630 		super("Missing Argument: " ~ this.argumentName, file, line, next);
9631 	}
9632 }
9633 
9634 /++
9635 	You can throw this from an api handler to indicate a 404 response. This is done by the presentExceptionAsHtml function in the presenter.
9636 
9637 	History:
9638 		Added December 15, 2021 (dub v10.5)
9639 +/
9640 class ResourceNotFoundException : Exception {
9641 	string resourceType;
9642 	string resourceId;
9643 
9644 	this(string resourceType, string resourceId, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
9645 		this.resourceType = resourceType;
9646 		this.resourceId = resourceId;
9647 
9648 		super("Resource not found: " ~ resourceType ~ " " ~ resourceId, file, line, next);
9649 	}
9650 
9651 }
9652 
9653 /++
9654 	This can be attached to any constructor or function called from the cgi system.
9655 
9656 	If it is present, the function argument can NOT be set from web params, but instead
9657 	is set to the return value of the given `func`.
9658 
9659 	If `func` can take a parameter of type [Cgi], it will be passed the one representing
9660 	the current request. Otherwise, it must take zero arguments.
9661 
9662 	Any params in your function of type `Cgi` are automatically assumed to take the cgi object
9663 	for the connection. Any of type [Session] (with an argument) is	also assumed to come from
9664 	the cgi object.
9665 
9666 	const arguments are also supported.
9667 +/
9668 struct ifCalledFromWeb(alias func) {}
9669 
9670 // it only looks at query params for GET requests, the rest must be in the body for a function argument.
9671 auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
9672 
9673 	// FIXME: any array of structs should also be settable or gettable from csv as well.
9674 
9675 	// FIXME: think more about checkboxes and bools.
9676 
9677 	import std.traits;
9678 
9679 	Parameters!method params;
9680 	alias idents = ParameterIdentifierTuple!method;
9681 	alias defaults = ParameterDefaults!method;
9682 
9683 	const(string)[] names;
9684 	const(string)[] values;
9685 
9686 	// first, check for missing arguments and initialize to defaults if necessary
9687 
9688 	static if(is(typeof(method) P == __parameters))
9689 	foreach(idx, param; P) {{
9690 		// see: mustNotBeSetFromWebParams
9691 		static if(is(param : Cgi)) {
9692 			static assert(!is(param == immutable));
9693 			cast() params[idx] = cgi;
9694 		} else static if(is(param == Session!D, D)) {
9695 			static assert(!is(param == immutable));
9696 			cast() params[idx] = cgi.getSessionObject!D();
9697 		} else {
9698 			bool populated;
9699 			foreach(uda; __traits(getAttributes, P[idx .. idx + 1])) {
9700 				static if(is(uda == ifCalledFromWeb!func, alias func)) {
9701 					static if(is(typeof(func(cgi))))
9702 						params[idx] = func(cgi);
9703 					else
9704 						params[idx] = func();
9705 
9706 					populated = true;
9707 				}
9708 			}
9709 
9710 			if(!populated) {
9711 				static if(__traits(compiles, { params[idx] = param.getAutomaticallyForCgi(cgi); } )) {
9712 					params[idx] = param.getAutomaticallyForCgi(cgi);
9713 					populated = true;
9714 				}
9715 			}
9716 
9717 			if(!populated) {
9718 				auto ident = idents[idx];
9719 				if(cgi.requestMethod == Cgi.RequestMethod.GET) {
9720 					if(ident !in cgi.get) {
9721 						static if(is(defaults[idx] == void)) {
9722 							static if(is(param == bool))
9723 								params[idx] = false;
9724 							else
9725 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
9726 						} else
9727 							params[idx] = defaults[idx];
9728 					}
9729 				} else {
9730 					if(ident !in cgi.post) {
9731 						static if(is(defaults[idx] == void)) {
9732 							static if(is(param == bool))
9733 								params[idx] = false;
9734 							else
9735 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
9736 						} else
9737 							params[idx] = defaults[idx];
9738 					}
9739 				}
9740 			}
9741 		}
9742 	}}
9743 
9744 	// second, parse the arguments in order to build up arrays, etc.
9745 
9746 	static bool setVariable(T)(string name, string paramName, T* what, string value) {
9747 		static if(is(T == struct)) {
9748 			if(name == paramName) {
9749 				*what = T.init;
9750 				return true;
9751 			} else {
9752 				// could be a child. gonna allow either obj.field OR obj[field]
9753 
9754 				string afterName;
9755 
9756 				if(name[paramName.length] == '[') {
9757 					int count = 1;
9758 					auto idx = paramName.length + 1;
9759 					while(idx < name.length && count > 0) {
9760 						if(name[idx] == '[')
9761 							count++;
9762 						else if(name[idx] == ']') {
9763 							count--;
9764 							if(count == 0) break;
9765 						}
9766 						idx++;
9767 					}
9768 
9769 					if(idx == name.length)
9770 						return false; // malformed
9771 
9772 					auto insideBrackets = name[paramName.length + 1 .. idx];
9773 					afterName = name[idx + 1 .. $];
9774 
9775 					name = name[0 .. paramName.length];
9776 
9777 					paramName = insideBrackets;
9778 
9779 				} else if(name[paramName.length] == '.') {
9780 					paramName = name[paramName.length + 1 .. $];
9781 					name = paramName;
9782 					int p = 0;
9783 					foreach(ch; paramName) {
9784 						if(ch == '.' || ch == '[')
9785 							break;
9786 						p++;
9787 					}
9788 
9789 					afterName = paramName[p .. $];
9790 					paramName = paramName[0 .. p];
9791 				} else {
9792 					return false;
9793 				}
9794 
9795 				if(paramName.length)
9796 				// set the child member
9797 				switch(paramName) {
9798 					foreach(idx, memberName; __traits(allMembers, T))
9799 					static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
9800 						// data member!
9801 						case memberName:
9802 							return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
9803 					}
9804 					default:
9805 						// ok, not a member
9806 				}
9807 			}
9808 
9809 			return false;
9810 		} else static if(is(T == enum)) {
9811 			*what = to!T(value);
9812 			return true;
9813 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
9814 			*what = to!T(value);
9815 			return true;
9816 		} else static if(is(T == bool)) {
9817 			*what = value == "1" || value == "yes" || value == "t" || value == "true" || value == "on";
9818 			return true;
9819 		} else static if(is(T == K[], K)) {
9820 			K tmp;
9821 			if(name == paramName) {
9822 				// direct - set and append
9823 				if(setVariable(name, paramName, &tmp, value)) {
9824 					(*what) ~= tmp;
9825 					return true;
9826 				} else {
9827 					return false;
9828 				}
9829 			} else {
9830 				// child, append to last element
9831 				// FIXME: what about range violations???
9832 				auto ptr = &(*what)[(*what).length - 1];
9833 				return setVariable(name, paramName, ptr, value);
9834 
9835 			}
9836 		} else static if(is(T == V[K], K, V)) {
9837 			// assoc array, name[key] is valid
9838 			if(name == paramName) {
9839 				// no action necessary
9840 				return true;
9841 			} else if(name[paramName.length] == '[') {
9842 				int count = 1;
9843 				auto idx = paramName.length + 1;
9844 				while(idx < name.length && count > 0) {
9845 					if(name[idx] == '[')
9846 						count++;
9847 					else if(name[idx] == ']') {
9848 						count--;
9849 						if(count == 0) break;
9850 					}
9851 					idx++;
9852 				}
9853 				if(idx == name.length)
9854 					return false; // malformed
9855 
9856 				auto insideBrackets = name[paramName.length + 1 .. idx];
9857 				auto afterName = name[idx + 1 .. $];
9858 
9859 				auto k = to!K(insideBrackets);
9860 				V v;
9861 				if(auto ptr = k in *what)
9862 					v = *ptr;
9863 
9864 				name = name[0 .. paramName.length];
9865 				//writeln(name, afterName, " ", paramName);
9866 
9867 				auto ret = setVariable(name ~ afterName, paramName, &v, value);
9868 				if(ret) {
9869 					(*what)[k] = v;
9870 					return true;
9871 				}
9872 			}
9873 
9874 			return false;
9875 		} else {
9876 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
9877 		}
9878 
9879 		//return false;
9880 	}
9881 
9882 	void setArgument(string name, string value) {
9883 		int p;
9884 		foreach(ch; name) {
9885 			if(ch == '.' || ch == '[')
9886 				break;
9887 			p++;
9888 		}
9889 
9890 		auto paramName = name[0 .. p];
9891 
9892 		sw: switch(paramName) {
9893 			static if(is(typeof(method) P == __parameters))
9894 			foreach(idx, param; P) {
9895 				static if(mustNotBeSetFromWebParams!(P[idx], __traits(getAttributes, P[idx .. idx + 1]))) {
9896 					// cannot be set from the outside
9897 				} else {
9898 					case idents[idx]:
9899 						static if(is(param == Cgi.UploadedFile)) {
9900 							params[idx] = cgi.files[name];
9901 						} else static if(is(param : const Cgi.UploadedFile[])) {
9902 							(cast() params[idx]) = cgi.filesArray[name];
9903 						} else {
9904 							setVariable(name, paramName, &params[idx], value);
9905 						}
9906 					break sw;
9907 				}
9908 			}
9909 			default:
9910 				// ignore; not relevant argument
9911 		}
9912 	}
9913 
9914 	if(cgi.requestMethod == Cgi.RequestMethod.GET) {
9915 		names = cgi.allGetNamesInOrder;
9916 		values = cgi.allGetValuesInOrder;
9917 	} else {
9918 		names = cgi.allPostNamesInOrder;
9919 		values = cgi.allPostValuesInOrder;
9920 	}
9921 
9922 	foreach(idx, name; names) {
9923 		setArgument(name, values[idx]);
9924 	}
9925 
9926 	static if(is(ReturnType!method == void)) {
9927 		typeof(null) ret;
9928 		dg(params);
9929 	} else {
9930 		auto ret = dg(params);
9931 	}
9932 
9933 	// FIXME: format return values
9934 	// options are: json, html, csv.
9935 	// also may need to wrap in envelope format: none, html, or json.
9936 	return ret;
9937 }
9938 
9939 private bool mustNotBeSetFromWebParams(T, attrs...)() {
9940 	static if(is(T : const(Cgi))) {
9941 		return true;
9942 	} else static if(is(T : const(Session!D), D)) {
9943 		return true;
9944 	} else static if(__traits(compiles, T.getAutomaticallyForCgi(Cgi.init))) {
9945 		return true;
9946 	} else {
9947 		foreach(uda; attrs)
9948 			static if(is(uda == ifCalledFromWeb!func, alias func))
9949 				return true;
9950 		return false;
9951 	}
9952 }
9953 
9954 private bool hasIfCalledFromWeb(attrs...)() {
9955 	foreach(uda; attrs)
9956 		static if(is(uda == ifCalledFromWeb!func, alias func))
9957 			return true;
9958 	return false;
9959 }
9960 
9961 /++
9962 	Implies POST path for the thing itself, then GET will get the automatic form.
9963 
9964 	The given customizer, if present, will be called as a filter on the Form object.
9965 
9966 	History:
9967 		Added December 27, 2020
9968 +/
9969 template AutomaticForm(alias customizer) { }
9970 
9971 /++
9972 	This is meant to be returned by a function that takes a form POST submission. You
9973 	want to set the url of the new resource it created, which is set as the http
9974 	Location header for a "201 Created" result, and you can also set a separate
9975 	destination for browser users, which it sets via a "Refresh" header.
9976 
9977 	The `resourceRepresentation` should generally be the thing you just created, and
9978 	it will be the body of the http response when formatted through the presenter.
9979 	The exact thing is up to you - it could just return an id, or the whole object, or
9980 	perhaps a partial object.
9981 
9982 	Examples:
9983 	---
9984 	class Test : WebObject {
9985 		@(Cgi.RequestMethod.POST)
9986 		CreatedResource!int makeThing(string value) {
9987 			return CreatedResource!int(value.to!int, "/resources/id");
9988 		}
9989 	}
9990 	---
9991 
9992 	History:
9993 		Added December 18, 2021
9994 +/
9995 struct CreatedResource(T) {
9996 	static if(!is(T == void))
9997 		T resourceRepresentation;
9998 	string resourceUrl;
9999 	string refreshUrl;
10000 }
10001 
10002 /+
10003 /++
10004 	This can be attached as a UDA to a handler to add a http Refresh header on a
10005 	successful run. (It will not be attached if the function throws an exception.)
10006 	This will refresh the browser the given number of seconds after the page loads,
10007 	to the url returned by `urlFunc`, which can be either a static function or a
10008 	member method of the current handler object.
10009 
10010 	You might use this for a POST handler that is normally used from ajax, but you
10011 	want it to degrade gracefully to a temporarily flashed message before reloading
10012 	the main page.
10013 
10014 	History:
10015 		Added December 18, 2021
10016 +/
10017 struct Refresh(alias urlFunc) {
10018 	int waitInSeconds;
10019 
10020 	string url() {
10021 		static if(__traits(isStaticFunction, urlFunc))
10022 			return urlFunc();
10023 		else static if(is(urlFunc : string))
10024 			return urlFunc;
10025 	}
10026 }
10027 +/
10028 
10029 /+
10030 /++
10031 	Sets a filter to be run before
10032 
10033 	A before function can do validations of params and log and stop the function from running.
10034 +/
10035 template Before(alias b) {}
10036 template After(alias b) {}
10037 +/
10038 
10039 /+
10040 	Argument conversions: for the most part, it is to!Thing(string).
10041 
10042 	But arrays and structs are a bit different. Arrays come from the cgi array. Thus
10043 	they are passed
10044 
10045 	arr=foo&arr=bar <-- notice the same name.
10046 
10047 	Structs are first declared with an empty thing, then have their members set individually,
10048 	with dot notation. The members are not required, just the initial declaration.
10049 
10050 	struct Foo {
10051 		int a;
10052 		string b;
10053 	}
10054 	void test(Foo foo){}
10055 
10056 	foo&foo.a=5&foo.b=str <-- the first foo declares the arg, the others set the members
10057 
10058 	Arrays of structs use this declaration.
10059 
10060 	void test(Foo[] foo) {}
10061 
10062 	foo&foo.a=5&foo.b=bar&foo&foo.a=9
10063 
10064 	You can use a hidden input field in HTML forms to achieve this. The value of the naked name
10065 	declaration is ignored.
10066 
10067 	Mind that order matters! The declaration MUST come first in the string.
10068 
10069 	Arrays of struct members follow this rule recursively.
10070 
10071 	struct Foo {
10072 		int[] a;
10073 	}
10074 
10075 	foo&foo.a=1&foo.a=2&foo&foo.a=1
10076 
10077 
10078 	Associative arrays are formatted with brackets, after a declaration, like structs:
10079 
10080 	foo&foo[key]=value&foo[other_key]=value
10081 
10082 
10083 	Note: for maximum compatibility with outside code, keep your types simple. Some libraries
10084 	do not support the strict ordering requirements to work with these struct protocols.
10085 
10086 	FIXME: also perhaps accept application/json to better work with outside trash.
10087 
10088 
10089 	Return values are also auto-formatted according to user-requested type:
10090 		for json, it loops over and converts.
10091 		for html, basic types are strings. Arrays are <ol>. Structs are <dl>. Arrays of structs are tables!
10092 +/
10093 
10094 /++
10095 	A web presenter is responsible for rendering things to HTML to be usable
10096 	in a web browser.
10097 
10098 	They are passed as template arguments to the base classes of [WebObject]
10099 
10100 	Responsible for displaying stuff as HTML. You can put this into your own aggregate
10101 	and override it. Use forwarding and specialization to customize it.
10102 
10103 	When you inherit from it, pass your own class as the CRTP argument. This lets the base
10104 	class templates and your overridden templates work with each other.
10105 
10106 	---
10107 	class MyPresenter : WebPresenter!(MyPresenter) {
10108 		@Override
10109 		void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret, typeof(null) meta) {
10110 			// present the CustomType
10111 		}
10112 		@Override
10113 		void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
10114 			// handle everything else via the super class, which will call
10115 			// back to your class when appropriate
10116 			super.presentSuccessfulReturnAsHtml(cgi, ret);
10117 		}
10118 	}
10119 	---
10120 
10121 	The meta argument in there can be overridden by your own facility.
10122 
10123 +/
10124 class WebPresenter(CRTP) {
10125 
10126 	/// A UDA version of the built-in `override`, to be used for static template polymorphism
10127 	/// If you override a plain method, use `override`. If a template, use `@Override`.
10128 	enum Override;
10129 
10130 	string script() {
10131 		return `
10132 		`;
10133 	}
10134 
10135 	string style() {
10136 		return `
10137 			:root {
10138 				--mild-border: #ccc;
10139 				--middle-border: #999;
10140 				--accent-color: #f2f2f2;
10141 				--sidebar-color: #fefefe;
10142 			}
10143 		` ~ genericFormStyling() ~ genericSiteStyling();
10144 	}
10145 
10146 	string genericFormStyling() {
10147 		return
10148 q"css
10149 			table.automatic-data-display {
10150 				border-collapse: collapse;
10151 				border: solid 1px var(--mild-border);
10152 			}
10153 
10154 			table.automatic-data-display td {
10155 				vertical-align: top;
10156 				border: solid 1px var(--mild-border);
10157 				padding: 2px 4px;
10158 			}
10159 
10160 			table.automatic-data-display th {
10161 				border: solid 1px var(--mild-border);
10162 				border-bottom: solid 1px var(--middle-border);
10163 				padding: 2px 4px;
10164 			}
10165 
10166 			ol.automatic-data-display {
10167 				margin: 0px;
10168 				/*
10169 				list-style-position: inside;
10170 				padding: 0px;
10171 				*/
10172 			}
10173 
10174 			dl.automatic-data-display {
10175 
10176 			}
10177 
10178 			.automatic-form {
10179 				max-width: 600px;
10180 			}
10181 
10182 			.form-field {
10183 				margin: 0.5em;
10184 				padding-left: 0.5em;
10185 			}
10186 
10187 			.label-text {
10188 				display: block;
10189 				font-weight: bold;
10190 				margin-left: -0.5em;
10191 			}
10192 
10193 			.submit-button-holder {
10194 				padding-left: 2em;
10195 			}
10196 
10197 			.add-array-button {
10198 
10199 			}
10200 css";
10201 	}
10202 
10203 	string genericSiteStyling() {
10204 		return
10205 q"css
10206 			* { box-sizing: border-box; }
10207 			html, body { margin: 0px; }
10208 			body {
10209 				font-family: sans-serif;
10210 			}
10211 			header {
10212 				background: var(--accent-color);
10213 				height: 64px;
10214 			}
10215 			footer {
10216 				background: var(--accent-color);
10217 				height: 64px;
10218 			}
10219 			#site-container {
10220 				display: flex;
10221 				flex-wrap: wrap;
10222 			}
10223 			main {
10224 				flex: 1 1 auto;
10225 				order: 2;
10226 				min-height: calc(100vh - 64px - 64px);
10227 				min-width: 80ch;
10228 				padding: 4px;
10229 				padding-left: 1em;
10230 			}
10231 			#sidebar {
10232 				flex: 0 0 16em;
10233 				order: 1;
10234 				background: var(--sidebar-color);
10235 			}
10236 css";
10237 	}
10238 
10239 	import arsd.dom;
10240 	Element htmlContainer() {
10241 		auto document = new Document(q"html
10242 <!DOCTYPE html>
10243 <html class="no-script">
10244 <head>
10245 	<script>document.documentElement.classList.remove("no-script");</script>
10246 	<style>.no-script requires-script { display: none; }</style>
10247 	<title>D Application</title>
10248 	<meta name="viewport" content="initial-scale=1, width=device-width" />
10249 	<link rel="stylesheet" href="style.css" />
10250 </head>
10251 <body>
10252 	<header></header>
10253 	<div id="site-container">
10254 		<main></main>
10255 		<div id="sidebar"></div>
10256 	</div>
10257 	<footer></footer>
10258 	<script src="script.js"></script>
10259 </body>
10260 </html>
10261 html", true, true);
10262 
10263 		return document.requireSelector("main");
10264 	}
10265 
10266 	/// Renders a response as an HTTP error with associated html body
10267 	void renderBasicError(Cgi cgi, int httpErrorCode) {
10268 		cgi.setResponseStatus(getHttpCodeText(httpErrorCode));
10269 		auto c = htmlContainer();
10270 		c.innerText = getHttpCodeText(httpErrorCode);
10271 		cgi.setResponseContentType("text/html; charset=utf-8");
10272 		cgi.write(c.parentDocument.toString(), true);
10273 	}
10274 
10275 	template methodMeta(alias method) {
10276 		enum methodMeta = null;
10277 	}
10278 
10279 	void presentSuccessfulReturn(T, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10280 		switch(format) {
10281 			case "html":
10282 				(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret, meta);
10283 			break;
10284 			case "json":
10285 				import arsd.jsvar;
10286 				static if(is(typeof(ret) == MultipleResponses!Types, Types...)) {
10287 					var json;
10288 					foreach(index, type; Types) {
10289 						if(ret.contains == index)
10290 							json = ret.payload[index];
10291 					}
10292 				} else {
10293 					var json = ret;
10294 				}
10295 				var envelope = json; // var.emptyObject;
10296 				/*
10297 				envelope.success = true;
10298 				envelope.result = json;
10299 				envelope.error = null;
10300 				*/
10301 				cgi.setResponseContentType("application/json");
10302 				cgi.write(envelope.toJson(), true);
10303 			break;
10304 			default:
10305 				cgi.setResponseStatus("406 Not Acceptable"); // not exactly but sort of.
10306 		}
10307 	}
10308 
10309 	/// typeof(null) (which is also used to represent functions returning `void`) do nothing
10310 	/// in the default presenter - allowing the function to have full low-level control over the
10311 	/// response.
10312 	void presentSuccessfulReturn(T : typeof(null), Meta)(Cgi cgi, T ret, Meta meta, string format) {
10313 		// nothing intentionally!
10314 	}
10315 
10316 	/// Redirections are forwarded to [Cgi.setResponseLocation]
10317 	void presentSuccessfulReturn(T : Redirection, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10318 		cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
10319 	}
10320 
10321 	/// [CreatedResource]s send code 201 and will set the given urls, then present the given representation.
10322 	void presentSuccessfulReturn(T : CreatedResource!R, Meta, R)(Cgi cgi, T ret, Meta meta, string format) {
10323 		cgi.setResponseStatus(getHttpCodeText(201));
10324 		if(ret.resourceUrl.length)
10325 			cgi.header("Location: " ~ ret.resourceUrl);
10326 		if(ret.refreshUrl.length)
10327 			cgi.header("Refresh: 0;" ~ ret.refreshUrl);
10328 		static if(!is(R == void))
10329 			presentSuccessfulReturn(cgi, ret.resourceRepresentation, meta, format);
10330 	}
10331 
10332 	/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
10333 	void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
10334 		bool outputted = false;
10335 		foreach(index, type; Types) {
10336 			if(ret.contains == index) {
10337 				assert(!outputted);
10338 				outputted = true;
10339 				(cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
10340 			}
10341 		}
10342 		if(!outputted)
10343 			assert(0);
10344 	}
10345 
10346 	/++
10347 		An instance of the [arsd.dom.FileResource] interface has its own content type; assume it is a download of some sort if the filename member is non-null of the FileResource interface.
10348 	+/
10349 	void presentSuccessfulReturn(T : FileResource, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10350 		cgi.setCache(true); // not necessarily true but meh
10351 		if(auto fn = ret.filename()) {
10352 			cgi.header("Content-Disposition: attachment; filename="~fn~";");
10353 		}
10354 		cgi.setResponseContentType(ret.contentType);
10355 		cgi.write(ret.getData(), true);
10356 	}
10357 
10358 	/// And the default handler for HTML will call [formatReturnValueAsHtml] and place it inside the [htmlContainer].
10359 	void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
10360 		auto container = this.htmlContainer();
10361 		container.appendChild(formatReturnValueAsHtml(ret));
10362 		cgi.write(container.parentDocument.toString(), true);
10363 	}
10364 
10365 	/++
10366 
10367 		History:
10368 			Added January 23, 2023 (dub v11.0)
10369 	+/
10370 	void presentExceptionalReturn(Meta)(Cgi cgi, Throwable t, Meta meta, string format) {
10371 		switch(format) {
10372 			case "html":
10373 				presentExceptionAsHtml(cgi, t, meta);
10374 			break;
10375 			case "json":
10376 				presentExceptionAsJsonImpl(cgi, t);
10377 			break;
10378 			default:
10379 		}
10380 	}
10381 
10382 	private void presentExceptionAsJsonImpl()(Cgi cgi, Throwable t) {
10383 		cgi.setResponseStatus("500 Internal Server Error");
10384 		cgi.setResponseContentType("application/json");
10385 		import arsd.jsvar;
10386 		var v = var.emptyObject;
10387 		v.type = typeid(t).toString;
10388 		v.msg = t.msg;
10389 		v.fullString = t.toString();
10390 		cgi.write(v.toJson(), true);
10391 	}
10392 
10393 
10394 	/++
10395 		If you override this, you will need to cast the exception type `t` dynamically,
10396 		but can then use the template arguments here to refer back to the function.
10397 
10398 		`func` is an alias to the method itself, and `dg` is a callable delegate to the same
10399 		method on the live object. You could, in theory, change arguments and retry, but I
10400 		provide that information mostly with the expectation that you will use them to make
10401 		useful forms or richer error messages for the user.
10402 
10403 		History:
10404 			BREAKING CHANGE on January 23, 2023 (v11.0 ): it previously took an `alias func` and `T dg` to call the function again.
10405 			I removed this in favor of a `Meta` param.
10406 
10407 			Before: `void presentExceptionAsHtml(alias func, T)(Cgi cgi, Throwable t, T dg)`
10408 
10409 			After: `void presentExceptionAsHtml(Meta)(Cgi cgi, Throwable t, Meta meta)`
10410 
10411 			If you used the func for something, move that something into your `methodMeta` template.
10412 
10413 			What is the benefit of this change? Somewhat smaller executables and faster builds thanks to more reused functions, together with
10414 			enabling an easier implementation of [presentExceptionalReturn].
10415 	+/
10416 	void presentExceptionAsHtml(Meta)(Cgi cgi, Throwable t, Meta meta) {
10417 		Form af;
10418 		/+
10419 		foreach(attr; __traits(getAttributes, func)) {
10420 			static if(__traits(isSame, attr, AutomaticForm)) {
10421 				af = createAutomaticFormForFunction!(func)(dg);
10422 			}
10423 		}
10424 		+/
10425 		presentExceptionAsHtmlImpl(cgi, t, af);
10426 	}
10427 
10428 	void presentExceptionAsHtmlImpl(Cgi cgi, Throwable t, Form automaticForm) {
10429 		if(auto e = cast(ResourceNotFoundException) t) {
10430 			auto container = this.htmlContainer();
10431 
10432 			container.addChild("p", e.msg);
10433 
10434 			if(!cgi.outputtedResponseData)
10435 				cgi.setResponseStatus("404 Not Found");
10436 			cgi.write(container.parentDocument.toString(), true);
10437 		} else if(auto mae = cast(MissingArgumentException) t) {
10438 			if(automaticForm is null)
10439 				goto generic;
10440 			auto container = this.htmlContainer();
10441 			if(cgi.requestMethod == Cgi.RequestMethod.POST)
10442 				container.appendChild(Element.make("p", "Argument `" ~ mae.argumentName ~ "` of type `" ~ mae.argumentType ~ "` is missing"));
10443 			container.appendChild(automaticForm);
10444 
10445 			cgi.write(container.parentDocument.toString(), true);
10446 		} else {
10447 			generic:
10448 			auto container = this.htmlContainer();
10449 
10450 			// import std.stdio; writeln(t.toString());
10451 
10452 			container.appendChild(exceptionToElement(t));
10453 
10454 			container.addChild("h4", "GET");
10455 			foreach(k, v; cgi.get) {
10456 				auto deets = container.addChild("details");
10457 				deets.addChild("summary", k);
10458 				deets.addChild("div", v);
10459 			}
10460 
10461 			container.addChild("h4", "POST");
10462 			foreach(k, v; cgi.post) {
10463 				auto deets = container.addChild("details");
10464 				deets.addChild("summary", k);
10465 				deets.addChild("div", v);
10466 			}
10467 
10468 
10469 			if(!cgi.outputtedResponseData)
10470 				cgi.setResponseStatus("500 Internal Server Error");
10471 			cgi.write(container.parentDocument.toString(), true);
10472 		}
10473 	}
10474 
10475 	Element exceptionToElement(Throwable t) {
10476 		auto div = Element.make("div");
10477 		div.addClass("exception-display");
10478 
10479 		div.addChild("p", t.msg);
10480 		div.addChild("p", "Inner code origin: " ~ typeid(t).name ~ "@" ~ t.file ~ ":" ~ to!string(t.line));
10481 
10482 		auto pre = div.addChild("pre");
10483 		string s;
10484 		s = t.toString();
10485 		Element currentBox;
10486 		bool on = false;
10487 		foreach(line; s.splitLines) {
10488 			if(!on && line.startsWith("-----"))
10489 				on = true;
10490 			if(!on) continue;
10491 			if(line.indexOf("arsd/") != -1) {
10492 				if(currentBox is null) {
10493 					currentBox = pre.addChild("details");
10494 					currentBox.addChild("summary", "Framework code");
10495 				}
10496 				currentBox.addChild("span", line ~ "\n");
10497 			} else {
10498 				pre.addChild("span", line ~ "\n");
10499 				currentBox = null;
10500 			}
10501 		}
10502 
10503 		return div;
10504 	}
10505 
10506 	/++
10507 		Returns an element for a particular type
10508 	+/
10509 	Element elementFor(T)(string displayName, string name, Element function() udaSuggestion) {
10510 		import std.traits;
10511 
10512 		auto div = Element.make("div");
10513 		div.addClass("form-field");
10514 
10515 		static if(is(T : const Cgi.UploadedFile)) {
10516 			Element lbl;
10517 			if(displayName !is null) {
10518 				lbl = div.addChild("label");
10519 				lbl.addChild("span", displayName, "label-text");
10520 				lbl.appendText(" ");
10521 			} else {
10522 				lbl = div;
10523 			}
10524 			auto i = lbl.addChild("input", name);
10525 			i.attrs.name = name;
10526 			i.attrs.type = "file";
10527 			i.attrs.multiple = "multiple";
10528 		} else static if(is(T == Cgi.UploadedFile)) {
10529 			Element lbl;
10530 			if(displayName !is null) {
10531 				lbl = div.addChild("label");
10532 				lbl.addChild("span", displayName, "label-text");
10533 				lbl.appendText(" ");
10534 			} else {
10535 				lbl = div;
10536 			}
10537 			auto i = lbl.addChild("input", name);
10538 			i.attrs.name = name;
10539 			i.attrs.type = "file";
10540 		} else static if(is(T == enum)) {
10541 			Element lbl;
10542 			if(displayName !is null) {
10543 				lbl = div.addChild("label");
10544 				lbl.addChild("span", displayName, "label-text");
10545 				lbl.appendText(" ");
10546 			} else {
10547 				lbl = div;
10548 			}
10549 			auto i = lbl.addChild("select", name);
10550 			i.attrs.name = name;
10551 
10552 			foreach(memberName; __traits(allMembers, T))
10553 				i.addChild("option", memberName);
10554 
10555 		} else static if(is(T == struct)) {
10556 			if(displayName !is null)
10557 				div.addChild("span", displayName, "label-text");
10558 			auto fieldset = div.addChild("fieldset");
10559 			fieldset.addChild("legend", beautify(T.stringof)); // FIXME
10560 			fieldset.addChild("input", name);
10561 			foreach(idx, memberName; __traits(allMembers, T))
10562 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
10563 				fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName, null /* FIXME: pull off the UDA */));
10564 			}
10565 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
10566 			Element lbl;
10567 			if(displayName !is null) {
10568 				lbl = div.addChild("label");
10569 				lbl.addChild("span", displayName, "label-text");
10570 				lbl.appendText(" ");
10571 			} else {
10572 				lbl = div;
10573 			}
10574 			Element i;
10575 			if(udaSuggestion) {
10576 				i = udaSuggestion();
10577 				lbl.appendChild(i);
10578 			} else {
10579 				i = lbl.addChild("input", name);
10580 			}
10581 			i.attrs.name = name;
10582 			static if(isSomeString!T)
10583 				i.attrs.type = "text";
10584 			else
10585 				i.attrs.type = "number";
10586 			if(i.tagName == "textarea")
10587 				i.textContent = to!string(T.init);
10588 			else
10589 				i.attrs.value = to!string(T.init);
10590 		} else static if(is(T == bool)) {
10591 			Element lbl;
10592 			if(displayName !is null) {
10593 				lbl = div.addChild("label");
10594 				lbl.addChild("span", displayName, "label-text");
10595 				lbl.appendText(" ");
10596 			} else {
10597 				lbl = div;
10598 			}
10599 			auto i = lbl.addChild("input", name);
10600 			i.attrs.type = "checkbox";
10601 			i.attrs.value = "true";
10602 			i.attrs.name = name;
10603 		} else static if(is(T == K[], K)) {
10604 			auto templ = div.addChild("template");
10605 			templ.appendChild(elementFor!(K)(null, name, null /* uda??*/));
10606 			if(displayName !is null)
10607 				div.addChild("span", displayName, "label-text");
10608 			auto btn = div.addChild("button");
10609 			btn.addClass("add-array-button");
10610 			btn.attrs.type = "button";
10611 			btn.innerText = "Add";
10612 			btn.attrs.onclick = q{
10613 				var a = document.importNode(this.parentNode.firstChild.content, true);
10614 				this.parentNode.insertBefore(a, this);
10615 			};
10616 		} else static if(is(T == V[K], K, V)) {
10617 			div.innerText = "assoc array not implemented for automatic form at this time";
10618 		} else {
10619 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
10620 		}
10621 
10622 
10623 		return div;
10624 	}
10625 
10626 	/// creates a form for gathering the function's arguments
10627 	Form createAutomaticFormForFunction(alias method, T)(T dg) {
10628 
10629 		auto form = cast(Form) Element.make("form");
10630 
10631 		form.method = "POST"; // FIXME
10632 
10633 		form.addClass("automatic-form");
10634 
10635 		string formDisplayName = beautify(__traits(identifier, method));
10636 		foreach(attr; __traits(getAttributes, method))
10637 			static if(is(typeof(attr) == DisplayName))
10638 				formDisplayName = attr.name;
10639 		form.addChild("h3", formDisplayName);
10640 
10641 		import std.traits;
10642 
10643 		//Parameters!method params;
10644 		//alias idents = ParameterIdentifierTuple!method;
10645 		//alias defaults = ParameterDefaults!method;
10646 
10647 		static if(is(typeof(method) P == __parameters))
10648 		foreach(idx, _; P) {{
10649 
10650 			alias param = P[idx .. idx + 1];
10651 
10652 			static if(!mustNotBeSetFromWebParams!(param[0], __traits(getAttributes, param))) {
10653 				string displayName = beautify(__traits(identifier, param));
10654 				Element function() element;
10655 				foreach(attr; __traits(getAttributes, param)) {
10656 					static if(is(typeof(attr) == DisplayName))
10657 						displayName = attr.name;
10658 					else static if(is(typeof(attr) : typeof(element))) {
10659 						element = attr;
10660 					}
10661 				}
10662 				auto i = form.appendChild(elementFor!(param)(displayName, __traits(identifier, param), element));
10663 				if(i.querySelector("input[type=file]") !is null)
10664 					form.setAttribute("enctype", "multipart/form-data");
10665 			}
10666 		}}
10667 
10668 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
10669 
10670 		return form;
10671 	}
10672 
10673 	/// creates a form for gathering object members (for the REST object thing right now)
10674 	Form createAutomaticFormForObject(T)(T obj) {
10675 		auto form = cast(Form) Element.make("form");
10676 
10677 		form.addClass("automatic-form");
10678 
10679 		form.addChild("h3", beautify(__traits(identifier, T)));
10680 
10681 		import std.traits;
10682 
10683 		//Parameters!method params;
10684 		//alias idents = ParameterIdentifierTuple!method;
10685 		//alias defaults = ParameterDefaults!method;
10686 
10687 		foreach(idx, memberName; __traits(derivedMembers, T)) {{
10688 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
10689 			string displayName = beautify(memberName);
10690 			Element function() element;
10691 			foreach(attr; __traits(getAttributes,  __traits(getMember, T, memberName)))
10692 				static if(is(typeof(attr) == DisplayName))
10693 					displayName = attr.name;
10694 				else static if(is(typeof(attr) : typeof(element)))
10695 					element = attr;
10696 			form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName, element));
10697 
10698 			form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
10699 		}}}
10700 
10701 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
10702 
10703 		return form;
10704 	}
10705 
10706 	///
10707 	Element formatReturnValueAsHtml(T)(T t) {
10708 		import std.traits;
10709 
10710 		static if(is(T == typeof(null))) {
10711 			return Element.make("span");
10712 		} else static if(is(T : Element)) {
10713 			return t;
10714 		} else static if(is(T == MultipleResponses!Types, Types...)) {
10715 			foreach(index, type; Types) {
10716 				if(t.contains == index)
10717 					return formatReturnValueAsHtml(t.payload[index]);
10718 			}
10719 			assert(0);
10720 		} else static if(is(T == Paginated!E, E)) {
10721 			auto e = Element.make("div").addClass("paginated-result");
10722 			e.appendChild(formatReturnValueAsHtml(t.items));
10723 			if(t.nextPageUrl.length)
10724 				e.appendChild(Element.make("a", "Next Page", t.nextPageUrl));
10725 			return e;
10726 		} else static if(isIntegral!T || isSomeString!T || isFloatingPoint!T) {
10727 			return Element.make("span", to!string(t), "automatic-data-display");
10728 		} else static if(is(T == V[K], K, V)) {
10729 			auto dl = Element.make("dl");
10730 			dl.addClass("automatic-data-display associative-array");
10731 			foreach(k, v; t) {
10732 				dl.addChild("dt", to!string(k));
10733 				dl.addChild("dd", formatReturnValueAsHtml(v));
10734 			}
10735 			return dl;
10736 		} else static if(is(T == struct)) {
10737 			auto dl = Element.make("dl");
10738 			dl.addClass("automatic-data-display struct");
10739 
10740 			foreach(idx, memberName; __traits(allMembers, T))
10741 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
10742 				dl.addChild("dt", beautify(memberName));
10743 				dl.addChild("dd", formatReturnValueAsHtml(__traits(getMember, t, memberName)));
10744 			}
10745 
10746 			return dl;
10747 		} else static if(is(T == bool)) {
10748 			return Element.make("span", t ? "true" : "false", "automatic-data-display");
10749 		} else static if(is(T == E[], E) || is(T == E[N], E, size_t N)) {
10750 			static if(is(E : RestObject!Proxy, Proxy)) {
10751 				// treat RestObject similar to struct
10752 				auto table = cast(Table) Element.make("table");
10753 				table.addClass("automatic-data-display");
10754 				string[] names;
10755 				foreach(idx, memberName; __traits(derivedMembers, E))
10756 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10757 					names ~= beautify(memberName);
10758 				}
10759 				table.appendHeaderRow(names);
10760 
10761 				foreach(l; t) {
10762 					auto tr = table.appendRow();
10763 					foreach(idx, memberName; __traits(derivedMembers, E))
10764 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10765 						static if(memberName == "id") {
10766 							string val = to!string(__traits(getMember, l, memberName));
10767 							tr.addChild("td", Element.make("a", val, E.stringof.toLower ~ "s/" ~ val)); // FIXME
10768 						} else {
10769 							tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
10770 						}
10771 					}
10772 				}
10773 
10774 				return table;
10775 			} else static if(is(E == struct)) {
10776 				// an array of structs is kinda special in that I like
10777 				// having those formatted as tables.
10778 				auto table = cast(Table) Element.make("table");
10779 				table.addClass("automatic-data-display");
10780 				string[] names;
10781 				foreach(idx, memberName; __traits(allMembers, E))
10782 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10783 					names ~= beautify(memberName);
10784 				}
10785 				table.appendHeaderRow(names);
10786 
10787 				foreach(l; t) {
10788 					auto tr = table.appendRow();
10789 					foreach(idx, memberName; __traits(allMembers, E))
10790 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10791 						tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
10792 					}
10793 				}
10794 
10795 				return table;
10796 			} else {
10797 				// otherwise, I will just make a list.
10798 				auto ol = Element.make("ol");
10799 				ol.addClass("automatic-data-display");
10800 				foreach(e; t)
10801 					ol.addChild("li", formatReturnValueAsHtml(e));
10802 				return ol;
10803 			}
10804 		} else static if(is(T : Object)) {
10805 			static if(is(typeof(t.toHtml()))) // FIXME: maybe i will make this an interface
10806 				return Element.make("div", t.toHtml());
10807 			else
10808 				return Element.make("div", t.toString());
10809 		} else static assert(0, "bad return value for cgi call " ~ T.stringof);
10810 
10811 		assert(0);
10812 	}
10813 
10814 }
10815 
10816 /++
10817 	The base class for the [dispatcher] function and object support.
10818 +/
10819 class WebObject {
10820 	//protected Cgi cgi;
10821 
10822 	protected void initialize(Cgi cgi) {
10823 		//this.cgi = cgi;
10824 	}
10825 }
10826 
10827 /++
10828 	Can return one of the given types, decided at runtime. The syntax
10829 	is to declare all the possible types in the return value, then you
10830 	can `return typeof(return)(...value...)` to construct it.
10831 
10832 	It has an auto-generated constructor for each value it can hold.
10833 
10834 	---
10835 	MultipleResponses!(Redirection, string) getData(int how) {
10836 		if(how & 1)
10837 			return typeof(return)(Redirection("http://dpldocs.info/"));
10838 		else
10839 			return typeof(return)("hi there!");
10840 	}
10841 	---
10842 
10843 	If you have lots of returns, you could, inside the function, `alias r = typeof(return);` to shorten it a little.
10844 +/
10845 struct MultipleResponses(T...) {
10846 	private size_t contains;
10847 	private union {
10848 		private T payload;
10849 	}
10850 
10851 	static foreach(index, type; T)
10852 	public this(type t) {
10853 		contains = index;
10854 		payload[index] = t;
10855 	}
10856 
10857 	/++
10858 		This is primarily for testing. It is your way of getting to the response.
10859 
10860 		Let's say you wanted to test that one holding a Redirection and a string actually
10861 		holds a string, by name of "test":
10862 
10863 		---
10864 			auto valueToTest = your_test_function();
10865 
10866 			valueToTest.visit(
10867 				(Redirection r) { assert(0); }, // got a redirection instead of a string, fail the test
10868 				(string s) { assert(s == "test"); } // right value, go ahead and test it.
10869 			);
10870 		---
10871 
10872 		History:
10873 			Was horribly broken until June 16, 2022. Ironically, I wrote it for tests but never actually tested it.
10874 			It tried to use alias lambdas before, but runtime delegates work much better so I changed it.
10875 	+/
10876 	void visit(Handlers...)(Handlers handlers) {
10877 		template findHandler(type, int count, HandlersToCheck...) {
10878 			static if(HandlersToCheck.length == 0)
10879 				enum findHandler = -1;
10880 			else {
10881 				static if(is(typeof(HandlersToCheck[0].init(type.init))))
10882 					enum findHandler = count;
10883 				else
10884 					enum findHandler = findHandler!(type, count + 1, HandlersToCheck[1 .. $]);
10885 			}
10886 		}
10887 		foreach(index, type; T) {
10888 			enum handlerIndex = findHandler!(type, 0, Handlers);
10889 			static if(handlerIndex == -1)
10890 				static assert(0, "Type " ~ type.stringof ~ " was not handled by visitor");
10891 			else {
10892 				if(index == this.contains)
10893 					handlers[handlerIndex](this.payload[index]);
10894 			}
10895 		}
10896 	}
10897 
10898 	/+
10899 	auto toArsdJsvar()() {
10900 		import arsd.jsvar;
10901 		return var(null);
10902 	}
10903 	+/
10904 }
10905 
10906 // FIXME: implement this somewhere maybe
10907 struct RawResponse {
10908 	int code;
10909 	string[] headers;
10910 	const(ubyte)[] responseBody;
10911 }
10912 
10913 /++
10914 	You can return this from [WebObject] subclasses for redirections.
10915 
10916 	(though note the static types means that class must ALWAYS redirect if
10917 	you return this directly. You might want to return [MultipleResponses] if it
10918 	can be conditional)
10919 +/
10920 struct Redirection {
10921 	string to; /// The URL to redirect to.
10922 	int code = 303; /// The HTTP code to return.
10923 }
10924 
10925 /++
10926 	Serves a class' methods, as a kind of low-state RPC over the web. To be used with [dispatcher].
10927 
10928 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar] unless you have overriden
10929 	the presenter in the dispatcher.
10930 
10931 	FIXME: explain this better
10932 
10933 	You can overload functions to a limited extent: you can provide a zero-arg and non-zero-arg function,
10934 	and non-zero-arg functions can filter via UDAs for various http methods. Do not attempt other overloads,
10935 	the runtime result of that is undefined.
10936 
10937 	A method is assumed to allow any http method unless it lists some in UDAs, in which case it is limited to only those.
10938 	(this might change, like maybe i will use pure as an indicator GET is ok. idk.)
10939 
10940 	$(WARNING
10941 		---
10942 		// legal in D, undefined runtime behavior with cgi.d, it may call either method
10943 		// even if you put different URL udas on it, the current code ignores them.
10944 		void foo(int a) {}
10945 		void foo(string a) {}
10946 		---
10947 	)
10948 
10949 	See_Also: [serveRestObject], [serveStaticFile]
10950 +/
10951 auto serveApi(T)(string urlPrefix) {
10952 	assert(urlPrefix[$ - 1] == '/');
10953 	return serveApiInternal!T(urlPrefix);
10954 }
10955 
10956 private string nextPieceFromSlash(ref string remainingUrl) {
10957 	if(remainingUrl.length == 0)
10958 		return remainingUrl;
10959 	int slash = 0;
10960 	while(slash < remainingUrl.length && remainingUrl[slash] != '/') // && remainingUrl[slash] != '.')
10961 		slash++;
10962 
10963 	// I am specifically passing `null` to differentiate it vs empty string
10964 	// so in your ctor, `items` means new T(null) and `items/` means new T("")
10965 	auto ident = remainingUrl.length == 0 ? null : remainingUrl[0 .. slash];
10966 	// so if it is the last item, the dot can be used to load an alternative view
10967 	// otherwise tho the dot is considered part of the identifier
10968 	// FIXME
10969 
10970 	// again notice "" vs null here!
10971 	if(slash == remainingUrl.length)
10972 		remainingUrl = null;
10973 	else
10974 		remainingUrl = remainingUrl[slash + 1 .. $];
10975 
10976 	return ident;
10977 }
10978 
10979 /++
10980 	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.
10981 +/
10982 enum AddTrailingSlash;
10983 /// ditto
10984 enum RemoveTrailingSlash;
10985 
10986 private auto serveApiInternal(T)(string urlPrefix) {
10987 
10988 	import arsd.dom;
10989 	import arsd.jsvar;
10990 
10991 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
10992 		string remainingUrl = cgi.pathInfo[urlPrefix.length .. $];
10993 
10994 		try {
10995 			// see duplicated code below by searching subresource_ctor
10996 			// also see mustNotBeSetFromWebParams
10997 
10998 			static if(is(typeof(T.__ctor) P == __parameters)) {
10999 				P params;
11000 
11001 				foreach(pidx, param; P) {
11002 					static if(is(param : Cgi)) {
11003 						static assert(!is(param == immutable));
11004 						cast() params[pidx] = cgi;
11005 					} else static if(is(param == Session!D, D)) {
11006 						static assert(!is(param == immutable));
11007 						cast() params[pidx] = cgi.getSessionObject!D();
11008 
11009 					} else {
11010 						static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
11011 							foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
11012 								static if(is(uda == ifCalledFromWeb!func, alias func)) {
11013 									static if(is(typeof(func(cgi))))
11014 										params[pidx] = func(cgi);
11015 									else
11016 										params[pidx] = func();
11017 								}
11018 							}
11019 						} else {
11020 
11021 							static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
11022 								params[pidx] = param.getAutomaticallyForCgi(cgi);
11023 							} else static if(is(param == string)) {
11024 								auto ident = nextPieceFromSlash(remainingUrl);
11025 								params[pidx] = ident;
11026 							} else static assert(0, "illegal type for subresource " ~ param.stringof);
11027 						}
11028 					}
11029 				}
11030 
11031 				auto obj = new T(params);
11032 			} else {
11033 				auto obj = new T();
11034 			}
11035 
11036 			return internalHandlerWithObject(obj, remainingUrl, cgi, presenter);
11037 		} catch(Throwable t) {
11038 			switch(cgi.request("format", "html")) {
11039 				case "html":
11040 					static void dummy() {}
11041 					presenter.presentExceptionAsHtml(cgi, t, null);
11042 				return true;
11043 				case "json":
11044 					var envelope = var.emptyObject;
11045 					envelope.success = false;
11046 					envelope.result = null;
11047 					envelope.error = t.toString();
11048 					cgi.setResponseContentType("application/json");
11049 					cgi.write(envelope.toJson(), true);
11050 				return true;
11051 				default:
11052 					throw t;
11053 				// return true;
11054 			}
11055 			// return true;
11056 		}
11057 
11058 		assert(0);
11059 	}
11060 
11061 	static bool internalHandlerWithObject(T, Presenter)(T obj, string remainingUrl, Cgi cgi, Presenter presenter) {
11062 
11063 		obj.initialize(cgi);
11064 
11065 		/+
11066 			Overload rules:
11067 				Any unique combination of HTTP verb and url path can be dispatched to function overloads
11068 				statically.
11069 
11070 				Moreover, some args vs no args can be overloaded dynamically.
11071 		+/
11072 
11073 		auto methodNameFromUrl = nextPieceFromSlash(remainingUrl);
11074 		/+
11075 		auto orig = remainingUrl;
11076 		assert(0,
11077 			(orig is null ? "__null" : orig)
11078 			~ " .. " ~
11079 			(methodNameFromUrl is null ? "__null" : methodNameFromUrl));
11080 		+/
11081 
11082 		if(methodNameFromUrl is null)
11083 			methodNameFromUrl = "__null";
11084 
11085 		string hack = to!string(cgi.requestMethod) ~ " " ~ methodNameFromUrl;
11086 
11087 		if(remainingUrl.length)
11088 			hack ~= "/";
11089 
11090 		switch(hack) {
11091 			foreach(methodName; __traits(derivedMembers, T))
11092 			static if(methodName != "__ctor")
11093 			foreach(idx, overload; __traits(getOverloads, T, methodName)) {
11094 			static if(is(typeof(overload) P == __parameters))
11095 			static if(is(typeof(overload) R == return))
11096 			static if(__traits(getProtection, overload) == "public" || __traits(getProtection, overload) == "export")
11097 			{
11098 			static foreach(urlNameForMethod; urlNamesForMethod!(overload, urlify(methodName)))
11099 			case urlNameForMethod:
11100 
11101 				static if(is(R : WebObject)) {
11102 					// if it returns a WebObject, it is considered a subresource. That means the url is dispatched like the ctor above.
11103 
11104 					// the only argument it is allowed to take, outside of cgi, session, and set up thingies, is a single string
11105 
11106 					// subresource_ctor
11107 					// also see mustNotBeSetFromWebParams
11108 
11109 					P params;
11110 
11111 					string ident;
11112 
11113 					foreach(pidx, param; P) {
11114 						static if(is(param : Cgi)) {
11115 							static assert(!is(param == immutable));
11116 							cast() params[pidx] = cgi;
11117 						} else static if(is(param == typeof(presenter))) {
11118 							cast() param[pidx] = presenter;
11119 						} else static if(is(param == Session!D, D)) {
11120 							static assert(!is(param == immutable));
11121 							cast() params[pidx] = cgi.getSessionObject!D();
11122 						} else {
11123 							static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
11124 								foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
11125 									static if(is(uda == ifCalledFromWeb!func, alias func)) {
11126 										static if(is(typeof(func(cgi))))
11127 											params[pidx] = func(cgi);
11128 										else
11129 											params[pidx] = func();
11130 									}
11131 								}
11132 							} else {
11133 
11134 								static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
11135 									params[pidx] = param.getAutomaticallyForCgi(cgi);
11136 								} else static if(is(param == string)) {
11137 									ident = nextPieceFromSlash(remainingUrl);
11138 									if(ident is null) {
11139 										// trailing slash mandated on subresources
11140 										cgi.setResponseLocation(cgi.pathInfo ~ "/");
11141 										return true;
11142 									} else {
11143 										params[pidx] = ident;
11144 									}
11145 								} else static assert(0, "illegal type for subresource " ~ param.stringof);
11146 							}
11147 						}
11148 					}
11149 
11150 					auto nobj = (__traits(getOverloads, obj, methodName)[idx])(ident);
11151 					return internalHandlerWithObject!(typeof(nobj), Presenter)(nobj, remainingUrl, cgi, presenter);
11152 				} else {
11153 					// 404 it if any url left - not a subresource means we don't get to play with that!
11154 					if(remainingUrl.length)
11155 						return false;
11156 
11157 					bool automaticForm;
11158 
11159 					foreach(attr; __traits(getAttributes, overload))
11160 						static if(is(attr == AddTrailingSlash)) {
11161 							if(remainingUrl is null) {
11162 								cgi.setResponseLocation(cgi.pathInfo ~ "/");
11163 								return true;
11164 							}
11165 						} else static if(is(attr == RemoveTrailingSlash)) {
11166 							if(remainingUrl !is null) {
11167 								cgi.setResponseLocation(cgi.pathInfo[0 .. lastIndexOf(cgi.pathInfo, "/")]);
11168 								return true;
11169 							}
11170 
11171 						} else static if(__traits(isSame, AutomaticForm, attr)) {
11172 							automaticForm = true;
11173 						}
11174 
11175 				/+
11176 				int zeroArgOverload = -1;
11177 				int overloadCount = cast(int) __traits(getOverloads, T, methodName).length;
11178 				bool calledWithZeroArgs = true;
11179 				foreach(k, v; cgi.get)
11180 					if(k != "format") {
11181 						calledWithZeroArgs = false;
11182 						break;
11183 					}
11184 				foreach(k, v; cgi.post)
11185 					if(k != "format") {
11186 						calledWithZeroArgs = false;
11187 						break;
11188 					}
11189 
11190 				// first, we need to go through and see if there is an empty one, since that
11191 				// changes inside. But otherwise, all the stuff I care about can be done via
11192 				// simple looping (other improper overloads might be flagged for runtime semantic check)
11193 				//
11194 				// an argument of type Cgi is ignored for these purposes
11195 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
11196 					static if(is(typeof(overload) P == __parameters))
11197 						static if(P.length == 0)
11198 							zeroArgOverload = cast(int) idx;
11199 						else static if(P.length == 1 && is(P[0] : Cgi))
11200 							zeroArgOverload = cast(int) idx;
11201 				}}
11202 				// FIXME: static assert if there are multiple non-zero-arg overloads usable with a single http method.
11203 				bool overloadHasBeenCalled = false;
11204 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
11205 					bool callFunction = true;
11206 					// there is a zero arg overload and this is NOT it, and we have zero args - don't call this
11207 					if(overloadCount > 1 && zeroArgOverload != -1 && idx != zeroArgOverload && calledWithZeroArgs)
11208 						callFunction = false;
11209 					// if this is the zero-arg overload, obviously it cannot be called if we got any args.
11210 					if(overloadCount > 1 && idx == zeroArgOverload && !calledWithZeroArgs)
11211 						callFunction = false;
11212 
11213 					// FIXME: so if you just add ?foo it will give the error below even when. this might not be a great idea.
11214 
11215 					bool hadAnyMethodRestrictions = false;
11216 					bool foundAcceptableMethod = false;
11217 					foreach(attr; __traits(getAttributes, overload)) {
11218 						static if(is(typeof(attr) == Cgi.RequestMethod)) {
11219 							hadAnyMethodRestrictions = true;
11220 							if(attr == cgi.requestMethod)
11221 								foundAcceptableMethod = true;
11222 						}
11223 					}
11224 
11225 					if(hadAnyMethodRestrictions && !foundAcceptableMethod)
11226 						callFunction = false;
11227 
11228 					/+
11229 						The overloads we really want to allow are the sane ones
11230 						from the web perspective. Which is likely on HTTP verbs,
11231 						for the most part, but might also be potentially based on
11232 						some args vs zero args, or on argument names. Can't really
11233 						do argument types very reliable through the web though; those
11234 						should probably be different URLs.
11235 
11236 						Even names I feel is better done inside the function, so I'm not
11237 						going to support that here. But the HTTP verbs and zero vs some
11238 						args makes sense - it lets you define custom forms pretty easily.
11239 
11240 						Moreover, I'm of the opinion that empty overload really only makes
11241 						sense on GET for this case. On a POST, it is just a missing argument
11242 						exception and that should be handled by the presenter. But meh, I'll
11243 						let the user define that, D only allows one empty arg thing anyway
11244 						so the method UDAs are irrelevant.
11245 					+/
11246 					if(callFunction)
11247 				+/
11248 
11249 					auto format = cgi.request("format", defaultFormat!overload());
11250 					auto wantsFormFormat = format.startsWith("form-");
11251 
11252 					if(wantsFormFormat || (automaticForm && cgi.requestMethod == Cgi.RequestMethod.GET)) {
11253 						// Should I still show the form on a json thing? idk...
11254 						auto ret = presenter.createAutomaticFormForFunction!((__traits(getOverloads, obj, methodName)[idx]))(&(__traits(getOverloads, obj, methodName)[idx]));
11255 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), wantsFormFormat ? format["form_".length .. $] : "html");
11256 						return true;
11257 					}
11258 
11259 					try {
11260 						// a void return (or typeof(null) lol) means you, the user, is doing it yourself. Gives full control.
11261 						auto ret = callFromCgi!(__traits(getOverloads, obj, methodName)[idx])(&(__traits(getOverloads, obj, methodName)[idx]), cgi);
11262 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), format);
11263 					} catch(Throwable t) {
11264 						// presenter.presentExceptionAsHtml!(__traits(getOverloads, obj, methodName)[idx])(cgi, t, &(__traits(getOverloads, obj, methodName)[idx]));
11265 						presenter.presentExceptionalReturn(cgi, t, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), format);
11266 					}
11267 					return true;
11268 				//}}
11269 
11270 				//cgi.header("Accept: POST"); // FIXME list the real thing
11271 				//cgi.setResponseStatus("405 Method Not Allowed"); // again, not exactly, but sort of. no overload matched our args, almost certainly due to http verb filtering.
11272 				//return true;
11273 				}
11274 			}
11275 			}
11276 			case "GET script.js":
11277 				cgi.setResponseContentType("text/javascript");
11278 				cgi.gzipResponse = true;
11279 				cgi.write(presenter.script(), true);
11280 				return true;
11281 			case "GET style.css":
11282 				cgi.setResponseContentType("text/css");
11283 				cgi.gzipResponse = true;
11284 				cgi.write(presenter.style(), true);
11285 				return true;
11286 			default:
11287 				return false;
11288 		}
11289 
11290 		assert(0);
11291 	}
11292 	return DispatcherDefinition!internalHandler(urlPrefix, false);
11293 }
11294 
11295 string defaultFormat(alias method)() {
11296 	bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true;
11297 	foreach(attr; __traits(getAttributes, method)) {
11298 		static if(is(typeof(attr) == DefaultFormat)) {
11299 			if(nonConstConditionForWorkingAroundASpuriousDmdWarning)
11300 				return attr.value;
11301 		}
11302 	}
11303 	return "html";
11304 }
11305 
11306 struct Paginated(T) {
11307 	T[] items;
11308 	string nextPageUrl;
11309 }
11310 
11311 template urlNamesForMethod(alias method, string default_) {
11312 	string[] helper() {
11313 		auto verb = Cgi.RequestMethod.GET;
11314 		bool foundVerb = false;
11315 		bool foundNoun = false;
11316 
11317 		string def = default_;
11318 
11319 		bool hasAutomaticForm = false;
11320 
11321 		foreach(attr; __traits(getAttributes, method)) {
11322 			static if(is(typeof(attr) == Cgi.RequestMethod)) {
11323 				verb = attr;
11324 				if(foundVerb)
11325 					assert(0, "Multiple http verbs on one function is not currently supported");
11326 				foundVerb = true;
11327 			}
11328 			static if(is(typeof(attr) == UrlName)) {
11329 				if(foundNoun)
11330 					assert(0, "Multiple url names on one function is not currently supported");
11331 				foundNoun = true;
11332 				def = attr.name;
11333 			}
11334 			static if(__traits(isSame, attr, AutomaticForm)) {
11335 				hasAutomaticForm = true;
11336 			}
11337 		}
11338 
11339 		if(def is null)
11340 			def = "__null";
11341 
11342 		string[] ret;
11343 
11344 		static if(is(typeof(method) R == return)) {
11345 			static if(is(R : WebObject)) {
11346 				def ~= "/";
11347 				foreach(v; __traits(allMembers, Cgi.RequestMethod))
11348 					ret ~= v ~ " " ~ def;
11349 			} else {
11350 				if(hasAutomaticForm) {
11351 					ret ~= "GET " ~ def;
11352 					ret ~= "POST " ~ def;
11353 				} else {
11354 					ret ~= to!string(verb) ~ " " ~ def;
11355 				}
11356 			}
11357 		} else static assert(0);
11358 
11359 		return ret;
11360 	}
11361 	enum urlNamesForMethod = helper();
11362 }
11363 
11364 
11365 	enum AccessCheck {
11366 		allowed,
11367 		denied,
11368 		nonExistant,
11369 	}
11370 
11371 	enum Operation {
11372 		show,
11373 		create,
11374 		replace,
11375 		remove,
11376 		update
11377 	}
11378 
11379 	enum UpdateResult {
11380 		accessDenied,
11381 		noSuchResource,
11382 		success,
11383 		failure,
11384 		unnecessary
11385 	}
11386 
11387 	enum ValidationResult {
11388 		valid,
11389 		invalid
11390 	}
11391 
11392 
11393 /++
11394 	The base of all REST objects, to be used with [serveRestObject] and [serveRestCollectionOf].
11395 
11396 	WARNING: this is not stable.
11397 +/
11398 class RestObject(CRTP) : WebObject {
11399 
11400 	import arsd.dom;
11401 	import arsd.jsvar;
11402 
11403 	/// Prepare the object to be shown.
11404 	void show() {}
11405 	/// ditto
11406 	void show(string urlId) {
11407 		load(urlId);
11408 		show();
11409 	}
11410 
11411 	/// Override this to provide access control to this object.
11412 	AccessCheck accessCheck(string urlId, Operation operation) {
11413 		return AccessCheck.allowed;
11414 	}
11415 
11416 	ValidationResult validate() {
11417 		// FIXME
11418 		return ValidationResult.valid;
11419 	}
11420 
11421 	string getUrlSlug() {
11422 		import std.conv;
11423 		static if(is(typeof(CRTP.id)))
11424 			return to!string((cast(CRTP) this).id);
11425 		else
11426 			return null;
11427 	}
11428 
11429 	// The functions with more arguments are the low-level ones,
11430 	// they forward to the ones with fewer arguments by default.
11431 
11432 	// POST on a parent collection - this is called from a collection class after the members are updated
11433 	/++
11434 		Given a populated object, this creates a new entry. Returns the url identifier
11435 		of the new object.
11436 	+/
11437 	string create(scope void delegate() applyChanges) {
11438 		applyChanges();
11439 		save();
11440 		return getUrlSlug();
11441 	}
11442 
11443 	void replace() {
11444 		save();
11445 	}
11446 	void replace(string urlId, scope void delegate() applyChanges) {
11447 		load(urlId);
11448 		applyChanges();
11449 		replace();
11450 	}
11451 
11452 	void update(string[] fieldList) {
11453 		save();
11454 	}
11455 	void update(string urlId, scope void delegate() applyChanges, string[] fieldList) {
11456 		load(urlId);
11457 		applyChanges();
11458 		update(fieldList);
11459 	}
11460 
11461 	void remove() {}
11462 
11463 	void remove(string urlId) {
11464 		load(urlId);
11465 		remove();
11466 	}
11467 
11468 	abstract void load(string urlId);
11469 	abstract void save();
11470 
11471 	Element toHtml(Presenter)(Presenter presenter) {
11472 		import arsd.dom;
11473 		import std.conv;
11474 		auto obj = cast(CRTP) this;
11475 		auto div = Element.make("div");
11476 		div.addClass("Dclass_" ~ CRTP.stringof);
11477 		div.dataset.url = getUrlSlug();
11478 		bool first = true;
11479 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
11480 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11481 			if(!first) div.addChild("br"); else first = false;
11482 			div.appendChild(presenter.formatReturnValueAsHtml(__traits(getMember, obj, memberName)));
11483 		}
11484 		return div;
11485 	}
11486 
11487 	var toJson() {
11488 		import arsd.jsvar;
11489 		var v = var.emptyObject();
11490 		auto obj = cast(CRTP) this;
11491 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
11492 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11493 			v[memberName] = __traits(getMember, obj, memberName);
11494 		}
11495 		return v;
11496 	}
11497 
11498 	/+
11499 	auto structOf(this This) {
11500 
11501 	}
11502 	+/
11503 }
11504 
11505 // FIXME XSRF token, prolly can just put in a cookie and then it needs to be copied to header or form hidden value
11506 // https://use-the-index-luke.com/sql/partial-results/fetch-next-page
11507 
11508 /++
11509 	Base class for REST collections.
11510 +/
11511 class CollectionOf(Obj) : RestObject!(CollectionOf) {
11512 	/// You might subclass this and use the cgi object's query params
11513 	/// to implement a search filter, for example.
11514 	///
11515 	/// FIXME: design a way to auto-generate that form
11516 	/// (other than using the WebObject thing above lol
11517 	// it'll prolly just be some searchParams UDA or maybe an enum.
11518 	//
11519 	// pagination too perhaps.
11520 	//
11521 	// and sorting too
11522 	IndexResult index() { return IndexResult.init; }
11523 
11524 	string[] sortableFields() { return null; }
11525 	string[] searchableFields() { return null; }
11526 
11527 	struct IndexResult {
11528 		Obj[] results;
11529 
11530 		string[] sortableFields;
11531 
11532 		string previousPageIdentifier;
11533 		string nextPageIdentifier;
11534 		string firstPageIdentifier;
11535 		string lastPageIdentifier;
11536 
11537 		int numberOfPages;
11538 	}
11539 
11540 	override string create(scope void delegate() applyChanges) { assert(0); }
11541 	override void load(string urlId) { assert(0); }
11542 	override void save() { assert(0); }
11543 	override void show() {
11544 		index();
11545 	}
11546 	override void show(string urlId) {
11547 		show();
11548 	}
11549 
11550 	/// Proxy POST requests (create calls) to the child collection
11551 	alias PostProxy = Obj;
11552 }
11553 
11554 /++
11555 	Serves a REST object, similar to a Ruby on Rails resource.
11556 
11557 	You put data members in your class. cgi.d will automatically make something out of those.
11558 
11559 	It will call your constructor with the ID from the URL. This may be null.
11560 	It will then populate the data members from the request.
11561 	It will then call a method, if present, telling what happened. You don't need to write these!
11562 	It finally returns a reply.
11563 
11564 	Your methods are passed a list of fields it actually set.
11565 
11566 	The URL mapping - despite my general skepticism of the wisdom - matches up with what most REST
11567 	APIs I have used seem to follow. (I REALLY want to put trailing slashes on it though. Works better
11568 	with relative linking. But meh.)
11569 
11570 	GET /items -> index. all values not set.
11571 	GET /items/id -> get. only ID will be set, other params ignored.
11572 	POST /items -> create. values set as given
11573 	PUT /items/id -> replace. values set as given
11574 		or POST /items/id with cgi.post["_method"] (thus urlencoded or multipart content-type) set to "PUT" to work around browser/html limitation
11575 		a GET with cgi.get["_method"] (in the url) set to "PUT" will render a form.
11576 	PATCH /items/id -> update. values set as given, list of changed fields passed
11577 		or POST /items/id with cgi.post["_method"] == "PATCH"
11578 	DELETE /items/id -> destroy. only ID guaranteed to be set
11579 		or POST /items/id with cgi.post["_method"] == "DELETE"
11580 
11581 	Following the stupid convention, there will never be a trailing slash here, and if it is there, it will
11582 	redirect you away from it.
11583 
11584 	API clients should set the `Accept` HTTP header to application/json or the cgi.get["_format"] = "json" var.
11585 
11586 	I will also let you change the default, if you must.
11587 
11588 	// One add-on is validation. You can issue a HTTP GET to a resource with _method = VALIDATE to check potential changes.
11589 
11590 	You can define sub-resources on your object inside the object. These sub-resources are also REST objects
11591 	that follow the same thing. They may be individual resources or collections themselves.
11592 
11593 	Your class is expected to have at least the following methods:
11594 
11595 	FIXME: i kinda wanna add a routes object to the initialize call
11596 
11597 	create
11598 		Create returns the new address on success, some code on failure.
11599 	show
11600 	index
11601 	update
11602 	remove
11603 
11604 	You will want to be able to customize the HTTP, HTML, and JSON returns but generally shouldn't have to - the defaults
11605 	should usually work. The returned JSON will include a field "href" on all returned objects along with "id". Or omething like that.
11606 
11607 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar].
11608 
11609 	NOT IMPLEMENTED
11610 
11611 
11612 	Really, a collection is a resource with a bunch of subresources.
11613 
11614 		GET /items
11615 			index because it is GET on the top resource
11616 
11617 		GET /items/foo
11618 			item but different than items?
11619 
11620 		class Items {
11621 
11622 		}
11623 
11624 	... but meh, a collection can be automated. not worth making it
11625 	a separate thing, let's look at a real example. Users has many
11626 	items and a virtual one, /users/current.
11627 
11628 	the individual users have properties and two sub-resources:
11629 	session, which is just one, and comments, a collection.
11630 
11631 	class User : RestObject!() { // no parent
11632 		int id;
11633 		string name;
11634 
11635 		// the default implementations of the urlId ones is to call load(that_id) then call the arg-less one.
11636 		// but you can override them to do it differently.
11637 
11638 		// any member which is of type RestObject can be linked automatically via href btw.
11639 
11640 		void show() {}
11641 		void show(string urlId) {} // automated! GET of this specific thing
11642 		void create() {} // POST on a parent collection - this is called from a collection class after the members are updated
11643 		void replace(string urlId) {} // this is the PUT; really, it just updates all fields.
11644 		void update(string urlId, string[] fieldList) {} // PATCH, it updates some fields.
11645 		void remove(string urlId) {} // DELETE
11646 
11647 		void load(string urlId) {} // the default implementation of show() populates the id, then
11648 
11649 		this() {}
11650 
11651 		mixin Subresource!Session;
11652 		mixin Subresource!Comment;
11653 	}
11654 
11655 	class Session : RestObject!() {
11656 		// the parent object may not be fully constructed/loaded
11657 		this(User parent) {}
11658 
11659 	}
11660 
11661 	class Comment : CollectionOf!Comment {
11662 		this(User parent) {}
11663 	}
11664 
11665 	class Users : CollectionOf!User {
11666 		// but you don't strictly need ANYTHING on a collection; it will just... collect. Implement the subobjects.
11667 		void index() {} // GET on this specific thing; just like show really, just different name for the different semantics.
11668 		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
11669 	}
11670 
11671 +/
11672 auto serveRestObject(T)(string urlPrefix) {
11673 	assert(urlPrefix[0] == '/');
11674 	assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
11675 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
11676 		string url = cgi.pathInfo[urlPrefix.length .. $];
11677 
11678 		if(url.length && url[$ - 1] == '/') {
11679 			// remove the final slash...
11680 			cgi.setResponseLocation(cgi.scriptName ~ cgi.pathInfo[0 .. $ - 1]);
11681 			return true;
11682 		}
11683 
11684 		return restObjectServeHandler!T(cgi, presenter, url);
11685 	}
11686 	return DispatcherDefinition!internalHandler(urlPrefix, false);
11687 }
11688 
11689 /+
11690 /// Convenience method for serving a collection. It will be named the same
11691 /// as type T, just with an s at the end. If you need any further, just
11692 /// write the class yourself.
11693 auto serveRestCollectionOf(T)(string urlPrefix) {
11694 	assert(urlPrefix[0] == '/');
11695 	mixin(`static class `~T.stringof~`s : CollectionOf!(T) {}`);
11696 	return serveRestObject!(mixin(T.stringof ~ "s"))(urlPrefix);
11697 }
11698 +/
11699 
11700 bool restObjectServeHandler(T, Presenter)(Cgi cgi, Presenter presenter, string url) {
11701 	string urlId = null;
11702 	if(url.length && url[0] == '/') {
11703 		// asking for a subobject
11704 		urlId = url[1 .. $];
11705 		foreach(idx, ch; urlId) {
11706 			if(ch == '/') {
11707 				urlId = urlId[0 .. idx];
11708 				break;
11709 			}
11710 		}
11711 	}
11712 
11713 	// FIXME handle other subresources
11714 
11715 	static if(is(T : CollectionOf!(C), C)) {
11716 		if(urlId !is null) {
11717 			return restObjectServeHandler!(C, Presenter)(cgi, presenter, url); // FIXME?  urlId);
11718 		}
11719 	}
11720 
11721 	// FIXME: support precondition failed, if-modified-since, expectation failed, etc.
11722 
11723 	auto obj = new T();
11724 	obj.initialize(cgi);
11725 	// FIXME: populate reflection info delegates
11726 
11727 
11728 	// FIXME: I am not happy with this.
11729 	switch(urlId) {
11730 		case "script.js":
11731 			cgi.setResponseContentType("text/javascript");
11732 			cgi.gzipResponse = true;
11733 			cgi.write(presenter.script(), true);
11734 			return true;
11735 		case "style.css":
11736 			cgi.setResponseContentType("text/css");
11737 			cgi.gzipResponse = true;
11738 			cgi.write(presenter.style(), true);
11739 			return true;
11740 		default:
11741 			// intentionally blank
11742 	}
11743 
11744 
11745 
11746 
11747 	static void applyChangesTemplate(Obj)(Cgi cgi, Obj obj) {
11748 		foreach(idx, memberName; __traits(derivedMembers, Obj))
11749 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11750 			__traits(getMember, obj, memberName) = cgi.request(memberName, __traits(getMember, obj, memberName));
11751 		}
11752 	}
11753 	void applyChanges() {
11754 		applyChangesTemplate(cgi, obj);
11755 	}
11756 
11757 	string[] modifiedList;
11758 
11759 	void writeObject(bool addFormLinks) {
11760 		if(cgi.request("format") == "json") {
11761 			cgi.setResponseContentType("application/json");
11762 			cgi.write(obj.toJson().toString, true);
11763 		} else {
11764 			auto container = presenter.htmlContainer();
11765 			if(addFormLinks) {
11766 				static if(is(T : CollectionOf!(C), C))
11767 				container.appendHtml(`
11768 					<form>
11769 						<button type="submit" name="_method" value="POST">Create New</button>
11770 					</form>
11771 				`);
11772 				else
11773 				container.appendHtml(`
11774 					<a href="..">Back</a>
11775 					<form>
11776 						<button type="submit" name="_method" value="PATCH">Edit</button>
11777 						<button type="submit" name="_method" value="DELETE">Delete</button>
11778 					</form>
11779 				`);
11780 			}
11781 			container.appendChild(obj.toHtml(presenter));
11782 			cgi.write(container.parentDocument.toString, true);
11783 		}
11784 	}
11785 
11786 	// FIXME: I think I need a set type in here....
11787 	// it will be nice to pass sets of members.
11788 
11789 	try
11790 	switch(cgi.requestMethod) {
11791 		case Cgi.RequestMethod.GET:
11792 			// I could prolly use template this parameters in the implementation above for some reflection stuff.
11793 			// sure, it doesn't automatically work in subclasses... but I instantiate here anyway...
11794 
11795 			// automatic forms here for usable basic auto site from browser.
11796 			// even if the format is json, it could actually send out the links and formats, but really there i'ma be meh.
11797 			switch(cgi.request("_method", "GET")) {
11798 				case "GET":
11799 					static if(is(T : CollectionOf!(C), C)) {
11800 						auto results = obj.index();
11801 						if(cgi.request("format", "html") == "html") {
11802 							auto container = presenter.htmlContainer();
11803 							auto html = presenter.formatReturnValueAsHtml(results.results);
11804 							container.appendHtml(`
11805 								<form>
11806 									<button type="submit" name="_method" value="POST">Create New</button>
11807 								</form>
11808 							`);
11809 
11810 							container.appendChild(html);
11811 							cgi.write(container.parentDocument.toString, true);
11812 						} else {
11813 							cgi.setResponseContentType("application/json");
11814 							import arsd.jsvar;
11815 							var json = var.emptyArray;
11816 							foreach(r; results.results) {
11817 								var o = var.emptyObject;
11818 								foreach(idx, memberName; __traits(derivedMembers, typeof(r)))
11819 								static if(__traits(compiles, __traits(getMember, r, memberName).offsetof)) {
11820 									o[memberName] = __traits(getMember, r, memberName);
11821 								}
11822 
11823 								json ~= o;
11824 							}
11825 							cgi.write(json.toJson(), true);
11826 						}
11827 					} else {
11828 						obj.show(urlId);
11829 						writeObject(true);
11830 					}
11831 				break;
11832 				case "PATCH":
11833 					obj.load(urlId);
11834 				goto case;
11835 				case "PUT":
11836 				case "POST":
11837 					// an editing form for the object
11838 					auto container = presenter.htmlContainer();
11839 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
11840 						auto form = (cgi.request("_method") == "POST") ? presenter.createAutomaticFormForObject(new obj.PostProxy()) : presenter.createAutomaticFormForObject(obj);
11841 					} else {
11842 						auto form = presenter.createAutomaticFormForObject(obj);
11843 					}
11844 					form.attrs.method = "POST";
11845 					form.setValue("_method", cgi.request("_method", "GET"));
11846 					container.appendChild(form);
11847 					cgi.write(container.parentDocument.toString(), true);
11848 				break;
11849 				case "DELETE":
11850 					// FIXME: a delete form for the object (can be phrased "are you sure?")
11851 					auto container = presenter.htmlContainer();
11852 					container.appendHtml(`
11853 						<form method="POST">
11854 							Are you sure you want to delete this item?
11855 							<input type="hidden" name="_method" value="DELETE" />
11856 							<input type="submit" value="Yes, Delete It" />
11857 						</form>
11858 
11859 					`);
11860 					cgi.write(container.parentDocument.toString(), true);
11861 				break;
11862 				default:
11863 					cgi.write("bad method\n", true);
11864 			}
11865 		break;
11866 		case Cgi.RequestMethod.POST:
11867 			// this is to allow compatibility with HTML forms
11868 			switch(cgi.request("_method", "POST")) {
11869 				case "PUT":
11870 					goto PUT;
11871 				case "PATCH":
11872 					goto PATCH;
11873 				case "DELETE":
11874 					goto DELETE;
11875 				case "POST":
11876 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
11877 						auto p = new obj.PostProxy();
11878 						void specialApplyChanges() {
11879 							applyChangesTemplate(cgi, p);
11880 						}
11881 						string n = p.create(&specialApplyChanges);
11882 					} else {
11883 						string n = obj.create(&applyChanges);
11884 					}
11885 
11886 					auto newUrl = cgi.scriptName ~ cgi.pathInfo ~ "/" ~ n;
11887 					cgi.setResponseLocation(newUrl);
11888 					cgi.setResponseStatus("201 Created");
11889 					cgi.write(`The object has been created.`);
11890 				break;
11891 				default:
11892 					cgi.write("bad method\n", true);
11893 			}
11894 			// FIXME this should be valid on the collection, but not the child....
11895 			// 303 See Other
11896 		break;
11897 		case Cgi.RequestMethod.PUT:
11898 		PUT:
11899 			obj.replace(urlId, &applyChanges);
11900 			writeObject(false);
11901 		break;
11902 		case Cgi.RequestMethod.PATCH:
11903 		PATCH:
11904 			obj.update(urlId, &applyChanges, modifiedList);
11905 			writeObject(false);
11906 		break;
11907 		case Cgi.RequestMethod.DELETE:
11908 		DELETE:
11909 			obj.remove(urlId);
11910 			cgi.setResponseStatus("204 No Content");
11911 		break;
11912 		default:
11913 			// FIXME: OPTIONS, HEAD
11914 	}
11915 	catch(Throwable t) {
11916 		presenter.presentExceptionAsHtml(cgi, t);
11917 	}
11918 
11919 	return true;
11920 }
11921 
11922 /+
11923 struct SetOfFields(T) {
11924 	private void[0][string] storage;
11925 	void set(string what) {
11926 		//storage[what] =
11927 	}
11928 	void unset(string what) {}
11929 	void setAll() {}
11930 	void unsetAll() {}
11931 	bool isPresent(string what) { return false; }
11932 }
11933 +/
11934 
11935 /+
11936 enum readonly;
11937 enum hideonindex;
11938 +/
11939 
11940 /++
11941 	Returns true if I recommend gzipping content of this type. You might
11942 	want to call it from your Presenter classes before calling cgi.write.
11943 
11944 	---
11945 	cgi.setResponseContentType(yourContentType);
11946 	cgi.gzipResponse = gzipRecommendedForContentType(yourContentType);
11947 	cgi.write(yourData, true);
11948 	---
11949 
11950 	This is used internally by [serveStaticFile], [serveStaticFileDirectory], [serveStaticData], and maybe others I forgot to update this doc about.
11951 
11952 
11953 	The implementation considers text content to be recommended to gzip. This may change, but it seems reasonable enough for now.
11954 
11955 	History:
11956 		Added January 28, 2023 (dub v11.0)
11957 +/
11958 bool gzipRecommendedForContentType(string contentType) {
11959 	if(contentType.startsWith("text/"))
11960 		return true;
11961 	if(contentType.startsWith("application/javascript"))
11962 		return true;
11963 
11964 	return false;
11965 }
11966 
11967 /++
11968 	Serves a static file. To be used with [dispatcher].
11969 
11970 	See_Also: [serveApi], [serveRestObject], [dispatcher], [serveRedirect]
11971 +/
11972 auto serveStaticFile(string urlPrefix, string filename = null, string contentType = null) {
11973 // https://baus.net/on-tcp_cork/
11974 // man 2 sendfile
11975 	assert(urlPrefix[0] == '/');
11976 	if(filename is null)
11977 		filename = decodeUriComponent(urlPrefix[1 .. $]); // FIXME is this actually correct?
11978 	if(contentType is null) {
11979 		contentType = contentTypeFromFileExtension(filename);
11980 	}
11981 
11982 	static struct DispatcherDetails {
11983 		string filename;
11984 		string contentType;
11985 	}
11986 
11987 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
11988 		if(details.contentType.indexOf("image/") == 0 || details.contentType.indexOf("audio/") == 0)
11989 			cgi.setCache(true);
11990 		cgi.setResponseContentType(details.contentType);
11991 		cgi.gzipResponse = gzipRecommendedForContentType(details.contentType);
11992 		cgi.write(std.file.read(details.filename), true);
11993 		return true;
11994 	}
11995 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(filename, contentType));
11996 }
11997 
11998 /++
11999 	Serves static data. To be used with [dispatcher].
12000 
12001 	History:
12002 		Added October 31, 2021
12003 +/
12004 auto serveStaticData(string urlPrefix, immutable(void)[] data, string contentType = null) {
12005 	assert(urlPrefix[0] == '/');
12006 	if(contentType is null) {
12007 		contentType = contentTypeFromFileExtension(urlPrefix);
12008 	}
12009 
12010 	static struct DispatcherDetails {
12011 		immutable(void)[] data;
12012 		string contentType;
12013 	}
12014 
12015 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12016 		cgi.setCache(true);
12017 		cgi.setResponseContentType(details.contentType);
12018 		cgi.write(details.data, true);
12019 		return true;
12020 	}
12021 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType));
12022 }
12023 
12024 string contentTypeFromFileExtension(string filename) {
12025 	import arsd.core;
12026 	return FilePath(filename).contentTypeFromFileExtension();
12027 }
12028 
12029 /// This serves a directory full of static files, figuring out the content-types from file extensions.
12030 /// It does not let you to descend into subdirectories (or ascend out of it, of course)
12031 auto serveStaticFileDirectory(string urlPrefix, string directory = null, bool recursive = false) {
12032 	assert(urlPrefix[0] == '/');
12033 	assert(urlPrefix[$-1] == '/');
12034 
12035 	static struct DispatcherDetails {
12036 		string directory;
12037 		bool recursive;
12038 	}
12039 
12040 	if(directory is null)
12041 		directory = urlPrefix[1 .. $];
12042 
12043 	if(directory.length == 0)
12044 		directory = "./";
12045 
12046 	assert(directory[$-1] == '/');
12047 
12048 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12049 		auto file = decodeUriComponent(cgi.pathInfo[urlPrefix.length .. $]); // FIXME: is this actually correct
12050 
12051 		if(details.recursive) {
12052 			// never allow a backslash since it isn't in a typical url anyway and makes the following checks easier
12053 			if(file.indexOf("\\") != -1)
12054 				return false;
12055 
12056 			import std.path;
12057 
12058 			file = std.path.buildNormalizedPath(file);
12059 			enum upOneDir = ".." ~ std.path.dirSeparator;
12060 
12061 			// also no point doing any kind of up directory things since that makes it more likely to break out of the parent
12062 			if(file == ".." || file.startsWith(upOneDir))
12063 				return false;
12064 			if(std.path.isAbsolute(file))
12065 				return false;
12066 
12067 			// FIXME: if it has slashes and stuff, should we redirect to the canonical resource? or what?
12068 
12069 			// once it passes these filters it is probably ok.
12070 		} else {
12071 			if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
12072 				return false;
12073 		}
12074 
12075 		if(file.length == 0)
12076 			return false;
12077 
12078 		auto contentType = contentTypeFromFileExtension(file);
12079 
12080 		auto fn = details.directory ~ file;
12081 		if(std.file.exists(fn)) {
12082 			//if(contentType.indexOf("image/") == 0)
12083 				//cgi.setCache(true);
12084 			//else if(contentType.indexOf("audio/") == 0)
12085 				cgi.setCache(true);
12086 			cgi.setResponseContentType(contentType);
12087 			cgi.gzipResponse = gzipRecommendedForContentType(contentType);
12088 			cgi.write(std.file.read(fn), true);
12089 			return true;
12090 		} else {
12091 			return false;
12092 		}
12093 	}
12094 
12095 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, false, DispatcherDetails(directory, recursive));
12096 }
12097 
12098 /++
12099 	Redirects one url to another
12100 
12101 	See_Also: [dispatcher], [serveStaticFile]
12102 +/
12103 auto serveRedirect(string urlPrefix, string redirectTo, int code = 303) {
12104 	assert(urlPrefix[0] == '/');
12105 	static struct DispatcherDetails {
12106 		string redirectTo;
12107 		string code;
12108 	}
12109 
12110 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12111 		cgi.setResponseLocation(details.redirectTo, true, details.code);
12112 		return true;
12113 	}
12114 
12115 
12116 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(redirectTo, getHttpCodeText(code)));
12117 }
12118 
12119 /// Used exclusively with `dispatchTo`
12120 struct DispatcherData(Presenter) {
12121 	Cgi cgi; /// You can use this cgi object.
12122 	Presenter presenter; /// This is the presenter from top level, and will be forwarded to the sub-dispatcher.
12123 	size_t pathInfoStart; /// This is forwarded to the sub-dispatcher. It may be marked private later, or at least read-only.
12124 }
12125 
12126 /++
12127 	Dispatches the URL to a specific function.
12128 +/
12129 auto handleWith(alias handler)(string urlPrefix) {
12130 	// cuz I'm too lazy to do it better right now
12131 	static class Hack : WebObject {
12132 		static import std.traits;
12133 		@UrlName("")
12134 		auto handle(std.traits.Parameters!handler args) {
12135 			return handler(args);
12136 		}
12137 	}
12138 
12139 	return urlPrefix.serveApiInternal!Hack;
12140 }
12141 
12142 /++
12143 	Dispatches the URL (and anything under it) to another dispatcher function. The function should look something like this:
12144 
12145 	---
12146 	bool other(DD)(DD dd) {
12147 		return dd.dispatcher!(
12148 			"/whatever".serveRedirect("/success"),
12149 			"/api/".serveApi!MyClass
12150 		);
12151 	}
12152 	---
12153 
12154 	The `DD` in there will be an instance of [DispatcherData] which you can inspect, or forward to another dispatcher
12155 	here. It is a template to account for any Presenter type, so you can do compile-time analysis in your presenters.
12156 	Or, of course, you could just use the exact type in your own code.
12157 
12158 	You return true if you handle the given url, or false if not. Just returning the result of [dispatcher] will do a
12159 	good job.
12160 
12161 
12162 +/
12163 auto dispatchTo(alias handler)(string urlPrefix) {
12164 	assert(urlPrefix[0] == '/');
12165 	assert(urlPrefix[$-1] != '/');
12166 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
12167 		return handler(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
12168 	}
12169 
12170 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
12171 }
12172 
12173 /++
12174 	See [serveStaticFile] if you want to serve a file off disk.
12175 
12176 	History:
12177 		Added January 28, 2023 (dub v11.0)
12178 +/
12179 auto serveStaticData(string urlPrefix, immutable(ubyte)[] data, string contentType, string filenameToSuggestAsDownload = null) {
12180 	assert(urlPrefix[0] == '/');
12181 
12182 	static struct DispatcherDetails {
12183 		immutable(ubyte)[] data;
12184 		string contentType;
12185 		string filenameToSuggestAsDownload;
12186 	}
12187 
12188 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12189 		cgi.setCache(true);
12190 		cgi.setResponseContentType(details.contentType);
12191 		if(details.filenameToSuggestAsDownload.length)
12192     			cgi.header("Content-Disposition: attachment; filename=\""~details.filenameToSuggestAsDownload~"\"");
12193 		cgi.gzipResponse = gzipRecommendedForContentType(details.contentType);
12194 		cgi.write(details.data, true);
12195 		return true;
12196 	}
12197 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType, filenameToSuggestAsDownload));
12198 }
12199 
12200 /++
12201 	Placeholder for use with [dispatchSubsection]'s `NewPresenter` argument to indicate you want to keep the parent's presenter.
12202 
12203 	History:
12204 		Added January 28, 2023 (dub v11.0)
12205 +/
12206 alias KeepExistingPresenter = typeof(null);
12207 
12208 /++
12209 	For use with [dispatchSubsection]. Calls your filter with the request and if your filter returns false,
12210 	this issues the given errorCode and stops processing.
12211 
12212 	---
12213 		bool hasAdminPermissions(Cgi cgi) {
12214 			return true;
12215 		}
12216 
12217 		mixin DispatcherMain!(
12218 			"/admin".dispatchSubsection!(
12219 				passFilterOrIssueError!(hasAdminPermissions, 403),
12220 				KeepExistingPresenter,
12221 				"/".serveApi!AdminFunctions
12222 			)
12223 		);
12224 	---
12225 
12226 	History:
12227 		Added January 28, 2023 (dub v11.0)
12228 +/
12229 template passFilterOrIssueError(alias filter, int errorCode) {
12230 	bool passFilterOrIssueError(DispatcherDetails)(DispatcherDetails dd) {
12231 		if(filter(dd.cgi))
12232 			return true;
12233 		dd.presenter.renderBasicError(dd.cgi, errorCode);
12234 		return false;
12235 	}
12236 }
12237 
12238 /++
12239 	Allows for a subsection of your dispatched urls to be passed through other a pre-request filter, optionally pick up an new presenter class,
12240 	and then be dispatched to their own handlers.
12241 
12242 	---
12243 	/+
12244 	// a long-form filter function
12245 	bool permissionCheck(DispatcherData)(DispatcherData dd) {
12246 		// you are permitted to call mutable methods on the Cgi object
12247 		// Note if you use a Cgi subclass, you can try dynamic casting it back to your custom type to attach per-request data
12248 		// though much of the request is immutable so there's only so much you're allowed to do to modify it.
12249 
12250 		if(checkPermissionOnRequest(dd.cgi)) {
12251 			return true; // OK, allow processing to continue
12252 		} else {
12253 			dd.presenter.renderBasicError(dd.cgi, 403); // reply forbidden to the requester
12254 			return false; // and stop further processing into this subsection
12255 		}
12256 	}
12257 	+/
12258 
12259 	// but you can also do short-form filters:
12260 
12261 	bool permissionCheck(Cgi cgi) {
12262 		return ("ok" in cgi.get) !is null;
12263 	}
12264 
12265 	// handler for the subsection
12266 	class AdminClass : WebObject {
12267 		int foo() { return 5; }
12268 	}
12269 
12270 	// handler for the main site
12271 	class TheMainSite : WebObject {}
12272 
12273 	mixin DispatcherMain!(
12274 		"/admin".dispatchSubsection!(
12275 			// converts our short-form filter into a long-form filter
12276 			passFilterOrIssueError!(permissionCheck, 403),
12277 			// can use a new presenter if wanted for the subsection
12278 			KeepExistingPresenter,
12279 			// and then provide child route dispatchers
12280 			"/".serveApi!AdminClass
12281 		),
12282 		// and back to the top level
12283 		"/".serveApi!TheMainSite
12284 	);
12285 	---
12286 
12287 	Note you can encapsulate sections in files like this:
12288 
12289 	---
12290 	auto adminDispatcher(string urlPrefix) {
12291 		return urlPrefix.dispatchSubsection!(
12292 			....
12293 		);
12294 	}
12295 
12296 	mixin DispatcherMain!(
12297 		"/admin".adminDispatcher,
12298 		// and so on
12299 	)
12300 	---
12301 
12302 	If you want no filter, you can pass `(cgi) => true` as the filter to approve all requests.
12303 
12304 	If you want to keep the same presenter as the parent, use [KeepExistingPresenter] as the presenter argument.
12305 
12306 
12307 	History:
12308 		Added January 28, 2023 (dub v11.0)
12309 +/
12310 auto dispatchSubsection(alias PreRequestFilter, NewPresenter, definitions...)(string urlPrefix) {
12311 	assert(urlPrefix[0] == '/');
12312 	assert(urlPrefix[$-1] != '/');
12313 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
12314 		static if(!is(PreRequestFilter == typeof(null))) {
12315 			if(!PreRequestFilter(DispatcherData!Presenter(cgi, presenter, urlPrefix.length)))
12316 				return true; // we handled it by rejecting it
12317 		}
12318 
12319 		static if(is(NewPresenter == Presenter) || is(NewPresenter == typeof(null))) {
12320 			return dispatcher!definitions(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
12321 		} else {
12322 			auto newPresenter = new NewPresenter();
12323 			return dispatcher!(definitions(DispatcherData!NewPresenter(cgi, presenter, urlPrefix.length)));
12324 		}
12325 	}
12326 
12327 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
12328 }
12329 
12330 /++
12331 	A URL dispatcher.
12332 
12333 	---
12334 	if(cgi.dispatcher!(
12335 		"/api/".serveApi!MyApiClass,
12336 		"/objects/lol".serveRestObject!MyRestObject,
12337 		"/file.js".serveStaticFile,
12338 		"/admin/".dispatchTo!adminHandler
12339 	)) return;
12340 	---
12341 
12342 
12343 	You define a series of url prefixes followed by handlers.
12344 
12345 	You may want to do different pre- and post- processing there, for example,
12346 	an authorization check and different page layout. You can use different
12347 	presenters and different function chains. See [dispatchSubsection] for details.
12348 
12349 	[dispatchTo] will send the request to another function for handling.
12350 +/
12351 template dispatcher(definitions...) {
12352 	bool dispatcher(Presenter)(Cgi cgi, Presenter presenterArg = null) {
12353 		static if(is(Presenter == typeof(null))) {
12354 			static class GenericWebPresenter : WebPresenter!(GenericWebPresenter) {}
12355 			auto presenter = new GenericWebPresenter();
12356 		} else
12357 			alias presenter = presenterArg;
12358 
12359 		return dispatcher(DispatcherData!(typeof(presenter))(cgi, presenter, 0));
12360 	}
12361 
12362 	bool dispatcher(DispatcherData)(DispatcherData dispatcherData) if(!is(DispatcherData : Cgi)) {
12363 		// I can prolly make this more efficient later but meh.
12364 		foreach(definition; definitions) {
12365 			if(definition.rejectFurther) {
12366 				if(dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $] == definition.urlPrefix) {
12367 					auto ret = definition.handler(
12368 						dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
12369 						dispatcherData.cgi, dispatcherData.presenter, definition.details);
12370 					if(ret)
12371 						return true;
12372 				}
12373 			} else if(
12374 				dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $].startsWith(definition.urlPrefix) &&
12375 				// cgi.d dispatcher urls must be complete or have a /;
12376 				// "foo" -> thing should NOT match "foobar", just "foo" or "foo/thing"
12377 				(definition.urlPrefix[$-1] == '/' || (dispatcherData.pathInfoStart + definition.urlPrefix.length) == dispatcherData.cgi.pathInfo.length
12378 				|| dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart + definition.urlPrefix.length] == '/')
12379 				) {
12380 				auto ret = definition.handler(
12381 					dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
12382 					dispatcherData.cgi, dispatcherData.presenter, definition.details);
12383 				if(ret)
12384 					return true;
12385 			}
12386 		}
12387 		return false;
12388 	}
12389 }
12390 
12391 });
12392 
12393 private struct StackBuffer {
12394 	char[1024] initial = void;
12395 	char[] buffer;
12396 	size_t position;
12397 
12398 	this(int a) {
12399 		buffer = initial[];
12400 		position = 0;
12401 	}
12402 
12403 	void add(in char[] what) {
12404 		if(position + what.length > buffer.length)
12405 			buffer.length = position + what.length + 1024; // reallocate with GC to handle special cases
12406 		buffer[position .. position + what.length] = what[];
12407 		position += what.length;
12408 	}
12409 
12410 	void add(in char[] w1, in char[] w2, in char[] w3 = null) {
12411 		add(w1);
12412 		add(w2);
12413 		add(w3);
12414 	}
12415 
12416 	void add(long v) {
12417 		char[16] buffer = void;
12418 		auto pos = buffer.length;
12419 		bool negative;
12420 		if(v < 0) {
12421 			negative = true;
12422 			v = -v;
12423 		}
12424 		do {
12425 			buffer[--pos] = cast(char) (v % 10 + '0');
12426 			v /= 10;
12427 		} while(v);
12428 
12429 		if(negative)
12430 			buffer[--pos] = '-';
12431 
12432 		auto res = buffer[pos .. $];
12433 
12434 		add(res[]);
12435 	}
12436 
12437 	char[] get() @nogc {
12438 		return buffer[0 .. position];
12439 	}
12440 }
12441 
12442 // duplicated in http2.d
12443 private static string getHttpCodeText(int code) pure nothrow @nogc {
12444 	switch(code) {
12445 		case 200: return "200 OK";
12446 		case 201: return "201 Created";
12447 		case 202: return "202 Accepted";
12448 		case 203: return "203 Non-Authoritative Information";
12449 		case 204: return "204 No Content";
12450 		case 205: return "205 Reset Content";
12451 		case 206: return "206 Partial Content";
12452 		//
12453 		case 300: return "300 Multiple Choices";
12454 		case 301: return "301 Moved Permanently";
12455 		case 302: return "302 Found";
12456 		case 303: return "303 See Other";
12457 		case 304: return "304 Not Modified";
12458 		case 305: return "305 Use Proxy";
12459 		case 307: return "307 Temporary Redirect";
12460 		case 308: return "308 Permanent Redirect";
12461 
12462 		//
12463 		case 400: return "400 Bad Request";
12464 		case 401: return "401 Unauthorized";
12465 		case 402: return "402 Payment Required";
12466 		case 403: return "403 Forbidden";
12467 		case 404: return "404 Not Found";
12468 		case 405: return "405 Method Not Allowed";
12469 		case 406: return "406 Not Acceptable";
12470 		case 407: return "407 Proxy Authentication Required";
12471 		case 408: return "408 Request Timeout";
12472 		case 409: return "409 Conflict";
12473 		case 410: return "410 Gone";
12474 		case 411: return "411 Length Required";
12475 		case 412: return "412 Precondition Failed";
12476 		case 413: return "413 Payload Too Large";
12477 		case 414: return "414 URI Too Long";
12478 		case 415: return "415 Unsupported Media Type";
12479 		case 416: return "416 Range Not Satisfiable";
12480 		case 417: return "417 Expectation Failed";
12481 		case 418: return "418 I'm a teapot";
12482 		case 421: return "421 Misdirected Request";
12483 		case 422: return "422 Unprocessable Entity (WebDAV)";
12484 		case 423: return "423 Locked (WebDAV)";
12485 		case 424: return "424 Failed Dependency (WebDAV)";
12486 		case 425: return "425 Too Early";
12487 		case 426: return "426 Upgrade Required";
12488 		case 428: return "428 Precondition Required";
12489 		case 431: return "431 Request Header Fields Too Large";
12490 		case 451: return "451 Unavailable For Legal Reasons";
12491 
12492 		case 500: return "500 Internal Server Error";
12493 		case 501: return "501 Not Implemented";
12494 		case 502: return "502 Bad Gateway";
12495 		case 503: return "503 Service Unavailable";
12496 		case 504: return "504 Gateway Timeout";
12497 		case 505: return "505 HTTP Version Not Supported";
12498 		case 506: return "506 Variant Also Negotiates";
12499 		case 507: return "507 Insufficient Storage (WebDAV)";
12500 		case 508: return "508 Loop Detected (WebDAV)";
12501 		case 510: return "510 Not Extended";
12502 		case 511: return "511 Network Authentication Required";
12503 		//
12504 		default: assert(0, "Unsupported http code");
12505 	}
12506 }
12507 
12508 
12509 /+
12510 /++
12511 	This is the beginnings of my web.d 2.0 - it dispatches web requests to a class object.
12512 
12513 	It relies on jsvar.d and dom.d.
12514 
12515 
12516 	You can get javascript out of it to call. The generated functions need to look
12517 	like
12518 
12519 	function name(a,b,c,d,e) {
12520 		return _call("name", {"realName":a,"sds":b});
12521 	}
12522 
12523 	And _call returns an object you can call or set up or whatever.
12524 +/
12525 bool apiDispatcher()(Cgi cgi) {
12526 	import arsd.jsvar;
12527 	import arsd.dom;
12528 }
12529 +/
12530 version(linux)
12531 private extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
12532 /*
12533 Copyright: Adam D. Ruppe, 2008 - 2023
12534 License:   [http://www.boost.org/LICENSE_1_0.txt|Boost License 1.0].
12535 Authors: Adam D. Ruppe
12536 
12537 	Copyright Adam D. Ruppe 2008 - 2023.
12538 Distributed under the Boost Software License, Version 1.0.
12539    (See accompanying file LICENSE_1_0.txt or copy at
12540 	http://www.boost.org/LICENSE_1_0.txt)
12541 */