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 			bfr.popFront(0);
6936 			if(bfr.sourceClosed)
6937 				return false;
6938 			goto top;
6939 		}
6940 
6941 
6942 		bool isDataPending(Duration timeout = 0.seconds) {
6943 			Socket socket = cgi.idlol.source;
6944 
6945 			auto check = new SocketSet();
6946 			check.add(socket);
6947 
6948 			auto got = Socket.select(check, null, null, timeout);
6949 			if(got > 0)
6950 				return true;
6951 			return false;
6952 		}
6953 
6954 		// note: this blocks
6955 		WebSocketFrame recv() {
6956 			return waitForNextMessage();
6957 		}
6958 
6959 
6960 
6961 
6962 		private void llclose() {
6963 			cgi.close();
6964 		}
6965 
6966 		private void llsend(ubyte[] data) {
6967 			cgi.write(data);
6968 			cgi.flush();
6969 		}
6970 
6971 		void unregisterActiveSocket(WebSocket) {}
6972 
6973 		/* copy/paste section { */
6974 
6975 		private int readyState_;
6976 		private ubyte[] receiveBuffer;
6977 		private size_t receiveBufferUsedLength;
6978 
6979 		private Config config;
6980 
6981 		enum CONNECTING = 0; /// Socket has been created. The connection is not yet open.
6982 		enum OPEN = 1; /// The connection is open and ready to communicate.
6983 		enum CLOSING = 2; /// The connection is in the process of closing.
6984 		enum CLOSED = 3; /// The connection is closed or couldn't be opened.
6985 
6986 		/++
6987 
6988 		+/
6989 		/// Group: foundational
6990 		static struct Config {
6991 			/++
6992 				These control the size of the receive buffer.
6993 
6994 				It starts at the initial size, will temporarily
6995 				balloon up to the maximum size, and will reuse
6996 				a buffer up to the likely size.
6997 
6998 				Anything larger than the maximum size will cause
6999 				the connection to be aborted and an exception thrown.
7000 				This is to protect you against a peer trying to
7001 				exhaust your memory, while keeping the user-level
7002 				processing simple.
7003 			+/
7004 			size_t initialReceiveBufferSize = 4096;
7005 			size_t likelyReceiveBufferSize = 4096; /// ditto
7006 			size_t maximumReceiveBufferSize = 10 * 1024 * 1024; /// ditto
7007 
7008 			/++
7009 				Maximum combined size of a message.
7010 			+/
7011 			size_t maximumMessageSize = 10 * 1024 * 1024;
7012 
7013 			string[string] cookies; /// Cookies to send with the initial request. cookies[name] = value;
7014 			string origin; /// Origin URL to send with the handshake, if desired.
7015 			string protocol; /// the protocol header, if desired.
7016 
7017 			/++
7018 				Additional headers to put in the HTTP request. These should be formatted `Name: value`, like for example:
7019 
7020 				---
7021 				Config config;
7022 				config.additionalHeaders ~= "Authorization: Bearer your_auth_token_here";
7023 				---
7024 
7025 				History:
7026 					Added February 19, 2021 (included in dub version 9.2)
7027 			+/
7028 			string[] additionalHeaders;
7029 
7030 			/++
7031 				Amount of time (in msecs) of idleness after which to send an automatic ping
7032 
7033 				Please note how this interacts with [timeoutFromInactivity] - a ping counts as activity that
7034 				keeps the socket alive.
7035 			+/
7036 			int pingFrequency = 5000;
7037 
7038 			/++
7039 				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.
7040 
7041 				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!
7042 
7043 				History:
7044 					Added March 31, 2021 (included in dub version 9.4)
7045 			+/
7046 			Duration timeoutFromInactivity = 1.minutes;
7047 
7048 			/++
7049 				For https connections, if this is `true`, it will fail to connect if the TLS certificate can not be
7050 				verified. Setting this to `false` will skip this check and allow the connection to continue anyway.
7051 
7052 				History:
7053 					Added April 5, 2022 (dub v10.8)
7054 
7055 					Prior to this, it always used the global (but undocumented) `defaultVerifyPeer` setting, and sometimes
7056 					even if it was true, it would skip the verification. Now, it always respects this local setting.
7057 			+/
7058 			bool verifyPeer = true;
7059 		}
7060 
7061 		/++
7062 			Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED].
7063 		+/
7064 		int readyState() {
7065 			return readyState_;
7066 		}
7067 
7068 		/++
7069 			Closes the connection, sending a graceful teardown message to the other side.
7070 
7071 			Code 1000 is the normal closure code.
7072 
7073 			History:
7074 				The default `code` was changed to 1000 on January 9, 2023. Previously it was 0,
7075 				but also ignored anyway.
7076 		+/
7077 		/// Group: foundational
7078 		void close(int code = 1000, string reason = null)
7079 			//in (reason.length < 123)
7080 			in { assert(reason.length < 123); } do
7081 		{
7082 			if(readyState_ != OPEN)
7083 				return; // it cool, we done
7084 			WebSocketFrame wss;
7085 			wss.fin = true;
7086 			wss.masked = this.isClient;
7087 			wss.opcode = WebSocketOpcode.close;
7088 			wss.data = [ubyte((code >> 8) & 0xff), ubyte(code & 0xff)] ~ cast(ubyte[]) reason.dup;
7089 			wss.send(&llsend);
7090 
7091 			readyState_ = CLOSING;
7092 
7093 			closeCalled = true;
7094 
7095 			llclose();
7096 		}
7097 
7098 		private bool closeCalled;
7099 
7100 		/++
7101 			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.
7102 		+/
7103 		/// Group: foundational
7104 		void ping(in ubyte[] data = null) {
7105 			WebSocketFrame wss;
7106 			wss.fin = true;
7107 			wss.masked = this.isClient;
7108 			wss.opcode = WebSocketOpcode.ping;
7109 			if(data !is null) wss.data = data.dup;
7110 			wss.send(&llsend);
7111 		}
7112 
7113 		/++
7114 			Sends a pong message to the server. This is normally done automatically in response to pings.
7115 		+/
7116 		/// Group: foundational
7117 		void pong(in ubyte[] data = null) {
7118 			WebSocketFrame wss;
7119 			wss.fin = true;
7120 			wss.masked = this.isClient;
7121 			wss.opcode = WebSocketOpcode.pong;
7122 			if(data !is null) wss.data = data.dup;
7123 			wss.send(&llsend);
7124 		}
7125 
7126 		/++
7127 			Sends a text message through the websocket.
7128 		+/
7129 		/// Group: foundational
7130 		void send(in char[] textData) {
7131 			WebSocketFrame wss;
7132 			wss.fin = true;
7133 			wss.masked = this.isClient;
7134 			wss.opcode = WebSocketOpcode.text;
7135 			wss.data = cast(ubyte[]) textData.dup;
7136 			wss.send(&llsend);
7137 		}
7138 
7139 		/++
7140 			Sends a binary message through the websocket.
7141 		+/
7142 		/// Group: foundational
7143 		void send(in ubyte[] binaryData) {
7144 			WebSocketFrame wss;
7145 			wss.masked = this.isClient;
7146 			wss.fin = true;
7147 			wss.opcode = WebSocketOpcode.binary;
7148 			wss.data = cast(ubyte[]) binaryData.dup;
7149 			wss.send(&llsend);
7150 		}
7151 
7152 		/++
7153 			Waits for and returns the next complete message on the socket.
7154 
7155 			Note that the onmessage function is still called, right before
7156 			this returns.
7157 		+/
7158 		/// Group: blocking_api
7159 		public WebSocketFrame waitForNextMessage() {
7160 			do {
7161 				auto m = processOnce();
7162 				if(m.populated)
7163 					return m;
7164 			} while(lowLevelReceive());
7165 
7166 			throw new ConnectionClosedException("Websocket receive timed out");
7167 			//return WebSocketFrame.init; // FIXME? maybe.
7168 		}
7169 
7170 		/++
7171 			Tells if [waitForNextMessage] would block.
7172 		+/
7173 		/// Group: blocking_api
7174 		public bool waitForNextMessageWouldBlock() {
7175 			checkAgain:
7176 			if(isMessageBuffered())
7177 				return false;
7178 			if(!isDataPending())
7179 				return true;
7180 
7181 			while(isDataPending()) {
7182 				if(lowLevelReceive() == false)
7183 					throw new ConnectionClosedException("Connection closed in middle of message");
7184 			}
7185 
7186 			goto checkAgain;
7187 		}
7188 
7189 		/++
7190 			Is there a message in the buffer already?
7191 			If `true`, [waitForNextMessage] is guaranteed to return immediately.
7192 			If `false`, check [isDataPending] as the next step.
7193 		+/
7194 		/// Group: blocking_api
7195 		public bool isMessageBuffered() {
7196 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
7197 			auto s = d;
7198 			if(d.length) {
7199 				auto orig = d;
7200 				auto m = WebSocketFrame.read(d);
7201 				// that's how it indicates that it needs more data
7202 				if(d !is orig)
7203 					return true;
7204 			}
7205 
7206 			return false;
7207 		}
7208 
7209 		private ubyte continuingType;
7210 		private ubyte[] continuingData;
7211 		//private size_t continuingDataLength;
7212 
7213 		private WebSocketFrame processOnce() {
7214 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
7215 			auto s = d;
7216 			// FIXME: handle continuation frames more efficiently. it should really just reuse the receive buffer.
7217 			WebSocketFrame m;
7218 			if(d.length) {
7219 				auto orig = d;
7220 				m = WebSocketFrame.read(d);
7221 				// that's how it indicates that it needs more data
7222 				if(d is orig)
7223 					return WebSocketFrame.init;
7224 				m.unmaskInPlace();
7225 				switch(m.opcode) {
7226 					case WebSocketOpcode.continuation:
7227 						if(continuingData.length + m.data.length > config.maximumMessageSize)
7228 							throw new Exception("message size exceeded");
7229 
7230 						continuingData ~= m.data;
7231 						if(m.fin) {
7232 							if(ontextmessage)
7233 								ontextmessage(cast(char[]) continuingData);
7234 							if(onbinarymessage)
7235 								onbinarymessage(continuingData);
7236 
7237 							continuingData = null;
7238 						}
7239 					break;
7240 					case WebSocketOpcode.text:
7241 						if(m.fin) {
7242 							if(ontextmessage)
7243 								ontextmessage(m.textData);
7244 						} else {
7245 							continuingType = m.opcode;
7246 							//continuingDataLength = 0;
7247 							continuingData = null;
7248 							continuingData ~= m.data;
7249 						}
7250 					break;
7251 					case WebSocketOpcode.binary:
7252 						if(m.fin) {
7253 							if(onbinarymessage)
7254 								onbinarymessage(m.data);
7255 						} else {
7256 							continuingType = m.opcode;
7257 							//continuingDataLength = 0;
7258 							continuingData = null;
7259 							continuingData ~= m.data;
7260 						}
7261 					break;
7262 					case WebSocketOpcode.close:
7263 
7264 						//import std.stdio; writeln("closed ", cast(string) m.data);
7265 
7266 						ushort code = CloseEvent.StandardCloseCodes.noStatusCodePresent;
7267 						const(char)[] reason;
7268 
7269 						if(m.data.length >= 2) {
7270 							code = (m.data[0] << 8) | m.data[1];
7271 							reason = (cast(char[]) m.data[2 .. $]);
7272 						}
7273 
7274 						if(onclose)
7275 							onclose(CloseEvent(code, reason, true));
7276 
7277 						// if we receive one and haven't sent one back we're supposed to echo it back and close.
7278 						if(!closeCalled)
7279 							close(code, reason.idup);
7280 
7281 						readyState_ = CLOSED;
7282 
7283 						unregisterActiveSocket(this);
7284 					break;
7285 					case WebSocketOpcode.ping:
7286 						// import std.stdio; writeln("ping received ", m.data);
7287 						pong(m.data);
7288 					break;
7289 					case WebSocketOpcode.pong:
7290 						// import std.stdio; writeln("pong received ", m.data);
7291 						// just really references it is still alive, nbd.
7292 					break;
7293 					default: // ignore though i could and perhaps should throw too
7294 				}
7295 			}
7296 
7297 			if(d.length) {
7298 				m.data = m.data.dup();
7299 			}
7300 
7301 			import core.stdc.string;
7302 			memmove(receiveBuffer.ptr, d.ptr, d.length);
7303 			receiveBufferUsedLength = d.length;
7304 
7305 			return m;
7306 		}
7307 
7308 		private void autoprocess() {
7309 			// FIXME
7310 			do {
7311 				processOnce();
7312 			} while(lowLevelReceive());
7313 		}
7314 
7315 		/++
7316 			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.
7317 
7318 			$(PITFALL
7319 				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.
7320 			)
7321 
7322 			History:
7323 				Added March 19, 2023 (dub v11.0).
7324 		+/
7325 		static struct CloseEvent {
7326 			ushort code;
7327 			const(char)[] reason;
7328 			bool wasClean;
7329 
7330 			string extendedErrorInformationUnstable;
7331 
7332 			/++
7333 				See https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 for details.
7334 			+/
7335 			enum StandardCloseCodes {
7336 				purposeFulfilled = 1000,
7337 				goingAway = 1001,
7338 				protocolError = 1002,
7339 				unacceptableData = 1003, // e.g. got text message when you can only handle binary
7340 				Reserved = 1004,
7341 				noStatusCodePresent = 1005, // not set by endpoint.
7342 				abnormalClosure = 1006, // not set by endpoint. closed without a Close control. FIXME: maybe keep a copy of errno around for these
7343 				inconsistentData = 1007, // e.g. utf8 validation failed
7344 				genericPolicyViolation = 1008,
7345 				messageTooBig = 1009,
7346 				clientRequiredExtensionMissing = 1010, // only the client should send this
7347 				unnexpectedCondition = 1011,
7348 				unverifiedCertificate = 1015, // not set by client
7349 			}
7350 		}
7351 
7352 		/++
7353 			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.
7354 
7355 			History:
7356 				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.
7357 
7358 				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.
7359 		+/
7360 		arsd.core.FlexibleDelegate!(void delegate(CloseEvent event)) onclose;
7361 		void delegate() onerror; ///
7362 		void delegate(in char[]) ontextmessage; ///
7363 		void delegate(in ubyte[]) onbinarymessage; ///
7364 		void delegate() onopen; ///
7365 
7366 		/++
7367 
7368 		+/
7369 		/// Group: browser_api
7370 		void onmessage(void delegate(in char[]) dg) {
7371 			ontextmessage = dg;
7372 		}
7373 
7374 		/// ditto
7375 		void onmessage(void delegate(in ubyte[]) dg) {
7376 			onbinarymessage = dg;
7377 		}
7378 
7379 	/* } end copy/paste */
7380 
7381 
7382 
7383 	}
7384 
7385 	/++
7386 		Returns true if the request headers are asking for a websocket upgrade.
7387 
7388 		If this returns true, and you want to accept it, call [acceptWebsocket].
7389 	+/
7390 	bool websocketRequested(Cgi cgi) {
7391 		return
7392 			"sec-websocket-key" in cgi.requestHeaders
7393 			&&
7394 			"connection" in cgi.requestHeaders &&
7395 				cgi.requestHeaders["connection"].asLowerCase().canFind("upgrade")
7396 			&&
7397 			"upgrade" in cgi.requestHeaders &&
7398 				cgi.requestHeaders["upgrade"].asLowerCase().equal("websocket")
7399 			;
7400 	}
7401 
7402 	/++
7403 		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.
7404 	+/
7405 	WebSocket acceptWebsocket(Cgi cgi) {
7406 		assert(!cgi.closed);
7407 		assert(!cgi.outputtedResponseData);
7408 		cgi.setResponseStatus("101 Switching Protocols");
7409 		cgi.header("Upgrade: WebSocket");
7410 		cgi.header("Connection: upgrade");
7411 
7412 		string key = cgi.requestHeaders["sec-websocket-key"];
7413 		key ~= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // the defined guid from the websocket spec
7414 
7415 		import std.digest.sha;
7416 		auto hash = sha1Of(key);
7417 		auto accept = Base64.encode(hash);
7418 
7419 		cgi.header(("Sec-WebSocket-Accept: " ~ accept).idup);
7420 
7421 		cgi.websocketMode = true;
7422 		cgi.write("");
7423 
7424 		cgi.flush();
7425 
7426 		auto ws = new WebSocket(cgi);
7427 		ws.readyState_ = WebSocket.OPEN;
7428 		return ws;
7429 	}
7430 
7431 	// FIXME get websocket to work on other modes, not just embedded_httpd
7432 
7433 	/* copy/paste in http2.d { */
7434 	enum WebSocketOpcode : ubyte {
7435 		continuation = 0,
7436 		text = 1,
7437 		binary = 2,
7438 		// 3, 4, 5, 6, 7 RESERVED
7439 		close = 8,
7440 		ping = 9,
7441 		pong = 10,
7442 		// 11,12,13,14,15 RESERVED
7443 	}
7444 
7445 	public struct WebSocketFrame {
7446 		private bool populated;
7447 		bool fin;
7448 		bool rsv1;
7449 		bool rsv2;
7450 		bool rsv3;
7451 		WebSocketOpcode opcode; // 4 bits
7452 		bool masked;
7453 		ubyte lengthIndicator; // don't set this when building one to send
7454 		ulong realLength; // don't use when sending
7455 		ubyte[4] maskingKey; // don't set this when sending
7456 		ubyte[] data;
7457 
7458 		static WebSocketFrame simpleMessage(WebSocketOpcode opcode, void[] data) {
7459 			WebSocketFrame msg;
7460 			msg.fin = true;
7461 			msg.opcode = opcode;
7462 			msg.data = cast(ubyte[]) data.dup;
7463 
7464 			return msg;
7465 		}
7466 
7467 		private void send(scope void delegate(ubyte[]) llsend) {
7468 			ubyte[64] headerScratch;
7469 			int headerScratchPos = 0;
7470 
7471 			realLength = data.length;
7472 
7473 			{
7474 				ubyte b1;
7475 				b1 |= cast(ubyte) opcode;
7476 				b1 |= rsv3 ? (1 << 4) : 0;
7477 				b1 |= rsv2 ? (1 << 5) : 0;
7478 				b1 |= rsv1 ? (1 << 6) : 0;
7479 				b1 |= fin  ? (1 << 7) : 0;
7480 
7481 				headerScratch[0] = b1;
7482 				headerScratchPos++;
7483 			}
7484 
7485 			{
7486 				headerScratchPos++; // we'll set header[1] at the end of this
7487 				auto rlc = realLength;
7488 				ubyte b2;
7489 				b2 |= masked ? (1 << 7) : 0;
7490 
7491 				assert(headerScratchPos == 2);
7492 
7493 				if(realLength > 65535) {
7494 					// use 64 bit length
7495 					b2 |= 0x7f;
7496 
7497 					// FIXME: double check endinaness
7498 					foreach(i; 0 .. 8) {
7499 						headerScratch[2 + 7 - i] = rlc & 0x0ff;
7500 						rlc >>>= 8;
7501 					}
7502 
7503 					headerScratchPos += 8;
7504 				} else if(realLength > 125) {
7505 					// use 16 bit length
7506 					b2 |= 0x7e;
7507 
7508 					// FIXME: double check endinaness
7509 					foreach(i; 0 .. 2) {
7510 						headerScratch[2 + 1 - i] = rlc & 0x0ff;
7511 						rlc >>>= 8;
7512 					}
7513 
7514 					headerScratchPos += 2;
7515 				} else {
7516 					// use 7 bit length
7517 					b2 |= realLength & 0b_0111_1111;
7518 				}
7519 
7520 				headerScratch[1] = b2;
7521 			}
7522 
7523 			//assert(!masked, "masking key not properly implemented");
7524 			if(masked) {
7525 				// FIXME: randomize this
7526 				headerScratch[headerScratchPos .. headerScratchPos + 4] = maskingKey[];
7527 				headerScratchPos += 4;
7528 
7529 				// we'll just mask it in place...
7530 				int keyIdx = 0;
7531 				foreach(i; 0 .. data.length) {
7532 					data[i] = data[i] ^ maskingKey[keyIdx];
7533 					if(keyIdx == 3)
7534 						keyIdx = 0;
7535 					else
7536 						keyIdx++;
7537 				}
7538 			}
7539 
7540 			//writeln("SENDING ", headerScratch[0 .. headerScratchPos], data);
7541 			llsend(headerScratch[0 .. headerScratchPos]);
7542 			llsend(data);
7543 		}
7544 
7545 		static WebSocketFrame read(ref ubyte[] d) {
7546 			WebSocketFrame msg;
7547 
7548 			auto orig = d;
7549 
7550 			WebSocketFrame needsMoreData() {
7551 				d = orig;
7552 				return WebSocketFrame.init;
7553 			}
7554 
7555 			if(d.length < 2)
7556 				return needsMoreData();
7557 
7558 			ubyte b = d[0];
7559 
7560 			msg.populated = true;
7561 
7562 			msg.opcode = cast(WebSocketOpcode) (b & 0x0f);
7563 			b >>= 4;
7564 			msg.rsv3 = b & 0x01;
7565 			b >>= 1;
7566 			msg.rsv2 = b & 0x01;
7567 			b >>= 1;
7568 			msg.rsv1 = b & 0x01;
7569 			b >>= 1;
7570 			msg.fin = b & 0x01;
7571 
7572 			b = d[1];
7573 			msg.masked = (b & 0b1000_0000) ? true : false;
7574 			msg.lengthIndicator = b & 0b0111_1111;
7575 
7576 			d = d[2 .. $];
7577 
7578 			if(msg.lengthIndicator == 0x7e) {
7579 				// 16 bit length
7580 				msg.realLength = 0;
7581 
7582 				if(d.length < 2) return needsMoreData();
7583 
7584 				foreach(i; 0 .. 2) {
7585 					msg.realLength |= d[0] << ((1-i) * 8);
7586 					d = d[1 .. $];
7587 				}
7588 			} else if(msg.lengthIndicator == 0x7f) {
7589 				// 64 bit length
7590 				msg.realLength = 0;
7591 
7592 				if(d.length < 8) return needsMoreData();
7593 
7594 				foreach(i; 0 .. 8) {
7595 					msg.realLength |= ulong(d[0]) << ((7-i) * 8);
7596 					d = d[1 .. $];
7597 				}
7598 			} else {
7599 				// 7 bit length
7600 				msg.realLength = msg.lengthIndicator;
7601 			}
7602 
7603 			if(msg.masked) {
7604 
7605 				if(d.length < 4) return needsMoreData();
7606 
7607 				msg.maskingKey = d[0 .. 4];
7608 				d = d[4 .. $];
7609 			}
7610 
7611 			if(msg.realLength > d.length) {
7612 				return needsMoreData();
7613 			}
7614 
7615 			msg.data = d[0 .. cast(size_t) msg.realLength];
7616 			d = d[cast(size_t) msg.realLength .. $];
7617 
7618 			return msg;
7619 		}
7620 
7621 		void unmaskInPlace() {
7622 			if(this.masked) {
7623 				int keyIdx = 0;
7624 				foreach(i; 0 .. this.data.length) {
7625 					this.data[i] = this.data[i] ^ this.maskingKey[keyIdx];
7626 					if(keyIdx == 3)
7627 						keyIdx = 0;
7628 					else
7629 						keyIdx++;
7630 				}
7631 			}
7632 		}
7633 
7634 		char[] textData() {
7635 			return cast(char[]) data;
7636 		}
7637 	}
7638 	/* } */
7639 }
7640 
7641 
7642 version(Windows)
7643 {
7644     version(CRuntime_DigitalMars)
7645     {
7646         extern(C) int setmode(int, int) nothrow @nogc;
7647     }
7648     else version(CRuntime_Microsoft)
7649     {
7650         extern(C) int _setmode(int, int) nothrow @nogc;
7651         alias setmode = _setmode;
7652     }
7653     else static assert(0);
7654 }
7655 
7656 version(Posix) {
7657 	import core.sys.posix.unistd;
7658 	version(CRuntime_Musl) {} else {
7659 		private extern(C) int posix_spawn(pid_t*, const char*, void*, void*, const char**, const char**);
7660 	}
7661 }
7662 
7663 
7664 // FIXME: these aren't quite public yet.
7665 //private:
7666 
7667 // template for laziness
7668 void startAddonServer()(string arg) {
7669 	version(OSX) {
7670 		assert(0, "Not implemented");
7671 	} else version(linux) {
7672 		import core.sys.posix.unistd;
7673 		pid_t pid;
7674 		const(char)*[16] args;
7675 		args[0] = "ARSD_CGI_ADDON_SERVER";
7676 		args[1] = arg.ptr;
7677 		posix_spawn(&pid, "/proc/self/exe",
7678 			null,
7679 			null,
7680 			args.ptr,
7681 			null // env
7682 		);
7683 	} else version(Windows) {
7684 		wchar[2048] filename;
7685 		auto len = GetModuleFileNameW(null, filename.ptr, cast(DWORD) filename.length);
7686 		if(len == 0 || len == filename.length)
7687 			throw new Exception("could not get process name to start helper server");
7688 
7689 		STARTUPINFOW startupInfo;
7690 		startupInfo.cb = cast(DWORD) startupInfo.sizeof;
7691 		PROCESS_INFORMATION processInfo;
7692 
7693 		import std.utf;
7694 
7695 		// I *MIGHT* need to run it as a new job or a service...
7696 		auto ret = CreateProcessW(
7697 			filename.ptr,
7698 			toUTF16z(arg),
7699 			null, // process attributes
7700 			null, // thread attributes
7701 			false, // inherit handles
7702 			0, // creation flags
7703 			null, // environment
7704 			null, // working directory
7705 			&startupInfo,
7706 			&processInfo
7707 		);
7708 
7709 		if(!ret)
7710 			throw new Exception("create process failed");
7711 
7712 		// when done with those, if we set them
7713 		/*
7714 		CloseHandle(hStdInput);
7715 		CloseHandle(hStdOutput);
7716 		CloseHandle(hStdError);
7717 		*/
7718 
7719 	} else static assert(0, "Websocket server not implemented on this system yet (email me, i can prolly do it if you need it)");
7720 }
7721 
7722 // template for laziness
7723 /*
7724 	The websocket server is a single-process, single-thread, event
7725 	I/O thing. It is passed websockets from other CGI processes
7726 	and is then responsible for handling their messages and responses.
7727 	Note that the CGI process is responsible for websocket setup,
7728 	including authentication, etc.
7729 
7730 	It also gets data sent to it by other processes and is responsible
7731 	for distributing that, as necessary.
7732 */
7733 void runWebsocketServer()() {
7734 	assert(0, "not implemented");
7735 }
7736 
7737 void sendToWebsocketServer(WebSocket ws, string group) {
7738 	assert(0, "not implemented");
7739 }
7740 
7741 void sendToWebsocketServer(string content, string group) {
7742 	assert(0, "not implemented");
7743 }
7744 
7745 
7746 void runEventServer()() {
7747 	runAddonServer("/tmp/arsd_cgi_event_server", new EventSourceServerImplementation());
7748 }
7749 
7750 void runTimerServer()() {
7751 	runAddonServer("/tmp/arsd_scheduled_job_server", new ScheduledJobServerImplementation());
7752 }
7753 
7754 version(Posix) {
7755 	alias LocalServerConnectionHandle = int;
7756 	alias CgiConnectionHandle = int;
7757 	alias SocketConnectionHandle = int;
7758 
7759 	enum INVALID_CGI_CONNECTION_HANDLE = -1;
7760 } else version(Windows) {
7761 	alias LocalServerConnectionHandle = HANDLE;
7762 	version(embedded_httpd_threads) {
7763 		alias CgiConnectionHandle = SOCKET;
7764 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
7765 	} else version(fastcgi) {
7766 		alias CgiConnectionHandle = void*; // Doesn't actually work! But I don't want compile to fail pointlessly at this point.
7767 		enum INVALID_CGI_CONNECTION_HANDLE = null;
7768 	} else version(scgi) {
7769 		alias CgiConnectionHandle = SOCKET;
7770 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
7771 	} else { /* version(plain_cgi) */
7772 		alias CgiConnectionHandle = HANDLE;
7773 		enum INVALID_CGI_CONNECTION_HANDLE = null;
7774 	}
7775 	alias SocketConnectionHandle = SOCKET;
7776 }
7777 
7778 version(with_addon_servers_connections)
7779 LocalServerConnectionHandle openLocalServerConnection()(string name, string arg) {
7780 	version(Posix) {
7781 		import core.sys.posix.unistd;
7782 		import core.sys.posix.sys.un;
7783 
7784 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
7785 		if(sock == -1)
7786 			throw new Exception("socket " ~ to!string(errno));
7787 
7788 		scope(failure)
7789 			close(sock);
7790 
7791 		cloexec(sock);
7792 
7793 		// add-on server processes are assumed to be local, and thus will
7794 		// use unix domain sockets. Besides, I want to pass sockets to them,
7795 		// so it basically must be local (except for the session server, but meh).
7796 		sockaddr_un addr;
7797 		addr.sun_family = AF_UNIX;
7798 		version(linux) {
7799 			// on linux, we will use the abstract namespace
7800 			addr.sun_path[0] = 0;
7801 			addr.sun_path[1 .. name.length + 1] = cast(typeof(addr.sun_path[])) name[];
7802 		} else {
7803 			// but otherwise, just use a file cuz we must.
7804 			addr.sun_path[0 .. name.length] = cast(typeof(addr.sun_path[])) name[];
7805 		}
7806 
7807 		bool alreadyTried;
7808 
7809 		try_again:
7810 
7811 		if(connect(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
7812 			if(!alreadyTried && errno == ECONNREFUSED) {
7813 				// try auto-spawning the server, then attempt connection again
7814 				startAddonServer(arg);
7815 				import core.thread;
7816 				Thread.sleep(50.msecs);
7817 				alreadyTried = true;
7818 				goto try_again;
7819 			} else
7820 				throw new Exception("connect " ~ to!string(errno));
7821 		}
7822 
7823 		return sock;
7824 	} else version(Windows) {
7825 		return null; // FIXME
7826 	}
7827 }
7828 
7829 version(with_addon_servers_connections)
7830 void closeLocalServerConnection(LocalServerConnectionHandle handle) {
7831 	version(Posix) {
7832 		import core.sys.posix.unistd;
7833 		close(handle);
7834 	} else version(Windows)
7835 		CloseHandle(handle);
7836 }
7837 
7838 void runSessionServer()() {
7839 	runAddonServer("/tmp/arsd_session_server", new BasicDataServerImplementation());
7840 }
7841 
7842 import core.stdc.errno;
7843 
7844 struct IoOp {
7845 	@disable this();
7846 	@disable this(this);
7847 
7848 	/*
7849 		So we want to be able to eventually handle generic sockets too.
7850 	*/
7851 
7852 	enum Read = 1;
7853 	enum Write = 2;
7854 	enum Accept = 3;
7855 	enum ReadSocketHandle = 4;
7856 
7857 	// Your handler may be called in a different thread than the one that initiated the IO request!
7858 	// It is also possible to have multiple io requests being called simultaneously. Use proper thread safety caution.
7859 	private bool delegate(IoOp*, int) handler; // returns true if you are done and want it to be closed
7860 	private void delegate(IoOp*) closeHandler;
7861 	private void delegate(IoOp*) completeHandler;
7862 	private int internalFd;
7863 	private int operation;
7864 	private int bufferLengthAllocated;
7865 	private int bufferLengthUsed;
7866 	private ubyte[1] internalBuffer; // it can be overallocated!
7867 
7868 	ubyte[] allocatedBuffer() return {
7869 		return internalBuffer.ptr[0 .. bufferLengthAllocated];
7870 	}
7871 
7872 	ubyte[] usedBuffer() return {
7873 		return allocatedBuffer[0 .. bufferLengthUsed];
7874 	}
7875 
7876 	void reset() {
7877 		bufferLengthUsed = 0;
7878 	}
7879 
7880 	int fd() {
7881 		return internalFd;
7882 	}
7883 }
7884 
7885 IoOp* allocateIoOp(int fd, int operation, int bufferSize, bool delegate(IoOp*, int) handler) {
7886 	import core.stdc.stdlib;
7887 
7888 	auto ptr = calloc(IoOp.sizeof + bufferSize, 1);
7889 	if(ptr is null)
7890 		assert(0); // out of memory!
7891 
7892 	auto op = cast(IoOp*) ptr;
7893 
7894 	op.handler = handler;
7895 	op.internalFd = fd;
7896 	op.operation = operation;
7897 	op.bufferLengthAllocated = bufferSize;
7898 	op.bufferLengthUsed = 0;
7899 
7900 	import core.memory;
7901 
7902 	GC.addRoot(ptr);
7903 
7904 	return op;
7905 }
7906 
7907 void freeIoOp(ref IoOp* ptr) {
7908 
7909 	import core.memory;
7910 	GC.removeRoot(ptr);
7911 
7912 	import core.stdc.stdlib;
7913 	free(ptr);
7914 	ptr = null;
7915 }
7916 
7917 version(Posix)
7918 version(with_addon_servers_connections)
7919 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
7920 
7921 	//import std.stdio : writeln; writeln(cast(string) data);
7922 
7923 	import core.sys.posix.unistd;
7924 
7925 	auto ret = write(connection, data.ptr, data.length);
7926 	if(ret != data.length) {
7927 		if(ret == 0 || (ret == -1 && (errno == EPIPE || errno == ETIMEDOUT))) {
7928 			// the file is closed, remove it
7929 			eis.fileClosed(connection);
7930 		} else
7931 			throw new Exception("alas " ~ to!string(ret) ~ " " ~ to!string(errno)); // FIXME
7932 	}
7933 }
7934 version(Windows)
7935 version(with_addon_servers_connections)
7936 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
7937 	// FIXME
7938 }
7939 
7940 bool isInvalidHandle(CgiConnectionHandle h) {
7941 	return h == INVALID_CGI_CONNECTION_HANDLE;
7942 }
7943 
7944 /+
7945 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsarecv
7946 https://support.microsoft.com/en-gb/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode
7947 https://stackoverflow.com/questions/18018489/should-i-use-iocps-or-overlapped-wsasend-receive
7948 https://docs.microsoft.com/en-us/windows/desktop/fileio/i-o-completion-ports
7949 https://docs.microsoft.com/en-us/windows/desktop/fileio/createiocompletionport
7950 https://docs.microsoft.com/en-us/windows/desktop/api/mswsock/nf-mswsock-acceptex
7951 https://docs.microsoft.com/en-us/windows/desktop/Sync/waitable-timer-objects
7952 https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-setwaitabletimer
7953 https://docs.microsoft.com/en-us/windows/desktop/Sync/using-a-waitable-timer-with-an-asynchronous-procedure-call
7954 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsagetoverlappedresult
7955 
7956 +/
7957 
7958 /++
7959 	You can customize your server by subclassing the appropriate server. Then, register your
7960 	subclass at compile time with the [registerEventIoServer] template, or implement your own
7961 	main function and call it yourself.
7962 
7963 	$(TIP If you make your subclass a `final class`, there is a slight performance improvement.)
7964 +/
7965 version(with_addon_servers_connections)
7966 interface EventIoServer {
7967 	bool handleLocalConnectionData(IoOp* op, int receivedFd);
7968 	void handleLocalConnectionClose(IoOp* op);
7969 	void handleLocalConnectionComplete(IoOp* op);
7970 	void wait_timeout();
7971 	void fileClosed(int fd);
7972 
7973 	void epoll_fd(int fd);
7974 }
7975 
7976 // the sink should buffer it
7977 private void serialize(T)(scope void delegate(scope ubyte[]) sink, T t) {
7978 	static if(is(T == struct)) {
7979 		foreach(member; __traits(allMembers, T))
7980 			serialize(sink, __traits(getMember, t, member));
7981 	} else static if(is(T : int)) {
7982 		// no need to think of endianness just because this is only used
7983 		// for local, same-machine stuff anyway. thanks private lol
7984 		sink((cast(ubyte*) &t)[0 .. t.sizeof]);
7985 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
7986 		// these are common enough to optimize
7987 		int len = cast(int) t.length; // want length consistent size tho, in case 32 bit program sends to 64 bit server, etc.
7988 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7989 		sink(cast(ubyte[]) t[]);
7990 	} else static if(is(T : A[], A)) {
7991 		// generic array is less optimal but still prolly ok
7992 		int len = cast(int) t.length;
7993 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7994 		foreach(item; t)
7995 			serialize(sink, item);
7996 	} else static assert(0, T.stringof);
7997 }
7998 
7999 // all may be stack buffers, so use cautio
8000 private void deserialize(T)(scope ubyte[] delegate(int sz) get, scope void delegate(T) dg) {
8001 	static if(is(T == struct)) {
8002 		T t;
8003 		foreach(member; __traits(allMembers, T))
8004 			deserialize!(typeof(__traits(getMember, T, member)))(get, (mbr) { __traits(getMember, t, member) = mbr; });
8005 		dg(t);
8006 	} else static if(is(T : int)) {
8007 		// no need to think of endianness just because this is only used
8008 		// for local, same-machine stuff anyway. thanks private lol
8009 		T t;
8010 		auto data = get(t.sizeof);
8011 		t = (cast(T[]) data)[0];
8012 		dg(t);
8013 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
8014 		// these are common enough to optimize
8015 		int len;
8016 		auto data = get(len.sizeof);
8017 		len = (cast(int[]) data)[0];
8018 
8019 		/*
8020 		typeof(T[0])[2000] stackBuffer;
8021 		T buffer;
8022 
8023 		if(len < stackBuffer.length)
8024 			buffer = stackBuffer[0 .. len];
8025 		else
8026 			buffer = new T(len);
8027 
8028 		data = get(len * typeof(T[0]).sizeof);
8029 		*/
8030 
8031 		T t = cast(T) get(len * cast(int) typeof(T.init[0]).sizeof);
8032 
8033 		dg(t);
8034 	} else static if(is(T == E[], E)) {
8035 		T t;
8036 		int len;
8037 		auto data = get(len.sizeof);
8038 		len = (cast(int[]) data)[0];
8039 		t.length = len;
8040 		foreach(ref e; t) {
8041 			deserialize!E(get, (ele) { e = ele; });
8042 		}
8043 		dg(t);
8044 	} else static assert(0, T.stringof);
8045 }
8046 
8047 unittest {
8048 	serialize((ubyte[] b) {
8049 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 1); });
8050 	}, 1);
8051 	serialize((ubyte[] b) {
8052 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 56674); });
8053 	}, 56674);
8054 	ubyte[1000] buffer;
8055 	int bufferPoint;
8056 	void add(scope ubyte[] b) {
8057 		buffer[bufferPoint ..  bufferPoint + b.length] = b[];
8058 		bufferPoint += b.length;
8059 	}
8060 	ubyte[] get(int sz) {
8061 		auto b = buffer[bufferPoint .. bufferPoint + sz];
8062 		bufferPoint += sz;
8063 		return b;
8064 	}
8065 	serialize(&add, "test here");
8066 	bufferPoint = 0;
8067 	deserialize!string(&get, (t) { assert(t == "test here"); });
8068 	bufferPoint = 0;
8069 
8070 	struct Foo {
8071 		int a;
8072 		ubyte c;
8073 		string d;
8074 	}
8075 	serialize(&add, Foo(403, 37, "amazing"));
8076 	bufferPoint = 0;
8077 	deserialize!Foo(&get, (t) {
8078 		assert(t.a == 403);
8079 		assert(t.c == 37);
8080 		assert(t.d == "amazing");
8081 	});
8082 	bufferPoint = 0;
8083 }
8084 
8085 /*
8086 	Here's the way the RPC interface works:
8087 
8088 	You define the interface that lists the functions you can call on the remote process.
8089 	The interface may also have static methods for convenience. These forward to a singleton
8090 	instance of an auto-generated class, which actually sends the args over the pipe.
8091 
8092 	An impl class actually implements it. A receiving server deserializes down the pipe and
8093 	calls methods on the class.
8094 
8095 	I went with the interface to get some nice compiler checking and documentation stuff.
8096 
8097 	I could have skipped the interface and just implemented it all from the server class definition
8098 	itself, but then the usage may call the method instead of rpcing it; I just like having the user
8099 	interface and the implementation separate so you aren't tempted to `new impl` to call the methods.
8100 
8101 
8102 	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.
8103 
8104 	Realistically though the bodies would just be
8105 		connection.call(this.mangleof, args...) sooooo.
8106 
8107 	FIXME: overloads aren't supported
8108 */
8109 
8110 /// Base for storing sessions in an array. Exists primarily for internal purposes and you should generally not use this.
8111 interface SessionObject {}
8112 
8113 private immutable void delegate(string[])[string] scheduledJobHandlers;
8114 private immutable void delegate(string[])[string] websocketServers;
8115 
8116 version(with_breaking_cgi_features)
8117 mixin(q{
8118 
8119 mixin template ImplementRpcClientInterface(T, string serverPath, string cmdArg) {
8120 	static import std.traits;
8121 
8122 	// 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.
8123 	static foreach(idx, member; __traits(derivedMembers, T)) {
8124 	static if(__traits(isVirtualMethod, __traits(getMember, T, member)))
8125 		mixin( q{
8126 		std.traits.ReturnType!(__traits(getMember, T, member))
8127 		} ~ member ~ q{(std.traits.Parameters!(__traits(getMember, T, member)) params)
8128 		{
8129 			SerializationBuffer buffer;
8130 			auto i = cast(ushort) idx;
8131 			serialize(&buffer.sink, i);
8132 			serialize(&buffer.sink, __traits(getMember, T, member).mangleof);
8133 			foreach(param; params)
8134 				serialize(&buffer.sink, param);
8135 
8136 			auto sendable = buffer.sendable;
8137 
8138 			version(Posix) {{
8139 				auto ret = send(connectionHandle, sendable.ptr, sendable.length, 0);
8140 
8141 				if(ret == -1) {
8142 					throw new Exception("send returned -1, errno: " ~ to!string(errno));
8143 				} else if(ret == 0) {
8144 					throw new Exception("Connection to addon server lost");
8145 				} if(ret < sendable.length)
8146 					throw new Exception("Send failed to send all");
8147 				assert(ret == sendable.length);
8148 			}} // FIXME Windows impl
8149 
8150 			static if(!is(typeof(return) == void)) {
8151 				// there is a return value; we need to wait for it too
8152 				version(Posix) {
8153 					ubyte[3000] revBuffer;
8154 					auto ret = recv(connectionHandle, revBuffer.ptr, revBuffer.length, 0);
8155 					auto got = revBuffer[0 .. ret];
8156 
8157 					int dataLocation;
8158 					ubyte[] grab(int sz) {
8159 						auto dataLocation1 = dataLocation;
8160 						dataLocation += sz;
8161 						return got[dataLocation1 .. dataLocation];
8162 					}
8163 
8164 					typeof(return) retu;
8165 					deserialize!(typeof(return))(&grab, (a) { retu = a; });
8166 					return retu;
8167 				} else {
8168 					// FIXME Windows impl
8169 					return typeof(return).init;
8170 				}
8171 
8172 			}
8173 		}});
8174 	}
8175 
8176 	private static typeof(this) singletonInstance;
8177 	private LocalServerConnectionHandle connectionHandle;
8178 
8179 	static typeof(this) connection() {
8180 		if(singletonInstance is null) {
8181 			singletonInstance = new typeof(this)();
8182 			singletonInstance.connect();
8183 		}
8184 		return singletonInstance;
8185 	}
8186 
8187 	void connect() {
8188 		connectionHandle = openLocalServerConnection(serverPath, cmdArg);
8189 	}
8190 
8191 	void disconnect() {
8192 		closeLocalServerConnection(connectionHandle);
8193 	}
8194 }
8195 
8196 void dispatchRpcServer(Interface, Class)(Class this_, ubyte[] data, int fd) if(is(Class : Interface)) {
8197 	ushort calledIdx;
8198 	string calledFunction;
8199 
8200 	int dataLocation;
8201 	ubyte[] grab(int sz) {
8202 		if(sz == 0) assert(0);
8203 		auto d = data[dataLocation .. dataLocation + sz];
8204 		dataLocation += sz;
8205 		return d;
8206 	}
8207 
8208 	again:
8209 
8210 	deserialize!ushort(&grab, (a) { calledIdx = a; });
8211 	deserialize!string(&grab, (a) { calledFunction = a; });
8212 
8213 	import std.traits;
8214 
8215 	sw: switch(calledIdx) {
8216 		foreach(idx, memberName; __traits(derivedMembers, Interface))
8217 		static if(__traits(isVirtualMethod, __traits(getMember, Interface, memberName))) {
8218 			case idx:
8219 				assert(calledFunction == __traits(getMember, Interface, memberName).mangleof);
8220 
8221 				Parameters!(__traits(getMember, Interface, memberName)) params;
8222 				foreach(ref param; params)
8223 					deserialize!(typeof(param))(&grab, (a) { param = a; });
8224 
8225 				static if(is(ReturnType!(__traits(getMember, Interface, memberName)) == void)) {
8226 					__traits(getMember, this_, memberName)(params);
8227 				} else {
8228 					auto ret = __traits(getMember, this_, memberName)(params);
8229 					SerializationBuffer buffer;
8230 					serialize(&buffer.sink, ret);
8231 
8232 					auto sendable = buffer.sendable;
8233 
8234 					version(Posix) {
8235 						auto r = send(fd, sendable.ptr, sendable.length, 0);
8236 						if(r == -1) {
8237 							throw new Exception("send returned -1, errno: " ~ to!string(errno));
8238 						} else if(r == 0) {
8239 							throw new Exception("Connection to addon client lost");
8240 						} if(r < sendable.length)
8241 							throw new Exception("Send failed to send all");
8242 
8243 					} // FIXME Windows impl
8244 				}
8245 			break sw;
8246 		}
8247 		default: assert(0);
8248 	}
8249 
8250 	if(dataLocation != data.length)
8251 		goto again;
8252 }
8253 
8254 
8255 private struct SerializationBuffer {
8256 	ubyte[2048] bufferBacking;
8257 	int bufferLocation;
8258 	void sink(scope ubyte[] data) {
8259 		bufferBacking[bufferLocation .. bufferLocation + data.length] = data[];
8260 		bufferLocation += data.length;
8261 	}
8262 
8263 	ubyte[] sendable() return {
8264 		return bufferBacking[0 .. bufferLocation];
8265 	}
8266 }
8267 
8268 /*
8269 	FIXME:
8270 		add a version command line arg
8271 		version data in the library
8272 		management gui as external program
8273 
8274 		at server with event_fd for each run
8275 		use .mangleof in the at function name
8276 
8277 		i think the at server will have to:
8278 			pipe args to the child
8279 			collect child output for logging
8280 			get child return value for logging
8281 
8282 			on windows timers work differently. idk how to best combine with the io stuff.
8283 
8284 			will have to have dump and restore too, so i can restart without losing stuff.
8285 */
8286 
8287 /++
8288 	A convenience object for talking to the [BasicDataServer] from a higher level.
8289 	See: [Cgi.getSessionObject].
8290 
8291 	You pass it a `Data` struct describing the data you want saved in the session.
8292 	Then, this class will generate getter and setter properties that allow access
8293 	to that data.
8294 
8295 	Note that each load and store will be done as-accessed; it doesn't front-load
8296 	mutable data nor does it batch updates out of fear of read-modify-write race
8297 	conditions. (In fact, right now it does this for everything, but in the future,
8298 	I might batch load `immutable` members of the Data struct.)
8299 
8300 	At some point in the future, I might also let it do different backends, like
8301 	a client-side cookie store too, but idk.
8302 
8303 	Note that the plain-old-data members of your `Data` struct are wrapped by this
8304 	interface via a static foreach to make property functions.
8305 
8306 	See_Also: [MockSession]
8307 +/
8308 interface Session(Data) : SessionObject {
8309 	@property string sessionId() const;
8310 
8311 	/++
8312 		Starts a new session. Note that a session is also
8313 		implicitly started as soon as you write data to it,
8314 		so if you need to alter these parameters from their
8315 		defaults, be sure to explicitly call this BEFORE doing
8316 		any writes to session data.
8317 
8318 		Params:
8319 			idleLifetime = How long, in seconds, the session
8320 			should remain in memory when not being read from
8321 			or written to. The default is one day.
8322 
8323 			NOT IMPLEMENTED
8324 
8325 			useExtendedLifetimeCookie = The session ID is always
8326 			stored in a HTTP cookie, and by default, that cookie
8327 			is discarded when the user closes their browser.
8328 
8329 			But if you set this to true, it will use a non-perishable
8330 			cookie for the given idleLifetime.
8331 
8332 			NOT IMPLEMENTED
8333 	+/
8334 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false);
8335 
8336 	/++
8337 		Regenerates the session ID and updates the associated
8338 		cookie.
8339 
8340 		This is also your chance to change immutable data
8341 		(not yet implemented).
8342 	+/
8343 	void regenerateId();
8344 
8345 	/++
8346 		Terminates this session, deleting all saved data.
8347 	+/
8348 	void terminate();
8349 
8350 	/++
8351 		Plain-old-data members of your `Data` struct are wrapped here via
8352 		the property getters and setters.
8353 
8354 		If the member is a non-string array, it returns a magical array proxy
8355 		object which allows for atomic appends and replaces via overloaded operators.
8356 		You can slice this to get a range representing a $(B const) view of the array.
8357 		This is to protect you against read-modify-write race conditions.
8358 	+/
8359 	static foreach(memberName; __traits(allMembers, Data))
8360 		static if(is(typeof(__traits(getMember, Data, memberName))))
8361 		mixin(q{
8362 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout;
8363 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value);
8364 		});
8365 
8366 }
8367 
8368 /++
8369 	An implementation of [Session] that works on real cgi connections utilizing the
8370 	[BasicDataServer].
8371 
8372 	As opposed to a [MockSession] which is made for testing purposes.
8373 
8374 	You will not construct one of these directly. See [Cgi.getSessionObject] instead.
8375 +/
8376 class BasicDataServerSession(Data) : Session!Data {
8377 	private Cgi cgi;
8378 	private string sessionId_;
8379 
8380 	public @property string sessionId() const {
8381 		return sessionId_;
8382 	}
8383 
8384 	protected @property string sessionId(string s) {
8385 		return this.sessionId_ = s;
8386 	}
8387 
8388 	private this(Cgi cgi) {
8389 		this.cgi = cgi;
8390 		if(auto ptr = "sessionId" in cgi.cookies)
8391 			sessionId = (*ptr).length ? *ptr : null;
8392 	}
8393 
8394 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {
8395 		assert(sessionId is null);
8396 
8397 		// FIXME: what if there is a session ID cookie, but no corresponding session on the server?
8398 
8399 		import std.random, std.conv;
8400 		sessionId = to!string(uniform(1, long.max));
8401 
8402 		BasicDataServer.connection.createSession(sessionId, idleLifetime);
8403 		setCookie();
8404 	}
8405 
8406 	protected void setCookie() {
8407 		cgi.setCookie(
8408 			"sessionId", sessionId,
8409 			0 /* expiration */,
8410 			"/" /* path */,
8411 			null /* domain */,
8412 			true /* http only */,
8413 			cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
8414 	}
8415 
8416 	void regenerateId() {
8417 		if(sessionId is null) {
8418 			start();
8419 			return;
8420 		}
8421 		import std.random, std.conv;
8422 		auto oldSessionId = sessionId;
8423 		sessionId = to!string(uniform(1, long.max));
8424 		BasicDataServer.connection.renameSession(oldSessionId, sessionId);
8425 		setCookie();
8426 	}
8427 
8428 	void terminate() {
8429 		BasicDataServer.connection.destroySession(sessionId);
8430 		sessionId = null;
8431 		setCookie();
8432 	}
8433 
8434 	static foreach(memberName; __traits(allMembers, Data))
8435 		static if(is(typeof(__traits(getMember, Data, memberName))))
8436 		mixin(q{
8437 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
8438 				if(sessionId is null)
8439 					return typeof(return).init;
8440 
8441 				import std.traits;
8442 				auto v = BasicDataServer.connection.getSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName);
8443 				if(v.length == 0)
8444 					return typeof(return).init;
8445 				import std.conv;
8446 				// why this cast? to doesn't like being given an inout argument. so need to do it without that, then
8447 				// we need to return it and that needed the cast. It should be fine since we basically respect constness..
8448 				// basically. Assuming the session is POD this should be fine.
8449 				return cast(typeof(return)) to!(typeof(__traits(getMember, Data, memberName)))(v);
8450 			}
8451 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
8452 				if(sessionId is null)
8453 					start();
8454 				import std.conv;
8455 				import std.traits;
8456 				BasicDataServer.connection.setSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName, to!string(value));
8457 				return value;
8458 			}
8459 		});
8460 }
8461 
8462 /++
8463 	A mock object that works like the real session, but doesn't actually interact with any actual database or http connection.
8464 	Simply stores the data in its instance members.
8465 +/
8466 class MockSession(Data) : Session!Data {
8467 	pure {
8468 		@property string sessionId() const { return "mock"; }
8469 		void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {}
8470 		void regenerateId() {}
8471 		void terminate() {}
8472 
8473 		private Data store_;
8474 
8475 		static foreach(memberName; __traits(allMembers, Data))
8476 			static if(is(typeof(__traits(getMember, Data, memberName))))
8477 			mixin(q{
8478 				@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
8479 					return __traits(getMember, store_, memberName);
8480 				}
8481 				@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
8482 					return __traits(getMember, store_, memberName) = value;
8483 				}
8484 			});
8485 	}
8486 }
8487 
8488 /++
8489 	Direct interface to the basic data add-on server. You can
8490 	typically use [Cgi.getSessionObject] as a more convenient interface.
8491 +/
8492 version(with_addon_servers_connections)
8493 interface BasicDataServer {
8494 	///
8495 	void createSession(string sessionId, int lifetime);
8496 	///
8497 	void renewSession(string sessionId, int lifetime);
8498 	///
8499 	void destroySession(string sessionId);
8500 	///
8501 	void renameSession(string oldSessionId, string newSessionId);
8502 
8503 	///
8504 	void setSessionData(string sessionId, string dataKey, string dataValue);
8505 	///
8506 	string getSessionData(string sessionId, string dataKey);
8507 
8508 	///
8509 	static BasicDataServerConnection connection() {
8510 		return BasicDataServerConnection.connection();
8511 	}
8512 }
8513 
8514 version(with_addon_servers_connections)
8515 class BasicDataServerConnection : BasicDataServer {
8516 	mixin ImplementRpcClientInterface!(BasicDataServer, "/tmp/arsd_session_server", "--session-server");
8517 }
8518 
8519 version(with_addon_servers)
8520 final class BasicDataServerImplementation : BasicDataServer, EventIoServer {
8521 
8522 	void createSession(string sessionId, int lifetime) {
8523 		sessions[sessionId.idup] = Session(lifetime);
8524 	}
8525 	void destroySession(string sessionId) {
8526 		sessions.remove(sessionId);
8527 	}
8528 	void renewSession(string sessionId, int lifetime) {
8529 		sessions[sessionId].lifetime = lifetime;
8530 	}
8531 	void renameSession(string oldSessionId, string newSessionId) {
8532 		sessions[newSessionId.idup] = sessions[oldSessionId];
8533 		sessions.remove(oldSessionId);
8534 	}
8535 	void setSessionData(string sessionId, string dataKey, string dataValue) {
8536 		if(sessionId !in sessions)
8537 			createSession(sessionId, 3600); // FIXME?
8538 		sessions[sessionId].values[dataKey.idup] = dataValue.idup;
8539 	}
8540 	string getSessionData(string sessionId, string dataKey) {
8541 		if(auto session = sessionId in sessions) {
8542 			if(auto data = dataKey in (*session).values)
8543 				return *data;
8544 			else
8545 				return null; // no such data
8546 
8547 		} else {
8548 			return null; // no session
8549 		}
8550 	}
8551 
8552 
8553 	protected:
8554 
8555 	struct Session {
8556 		int lifetime;
8557 
8558 		string[string] values;
8559 	}
8560 
8561 	Session[string] sessions;
8562 
8563 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8564 		auto data = op.usedBuffer;
8565 		dispatchRpcServer!BasicDataServer(this, data, op.fd);
8566 		return false;
8567 	}
8568 
8569 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
8570 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
8571 	void wait_timeout() {}
8572 	void fileClosed(int fd) {} // stateless so irrelevant
8573 	void epoll_fd(int fd) {}
8574 }
8575 
8576 /++
8577 	See [schedule] to make one of these. You then call one of the methods here to set it up:
8578 
8579 	---
8580 		schedule!fn(args).at(DateTime(2019, 8, 7, 12, 00, 00)); // run the function at August 7, 2019, 12 noon UTC
8581 		schedule!fn(args).delay(6.seconds); // run it after waiting 6 seconds
8582 		schedule!fn(args).asap(); // run it in the background as soon as the event loop gets around to it
8583 	---
8584 +/
8585 version(with_addon_servers_connections)
8586 struct ScheduledJobHelper {
8587 	private string func;
8588 	private string[] args;
8589 	private bool consumed;
8590 
8591 	private this(string func, string[] args) {
8592 		this.func = func;
8593 		this.args = args;
8594 	}
8595 
8596 	~this() {
8597 		assert(consumed);
8598 	}
8599 
8600 	/++
8601 		Schedules the job to be run at the given time.
8602 	+/
8603 	void at(DateTime when, immutable TimeZone timezone = UTC()) {
8604 		consumed = true;
8605 
8606 		auto conn = ScheduledJobServerConnection.connection;
8607 		import std.file;
8608 		auto st = SysTime(when, timezone);
8609 		auto jobId = conn.scheduleJob(1, cast(int) st.toUnixTime(), thisExePath, func, args);
8610 	}
8611 
8612 	/++
8613 		Schedules the job to run at least after the specified delay.
8614 	+/
8615 	void delay(Duration delay) {
8616 		consumed = true;
8617 
8618 		auto conn = ScheduledJobServerConnection.connection;
8619 		import std.file;
8620 		auto jobId = conn.scheduleJob(0, cast(int) delay.total!"seconds", thisExePath, func, args);
8621 	}
8622 
8623 	/++
8624 		Runs the job in the background ASAP.
8625 
8626 		$(NOTE It may run in a background thread. Don't segfault!)
8627 	+/
8628 	void asap() {
8629 		consumed = true;
8630 
8631 		auto conn = ScheduledJobServerConnection.connection;
8632 		import std.file;
8633 		auto jobId = conn.scheduleJob(0, 1, thisExePath, func, args);
8634 	}
8635 
8636 	/+
8637 	/++
8638 		Schedules the job to recur on the given pattern.
8639 	+/
8640 	void recur(string spec) {
8641 
8642 	}
8643 	+/
8644 }
8645 
8646 /++
8647 	First step to schedule a job on the scheduled job server.
8648 
8649 	The scheduled job needs to be a top-level function that doesn't read any
8650 	variables from outside its arguments because it may be run in a new process,
8651 	without any context existing later.
8652 
8653 	You MUST set details on the returned object to actually do anything!
8654 +/
8655 template schedule(alias fn, T...) if(is(typeof(fn) == function)) {
8656 	///
8657 	ScheduledJobHelper schedule(T args) {
8658 		// this isn't meant to ever be called, but instead just to
8659 		// get the compiler to type check the arguments passed for us
8660 		auto sample = delegate() {
8661 			fn(args);
8662 		};
8663 		string[] sargs;
8664 		foreach(arg; args)
8665 			sargs ~= to!string(arg);
8666 		return ScheduledJobHelper(fn.mangleof, sargs);
8667 	}
8668 
8669 	shared static this() {
8670 		scheduledJobHandlers[fn.mangleof] = delegate(string[] sargs) {
8671 			import std.traits;
8672 			Parameters!fn args;
8673 			foreach(idx, ref arg; args)
8674 				arg = to!(typeof(arg))(sargs[idx]);
8675 			fn(args);
8676 		};
8677 	}
8678 }
8679 
8680 ///
8681 interface ScheduledJobServer {
8682 	/// Use the [schedule] function for a higher-level interface.
8683 	int scheduleJob(int whenIs, int when, string executable, string func, string[] args);
8684 	///
8685 	void cancelJob(int jobId);
8686 }
8687 
8688 version(with_addon_servers_connections)
8689 class ScheduledJobServerConnection : ScheduledJobServer {
8690 	mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server", "--timer-server");
8691 }
8692 
8693 version(with_addon_servers)
8694 final class ScheduledJobServerImplementation : ScheduledJobServer, EventIoServer {
8695 	// FIXME: we need to handle SIGCHLD in this somehow
8696 	// whenIs is 0 for relative, 1 for absolute
8697 	protected int scheduleJob(int whenIs, int when, string executable, string func, string[] args) {
8698 		auto nj = nextJobId;
8699 		nextJobId++;
8700 
8701 		version(linux) {
8702 			import core.sys.linux.timerfd;
8703 			import core.sys.linux.epoll;
8704 			import core.sys.posix.unistd;
8705 
8706 
8707 			auto fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
8708 			if(fd == -1)
8709 				throw new Exception("fd timer create failed");
8710 
8711 			foreach(ref arg; args)
8712 				arg = arg.idup;
8713 			auto job = Job(executable.idup, func.idup, .dup(args), fd, nj);
8714 
8715 			itimerspec value;
8716 			value.it_value.tv_sec = when;
8717 			value.it_value.tv_nsec = 0;
8718 
8719 			value.it_interval.tv_sec = 0;
8720 			value.it_interval.tv_nsec = 0;
8721 
8722 			if(timerfd_settime(fd, whenIs == 1 ? TFD_TIMER_ABSTIME : 0, &value, null) == -1)
8723 				throw new Exception("couldn't set fd timer");
8724 
8725 			auto op = allocateIoOp(fd, IoOp.Read, 16, (IoOp* op, int fd) {
8726 				jobs.remove(nj);
8727 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, null);
8728 				close(fd);
8729 
8730 
8731 				spawnProcess([job.executable, "--timed-job", job.func] ~ job.args);
8732 
8733 				return true;
8734 			});
8735 			scope(failure)
8736 				freeIoOp(op);
8737 
8738 			epoll_event ev;
8739 			ev.events = EPOLLIN | EPOLLET;
8740 			ev.data.ptr = op;
8741 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1)
8742 				throw new Exception("epoll_ctl " ~ to!string(errno));
8743 
8744 			jobs[nj] = job;
8745 			return nj;
8746 		} else assert(0);
8747 	}
8748 
8749 	protected void cancelJob(int jobId) {
8750 		version(linux) {
8751 			auto job = jobId in jobs;
8752 			if(job is null)
8753 				return;
8754 
8755 			jobs.remove(jobId);
8756 
8757 			version(linux) {
8758 				import core.sys.linux.timerfd;
8759 				import core.sys.linux.epoll;
8760 				import core.sys.posix.unistd;
8761 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, job.timerfd, null);
8762 				close(job.timerfd);
8763 			}
8764 		}
8765 		jobs.remove(jobId);
8766 	}
8767 
8768 	int nextJobId = 1;
8769 	static struct Job {
8770 		string executable;
8771 		string func;
8772 		string[] args;
8773 		int timerfd;
8774 		int id;
8775 	}
8776 	Job[int] jobs;
8777 
8778 
8779 	// event io server methods below
8780 
8781 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8782 		auto data = op.usedBuffer;
8783 		dispatchRpcServer!ScheduledJobServer(this, data, op.fd);
8784 		return false;
8785 	}
8786 
8787 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
8788 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
8789 	void wait_timeout() {}
8790 	void fileClosed(int fd) {} // stateless so irrelevant
8791 
8792 	int epoll_fd_;
8793 	void epoll_fd(int fd) {this.epoll_fd_ = fd; }
8794 	int epoll_fd() { return epoll_fd_; }
8795 }
8796 
8797 /++
8798 	History:
8799 		Added January 6, 2019
8800 +/
8801 version(with_addon_servers_connections)
8802 interface EventSourceServer {
8803 	/++
8804 		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.
8805 
8806 		See_Also:
8807 			[sendEvent]
8808 
8809 		Bugs:
8810 			Not implemented on Windows!
8811 
8812 		History:
8813 			Officially stabilised on November 23, 2023 (dub v11.4). It actually worked pretty well in its original design.
8814 	+/
8815 	public static void adoptConnection(Cgi cgi, in char[] eventUrl) {
8816 		/*
8817 			If lastEventId is missing or empty, you just get new events as they come.
8818 
8819 			If it is set from something else, it sends all since then (that are still alive)
8820 			down the pipe immediately.
8821 
8822 			The reason it can come from the header is that's what the standard defines for
8823 			browser reconnects. The reason it can come from a query string is just convenience
8824 			in catching up in a user-defined manner.
8825 
8826 			The reason the header overrides the query string is if the browser tries to reconnect,
8827 			it will send the header AND the query (it reconnects to the same url), so we just
8828 			want to do the restart thing.
8829 
8830 			Note that if you ask for "0" as the lastEventId, it will get ALL still living events.
8831 		*/
8832 		string lastEventId = cgi.lastEventId;
8833 		if(lastEventId.length == 0 && "lastEventId" in cgi.get)
8834 			lastEventId = cgi.get["lastEventId"];
8835 
8836 		cgi.setResponseContentType("text/event-stream");
8837 		cgi.write(":\n", false); // to initialize the chunking and send headers before keeping the fd for later
8838 		cgi.flush();
8839 
8840 		cgi.closed = true;
8841 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
8842 		scope(exit)
8843 			closeLocalServerConnection(s);
8844 
8845 		version(fastcgi)
8846 			throw new Exception("sending fcgi connections not supported");
8847 		else {
8848 			auto fd = cgi.getOutputFileHandle();
8849 			if(isInvalidHandle(fd))
8850 				throw new Exception("bad fd from cgi!");
8851 
8852 			EventSourceServerImplementation.SendableEventConnection sec;
8853 			sec.populate(cgi.responseChunked, eventUrl, lastEventId);
8854 
8855 			version(Posix) {
8856 				auto res = write_fd(s, cast(void*) &sec, sec.sizeof, fd);
8857 				assert(res == sec.sizeof);
8858 			} else version(Windows) {
8859 				// FIXME
8860 			}
8861 		}
8862 	}
8863 
8864 	/++
8865 		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.
8866 
8867 		Params:
8868 			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.
8869 			event = the event type string, which is used in the Javascript addEventListener API on EventSource
8870 			data = the event data. Available in JS as `event.data`.
8871 			lifetime = the amount of time to keep this event for replaying on the event server.
8872 
8873 		Bugs:
8874 			Not implemented on Windows!
8875 
8876 		History:
8877 			Officially stabilised on November 23, 2023 (dub v11.4). It actually worked pretty well in its original design.
8878 	+/
8879 	public static void sendEvent(string url, string event, string data, int lifetime) {
8880 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
8881 		scope(exit)
8882 			closeLocalServerConnection(s);
8883 
8884 		EventSourceServerImplementation.SendableEvent sev;
8885 		sev.populate(url, event, data, lifetime);
8886 
8887 		version(Posix) {
8888 			auto ret = send(s, &sev, sev.sizeof, 0);
8889 			assert(ret == sev.sizeof);
8890 		} else version(Windows) {
8891 			// FIXME
8892 		}
8893 	}
8894 
8895 	/++
8896 		Messages sent to `url` will also be sent to anyone listening on `forwardUrl`.
8897 
8898 		See_Also: [disconnect]
8899 	+/
8900 	void connect(string url, string forwardUrl);
8901 
8902 	/++
8903 		Disconnects `forwardUrl` from `url`
8904 
8905 		See_Also: [connect]
8906 	+/
8907 	void disconnect(string url, string forwardUrl);
8908 }
8909 
8910 ///
8911 version(with_addon_servers)
8912 final class EventSourceServerImplementation : EventSourceServer, EventIoServer {
8913 
8914 	protected:
8915 
8916 	void connect(string url, string forwardUrl) {
8917 		pipes[url] ~= forwardUrl;
8918 	}
8919 	void disconnect(string url, string forwardUrl) {
8920 		auto t = url in pipes;
8921 		if(t is null)
8922 			return;
8923 		foreach(idx, n; (*t))
8924 			if(n == forwardUrl) {
8925 				(*t)[idx] = (*t)[$-1];
8926 				(*t) = (*t)[0 .. $-1];
8927 				break;
8928 			}
8929 	}
8930 
8931 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8932 		if(receivedFd != -1) {
8933 			//writeln("GOT FD ", receivedFd, " -- ", op.usedBuffer);
8934 
8935 			//core.sys.posix.unistd.write(receivedFd, "hello".ptr, 5);
8936 
8937 			SendableEventConnection* got = cast(SendableEventConnection*) op.usedBuffer.ptr;
8938 
8939 			auto url = got.url.idup;
8940 			eventConnectionsByUrl[url] ~= EventConnection(receivedFd, got.responseChunked > 0 ? true : false);
8941 
8942 			// FIXME: catch up on past messages here
8943 		} else {
8944 			auto data = op.usedBuffer;
8945 			auto event = cast(SendableEvent*) data.ptr;
8946 
8947 			if(event.magic == 0xdeadbeef) {
8948 				handleInputEvent(event);
8949 
8950 				if(event.url in pipes)
8951 				foreach(pipe; pipes[event.url]) {
8952 					event.url = pipe;
8953 					handleInputEvent(event);
8954 				}
8955 			} else {
8956 				dispatchRpcServer!EventSourceServer(this, data, op.fd);
8957 			}
8958 		}
8959 		return false;
8960 	}
8961 	void handleLocalConnectionClose(IoOp* op) {
8962 		fileClosed(op.fd);
8963 	}
8964 	void handleLocalConnectionComplete(IoOp* op) {}
8965 
8966 	void wait_timeout() {
8967 		// just keeping alive
8968 		foreach(url, connections; eventConnectionsByUrl)
8969 		foreach(connection; connections)
8970 			if(connection.needsChunking)
8971 				nonBlockingWrite(this, connection.fd, "1b\r\nevent: keepalive\ndata: ok\n\n\r\n");
8972 			else
8973 				nonBlockingWrite(this, connection.fd, "event: keepalive\ndata: ok\n\n\r\n");
8974 	}
8975 
8976 	void fileClosed(int fd) {
8977 		outer: foreach(url, ref connections; eventConnectionsByUrl) {
8978 			foreach(idx, conn; connections) {
8979 				if(fd == conn.fd) {
8980 					connections[idx] = connections[$-1];
8981 					connections = connections[0 .. $ - 1];
8982 					continue outer;
8983 				}
8984 			}
8985 		}
8986 	}
8987 
8988 	void epoll_fd(int fd) {}
8989 
8990 
8991 	private:
8992 
8993 
8994 	struct SendableEventConnection {
8995 		ubyte responseChunked;
8996 
8997 		int urlLength;
8998 		char[256] urlBuffer = 0;
8999 
9000 		int lastEventIdLength;
9001 		char[32] lastEventIdBuffer = 0;
9002 
9003 		char[] url() return {
9004 			return urlBuffer[0 .. urlLength];
9005 		}
9006 		void url(in char[] u) {
9007 			urlBuffer[0 .. u.length] = u[];
9008 			urlLength = cast(int) u.length;
9009 		}
9010 		char[] lastEventId() return {
9011 			return lastEventIdBuffer[0 .. lastEventIdLength];
9012 		}
9013 		void populate(bool responseChunked, in char[] url, in char[] lastEventId)
9014 		in {
9015 			assert(url.length < this.urlBuffer.length);
9016 			assert(lastEventId.length < this.lastEventIdBuffer.length);
9017 		}
9018 		do {
9019 			this.responseChunked = responseChunked ? 1 : 0;
9020 			this.urlLength = cast(int) url.length;
9021 			this.lastEventIdLength = cast(int) lastEventId.length;
9022 
9023 			this.urlBuffer[0 .. url.length] = url[];
9024 			this.lastEventIdBuffer[0 .. lastEventId.length] = lastEventId[];
9025 		}
9026 	}
9027 
9028 	struct SendableEvent {
9029 		int magic = 0xdeadbeef;
9030 		int urlLength;
9031 		char[256] urlBuffer = 0;
9032 		int typeLength;
9033 		char[32] typeBuffer = 0;
9034 		int messageLength;
9035 		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.
9036 		int _lifetime;
9037 
9038 		char[] message() return {
9039 			return messageBuffer[0 .. messageLength];
9040 		}
9041 		char[] type() return {
9042 			return typeBuffer[0 .. typeLength];
9043 		}
9044 		char[] url() return {
9045 			return urlBuffer[0 .. urlLength];
9046 		}
9047 		void url(in char[] u) {
9048 			urlBuffer[0 .. u.length] = u[];
9049 			urlLength = cast(int) u.length;
9050 		}
9051 		int lifetime() {
9052 			return _lifetime;
9053 		}
9054 
9055 		///
9056 		void populate(string url, string type, string message, int lifetime)
9057 		in {
9058 			assert(url.length < this.urlBuffer.length);
9059 			assert(type.length < this.typeBuffer.length);
9060 			assert(message.length < this.messageBuffer.length);
9061 		}
9062 		do {
9063 			this.urlLength = cast(int) url.length;
9064 			this.typeLength = cast(int) type.length;
9065 			this.messageLength = cast(int) message.length;
9066 			this._lifetime = lifetime;
9067 
9068 			this.urlBuffer[0 .. url.length] = url[];
9069 			this.typeBuffer[0 .. type.length] = type[];
9070 			this.messageBuffer[0 .. message.length] = message[];
9071 		}
9072 	}
9073 
9074 	struct EventConnection {
9075 		int fd;
9076 		bool needsChunking;
9077 	}
9078 
9079 	private EventConnection[][string] eventConnectionsByUrl;
9080 	private string[][string] pipes;
9081 
9082 	private void handleInputEvent(scope SendableEvent* event) {
9083 		static int eventId;
9084 
9085 		static struct StoredEvent {
9086 			int id;
9087 			string type;
9088 			string message;
9089 			int lifetimeRemaining;
9090 		}
9091 
9092 		StoredEvent[][string] byUrl;
9093 
9094 		int thisId = ++eventId;
9095 
9096 		if(event.lifetime)
9097 			byUrl[event.url.idup] ~= StoredEvent(thisId, event.type.idup, event.message.idup, event.lifetime);
9098 
9099 		auto connectionsPtr = event.url in eventConnectionsByUrl;
9100 		EventConnection[] connections;
9101 		if(connectionsPtr is null)
9102 			return;
9103 		else
9104 			connections = *connectionsPtr;
9105 
9106 		char[4096] buffer;
9107 		char[] formattedMessage;
9108 
9109 		void append(const char[] a) {
9110 			// the 6's here are to leave room for a HTTP chunk header, if it proves necessary
9111 			buffer[6 + formattedMessage.length .. 6 + formattedMessage.length + a.length] = a[];
9112 			formattedMessage = buffer[6 .. 6 + formattedMessage.length + a.length];
9113 		}
9114 
9115 		import std.algorithm.iteration;
9116 
9117 		if(connections.length) {
9118 			append("id: ");
9119 			append(to!string(thisId));
9120 			append("\n");
9121 
9122 			append("event: ");
9123 			append(event.type);
9124 			append("\n");
9125 
9126 			foreach(line; event.message.splitter("\n")) {
9127 				append("data: ");
9128 				append(line);
9129 				append("\n");
9130 			}
9131 
9132 			append("\n");
9133 		}
9134 
9135 		// chunk it for HTTP!
9136 		auto len = toHex(formattedMessage.length);
9137 		buffer[4 .. 6] = "\r\n"[];
9138 		buffer[4 - len.length .. 4] = len[];
9139 		buffer[6 + formattedMessage.length] = '\r';
9140 		buffer[6 + formattedMessage.length + 1] = '\n';
9141 
9142 		auto chunkedMessage = buffer[4 - len.length .. 6 + formattedMessage.length +2];
9143 		// done
9144 
9145 		// FIXME: send back requests when needed
9146 		// FIXME: send a single ":\n" every 15 seconds to keep alive
9147 
9148 		foreach(connection; connections) {
9149 			if(connection.needsChunking) {
9150 				nonBlockingWrite(this, connection.fd, chunkedMessage);
9151 			} else {
9152 				nonBlockingWrite(this, connection.fd, formattedMessage);
9153 			}
9154 		}
9155 	}
9156 }
9157 
9158 void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoServer)) {
9159 	version(Posix) {
9160 
9161 		import core.sys.posix.unistd;
9162 		import core.sys.posix.fcntl;
9163 		import core.sys.posix.sys.un;
9164 
9165 		import core.sys.posix.signal;
9166 		signal(SIGPIPE, SIG_IGN);
9167 
9168 		static extern(C) void sigchldhandler(int) {
9169 			int status;
9170 			import w = core.sys.posix.sys.wait;
9171 			w.wait(&status);
9172 		}
9173 		signal(SIGCHLD, &sigchldhandler);
9174 
9175 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
9176 		if(sock == -1)
9177 			throw new Exception("socket " ~ to!string(errno));
9178 
9179 		scope(failure)
9180 			close(sock);
9181 
9182 		cloexec(sock);
9183 
9184 		// add-on server processes are assumed to be local, and thus will
9185 		// use unix domain sockets. Besides, I want to pass sockets to them,
9186 		// so it basically must be local (except for the session server, but meh).
9187 		sockaddr_un addr;
9188 		addr.sun_family = AF_UNIX;
9189 		version(linux) {
9190 			// on linux, we will use the abstract namespace
9191 			addr.sun_path[0] = 0;
9192 			addr.sun_path[1 .. localListenerName.length + 1] = cast(typeof(addr.sun_path[])) localListenerName[];
9193 		} else {
9194 			// but otherwise, just use a file cuz we must.
9195 			addr.sun_path[0 .. localListenerName.length] = cast(typeof(addr.sun_path[])) localListenerName[];
9196 		}
9197 
9198 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1)
9199 			throw new Exception("bind " ~ to!string(errno));
9200 
9201 		if(listen(sock, 128) == -1)
9202 			throw new Exception("listen " ~ to!string(errno));
9203 
9204 		makeNonBlocking(sock);
9205 
9206 		version(linux) {
9207 			import core.sys.linux.epoll;
9208 			auto epoll_fd = epoll_create1(EPOLL_CLOEXEC);
9209 			if(epoll_fd == -1)
9210 				throw new Exception("epoll_create1 " ~ to!string(errno));
9211 			scope(failure)
9212 				close(epoll_fd);
9213 		} else {
9214 			import core.sys.posix.poll;
9215 		}
9216 
9217 		version(linux)
9218 		eis.epoll_fd = epoll_fd;
9219 
9220 		auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
9221 		scope(exit)
9222 			freeIoOp(acceptOp);
9223 
9224 		version(linux) {
9225 			epoll_event ev;
9226 			ev.events = EPOLLIN | EPOLLET;
9227 			ev.data.ptr = acceptOp;
9228 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev) == -1)
9229 				throw new Exception("epoll_ctl " ~ to!string(errno));
9230 
9231 			epoll_event[64] events;
9232 		} else {
9233 			pollfd[] pollfds;
9234 			IoOp*[int] ioops;
9235 			pollfds ~= pollfd(sock, POLLIN);
9236 			ioops[sock] = acceptOp;
9237 		}
9238 
9239 		import core.time : MonoTime, seconds;
9240 
9241 		MonoTime timeout = MonoTime.currTime + 15.seconds;
9242 
9243 		while(true) {
9244 
9245 			// FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently
9246 
9247 			int timeout_milliseconds = 0; //  -1; // infinite
9248 
9249 			timeout_milliseconds = cast(int) (timeout - MonoTime.currTime).total!"msecs";
9250 			if(timeout_milliseconds < 0)
9251 				timeout_milliseconds = 0;
9252 
9253 			//writeln("waiting for ", name);
9254 
9255 			version(linux) {
9256 				auto nfds = epoll_wait(epoll_fd, events.ptr, events.length, timeout_milliseconds);
9257 				if(nfds == -1) {
9258 					if(errno == EINTR)
9259 						continue;
9260 					throw new Exception("epoll_wait " ~ to!string(errno));
9261 				}
9262 			} else {
9263 				int nfds = poll(pollfds.ptr, cast(int) pollfds.length, timeout_milliseconds);
9264 				size_t lastIdx = 0;
9265 			}
9266 
9267 			if(nfds == 0) {
9268 				eis.wait_timeout();
9269 				timeout += 15.seconds;
9270 			}
9271 
9272 			foreach(idx; 0 .. nfds) {
9273 				version(linux) {
9274 					auto flags = events[idx].events;
9275 					auto ioop = cast(IoOp*) events[idx].data.ptr;
9276 				} else {
9277 					IoOp* ioop;
9278 					foreach(tidx, thing; pollfds[lastIdx .. $]) {
9279 						if(thing.revents) {
9280 							ioop = ioops[thing.fd];
9281 							lastIdx += tidx + 1;
9282 							break;
9283 						}
9284 					}
9285 				}
9286 
9287 				//writeln(flags, " ", ioop.fd);
9288 
9289 				void newConnection() {
9290 					// on edge triggering, it is important that we get it all
9291 					while(true) {
9292 						auto size = cast(socklen_t) addr.sizeof;
9293 						auto ns = accept(sock, cast(sockaddr*) &addr, &size);
9294 						if(ns == -1) {
9295 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9296 								// all done, got it all
9297 								break;
9298 							}
9299 							throw new Exception("accept " ~ to!string(errno));
9300 						}
9301 						cloexec(ns);
9302 
9303 						makeNonBlocking(ns);
9304 						auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096 * 4, &eis.handleLocalConnectionData);
9305 						niop.closeHandler = &eis.handleLocalConnectionClose;
9306 						niop.completeHandler = &eis.handleLocalConnectionComplete;
9307 						scope(failure) freeIoOp(niop);
9308 
9309 						version(linux) {
9310 							epoll_event nev;
9311 							nev.events = EPOLLIN | EPOLLET;
9312 							nev.data.ptr = niop;
9313 							if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ns, &nev) == -1)
9314 								throw new Exception("epoll_ctl " ~ to!string(errno));
9315 						} else {
9316 							bool found = false;
9317 							foreach(ref pfd; pollfds) {
9318 								if(pfd.fd < 0) {
9319 									pfd.fd = ns;
9320 									found = true;
9321 								}
9322 							}
9323 							if(!found)
9324 								pollfds ~= pollfd(ns, POLLIN);
9325 							ioops[ns] = niop;
9326 						}
9327 					}
9328 				}
9329 
9330 				bool newConnectionCondition() {
9331 					version(linux)
9332 						return ioop.fd == sock && (flags & EPOLLIN);
9333 					else
9334 						return pollfds[idx].fd == sock && (pollfds[idx].revents & POLLIN);
9335 				}
9336 
9337 				if(newConnectionCondition()) {
9338 					newConnection();
9339 				} else if(ioop.operation == IoOp.ReadSocketHandle) {
9340 					while(true) {
9341 						int in_fd;
9342 						auto got = read_fd(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, &in_fd);
9343 						if(got == -1) {
9344 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9345 								// all done, got it all
9346 								if(ioop.completeHandler)
9347 									ioop.completeHandler(ioop);
9348 								break;
9349 							}
9350 							throw new Exception("recv " ~ to!string(errno));
9351 						}
9352 
9353 						if(got == 0) {
9354 							if(ioop.closeHandler) {
9355 								ioop.closeHandler(ioop);
9356 								version(linux) {} // nothing needed
9357 								else {
9358 									foreach(ref pfd; pollfds) {
9359 										if(pfd.fd == ioop.fd)
9360 											pfd.fd = -1;
9361 									}
9362 								}
9363 							}
9364 							close(ioop.fd);
9365 							freeIoOp(ioop);
9366 							break;
9367 						}
9368 
9369 						ioop.bufferLengthUsed = cast(int) got;
9370 						ioop.handler(ioop, in_fd);
9371 					}
9372 				} else if(ioop.operation == IoOp.Read) {
9373 					while(true) {
9374 						auto got = read(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length);
9375 						if(got == -1) {
9376 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9377 								// all done, got it all
9378 								if(ioop.completeHandler)
9379 									ioop.completeHandler(ioop);
9380 								break;
9381 							}
9382 							throw new Exception("recv " ~ to!string(ioop.fd) ~ " errno " ~ to!string(errno));
9383 						}
9384 
9385 						if(got == 0) {
9386 							if(ioop.closeHandler)
9387 								ioop.closeHandler(ioop);
9388 							close(ioop.fd);
9389 							freeIoOp(ioop);
9390 							break;
9391 						}
9392 
9393 						ioop.bufferLengthUsed = cast(int) got;
9394 						if(ioop.handler(ioop, ioop.fd)) {
9395 							close(ioop.fd);
9396 							freeIoOp(ioop);
9397 							break;
9398 						}
9399 					}
9400 				}
9401 
9402 				// EPOLLHUP?
9403 			}
9404 		}
9405 	} else version(Windows) {
9406 
9407 		// set up a named pipe
9408 		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx
9409 		// https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsaduplicatesocketw
9410 		// https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getnamedpipeserverprocessid
9411 
9412 	} else static assert(0);
9413 }
9414 
9415 
9416 version(with_sendfd)
9417 // copied from the web and ported from C
9418 // see https://stackoverflow.com/questions/2358684/can-i-share-a-file-descriptor-to-another-process-on-linux-or-are-they-local-to-t
9419 ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd) {
9420 	msghdr msg;
9421 	iovec[1] iov;
9422 
9423 	version(OSX) {
9424 		//msg.msg_accrights = cast(cattr_t) &sendfd;
9425 		//msg.msg_accrightslen = int.sizeof;
9426 	} else version(Android) {
9427 	} else {
9428 		union ControlUnion {
9429 			cmsghdr cm;
9430 			char[CMSG_SPACE(int.sizeof)] control;
9431 		}
9432 
9433 		ControlUnion control_un;
9434 		cmsghdr* cmptr;
9435 
9436 		msg.msg_control = control_un.control.ptr;
9437 		msg.msg_controllen = control_un.control.length;
9438 
9439 		cmptr = CMSG_FIRSTHDR(&msg);
9440 		cmptr.cmsg_len = CMSG_LEN(int.sizeof);
9441 		cmptr.cmsg_level = SOL_SOCKET;
9442 		cmptr.cmsg_type = SCM_RIGHTS;
9443 		*(cast(int *) CMSG_DATA(cmptr)) = sendfd;
9444 	}
9445 
9446 	msg.msg_name = null;
9447 	msg.msg_namelen = 0;
9448 
9449 	iov[0].iov_base = ptr;
9450 	iov[0].iov_len = nbytes;
9451 	msg.msg_iov = iov.ptr;
9452 	msg.msg_iovlen = 1;
9453 
9454 	return sendmsg(fd, &msg, 0);
9455 }
9456 
9457 version(with_sendfd)
9458 // copied from the web and ported from C
9459 ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
9460 	msghdr msg;
9461 	iovec[1] iov;
9462 	ssize_t n;
9463 	int newfd;
9464 
9465 	version(OSX) {
9466 		//msg.msg_accrights = cast(cattr_t) recvfd;
9467 		//msg.msg_accrightslen = int.sizeof;
9468 	} else version(Android) {
9469 	} else {
9470 		union ControlUnion {
9471 			cmsghdr cm;
9472 			char[CMSG_SPACE(int.sizeof)] control;
9473 		}
9474 		ControlUnion control_un;
9475 		cmsghdr* cmptr;
9476 
9477 		msg.msg_control = control_un.control.ptr;
9478 		msg.msg_controllen = control_un.control.length;
9479 	}
9480 
9481 	msg.msg_name = null;
9482 	msg.msg_namelen = 0;
9483 
9484 	iov[0].iov_base = ptr;
9485 	iov[0].iov_len = nbytes;
9486 	msg.msg_iov = iov.ptr;
9487 	msg.msg_iovlen = 1;
9488 
9489 	if ( (n = recvmsg(fd, &msg, 0)) <= 0)
9490 		return n;
9491 
9492 	version(OSX) {
9493 		//if(msg.msg_accrightslen != int.sizeof)
9494 			//*recvfd = -1;
9495 	} else version(Android) {
9496 	} else {
9497 		if ( (cmptr = CMSG_FIRSTHDR(&msg)) != null &&
9498 				cmptr.cmsg_len == CMSG_LEN(int.sizeof)) {
9499 			if (cmptr.cmsg_level != SOL_SOCKET)
9500 				throw new Exception("control level != SOL_SOCKET");
9501 			if (cmptr.cmsg_type != SCM_RIGHTS)
9502 				throw new Exception("control type != SCM_RIGHTS");
9503 			*recvfd = *(cast(int *) CMSG_DATA(cmptr));
9504 		} else
9505 			*recvfd = -1;       /* descriptor was not passed */
9506 	}
9507 
9508 	return n;
9509 }
9510 /* end read_fd */
9511 
9512 
9513 /*
9514 	Event source stuff
9515 
9516 	The api is:
9517 
9518 	sendEvent(string url, string type, string data, int timeout = 60*10);
9519 
9520 	attachEventListener(string url, int fd, lastId)
9521 
9522 
9523 	It just sends to all attached listeners, and stores it until the timeout
9524 	for replaying via lastEventId.
9525 */
9526 
9527 /*
9528 	Session process stuff
9529 
9530 	it stores it all. the cgi object has a session object that can grab it
9531 
9532 	session may be done in the same process if possible, there is a version
9533 	switch to choose if you want to override.
9534 */
9535 
9536 struct DispatcherDefinition(alias dispatchHandler, DispatcherDetails = typeof(null)) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
9537 	alias handler = dispatchHandler;
9538 	string urlPrefix;
9539 	bool rejectFurther;
9540 	immutable(DispatcherDetails) details;
9541 }
9542 
9543 private string urlify(string name) pure {
9544 	return beautify(name, '-', true);
9545 }
9546 
9547 private string beautify(string name, char space = ' ', bool allLowerCase = false) pure {
9548 	if(name == "id")
9549 		return allLowerCase ? name : "ID";
9550 
9551 	char[160] buffer;
9552 	int bufferIndex = 0;
9553 	bool shouldCap = true;
9554 	bool shouldSpace;
9555 	bool lastWasCap;
9556 	foreach(idx, char ch; name) {
9557 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
9558 
9559 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
9560 			if(lastWasCap) {
9561 				// two caps in a row, don't change. Prolly acronym.
9562 			} else {
9563 				if(idx)
9564 					shouldSpace = true; // new word, add space
9565 			}
9566 
9567 			lastWasCap = true;
9568 		} else {
9569 			lastWasCap = false;
9570 		}
9571 
9572 		if(shouldSpace) {
9573 			buffer[bufferIndex++] = space;
9574 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
9575 			shouldSpace = false;
9576 		}
9577 		if(shouldCap) {
9578 			if(ch >= 'a' && ch <= 'z')
9579 				ch -= 32;
9580 			shouldCap = false;
9581 		}
9582 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
9583 			ch += 32;
9584 		buffer[bufferIndex++] = ch;
9585 	}
9586 	return buffer[0 .. bufferIndex].idup;
9587 }
9588 
9589 /*
9590 string urlFor(alias func)() {
9591 	return __traits(identifier, func);
9592 }
9593 */
9594 
9595 /++
9596 	UDA: The name displayed to the user in auto-generated HTML.
9597 
9598 	Default is `beautify(identifier)`.
9599 +/
9600 struct DisplayName {
9601 	string name;
9602 }
9603 
9604 /++
9605 	UDA: The name used in the URL or web parameter.
9606 
9607 	Default is `urlify(identifier)` for functions and `identifier` for parameters and data members.
9608 +/
9609 struct UrlName {
9610 	string name;
9611 }
9612 
9613 /++
9614 	UDA: default format to respond for this method
9615 +/
9616 struct DefaultFormat { string value; }
9617 
9618 class MissingArgumentException : Exception {
9619 	string functionName;
9620 	string argumentName;
9621 	string argumentType;
9622 
9623 	this(string functionName, string argumentName, string argumentType, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
9624 		this.functionName = functionName;
9625 		this.argumentName = argumentName;
9626 		this.argumentType = argumentType;
9627 
9628 		super("Missing Argument: " ~ this.argumentName, file, line, next);
9629 	}
9630 }
9631 
9632 /++
9633 	You can throw this from an api handler to indicate a 404 response. This is done by the presentExceptionAsHtml function in the presenter.
9634 
9635 	History:
9636 		Added December 15, 2021 (dub v10.5)
9637 +/
9638 class ResourceNotFoundException : Exception {
9639 	string resourceType;
9640 	string resourceId;
9641 
9642 	this(string resourceType, string resourceId, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
9643 		this.resourceType = resourceType;
9644 		this.resourceId = resourceId;
9645 
9646 		super("Resource not found: " ~ resourceType ~ " " ~ resourceId, file, line, next);
9647 	}
9648 
9649 }
9650 
9651 /++
9652 	This can be attached to any constructor or function called from the cgi system.
9653 
9654 	If it is present, the function argument can NOT be set from web params, but instead
9655 	is set to the return value of the given `func`.
9656 
9657 	If `func` can take a parameter of type [Cgi], it will be passed the one representing
9658 	the current request. Otherwise, it must take zero arguments.
9659 
9660 	Any params in your function of type `Cgi` are automatically assumed to take the cgi object
9661 	for the connection. Any of type [Session] (with an argument) is	also assumed to come from
9662 	the cgi object.
9663 
9664 	const arguments are also supported.
9665 +/
9666 struct ifCalledFromWeb(alias func) {}
9667 
9668 // it only looks at query params for GET requests, the rest must be in the body for a function argument.
9669 auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
9670 
9671 	// FIXME: any array of structs should also be settable or gettable from csv as well.
9672 
9673 	// FIXME: think more about checkboxes and bools.
9674 
9675 	import std.traits;
9676 
9677 	Parameters!method params;
9678 	alias idents = ParameterIdentifierTuple!method;
9679 	alias defaults = ParameterDefaults!method;
9680 
9681 	const(string)[] names;
9682 	const(string)[] values;
9683 
9684 	// first, check for missing arguments and initialize to defaults if necessary
9685 
9686 	static if(is(typeof(method) P == __parameters))
9687 	foreach(idx, param; P) {{
9688 		// see: mustNotBeSetFromWebParams
9689 		static if(is(param : Cgi)) {
9690 			static assert(!is(param == immutable));
9691 			cast() params[idx] = cgi;
9692 		} else static if(is(param == Session!D, D)) {
9693 			static assert(!is(param == immutable));
9694 			cast() params[idx] = cgi.getSessionObject!D();
9695 		} else {
9696 			bool populated;
9697 			foreach(uda; __traits(getAttributes, P[idx .. idx + 1])) {
9698 				static if(is(uda == ifCalledFromWeb!func, alias func)) {
9699 					static if(is(typeof(func(cgi))))
9700 						params[idx] = func(cgi);
9701 					else
9702 						params[idx] = func();
9703 
9704 					populated = true;
9705 				}
9706 			}
9707 
9708 			if(!populated) {
9709 				static if(__traits(compiles, { params[idx] = param.getAutomaticallyForCgi(cgi); } )) {
9710 					params[idx] = param.getAutomaticallyForCgi(cgi);
9711 					populated = true;
9712 				}
9713 			}
9714 
9715 			if(!populated) {
9716 				auto ident = idents[idx];
9717 				if(cgi.requestMethod == Cgi.RequestMethod.GET) {
9718 					if(ident !in cgi.get) {
9719 						static if(is(defaults[idx] == void)) {
9720 							static if(is(param == bool))
9721 								params[idx] = false;
9722 							else
9723 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
9724 						} else
9725 							params[idx] = defaults[idx];
9726 					}
9727 				} else {
9728 					if(ident !in cgi.post) {
9729 						static if(is(defaults[idx] == void)) {
9730 							static if(is(param == bool))
9731 								params[idx] = false;
9732 							else
9733 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
9734 						} else
9735 							params[idx] = defaults[idx];
9736 					}
9737 				}
9738 			}
9739 		}
9740 	}}
9741 
9742 	// second, parse the arguments in order to build up arrays, etc.
9743 
9744 	static bool setVariable(T)(string name, string paramName, T* what, string value) {
9745 		static if(is(T == struct)) {
9746 			if(name == paramName) {
9747 				*what = T.init;
9748 				return true;
9749 			} else {
9750 				// could be a child. gonna allow either obj.field OR obj[field]
9751 
9752 				string afterName;
9753 
9754 				if(name[paramName.length] == '[') {
9755 					int count = 1;
9756 					auto idx = paramName.length + 1;
9757 					while(idx < name.length && count > 0) {
9758 						if(name[idx] == '[')
9759 							count++;
9760 						else if(name[idx] == ']') {
9761 							count--;
9762 							if(count == 0) break;
9763 						}
9764 						idx++;
9765 					}
9766 
9767 					if(idx == name.length)
9768 						return false; // malformed
9769 
9770 					auto insideBrackets = name[paramName.length + 1 .. idx];
9771 					afterName = name[idx + 1 .. $];
9772 
9773 					name = name[0 .. paramName.length];
9774 
9775 					paramName = insideBrackets;
9776 
9777 				} else if(name[paramName.length] == '.') {
9778 					paramName = name[paramName.length + 1 .. $];
9779 					name = paramName;
9780 					int p = 0;
9781 					foreach(ch; paramName) {
9782 						if(ch == '.' || ch == '[')
9783 							break;
9784 						p++;
9785 					}
9786 
9787 					afterName = paramName[p .. $];
9788 					paramName = paramName[0 .. p];
9789 				} else {
9790 					return false;
9791 				}
9792 
9793 				if(paramName.length)
9794 				// set the child member
9795 				switch(paramName) {
9796 					foreach(idx, memberName; __traits(allMembers, T))
9797 					static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
9798 						// data member!
9799 						case memberName:
9800 							return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
9801 					}
9802 					default:
9803 						// ok, not a member
9804 				}
9805 			}
9806 
9807 			return false;
9808 		} else static if(is(T == enum)) {
9809 			*what = to!T(value);
9810 			return true;
9811 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
9812 			*what = to!T(value);
9813 			return true;
9814 		} else static if(is(T == bool)) {
9815 			*what = value == "1" || value == "yes" || value == "t" || value == "true" || value == "on";
9816 			return true;
9817 		} else static if(is(T == K[], K)) {
9818 			K tmp;
9819 			if(name == paramName) {
9820 				// direct - set and append
9821 				if(setVariable(name, paramName, &tmp, value)) {
9822 					(*what) ~= tmp;
9823 					return true;
9824 				} else {
9825 					return false;
9826 				}
9827 			} else {
9828 				// child, append to last element
9829 				// FIXME: what about range violations???
9830 				auto ptr = &(*what)[(*what).length - 1];
9831 				return setVariable(name, paramName, ptr, value);
9832 
9833 			}
9834 		} else static if(is(T == V[K], K, V)) {
9835 			// assoc array, name[key] is valid
9836 			if(name == paramName) {
9837 				// no action necessary
9838 				return true;
9839 			} else if(name[paramName.length] == '[') {
9840 				int count = 1;
9841 				auto idx = paramName.length + 1;
9842 				while(idx < name.length && count > 0) {
9843 					if(name[idx] == '[')
9844 						count++;
9845 					else if(name[idx] == ']') {
9846 						count--;
9847 						if(count == 0) break;
9848 					}
9849 					idx++;
9850 				}
9851 				if(idx == name.length)
9852 					return false; // malformed
9853 
9854 				auto insideBrackets = name[paramName.length + 1 .. idx];
9855 				auto afterName = name[idx + 1 .. $];
9856 
9857 				auto k = to!K(insideBrackets);
9858 				V v;
9859 				if(auto ptr = k in *what)
9860 					v = *ptr;
9861 
9862 				name = name[0 .. paramName.length];
9863 				//writeln(name, afterName, " ", paramName);
9864 
9865 				auto ret = setVariable(name ~ afterName, paramName, &v, value);
9866 				if(ret) {
9867 					(*what)[k] = v;
9868 					return true;
9869 				}
9870 			}
9871 
9872 			return false;
9873 		} else {
9874 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
9875 		}
9876 
9877 		//return false;
9878 	}
9879 
9880 	void setArgument(string name, string value) {
9881 		int p;
9882 		foreach(ch; name) {
9883 			if(ch == '.' || ch == '[')
9884 				break;
9885 			p++;
9886 		}
9887 
9888 		auto paramName = name[0 .. p];
9889 
9890 		sw: switch(paramName) {
9891 			static if(is(typeof(method) P == __parameters))
9892 			foreach(idx, param; P) {
9893 				static if(mustNotBeSetFromWebParams!(P[idx], __traits(getAttributes, P[idx .. idx + 1]))) {
9894 					// cannot be set from the outside
9895 				} else {
9896 					case idents[idx]:
9897 						static if(is(param == Cgi.UploadedFile)) {
9898 							params[idx] = cgi.files[name];
9899 						} else static if(is(param : const Cgi.UploadedFile[])) {
9900 							(cast() params[idx]) = cgi.filesArray[name];
9901 						} else {
9902 							setVariable(name, paramName, &params[idx], value);
9903 						}
9904 					break sw;
9905 				}
9906 			}
9907 			default:
9908 				// ignore; not relevant argument
9909 		}
9910 	}
9911 
9912 	if(cgi.requestMethod == Cgi.RequestMethod.GET) {
9913 		names = cgi.allGetNamesInOrder;
9914 		values = cgi.allGetValuesInOrder;
9915 	} else {
9916 		names = cgi.allPostNamesInOrder;
9917 		values = cgi.allPostValuesInOrder;
9918 	}
9919 
9920 	foreach(idx, name; names) {
9921 		setArgument(name, values[idx]);
9922 	}
9923 
9924 	static if(is(ReturnType!method == void)) {
9925 		typeof(null) ret;
9926 		dg(params);
9927 	} else {
9928 		auto ret = dg(params);
9929 	}
9930 
9931 	// FIXME: format return values
9932 	// options are: json, html, csv.
9933 	// also may need to wrap in envelope format: none, html, or json.
9934 	return ret;
9935 }
9936 
9937 private bool mustNotBeSetFromWebParams(T, attrs...)() {
9938 	static if(is(T : const(Cgi))) {
9939 		return true;
9940 	} else static if(is(T : const(Session!D), D)) {
9941 		return true;
9942 	} else static if(__traits(compiles, T.getAutomaticallyForCgi(Cgi.init))) {
9943 		return true;
9944 	} else {
9945 		foreach(uda; attrs)
9946 			static if(is(uda == ifCalledFromWeb!func, alias func))
9947 				return true;
9948 		return false;
9949 	}
9950 }
9951 
9952 private bool hasIfCalledFromWeb(attrs...)() {
9953 	foreach(uda; attrs)
9954 		static if(is(uda == ifCalledFromWeb!func, alias func))
9955 			return true;
9956 	return false;
9957 }
9958 
9959 /++
9960 	Implies POST path for the thing itself, then GET will get the automatic form.
9961 
9962 	The given customizer, if present, will be called as a filter on the Form object.
9963 
9964 	History:
9965 		Added December 27, 2020
9966 +/
9967 template AutomaticForm(alias customizer) { }
9968 
9969 /++
9970 	This is meant to be returned by a function that takes a form POST submission. You
9971 	want to set the url of the new resource it created, which is set as the http
9972 	Location header for a "201 Created" result, and you can also set a separate
9973 	destination for browser users, which it sets via a "Refresh" header.
9974 
9975 	The `resourceRepresentation` should generally be the thing you just created, and
9976 	it will be the body of the http response when formatted through the presenter.
9977 	The exact thing is up to you - it could just return an id, or the whole object, or
9978 	perhaps a partial object.
9979 
9980 	Examples:
9981 	---
9982 	class Test : WebObject {
9983 		@(Cgi.RequestMethod.POST)
9984 		CreatedResource!int makeThing(string value) {
9985 			return CreatedResource!int(value.to!int, "/resources/id");
9986 		}
9987 	}
9988 	---
9989 
9990 	History:
9991 		Added December 18, 2021
9992 +/
9993 struct CreatedResource(T) {
9994 	static if(!is(T == void))
9995 		T resourceRepresentation;
9996 	string resourceUrl;
9997 	string refreshUrl;
9998 }
9999 
10000 /+
10001 /++
10002 	This can be attached as a UDA to a handler to add a http Refresh header on a
10003 	successful run. (It will not be attached if the function throws an exception.)
10004 	This will refresh the browser the given number of seconds after the page loads,
10005 	to the url returned by `urlFunc`, which can be either a static function or a
10006 	member method of the current handler object.
10007 
10008 	You might use this for a POST handler that is normally used from ajax, but you
10009 	want it to degrade gracefully to a temporarily flashed message before reloading
10010 	the main page.
10011 
10012 	History:
10013 		Added December 18, 2021
10014 +/
10015 struct Refresh(alias urlFunc) {
10016 	int waitInSeconds;
10017 
10018 	string url() {
10019 		static if(__traits(isStaticFunction, urlFunc))
10020 			return urlFunc();
10021 		else static if(is(urlFunc : string))
10022 			return urlFunc;
10023 	}
10024 }
10025 +/
10026 
10027 /+
10028 /++
10029 	Sets a filter to be run before
10030 
10031 	A before function can do validations of params and log and stop the function from running.
10032 +/
10033 template Before(alias b) {}
10034 template After(alias b) {}
10035 +/
10036 
10037 /+
10038 	Argument conversions: for the most part, it is to!Thing(string).
10039 
10040 	But arrays and structs are a bit different. Arrays come from the cgi array. Thus
10041 	they are passed
10042 
10043 	arr=foo&arr=bar <-- notice the same name.
10044 
10045 	Structs are first declared with an empty thing, then have their members set individually,
10046 	with dot notation. The members are not required, just the initial declaration.
10047 
10048 	struct Foo {
10049 		int a;
10050 		string b;
10051 	}
10052 	void test(Foo foo){}
10053 
10054 	foo&foo.a=5&foo.b=str <-- the first foo declares the arg, the others set the members
10055 
10056 	Arrays of structs use this declaration.
10057 
10058 	void test(Foo[] foo) {}
10059 
10060 	foo&foo.a=5&foo.b=bar&foo&foo.a=9
10061 
10062 	You can use a hidden input field in HTML forms to achieve this. The value of the naked name
10063 	declaration is ignored.
10064 
10065 	Mind that order matters! The declaration MUST come first in the string.
10066 
10067 	Arrays of struct members follow this rule recursively.
10068 
10069 	struct Foo {
10070 		int[] a;
10071 	}
10072 
10073 	foo&foo.a=1&foo.a=2&foo&foo.a=1
10074 
10075 
10076 	Associative arrays are formatted with brackets, after a declaration, like structs:
10077 
10078 	foo&foo[key]=value&foo[other_key]=value
10079 
10080 
10081 	Note: for maximum compatibility with outside code, keep your types simple. Some libraries
10082 	do not support the strict ordering requirements to work with these struct protocols.
10083 
10084 	FIXME: also perhaps accept application/json to better work with outside trash.
10085 
10086 
10087 	Return values are also auto-formatted according to user-requested type:
10088 		for json, it loops over and converts.
10089 		for html, basic types are strings. Arrays are <ol>. Structs are <dl>. Arrays of structs are tables!
10090 +/
10091 
10092 /++
10093 	A web presenter is responsible for rendering things to HTML to be usable
10094 	in a web browser.
10095 
10096 	They are passed as template arguments to the base classes of [WebObject]
10097 
10098 	Responsible for displaying stuff as HTML. You can put this into your own aggregate
10099 	and override it. Use forwarding and specialization to customize it.
10100 
10101 	When you inherit from it, pass your own class as the CRTP argument. This lets the base
10102 	class templates and your overridden templates work with each other.
10103 
10104 	---
10105 	class MyPresenter : WebPresenter!(MyPresenter) {
10106 		@Override
10107 		void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret, typeof(null) meta) {
10108 			// present the CustomType
10109 		}
10110 		@Override
10111 		void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
10112 			// handle everything else via the super class, which will call
10113 			// back to your class when appropriate
10114 			super.presentSuccessfulReturnAsHtml(cgi, ret);
10115 		}
10116 	}
10117 	---
10118 
10119 	The meta argument in there can be overridden by your own facility.
10120 
10121 +/
10122 class WebPresenter(CRTP) {
10123 
10124 	/// A UDA version of the built-in `override`, to be used for static template polymorphism
10125 	/// If you override a plain method, use `override`. If a template, use `@Override`.
10126 	enum Override;
10127 
10128 	string script() {
10129 		return `
10130 		`;
10131 	}
10132 
10133 	string style() {
10134 		return `
10135 			:root {
10136 				--mild-border: #ccc;
10137 				--middle-border: #999;
10138 				--accent-color: #f2f2f2;
10139 				--sidebar-color: #fefefe;
10140 			}
10141 		` ~ genericFormStyling() ~ genericSiteStyling();
10142 	}
10143 
10144 	string genericFormStyling() {
10145 		return
10146 q"css
10147 			table.automatic-data-display {
10148 				border-collapse: collapse;
10149 				border: solid 1px var(--mild-border);
10150 			}
10151 
10152 			table.automatic-data-display td {
10153 				vertical-align: top;
10154 				border: solid 1px var(--mild-border);
10155 				padding: 2px 4px;
10156 			}
10157 
10158 			table.automatic-data-display th {
10159 				border: solid 1px var(--mild-border);
10160 				border-bottom: solid 1px var(--middle-border);
10161 				padding: 2px 4px;
10162 			}
10163 
10164 			ol.automatic-data-display {
10165 				margin: 0px;
10166 				/*
10167 				list-style-position: inside;
10168 				padding: 0px;
10169 				*/
10170 			}
10171 
10172 			dl.automatic-data-display {
10173 
10174 			}
10175 
10176 			.automatic-form {
10177 				max-width: 600px;
10178 			}
10179 
10180 			.form-field {
10181 				margin: 0.5em;
10182 				padding-left: 0.5em;
10183 			}
10184 
10185 			.label-text {
10186 				display: block;
10187 				font-weight: bold;
10188 				margin-left: -0.5em;
10189 			}
10190 
10191 			.submit-button-holder {
10192 				padding-left: 2em;
10193 			}
10194 
10195 			.add-array-button {
10196 
10197 			}
10198 css";
10199 	}
10200 
10201 	string genericSiteStyling() {
10202 		return
10203 q"css
10204 			* { box-sizing: border-box; }
10205 			html, body { margin: 0px; }
10206 			body {
10207 				font-family: sans-serif;
10208 			}
10209 			header {
10210 				background: var(--accent-color);
10211 				height: 64px;
10212 			}
10213 			footer {
10214 				background: var(--accent-color);
10215 				height: 64px;
10216 			}
10217 			#site-container {
10218 				display: flex;
10219 				flex-wrap: wrap;
10220 			}
10221 			main {
10222 				flex: 1 1 auto;
10223 				order: 2;
10224 				min-height: calc(100vh - 64px - 64px);
10225 				min-width: 80ch;
10226 				padding: 4px;
10227 				padding-left: 1em;
10228 			}
10229 			#sidebar {
10230 				flex: 0 0 16em;
10231 				order: 1;
10232 				background: var(--sidebar-color);
10233 			}
10234 css";
10235 	}
10236 
10237 	import arsd.dom;
10238 	Element htmlContainer() {
10239 		auto document = new Document(q"html
10240 <!DOCTYPE html>
10241 <html class="no-script">
10242 <head>
10243 	<script>document.documentElement.classList.remove("no-script");</script>
10244 	<style>.no-script requires-script { display: none; }</style>
10245 	<title>D Application</title>
10246 	<meta name="viewport" content="initial-scale=1, width=device-width" />
10247 	<link rel="stylesheet" href="style.css" />
10248 </head>
10249 <body>
10250 	<header></header>
10251 	<div id="site-container">
10252 		<main></main>
10253 		<div id="sidebar"></div>
10254 	</div>
10255 	<footer></footer>
10256 	<script src="script.js"></script>
10257 </body>
10258 </html>
10259 html", true, true);
10260 
10261 		return document.requireSelector("main");
10262 	}
10263 
10264 	/// Renders a response as an HTTP error with associated html body
10265 	void renderBasicError(Cgi cgi, int httpErrorCode) {
10266 		cgi.setResponseStatus(getHttpCodeText(httpErrorCode));
10267 		auto c = htmlContainer();
10268 		c.innerText = getHttpCodeText(httpErrorCode);
10269 		cgi.setResponseContentType("text/html; charset=utf-8");
10270 		cgi.write(c.parentDocument.toString(), true);
10271 	}
10272 
10273 	template methodMeta(alias method) {
10274 		enum methodMeta = null;
10275 	}
10276 
10277 	void presentSuccessfulReturn(T, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10278 		switch(format) {
10279 			case "html":
10280 				(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret, meta);
10281 			break;
10282 			case "json":
10283 				import arsd.jsvar;
10284 				static if(is(typeof(ret) == MultipleResponses!Types, Types...)) {
10285 					var json;
10286 					foreach(index, type; Types) {
10287 						if(ret.contains == index)
10288 							json = ret.payload[index];
10289 					}
10290 				} else {
10291 					var json = ret;
10292 				}
10293 				var envelope = json; // var.emptyObject;
10294 				/*
10295 				envelope.success = true;
10296 				envelope.result = json;
10297 				envelope.error = null;
10298 				*/
10299 				cgi.setResponseContentType("application/json");
10300 				cgi.write(envelope.toJson(), true);
10301 			break;
10302 			default:
10303 				cgi.setResponseStatus("406 Not Acceptable"); // not exactly but sort of.
10304 		}
10305 	}
10306 
10307 	/// typeof(null) (which is also used to represent functions returning `void`) do nothing
10308 	/// in the default presenter - allowing the function to have full low-level control over the
10309 	/// response.
10310 	void presentSuccessfulReturn(T : typeof(null), Meta)(Cgi cgi, T ret, Meta meta, string format) {
10311 		// nothing intentionally!
10312 	}
10313 
10314 	/// Redirections are forwarded to [Cgi.setResponseLocation]
10315 	void presentSuccessfulReturn(T : Redirection, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10316 		cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
10317 	}
10318 
10319 	/// [CreatedResource]s send code 201 and will set the given urls, then present the given representation.
10320 	void presentSuccessfulReturn(T : CreatedResource!R, Meta, R)(Cgi cgi, T ret, Meta meta, string format) {
10321 		cgi.setResponseStatus(getHttpCodeText(201));
10322 		if(ret.resourceUrl.length)
10323 			cgi.header("Location: " ~ ret.resourceUrl);
10324 		if(ret.refreshUrl.length)
10325 			cgi.header("Refresh: 0;" ~ ret.refreshUrl);
10326 		static if(!is(R == void))
10327 			presentSuccessfulReturn(cgi, ret.resourceRepresentation, meta, format);
10328 	}
10329 
10330 	/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
10331 	void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
10332 		bool outputted = false;
10333 		foreach(index, type; Types) {
10334 			if(ret.contains == index) {
10335 				assert(!outputted);
10336 				outputted = true;
10337 				(cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
10338 			}
10339 		}
10340 		if(!outputted)
10341 			assert(0);
10342 	}
10343 
10344 	/++
10345 		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.
10346 	+/
10347 	void presentSuccessfulReturn(T : FileResource, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10348 		cgi.setCache(true); // not necessarily true but meh
10349 		if(auto fn = ret.filename()) {
10350 			cgi.header("Content-Disposition: attachment; filename="~fn~";");
10351 		}
10352 		cgi.setResponseContentType(ret.contentType);
10353 		cgi.write(ret.getData(), true);
10354 	}
10355 
10356 	/// And the default handler for HTML will call [formatReturnValueAsHtml] and place it inside the [htmlContainer].
10357 	void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
10358 		auto container = this.htmlContainer();
10359 		container.appendChild(formatReturnValueAsHtml(ret));
10360 		cgi.write(container.parentDocument.toString(), true);
10361 	}
10362 
10363 	/++
10364 
10365 		History:
10366 			Added January 23, 2023 (dub v11.0)
10367 	+/
10368 	void presentExceptionalReturn(Meta)(Cgi cgi, Throwable t, Meta meta, string format) {
10369 		switch(format) {
10370 			case "html":
10371 				presentExceptionAsHtml(cgi, t, meta);
10372 			break;
10373 			case "json":
10374 				presentExceptionAsJsonImpl(cgi, t);
10375 			break;
10376 			default:
10377 		}
10378 	}
10379 
10380 	private void presentExceptionAsJsonImpl()(Cgi cgi, Throwable t) {
10381 		cgi.setResponseStatus("500 Internal Server Error");
10382 		cgi.setResponseContentType("application/json");
10383 		import arsd.jsvar;
10384 		var v = var.emptyObject;
10385 		v.type = typeid(t).toString;
10386 		v.msg = t.msg;
10387 		v.fullString = t.toString();
10388 		cgi.write(v.toJson(), true);
10389 	}
10390 
10391 
10392 	/++
10393 		If you override this, you will need to cast the exception type `t` dynamically,
10394 		but can then use the template arguments here to refer back to the function.
10395 
10396 		`func` is an alias to the method itself, and `dg` is a callable delegate to the same
10397 		method on the live object. You could, in theory, change arguments and retry, but I
10398 		provide that information mostly with the expectation that you will use them to make
10399 		useful forms or richer error messages for the user.
10400 
10401 		History:
10402 			BREAKING CHANGE on January 23, 2023 (v11.0 ): it previously took an `alias func` and `T dg` to call the function again.
10403 			I removed this in favor of a `Meta` param.
10404 
10405 			Before: `void presentExceptionAsHtml(alias func, T)(Cgi cgi, Throwable t, T dg)`
10406 
10407 			After: `void presentExceptionAsHtml(Meta)(Cgi cgi, Throwable t, Meta meta)`
10408 
10409 			If you used the func for something, move that something into your `methodMeta` template.
10410 
10411 			What is the benefit of this change? Somewhat smaller executables and faster builds thanks to more reused functions, together with
10412 			enabling an easier implementation of [presentExceptionalReturn].
10413 	+/
10414 	void presentExceptionAsHtml(Meta)(Cgi cgi, Throwable t, Meta meta) {
10415 		Form af;
10416 		/+
10417 		foreach(attr; __traits(getAttributes, func)) {
10418 			static if(__traits(isSame, attr, AutomaticForm)) {
10419 				af = createAutomaticFormForFunction!(func)(dg);
10420 			}
10421 		}
10422 		+/
10423 		presentExceptionAsHtmlImpl(cgi, t, af);
10424 	}
10425 
10426 	void presentExceptionAsHtmlImpl(Cgi cgi, Throwable t, Form automaticForm) {
10427 		if(auto e = cast(ResourceNotFoundException) t) {
10428 			auto container = this.htmlContainer();
10429 
10430 			container.addChild("p", e.msg);
10431 
10432 			if(!cgi.outputtedResponseData)
10433 				cgi.setResponseStatus("404 Not Found");
10434 			cgi.write(container.parentDocument.toString(), true);
10435 		} else if(auto mae = cast(MissingArgumentException) t) {
10436 			if(automaticForm is null)
10437 				goto generic;
10438 			auto container = this.htmlContainer();
10439 			if(cgi.requestMethod == Cgi.RequestMethod.POST)
10440 				container.appendChild(Element.make("p", "Argument `" ~ mae.argumentName ~ "` of type `" ~ mae.argumentType ~ "` is missing"));
10441 			container.appendChild(automaticForm);
10442 
10443 			cgi.write(container.parentDocument.toString(), true);
10444 		} else {
10445 			generic:
10446 			auto container = this.htmlContainer();
10447 
10448 			// import std.stdio; writeln(t.toString());
10449 
10450 			container.appendChild(exceptionToElement(t));
10451 
10452 			container.addChild("h4", "GET");
10453 			foreach(k, v; cgi.get) {
10454 				auto deets = container.addChild("details");
10455 				deets.addChild("summary", k);
10456 				deets.addChild("div", v);
10457 			}
10458 
10459 			container.addChild("h4", "POST");
10460 			foreach(k, v; cgi.post) {
10461 				auto deets = container.addChild("details");
10462 				deets.addChild("summary", k);
10463 				deets.addChild("div", v);
10464 			}
10465 
10466 
10467 			if(!cgi.outputtedResponseData)
10468 				cgi.setResponseStatus("500 Internal Server Error");
10469 			cgi.write(container.parentDocument.toString(), true);
10470 		}
10471 	}
10472 
10473 	Element exceptionToElement(Throwable t) {
10474 		auto div = Element.make("div");
10475 		div.addClass("exception-display");
10476 
10477 		div.addChild("p", t.msg);
10478 		div.addChild("p", "Inner code origin: " ~ typeid(t).name ~ "@" ~ t.file ~ ":" ~ to!string(t.line));
10479 
10480 		auto pre = div.addChild("pre");
10481 		string s;
10482 		s = t.toString();
10483 		Element currentBox;
10484 		bool on = false;
10485 		foreach(line; s.splitLines) {
10486 			if(!on && line.startsWith("-----"))
10487 				on = true;
10488 			if(!on) continue;
10489 			if(line.indexOf("arsd/") != -1) {
10490 				if(currentBox is null) {
10491 					currentBox = pre.addChild("details");
10492 					currentBox.addChild("summary", "Framework code");
10493 				}
10494 				currentBox.addChild("span", line ~ "\n");
10495 			} else {
10496 				pre.addChild("span", line ~ "\n");
10497 				currentBox = null;
10498 			}
10499 		}
10500 
10501 		return div;
10502 	}
10503 
10504 	/++
10505 		Returns an element for a particular type
10506 	+/
10507 	Element elementFor(T)(string displayName, string name, Element function() udaSuggestion) {
10508 		import std.traits;
10509 
10510 		auto div = Element.make("div");
10511 		div.addClass("form-field");
10512 
10513 		static if(is(T : const Cgi.UploadedFile)) {
10514 			Element lbl;
10515 			if(displayName !is null) {
10516 				lbl = div.addChild("label");
10517 				lbl.addChild("span", displayName, "label-text");
10518 				lbl.appendText(" ");
10519 			} else {
10520 				lbl = div;
10521 			}
10522 			auto i = lbl.addChild("input", name);
10523 			i.attrs.name = name;
10524 			i.attrs.type = "file";
10525 			i.attrs.multiple = "multiple";
10526 		} else static if(is(T == Cgi.UploadedFile)) {
10527 			Element lbl;
10528 			if(displayName !is null) {
10529 				lbl = div.addChild("label");
10530 				lbl.addChild("span", displayName, "label-text");
10531 				lbl.appendText(" ");
10532 			} else {
10533 				lbl = div;
10534 			}
10535 			auto i = lbl.addChild("input", name);
10536 			i.attrs.name = name;
10537 			i.attrs.type = "file";
10538 		} else static if(is(T == enum)) {
10539 			Element lbl;
10540 			if(displayName !is null) {
10541 				lbl = div.addChild("label");
10542 				lbl.addChild("span", displayName, "label-text");
10543 				lbl.appendText(" ");
10544 			} else {
10545 				lbl = div;
10546 			}
10547 			auto i = lbl.addChild("select", name);
10548 			i.attrs.name = name;
10549 
10550 			foreach(memberName; __traits(allMembers, T))
10551 				i.addChild("option", memberName);
10552 
10553 		} else static if(is(T == struct)) {
10554 			if(displayName !is null)
10555 				div.addChild("span", displayName, "label-text");
10556 			auto fieldset = div.addChild("fieldset");
10557 			fieldset.addChild("legend", beautify(T.stringof)); // FIXME
10558 			fieldset.addChild("input", name);
10559 			foreach(idx, memberName; __traits(allMembers, T))
10560 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
10561 				fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName, null /* FIXME: pull off the UDA */));
10562 			}
10563 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
10564 			Element lbl;
10565 			if(displayName !is null) {
10566 				lbl = div.addChild("label");
10567 				lbl.addChild("span", displayName, "label-text");
10568 				lbl.appendText(" ");
10569 			} else {
10570 				lbl = div;
10571 			}
10572 			Element i;
10573 			if(udaSuggestion) {
10574 				i = udaSuggestion();
10575 				lbl.appendChild(i);
10576 			} else {
10577 				i = lbl.addChild("input", name);
10578 			}
10579 			i.attrs.name = name;
10580 			static if(isSomeString!T)
10581 				i.attrs.type = "text";
10582 			else
10583 				i.attrs.type = "number";
10584 			if(i.tagName == "textarea")
10585 				i.textContent = to!string(T.init);
10586 			else
10587 				i.attrs.value = to!string(T.init);
10588 		} else static if(is(T == bool)) {
10589 			Element lbl;
10590 			if(displayName !is null) {
10591 				lbl = div.addChild("label");
10592 				lbl.addChild("span", displayName, "label-text");
10593 				lbl.appendText(" ");
10594 			} else {
10595 				lbl = div;
10596 			}
10597 			auto i = lbl.addChild("input", name);
10598 			i.attrs.type = "checkbox";
10599 			i.attrs.value = "true";
10600 			i.attrs.name = name;
10601 		} else static if(is(T == K[], K)) {
10602 			auto templ = div.addChild("template");
10603 			templ.appendChild(elementFor!(K)(null, name, null /* uda??*/));
10604 			if(displayName !is null)
10605 				div.addChild("span", displayName, "label-text");
10606 			auto btn = div.addChild("button");
10607 			btn.addClass("add-array-button");
10608 			btn.attrs.type = "button";
10609 			btn.innerText = "Add";
10610 			btn.attrs.onclick = q{
10611 				var a = document.importNode(this.parentNode.firstChild.content, true);
10612 				this.parentNode.insertBefore(a, this);
10613 			};
10614 		} else static if(is(T == V[K], K, V)) {
10615 			div.innerText = "assoc array not implemented for automatic form at this time";
10616 		} else {
10617 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
10618 		}
10619 
10620 
10621 		return div;
10622 	}
10623 
10624 	/// creates a form for gathering the function's arguments
10625 	Form createAutomaticFormForFunction(alias method, T)(T dg) {
10626 
10627 		auto form = cast(Form) Element.make("form");
10628 
10629 		form.method = "POST"; // FIXME
10630 
10631 		form.addClass("automatic-form");
10632 
10633 		string formDisplayName = beautify(__traits(identifier, method));
10634 		foreach(attr; __traits(getAttributes, method))
10635 			static if(is(typeof(attr) == DisplayName))
10636 				formDisplayName = attr.name;
10637 		form.addChild("h3", formDisplayName);
10638 
10639 		import std.traits;
10640 
10641 		//Parameters!method params;
10642 		//alias idents = ParameterIdentifierTuple!method;
10643 		//alias defaults = ParameterDefaults!method;
10644 
10645 		static if(is(typeof(method) P == __parameters))
10646 		foreach(idx, _; P) {{
10647 
10648 			alias param = P[idx .. idx + 1];
10649 
10650 			static if(!mustNotBeSetFromWebParams!(param[0], __traits(getAttributes, param))) {
10651 				string displayName = beautify(__traits(identifier, param));
10652 				Element function() element;
10653 				foreach(attr; __traits(getAttributes, param)) {
10654 					static if(is(typeof(attr) == DisplayName))
10655 						displayName = attr.name;
10656 					else static if(is(typeof(attr) : typeof(element))) {
10657 						element = attr;
10658 					}
10659 				}
10660 				auto i = form.appendChild(elementFor!(param)(displayName, __traits(identifier, param), element));
10661 				if(i.querySelector("input[type=file]") !is null)
10662 					form.setAttribute("enctype", "multipart/form-data");
10663 			}
10664 		}}
10665 
10666 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
10667 
10668 		return form;
10669 	}
10670 
10671 	/// creates a form for gathering object members (for the REST object thing right now)
10672 	Form createAutomaticFormForObject(T)(T obj) {
10673 		auto form = cast(Form) Element.make("form");
10674 
10675 		form.addClass("automatic-form");
10676 
10677 		form.addChild("h3", beautify(__traits(identifier, T)));
10678 
10679 		import std.traits;
10680 
10681 		//Parameters!method params;
10682 		//alias idents = ParameterIdentifierTuple!method;
10683 		//alias defaults = ParameterDefaults!method;
10684 
10685 		foreach(idx, memberName; __traits(derivedMembers, T)) {{
10686 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
10687 			string displayName = beautify(memberName);
10688 			Element function() element;
10689 			foreach(attr; __traits(getAttributes,  __traits(getMember, T, memberName)))
10690 				static if(is(typeof(attr) == DisplayName))
10691 					displayName = attr.name;
10692 				else static if(is(typeof(attr) : typeof(element)))
10693 					element = attr;
10694 			form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName, element));
10695 
10696 			form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
10697 		}}}
10698 
10699 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
10700 
10701 		return form;
10702 	}
10703 
10704 	///
10705 	Element formatReturnValueAsHtml(T)(T t) {
10706 		import std.traits;
10707 
10708 		static if(is(T == typeof(null))) {
10709 			return Element.make("span");
10710 		} else static if(is(T : Element)) {
10711 			return t;
10712 		} else static if(is(T == MultipleResponses!Types, Types...)) {
10713 			foreach(index, type; Types) {
10714 				if(t.contains == index)
10715 					return formatReturnValueAsHtml(t.payload[index]);
10716 			}
10717 			assert(0);
10718 		} else static if(is(T == Paginated!E, E)) {
10719 			auto e = Element.make("div").addClass("paginated-result");
10720 			e.appendChild(formatReturnValueAsHtml(t.items));
10721 			if(t.nextPageUrl.length)
10722 				e.appendChild(Element.make("a", "Next Page", t.nextPageUrl));
10723 			return e;
10724 		} else static if(isIntegral!T || isSomeString!T || isFloatingPoint!T) {
10725 			return Element.make("span", to!string(t), "automatic-data-display");
10726 		} else static if(is(T == V[K], K, V)) {
10727 			auto dl = Element.make("dl");
10728 			dl.addClass("automatic-data-display associative-array");
10729 			foreach(k, v; t) {
10730 				dl.addChild("dt", to!string(k));
10731 				dl.addChild("dd", formatReturnValueAsHtml(v));
10732 			}
10733 			return dl;
10734 		} else static if(is(T == struct)) {
10735 			auto dl = Element.make("dl");
10736 			dl.addClass("automatic-data-display struct");
10737 
10738 			foreach(idx, memberName; __traits(allMembers, T))
10739 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
10740 				dl.addChild("dt", beautify(memberName));
10741 				dl.addChild("dd", formatReturnValueAsHtml(__traits(getMember, t, memberName)));
10742 			}
10743 
10744 			return dl;
10745 		} else static if(is(T == bool)) {
10746 			return Element.make("span", t ? "true" : "false", "automatic-data-display");
10747 		} else static if(is(T == E[], E) || is(T == E[N], E, size_t N)) {
10748 			static if(is(E : RestObject!Proxy, Proxy)) {
10749 				// treat RestObject similar to struct
10750 				auto table = cast(Table) Element.make("table");
10751 				table.addClass("automatic-data-display");
10752 				string[] names;
10753 				foreach(idx, memberName; __traits(derivedMembers, E))
10754 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10755 					names ~= beautify(memberName);
10756 				}
10757 				table.appendHeaderRow(names);
10758 
10759 				foreach(l; t) {
10760 					auto tr = table.appendRow();
10761 					foreach(idx, memberName; __traits(derivedMembers, E))
10762 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10763 						static if(memberName == "id") {
10764 							string val = to!string(__traits(getMember, l, memberName));
10765 							tr.addChild("td", Element.make("a", val, E.stringof.toLower ~ "s/" ~ val)); // FIXME
10766 						} else {
10767 							tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
10768 						}
10769 					}
10770 				}
10771 
10772 				return table;
10773 			} else static if(is(E == struct)) {
10774 				// an array of structs is kinda special in that I like
10775 				// having those formatted as tables.
10776 				auto table = cast(Table) Element.make("table");
10777 				table.addClass("automatic-data-display");
10778 				string[] names;
10779 				foreach(idx, memberName; __traits(allMembers, E))
10780 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10781 					names ~= beautify(memberName);
10782 				}
10783 				table.appendHeaderRow(names);
10784 
10785 				foreach(l; t) {
10786 					auto tr = table.appendRow();
10787 					foreach(idx, memberName; __traits(allMembers, E))
10788 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10789 						tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
10790 					}
10791 				}
10792 
10793 				return table;
10794 			} else {
10795 				// otherwise, I will just make a list.
10796 				auto ol = Element.make("ol");
10797 				ol.addClass("automatic-data-display");
10798 				foreach(e; t)
10799 					ol.addChild("li", formatReturnValueAsHtml(e));
10800 				return ol;
10801 			}
10802 		} else static if(is(T : Object)) {
10803 			static if(is(typeof(t.toHtml()))) // FIXME: maybe i will make this an interface
10804 				return Element.make("div", t.toHtml());
10805 			else
10806 				return Element.make("div", t.toString());
10807 		} else static assert(0, "bad return value for cgi call " ~ T.stringof);
10808 
10809 		assert(0);
10810 	}
10811 
10812 }
10813 
10814 /++
10815 	The base class for the [dispatcher] function and object support.
10816 +/
10817 class WebObject {
10818 	//protected Cgi cgi;
10819 
10820 	protected void initialize(Cgi cgi) {
10821 		//this.cgi = cgi;
10822 	}
10823 }
10824 
10825 /++
10826 	Can return one of the given types, decided at runtime. The syntax
10827 	is to declare all the possible types in the return value, then you
10828 	can `return typeof(return)(...value...)` to construct it.
10829 
10830 	It has an auto-generated constructor for each value it can hold.
10831 
10832 	---
10833 	MultipleResponses!(Redirection, string) getData(int how) {
10834 		if(how & 1)
10835 			return typeof(return)(Redirection("http://dpldocs.info/"));
10836 		else
10837 			return typeof(return)("hi there!");
10838 	}
10839 	---
10840 
10841 	If you have lots of returns, you could, inside the function, `alias r = typeof(return);` to shorten it a little.
10842 +/
10843 struct MultipleResponses(T...) {
10844 	private size_t contains;
10845 	private union {
10846 		private T payload;
10847 	}
10848 
10849 	static foreach(index, type; T)
10850 	public this(type t) {
10851 		contains = index;
10852 		payload[index] = t;
10853 	}
10854 
10855 	/++
10856 		This is primarily for testing. It is your way of getting to the response.
10857 
10858 		Let's say you wanted to test that one holding a Redirection and a string actually
10859 		holds a string, by name of "test":
10860 
10861 		---
10862 			auto valueToTest = your_test_function();
10863 
10864 			valueToTest.visit(
10865 				(Redirection r) { assert(0); }, // got a redirection instead of a string, fail the test
10866 				(string s) { assert(s == "test"); } // right value, go ahead and test it.
10867 			);
10868 		---
10869 
10870 		History:
10871 			Was horribly broken until June 16, 2022. Ironically, I wrote it for tests but never actually tested it.
10872 			It tried to use alias lambdas before, but runtime delegates work much better so I changed it.
10873 	+/
10874 	void visit(Handlers...)(Handlers handlers) {
10875 		template findHandler(type, int count, HandlersToCheck...) {
10876 			static if(HandlersToCheck.length == 0)
10877 				enum findHandler = -1;
10878 			else {
10879 				static if(is(typeof(HandlersToCheck[0].init(type.init))))
10880 					enum findHandler = count;
10881 				else
10882 					enum findHandler = findHandler!(type, count + 1, HandlersToCheck[1 .. $]);
10883 			}
10884 		}
10885 		foreach(index, type; T) {
10886 			enum handlerIndex = findHandler!(type, 0, Handlers);
10887 			static if(handlerIndex == -1)
10888 				static assert(0, "Type " ~ type.stringof ~ " was not handled by visitor");
10889 			else {
10890 				if(index == this.contains)
10891 					handlers[handlerIndex](this.payload[index]);
10892 			}
10893 		}
10894 	}
10895 
10896 	/+
10897 	auto toArsdJsvar()() {
10898 		import arsd.jsvar;
10899 		return var(null);
10900 	}
10901 	+/
10902 }
10903 
10904 // FIXME: implement this somewhere maybe
10905 struct RawResponse {
10906 	int code;
10907 	string[] headers;
10908 	const(ubyte)[] responseBody;
10909 }
10910 
10911 /++
10912 	You can return this from [WebObject] subclasses for redirections.
10913 
10914 	(though note the static types means that class must ALWAYS redirect if
10915 	you return this directly. You might want to return [MultipleResponses] if it
10916 	can be conditional)
10917 +/
10918 struct Redirection {
10919 	string to; /// The URL to redirect to.
10920 	int code = 303; /// The HTTP code to return.
10921 }
10922 
10923 /++
10924 	Serves a class' methods, as a kind of low-state RPC over the web. To be used with [dispatcher].
10925 
10926 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar] unless you have overriden
10927 	the presenter in the dispatcher.
10928 
10929 	FIXME: explain this better
10930 
10931 	You can overload functions to a limited extent: you can provide a zero-arg and non-zero-arg function,
10932 	and non-zero-arg functions can filter via UDAs for various http methods. Do not attempt other overloads,
10933 	the runtime result of that is undefined.
10934 
10935 	A method is assumed to allow any http method unless it lists some in UDAs, in which case it is limited to only those.
10936 	(this might change, like maybe i will use pure as an indicator GET is ok. idk.)
10937 
10938 	$(WARNING
10939 		---
10940 		// legal in D, undefined runtime behavior with cgi.d, it may call either method
10941 		// even if you put different URL udas on it, the current code ignores them.
10942 		void foo(int a) {}
10943 		void foo(string a) {}
10944 		---
10945 	)
10946 
10947 	See_Also: [serveRestObject], [serveStaticFile]
10948 +/
10949 auto serveApi(T)(string urlPrefix) {
10950 	assert(urlPrefix[$ - 1] == '/');
10951 	return serveApiInternal!T(urlPrefix);
10952 }
10953 
10954 private string nextPieceFromSlash(ref string remainingUrl) {
10955 	if(remainingUrl.length == 0)
10956 		return remainingUrl;
10957 	int slash = 0;
10958 	while(slash < remainingUrl.length && remainingUrl[slash] != '/') // && remainingUrl[slash] != '.')
10959 		slash++;
10960 
10961 	// I am specifically passing `null` to differentiate it vs empty string
10962 	// so in your ctor, `items` means new T(null) and `items/` means new T("")
10963 	auto ident = remainingUrl.length == 0 ? null : remainingUrl[0 .. slash];
10964 	// so if it is the last item, the dot can be used to load an alternative view
10965 	// otherwise tho the dot is considered part of the identifier
10966 	// FIXME
10967 
10968 	// again notice "" vs null here!
10969 	if(slash == remainingUrl.length)
10970 		remainingUrl = null;
10971 	else
10972 		remainingUrl = remainingUrl[slash + 1 .. $];
10973 
10974 	return ident;
10975 }
10976 
10977 /++
10978 	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.
10979 +/
10980 enum AddTrailingSlash;
10981 /// ditto
10982 enum RemoveTrailingSlash;
10983 
10984 private auto serveApiInternal(T)(string urlPrefix) {
10985 
10986 	import arsd.dom;
10987 	import arsd.jsvar;
10988 
10989 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
10990 		string remainingUrl = cgi.pathInfo[urlPrefix.length .. $];
10991 
10992 		try {
10993 			// see duplicated code below by searching subresource_ctor
10994 			// also see mustNotBeSetFromWebParams
10995 
10996 			static if(is(typeof(T.__ctor) P == __parameters)) {
10997 				P params;
10998 
10999 				foreach(pidx, param; P) {
11000 					static if(is(param : Cgi)) {
11001 						static assert(!is(param == immutable));
11002 						cast() params[pidx] = cgi;
11003 					} else static if(is(param == Session!D, D)) {
11004 						static assert(!is(param == immutable));
11005 						cast() params[pidx] = cgi.getSessionObject!D();
11006 
11007 					} else {
11008 						static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
11009 							foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
11010 								static if(is(uda == ifCalledFromWeb!func, alias func)) {
11011 									static if(is(typeof(func(cgi))))
11012 										params[pidx] = func(cgi);
11013 									else
11014 										params[pidx] = func();
11015 								}
11016 							}
11017 						} else {
11018 
11019 							static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
11020 								params[pidx] = param.getAutomaticallyForCgi(cgi);
11021 							} else static if(is(param == string)) {
11022 								auto ident = nextPieceFromSlash(remainingUrl);
11023 								params[pidx] = ident;
11024 							} else static assert(0, "illegal type for subresource " ~ param.stringof);
11025 						}
11026 					}
11027 				}
11028 
11029 				auto obj = new T(params);
11030 			} else {
11031 				auto obj = new T();
11032 			}
11033 
11034 			return internalHandlerWithObject(obj, remainingUrl, cgi, presenter);
11035 		} catch(Throwable t) {
11036 			switch(cgi.request("format", "html")) {
11037 				case "html":
11038 					static void dummy() {}
11039 					presenter.presentExceptionAsHtml(cgi, t, null);
11040 				return true;
11041 				case "json":
11042 					var envelope = var.emptyObject;
11043 					envelope.success = false;
11044 					envelope.result = null;
11045 					envelope.error = t.toString();
11046 					cgi.setResponseContentType("application/json");
11047 					cgi.write(envelope.toJson(), true);
11048 				return true;
11049 				default:
11050 					throw t;
11051 				// return true;
11052 			}
11053 			// return true;
11054 		}
11055 
11056 		assert(0);
11057 	}
11058 
11059 	static bool internalHandlerWithObject(T, Presenter)(T obj, string remainingUrl, Cgi cgi, Presenter presenter) {
11060 
11061 		obj.initialize(cgi);
11062 
11063 		/+
11064 			Overload rules:
11065 				Any unique combination of HTTP verb and url path can be dispatched to function overloads
11066 				statically.
11067 
11068 				Moreover, some args vs no args can be overloaded dynamically.
11069 		+/
11070 
11071 		auto methodNameFromUrl = nextPieceFromSlash(remainingUrl);
11072 		/+
11073 		auto orig = remainingUrl;
11074 		assert(0,
11075 			(orig is null ? "__null" : orig)
11076 			~ " .. " ~
11077 			(methodNameFromUrl is null ? "__null" : methodNameFromUrl));
11078 		+/
11079 
11080 		if(methodNameFromUrl is null)
11081 			methodNameFromUrl = "__null";
11082 
11083 		string hack = to!string(cgi.requestMethod) ~ " " ~ methodNameFromUrl;
11084 
11085 		if(remainingUrl.length)
11086 			hack ~= "/";
11087 
11088 		switch(hack) {
11089 			foreach(methodName; __traits(derivedMembers, T))
11090 			static if(methodName != "__ctor")
11091 			foreach(idx, overload; __traits(getOverloads, T, methodName)) {
11092 			static if(is(typeof(overload) P == __parameters))
11093 			static if(is(typeof(overload) R == return))
11094 			static if(__traits(getProtection, overload) == "public" || __traits(getProtection, overload) == "export")
11095 			{
11096 			static foreach(urlNameForMethod; urlNamesForMethod!(overload, urlify(methodName)))
11097 			case urlNameForMethod:
11098 
11099 				static if(is(R : WebObject)) {
11100 					// if it returns a WebObject, it is considered a subresource. That means the url is dispatched like the ctor above.
11101 
11102 					// the only argument it is allowed to take, outside of cgi, session, and set up thingies, is a single string
11103 
11104 					// subresource_ctor
11105 					// also see mustNotBeSetFromWebParams
11106 
11107 					P params;
11108 
11109 					string ident;
11110 
11111 					foreach(pidx, param; P) {
11112 						static if(is(param : Cgi)) {
11113 							static assert(!is(param == immutable));
11114 							cast() params[pidx] = cgi;
11115 						} else static if(is(param == typeof(presenter))) {
11116 							cast() param[pidx] = presenter;
11117 						} else static if(is(param == Session!D, D)) {
11118 							static assert(!is(param == immutable));
11119 							cast() params[pidx] = cgi.getSessionObject!D();
11120 						} else {
11121 							static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
11122 								foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
11123 									static if(is(uda == ifCalledFromWeb!func, alias func)) {
11124 										static if(is(typeof(func(cgi))))
11125 											params[pidx] = func(cgi);
11126 										else
11127 											params[pidx] = func();
11128 									}
11129 								}
11130 							} else {
11131 
11132 								static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
11133 									params[pidx] = param.getAutomaticallyForCgi(cgi);
11134 								} else static if(is(param == string)) {
11135 									ident = nextPieceFromSlash(remainingUrl);
11136 									if(ident is null) {
11137 										// trailing slash mandated on subresources
11138 										cgi.setResponseLocation(cgi.pathInfo ~ "/");
11139 										return true;
11140 									} else {
11141 										params[pidx] = ident;
11142 									}
11143 								} else static assert(0, "illegal type for subresource " ~ param.stringof);
11144 							}
11145 						}
11146 					}
11147 
11148 					auto nobj = (__traits(getOverloads, obj, methodName)[idx])(ident);
11149 					return internalHandlerWithObject!(typeof(nobj), Presenter)(nobj, remainingUrl, cgi, presenter);
11150 				} else {
11151 					// 404 it if any url left - not a subresource means we don't get to play with that!
11152 					if(remainingUrl.length)
11153 						return false;
11154 
11155 					bool automaticForm;
11156 
11157 					foreach(attr; __traits(getAttributes, overload))
11158 						static if(is(attr == AddTrailingSlash)) {
11159 							if(remainingUrl is null) {
11160 								cgi.setResponseLocation(cgi.pathInfo ~ "/");
11161 								return true;
11162 							}
11163 						} else static if(is(attr == RemoveTrailingSlash)) {
11164 							if(remainingUrl !is null) {
11165 								cgi.setResponseLocation(cgi.pathInfo[0 .. lastIndexOf(cgi.pathInfo, "/")]);
11166 								return true;
11167 							}
11168 
11169 						} else static if(__traits(isSame, AutomaticForm, attr)) {
11170 							automaticForm = true;
11171 						}
11172 
11173 				/+
11174 				int zeroArgOverload = -1;
11175 				int overloadCount = cast(int) __traits(getOverloads, T, methodName).length;
11176 				bool calledWithZeroArgs = true;
11177 				foreach(k, v; cgi.get)
11178 					if(k != "format") {
11179 						calledWithZeroArgs = false;
11180 						break;
11181 					}
11182 				foreach(k, v; cgi.post)
11183 					if(k != "format") {
11184 						calledWithZeroArgs = false;
11185 						break;
11186 					}
11187 
11188 				// first, we need to go through and see if there is an empty one, since that
11189 				// changes inside. But otherwise, all the stuff I care about can be done via
11190 				// simple looping (other improper overloads might be flagged for runtime semantic check)
11191 				//
11192 				// an argument of type Cgi is ignored for these purposes
11193 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
11194 					static if(is(typeof(overload) P == __parameters))
11195 						static if(P.length == 0)
11196 							zeroArgOverload = cast(int) idx;
11197 						else static if(P.length == 1 && is(P[0] : Cgi))
11198 							zeroArgOverload = cast(int) idx;
11199 				}}
11200 				// FIXME: static assert if there are multiple non-zero-arg overloads usable with a single http method.
11201 				bool overloadHasBeenCalled = false;
11202 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
11203 					bool callFunction = true;
11204 					// there is a zero arg overload and this is NOT it, and we have zero args - don't call this
11205 					if(overloadCount > 1 && zeroArgOverload != -1 && idx != zeroArgOverload && calledWithZeroArgs)
11206 						callFunction = false;
11207 					// if this is the zero-arg overload, obviously it cannot be called if we got any args.
11208 					if(overloadCount > 1 && idx == zeroArgOverload && !calledWithZeroArgs)
11209 						callFunction = false;
11210 
11211 					// FIXME: so if you just add ?foo it will give the error below even when. this might not be a great idea.
11212 
11213 					bool hadAnyMethodRestrictions = false;
11214 					bool foundAcceptableMethod = false;
11215 					foreach(attr; __traits(getAttributes, overload)) {
11216 						static if(is(typeof(attr) == Cgi.RequestMethod)) {
11217 							hadAnyMethodRestrictions = true;
11218 							if(attr == cgi.requestMethod)
11219 								foundAcceptableMethod = true;
11220 						}
11221 					}
11222 
11223 					if(hadAnyMethodRestrictions && !foundAcceptableMethod)
11224 						callFunction = false;
11225 
11226 					/+
11227 						The overloads we really want to allow are the sane ones
11228 						from the web perspective. Which is likely on HTTP verbs,
11229 						for the most part, but might also be potentially based on
11230 						some args vs zero args, or on argument names. Can't really
11231 						do argument types very reliable through the web though; those
11232 						should probably be different URLs.
11233 
11234 						Even names I feel is better done inside the function, so I'm not
11235 						going to support that here. But the HTTP verbs and zero vs some
11236 						args makes sense - it lets you define custom forms pretty easily.
11237 
11238 						Moreover, I'm of the opinion that empty overload really only makes
11239 						sense on GET for this case. On a POST, it is just a missing argument
11240 						exception and that should be handled by the presenter. But meh, I'll
11241 						let the user define that, D only allows one empty arg thing anyway
11242 						so the method UDAs are irrelevant.
11243 					+/
11244 					if(callFunction)
11245 				+/
11246 
11247 					auto format = cgi.request("format", defaultFormat!overload());
11248 					auto wantsFormFormat = format.startsWith("form-");
11249 
11250 					if(wantsFormFormat || (automaticForm && cgi.requestMethod == Cgi.RequestMethod.GET)) {
11251 						// Should I still show the form on a json thing? idk...
11252 						auto ret = presenter.createAutomaticFormForFunction!((__traits(getOverloads, obj, methodName)[idx]))(&(__traits(getOverloads, obj, methodName)[idx]));
11253 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), wantsFormFormat ? format["form_".length .. $] : "html");
11254 						return true;
11255 					}
11256 
11257 					try {
11258 						// a void return (or typeof(null) lol) means you, the user, is doing it yourself. Gives full control.
11259 						auto ret = callFromCgi!(__traits(getOverloads, obj, methodName)[idx])(&(__traits(getOverloads, obj, methodName)[idx]), cgi);
11260 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), format);
11261 					} catch(Throwable t) {
11262 						// presenter.presentExceptionAsHtml!(__traits(getOverloads, obj, methodName)[idx])(cgi, t, &(__traits(getOverloads, obj, methodName)[idx]));
11263 						presenter.presentExceptionalReturn(cgi, t, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), format);
11264 					}
11265 					return true;
11266 				//}}
11267 
11268 				//cgi.header("Accept: POST"); // FIXME list the real thing
11269 				//cgi.setResponseStatus("405 Method Not Allowed"); // again, not exactly, but sort of. no overload matched our args, almost certainly due to http verb filtering.
11270 				//return true;
11271 				}
11272 			}
11273 			}
11274 			case "GET script.js":
11275 				cgi.setResponseContentType("text/javascript");
11276 				cgi.gzipResponse = true;
11277 				cgi.write(presenter.script(), true);
11278 				return true;
11279 			case "GET style.css":
11280 				cgi.setResponseContentType("text/css");
11281 				cgi.gzipResponse = true;
11282 				cgi.write(presenter.style(), true);
11283 				return true;
11284 			default:
11285 				return false;
11286 		}
11287 
11288 		assert(0);
11289 	}
11290 	return DispatcherDefinition!internalHandler(urlPrefix, false);
11291 }
11292 
11293 string defaultFormat(alias method)() {
11294 	bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true;
11295 	foreach(attr; __traits(getAttributes, method)) {
11296 		static if(is(typeof(attr) == DefaultFormat)) {
11297 			if(nonConstConditionForWorkingAroundASpuriousDmdWarning)
11298 				return attr.value;
11299 		}
11300 	}
11301 	return "html";
11302 }
11303 
11304 struct Paginated(T) {
11305 	T[] items;
11306 	string nextPageUrl;
11307 }
11308 
11309 template urlNamesForMethod(alias method, string default_) {
11310 	string[] helper() {
11311 		auto verb = Cgi.RequestMethod.GET;
11312 		bool foundVerb = false;
11313 		bool foundNoun = false;
11314 
11315 		string def = default_;
11316 
11317 		bool hasAutomaticForm = false;
11318 
11319 		foreach(attr; __traits(getAttributes, method)) {
11320 			static if(is(typeof(attr) == Cgi.RequestMethod)) {
11321 				verb = attr;
11322 				if(foundVerb)
11323 					assert(0, "Multiple http verbs on one function is not currently supported");
11324 				foundVerb = true;
11325 			}
11326 			static if(is(typeof(attr) == UrlName)) {
11327 				if(foundNoun)
11328 					assert(0, "Multiple url names on one function is not currently supported");
11329 				foundNoun = true;
11330 				def = attr.name;
11331 			}
11332 			static if(__traits(isSame, attr, AutomaticForm)) {
11333 				hasAutomaticForm = true;
11334 			}
11335 		}
11336 
11337 		if(def is null)
11338 			def = "__null";
11339 
11340 		string[] ret;
11341 
11342 		static if(is(typeof(method) R == return)) {
11343 			static if(is(R : WebObject)) {
11344 				def ~= "/";
11345 				foreach(v; __traits(allMembers, Cgi.RequestMethod))
11346 					ret ~= v ~ " " ~ def;
11347 			} else {
11348 				if(hasAutomaticForm) {
11349 					ret ~= "GET " ~ def;
11350 					ret ~= "POST " ~ def;
11351 				} else {
11352 					ret ~= to!string(verb) ~ " " ~ def;
11353 				}
11354 			}
11355 		} else static assert(0);
11356 
11357 		return ret;
11358 	}
11359 	enum urlNamesForMethod = helper();
11360 }
11361 
11362 
11363 	enum AccessCheck {
11364 		allowed,
11365 		denied,
11366 		nonExistant,
11367 	}
11368 
11369 	enum Operation {
11370 		show,
11371 		create,
11372 		replace,
11373 		remove,
11374 		update
11375 	}
11376 
11377 	enum UpdateResult {
11378 		accessDenied,
11379 		noSuchResource,
11380 		success,
11381 		failure,
11382 		unnecessary
11383 	}
11384 
11385 	enum ValidationResult {
11386 		valid,
11387 		invalid
11388 	}
11389 
11390 
11391 /++
11392 	The base of all REST objects, to be used with [serveRestObject] and [serveRestCollectionOf].
11393 
11394 	WARNING: this is not stable.
11395 +/
11396 class RestObject(CRTP) : WebObject {
11397 
11398 	import arsd.dom;
11399 	import arsd.jsvar;
11400 
11401 	/// Prepare the object to be shown.
11402 	void show() {}
11403 	/// ditto
11404 	void show(string urlId) {
11405 		load(urlId);
11406 		show();
11407 	}
11408 
11409 	/// Override this to provide access control to this object.
11410 	AccessCheck accessCheck(string urlId, Operation operation) {
11411 		return AccessCheck.allowed;
11412 	}
11413 
11414 	ValidationResult validate() {
11415 		// FIXME
11416 		return ValidationResult.valid;
11417 	}
11418 
11419 	string getUrlSlug() {
11420 		import std.conv;
11421 		static if(is(typeof(CRTP.id)))
11422 			return to!string((cast(CRTP) this).id);
11423 		else
11424 			return null;
11425 	}
11426 
11427 	// The functions with more arguments are the low-level ones,
11428 	// they forward to the ones with fewer arguments by default.
11429 
11430 	// POST on a parent collection - this is called from a collection class after the members are updated
11431 	/++
11432 		Given a populated object, this creates a new entry. Returns the url identifier
11433 		of the new object.
11434 	+/
11435 	string create(scope void delegate() applyChanges) {
11436 		applyChanges();
11437 		save();
11438 		return getUrlSlug();
11439 	}
11440 
11441 	void replace() {
11442 		save();
11443 	}
11444 	void replace(string urlId, scope void delegate() applyChanges) {
11445 		load(urlId);
11446 		applyChanges();
11447 		replace();
11448 	}
11449 
11450 	void update(string[] fieldList) {
11451 		save();
11452 	}
11453 	void update(string urlId, scope void delegate() applyChanges, string[] fieldList) {
11454 		load(urlId);
11455 		applyChanges();
11456 		update(fieldList);
11457 	}
11458 
11459 	void remove() {}
11460 
11461 	void remove(string urlId) {
11462 		load(urlId);
11463 		remove();
11464 	}
11465 
11466 	abstract void load(string urlId);
11467 	abstract void save();
11468 
11469 	Element toHtml(Presenter)(Presenter presenter) {
11470 		import arsd.dom;
11471 		import std.conv;
11472 		auto obj = cast(CRTP) this;
11473 		auto div = Element.make("div");
11474 		div.addClass("Dclass_" ~ CRTP.stringof);
11475 		div.dataset.url = getUrlSlug();
11476 		bool first = true;
11477 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
11478 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11479 			if(!first) div.addChild("br"); else first = false;
11480 			div.appendChild(presenter.formatReturnValueAsHtml(__traits(getMember, obj, memberName)));
11481 		}
11482 		return div;
11483 	}
11484 
11485 	var toJson() {
11486 		import arsd.jsvar;
11487 		var v = var.emptyObject();
11488 		auto obj = cast(CRTP) this;
11489 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
11490 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11491 			v[memberName] = __traits(getMember, obj, memberName);
11492 		}
11493 		return v;
11494 	}
11495 
11496 	/+
11497 	auto structOf(this This) {
11498 
11499 	}
11500 	+/
11501 }
11502 
11503 // FIXME XSRF token, prolly can just put in a cookie and then it needs to be copied to header or form hidden value
11504 // https://use-the-index-luke.com/sql/partial-results/fetch-next-page
11505 
11506 /++
11507 	Base class for REST collections.
11508 +/
11509 class CollectionOf(Obj) : RestObject!(CollectionOf) {
11510 	/// You might subclass this and use the cgi object's query params
11511 	/// to implement a search filter, for example.
11512 	///
11513 	/// FIXME: design a way to auto-generate that form
11514 	/// (other than using the WebObject thing above lol
11515 	// it'll prolly just be some searchParams UDA or maybe an enum.
11516 	//
11517 	// pagination too perhaps.
11518 	//
11519 	// and sorting too
11520 	IndexResult index() { return IndexResult.init; }
11521 
11522 	string[] sortableFields() { return null; }
11523 	string[] searchableFields() { return null; }
11524 
11525 	struct IndexResult {
11526 		Obj[] results;
11527 
11528 		string[] sortableFields;
11529 
11530 		string previousPageIdentifier;
11531 		string nextPageIdentifier;
11532 		string firstPageIdentifier;
11533 		string lastPageIdentifier;
11534 
11535 		int numberOfPages;
11536 	}
11537 
11538 	override string create(scope void delegate() applyChanges) { assert(0); }
11539 	override void load(string urlId) { assert(0); }
11540 	override void save() { assert(0); }
11541 	override void show() {
11542 		index();
11543 	}
11544 	override void show(string urlId) {
11545 		show();
11546 	}
11547 
11548 	/// Proxy POST requests (create calls) to the child collection
11549 	alias PostProxy = Obj;
11550 }
11551 
11552 /++
11553 	Serves a REST object, similar to a Ruby on Rails resource.
11554 
11555 	You put data members in your class. cgi.d will automatically make something out of those.
11556 
11557 	It will call your constructor with the ID from the URL. This may be null.
11558 	It will then populate the data members from the request.
11559 	It will then call a method, if present, telling what happened. You don't need to write these!
11560 	It finally returns a reply.
11561 
11562 	Your methods are passed a list of fields it actually set.
11563 
11564 	The URL mapping - despite my general skepticism of the wisdom - matches up with what most REST
11565 	APIs I have used seem to follow. (I REALLY want to put trailing slashes on it though. Works better
11566 	with relative linking. But meh.)
11567 
11568 	GET /items -> index. all values not set.
11569 	GET /items/id -> get. only ID will be set, other params ignored.
11570 	POST /items -> create. values set as given
11571 	PUT /items/id -> replace. values set as given
11572 		or POST /items/id with cgi.post["_method"] (thus urlencoded or multipart content-type) set to "PUT" to work around browser/html limitation
11573 		a GET with cgi.get["_method"] (in the url) set to "PUT" will render a form.
11574 	PATCH /items/id -> update. values set as given, list of changed fields passed
11575 		or POST /items/id with cgi.post["_method"] == "PATCH"
11576 	DELETE /items/id -> destroy. only ID guaranteed to be set
11577 		or POST /items/id with cgi.post["_method"] == "DELETE"
11578 
11579 	Following the stupid convention, there will never be a trailing slash here, and if it is there, it will
11580 	redirect you away from it.
11581 
11582 	API clients should set the `Accept` HTTP header to application/json or the cgi.get["_format"] = "json" var.
11583 
11584 	I will also let you change the default, if you must.
11585 
11586 	// One add-on is validation. You can issue a HTTP GET to a resource with _method = VALIDATE to check potential changes.
11587 
11588 	You can define sub-resources on your object inside the object. These sub-resources are also REST objects
11589 	that follow the same thing. They may be individual resources or collections themselves.
11590 
11591 	Your class is expected to have at least the following methods:
11592 
11593 	FIXME: i kinda wanna add a routes object to the initialize call
11594 
11595 	create
11596 		Create returns the new address on success, some code on failure.
11597 	show
11598 	index
11599 	update
11600 	remove
11601 
11602 	You will want to be able to customize the HTTP, HTML, and JSON returns but generally shouldn't have to - the defaults
11603 	should usually work. The returned JSON will include a field "href" on all returned objects along with "id". Or omething like that.
11604 
11605 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar].
11606 
11607 	NOT IMPLEMENTED
11608 
11609 
11610 	Really, a collection is a resource with a bunch of subresources.
11611 
11612 		GET /items
11613 			index because it is GET on the top resource
11614 
11615 		GET /items/foo
11616 			item but different than items?
11617 
11618 		class Items {
11619 
11620 		}
11621 
11622 	... but meh, a collection can be automated. not worth making it
11623 	a separate thing, let's look at a real example. Users has many
11624 	items and a virtual one, /users/current.
11625 
11626 	the individual users have properties and two sub-resources:
11627 	session, which is just one, and comments, a collection.
11628 
11629 	class User : RestObject!() { // no parent
11630 		int id;
11631 		string name;
11632 
11633 		// the default implementations of the urlId ones is to call load(that_id) then call the arg-less one.
11634 		// but you can override them to do it differently.
11635 
11636 		// any member which is of type RestObject can be linked automatically via href btw.
11637 
11638 		void show() {}
11639 		void show(string urlId) {} // automated! GET of this specific thing
11640 		void create() {} // POST on a parent collection - this is called from a collection class after the members are updated
11641 		void replace(string urlId) {} // this is the PUT; really, it just updates all fields.
11642 		void update(string urlId, string[] fieldList) {} // PATCH, it updates some fields.
11643 		void remove(string urlId) {} // DELETE
11644 
11645 		void load(string urlId) {} // the default implementation of show() populates the id, then
11646 
11647 		this() {}
11648 
11649 		mixin Subresource!Session;
11650 		mixin Subresource!Comment;
11651 	}
11652 
11653 	class Session : RestObject!() {
11654 		// the parent object may not be fully constructed/loaded
11655 		this(User parent) {}
11656 
11657 	}
11658 
11659 	class Comment : CollectionOf!Comment {
11660 		this(User parent) {}
11661 	}
11662 
11663 	class Users : CollectionOf!User {
11664 		// but you don't strictly need ANYTHING on a collection; it will just... collect. Implement the subobjects.
11665 		void index() {} // GET on this specific thing; just like show really, just different name for the different semantics.
11666 		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
11667 	}
11668 
11669 +/
11670 auto serveRestObject(T)(string urlPrefix) {
11671 	assert(urlPrefix[0] == '/');
11672 	assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
11673 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
11674 		string url = cgi.pathInfo[urlPrefix.length .. $];
11675 
11676 		if(url.length && url[$ - 1] == '/') {
11677 			// remove the final slash...
11678 			cgi.setResponseLocation(cgi.scriptName ~ cgi.pathInfo[0 .. $ - 1]);
11679 			return true;
11680 		}
11681 
11682 		return restObjectServeHandler!T(cgi, presenter, url);
11683 	}
11684 	return DispatcherDefinition!internalHandler(urlPrefix, false);
11685 }
11686 
11687 /+
11688 /// Convenience method for serving a collection. It will be named the same
11689 /// as type T, just with an s at the end. If you need any further, just
11690 /// write the class yourself.
11691 auto serveRestCollectionOf(T)(string urlPrefix) {
11692 	assert(urlPrefix[0] == '/');
11693 	mixin(`static class `~T.stringof~`s : CollectionOf!(T) {}`);
11694 	return serveRestObject!(mixin(T.stringof ~ "s"))(urlPrefix);
11695 }
11696 +/
11697 
11698 bool restObjectServeHandler(T, Presenter)(Cgi cgi, Presenter presenter, string url) {
11699 	string urlId = null;
11700 	if(url.length && url[0] == '/') {
11701 		// asking for a subobject
11702 		urlId = url[1 .. $];
11703 		foreach(idx, ch; urlId) {
11704 			if(ch == '/') {
11705 				urlId = urlId[0 .. idx];
11706 				break;
11707 			}
11708 		}
11709 	}
11710 
11711 	// FIXME handle other subresources
11712 
11713 	static if(is(T : CollectionOf!(C), C)) {
11714 		if(urlId !is null) {
11715 			return restObjectServeHandler!(C, Presenter)(cgi, presenter, url); // FIXME?  urlId);
11716 		}
11717 	}
11718 
11719 	// FIXME: support precondition failed, if-modified-since, expectation failed, etc.
11720 
11721 	auto obj = new T();
11722 	obj.initialize(cgi);
11723 	// FIXME: populate reflection info delegates
11724 
11725 
11726 	// FIXME: I am not happy with this.
11727 	switch(urlId) {
11728 		case "script.js":
11729 			cgi.setResponseContentType("text/javascript");
11730 			cgi.gzipResponse = true;
11731 			cgi.write(presenter.script(), true);
11732 			return true;
11733 		case "style.css":
11734 			cgi.setResponseContentType("text/css");
11735 			cgi.gzipResponse = true;
11736 			cgi.write(presenter.style(), true);
11737 			return true;
11738 		default:
11739 			// intentionally blank
11740 	}
11741 
11742 
11743 
11744 
11745 	static void applyChangesTemplate(Obj)(Cgi cgi, Obj obj) {
11746 		foreach(idx, memberName; __traits(derivedMembers, Obj))
11747 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11748 			__traits(getMember, obj, memberName) = cgi.request(memberName, __traits(getMember, obj, memberName));
11749 		}
11750 	}
11751 	void applyChanges() {
11752 		applyChangesTemplate(cgi, obj);
11753 	}
11754 
11755 	string[] modifiedList;
11756 
11757 	void writeObject(bool addFormLinks) {
11758 		if(cgi.request("format") == "json") {
11759 			cgi.setResponseContentType("application/json");
11760 			cgi.write(obj.toJson().toString, true);
11761 		} else {
11762 			auto container = presenter.htmlContainer();
11763 			if(addFormLinks) {
11764 				static if(is(T : CollectionOf!(C), C))
11765 				container.appendHtml(`
11766 					<form>
11767 						<button type="submit" name="_method" value="POST">Create New</button>
11768 					</form>
11769 				`);
11770 				else
11771 				container.appendHtml(`
11772 					<a href="..">Back</a>
11773 					<form>
11774 						<button type="submit" name="_method" value="PATCH">Edit</button>
11775 						<button type="submit" name="_method" value="DELETE">Delete</button>
11776 					</form>
11777 				`);
11778 			}
11779 			container.appendChild(obj.toHtml(presenter));
11780 			cgi.write(container.parentDocument.toString, true);
11781 		}
11782 	}
11783 
11784 	// FIXME: I think I need a set type in here....
11785 	// it will be nice to pass sets of members.
11786 
11787 	try
11788 	switch(cgi.requestMethod) {
11789 		case Cgi.RequestMethod.GET:
11790 			// I could prolly use template this parameters in the implementation above for some reflection stuff.
11791 			// sure, it doesn't automatically work in subclasses... but I instantiate here anyway...
11792 
11793 			// automatic forms here for usable basic auto site from browser.
11794 			// even if the format is json, it could actually send out the links and formats, but really there i'ma be meh.
11795 			switch(cgi.request("_method", "GET")) {
11796 				case "GET":
11797 					static if(is(T : CollectionOf!(C), C)) {
11798 						auto results = obj.index();
11799 						if(cgi.request("format", "html") == "html") {
11800 							auto container = presenter.htmlContainer();
11801 							auto html = presenter.formatReturnValueAsHtml(results.results);
11802 							container.appendHtml(`
11803 								<form>
11804 									<button type="submit" name="_method" value="POST">Create New</button>
11805 								</form>
11806 							`);
11807 
11808 							container.appendChild(html);
11809 							cgi.write(container.parentDocument.toString, true);
11810 						} else {
11811 							cgi.setResponseContentType("application/json");
11812 							import arsd.jsvar;
11813 							var json = var.emptyArray;
11814 							foreach(r; results.results) {
11815 								var o = var.emptyObject;
11816 								foreach(idx, memberName; __traits(derivedMembers, typeof(r)))
11817 								static if(__traits(compiles, __traits(getMember, r, memberName).offsetof)) {
11818 									o[memberName] = __traits(getMember, r, memberName);
11819 								}
11820 
11821 								json ~= o;
11822 							}
11823 							cgi.write(json.toJson(), true);
11824 						}
11825 					} else {
11826 						obj.show(urlId);
11827 						writeObject(true);
11828 					}
11829 				break;
11830 				case "PATCH":
11831 					obj.load(urlId);
11832 				goto case;
11833 				case "PUT":
11834 				case "POST":
11835 					// an editing form for the object
11836 					auto container = presenter.htmlContainer();
11837 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
11838 						auto form = (cgi.request("_method") == "POST") ? presenter.createAutomaticFormForObject(new obj.PostProxy()) : presenter.createAutomaticFormForObject(obj);
11839 					} else {
11840 						auto form = presenter.createAutomaticFormForObject(obj);
11841 					}
11842 					form.attrs.method = "POST";
11843 					form.setValue("_method", cgi.request("_method", "GET"));
11844 					container.appendChild(form);
11845 					cgi.write(container.parentDocument.toString(), true);
11846 				break;
11847 				case "DELETE":
11848 					// FIXME: a delete form for the object (can be phrased "are you sure?")
11849 					auto container = presenter.htmlContainer();
11850 					container.appendHtml(`
11851 						<form method="POST">
11852 							Are you sure you want to delete this item?
11853 							<input type="hidden" name="_method" value="DELETE" />
11854 							<input type="submit" value="Yes, Delete It" />
11855 						</form>
11856 
11857 					`);
11858 					cgi.write(container.parentDocument.toString(), true);
11859 				break;
11860 				default:
11861 					cgi.write("bad method\n", true);
11862 			}
11863 		break;
11864 		case Cgi.RequestMethod.POST:
11865 			// this is to allow compatibility with HTML forms
11866 			switch(cgi.request("_method", "POST")) {
11867 				case "PUT":
11868 					goto PUT;
11869 				case "PATCH":
11870 					goto PATCH;
11871 				case "DELETE":
11872 					goto DELETE;
11873 				case "POST":
11874 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
11875 						auto p = new obj.PostProxy();
11876 						void specialApplyChanges() {
11877 							applyChangesTemplate(cgi, p);
11878 						}
11879 						string n = p.create(&specialApplyChanges);
11880 					} else {
11881 						string n = obj.create(&applyChanges);
11882 					}
11883 
11884 					auto newUrl = cgi.scriptName ~ cgi.pathInfo ~ "/" ~ n;
11885 					cgi.setResponseLocation(newUrl);
11886 					cgi.setResponseStatus("201 Created");
11887 					cgi.write(`The object has been created.`);
11888 				break;
11889 				default:
11890 					cgi.write("bad method\n", true);
11891 			}
11892 			// FIXME this should be valid on the collection, but not the child....
11893 			// 303 See Other
11894 		break;
11895 		case Cgi.RequestMethod.PUT:
11896 		PUT:
11897 			obj.replace(urlId, &applyChanges);
11898 			writeObject(false);
11899 		break;
11900 		case Cgi.RequestMethod.PATCH:
11901 		PATCH:
11902 			obj.update(urlId, &applyChanges, modifiedList);
11903 			writeObject(false);
11904 		break;
11905 		case Cgi.RequestMethod.DELETE:
11906 		DELETE:
11907 			obj.remove(urlId);
11908 			cgi.setResponseStatus("204 No Content");
11909 		break;
11910 		default:
11911 			// FIXME: OPTIONS, HEAD
11912 	}
11913 	catch(Throwable t) {
11914 		presenter.presentExceptionAsHtml(cgi, t);
11915 	}
11916 
11917 	return true;
11918 }
11919 
11920 /+
11921 struct SetOfFields(T) {
11922 	private void[0][string] storage;
11923 	void set(string what) {
11924 		//storage[what] =
11925 	}
11926 	void unset(string what) {}
11927 	void setAll() {}
11928 	void unsetAll() {}
11929 	bool isPresent(string what) { return false; }
11930 }
11931 +/
11932 
11933 /+
11934 enum readonly;
11935 enum hideonindex;
11936 +/
11937 
11938 /++
11939 	Returns true if I recommend gzipping content of this type. You might
11940 	want to call it from your Presenter classes before calling cgi.write.
11941 
11942 	---
11943 	cgi.setResponseContentType(yourContentType);
11944 	cgi.gzipResponse = gzipRecommendedForContentType(yourContentType);
11945 	cgi.write(yourData, true);
11946 	---
11947 
11948 	This is used internally by [serveStaticFile], [serveStaticFileDirectory], [serveStaticData], and maybe others I forgot to update this doc about.
11949 
11950 
11951 	The implementation considers text content to be recommended to gzip. This may change, but it seems reasonable enough for now.
11952 
11953 	History:
11954 		Added January 28, 2023 (dub v11.0)
11955 +/
11956 bool gzipRecommendedForContentType(string contentType) {
11957 	if(contentType.startsWith("text/"))
11958 		return true;
11959 	if(contentType.startsWith("application/javascript"))
11960 		return true;
11961 
11962 	return false;
11963 }
11964 
11965 /++
11966 	Serves a static file. To be used with [dispatcher].
11967 
11968 	See_Also: [serveApi], [serveRestObject], [dispatcher], [serveRedirect]
11969 +/
11970 auto serveStaticFile(string urlPrefix, string filename = null, string contentType = null) {
11971 // https://baus.net/on-tcp_cork/
11972 // man 2 sendfile
11973 	assert(urlPrefix[0] == '/');
11974 	if(filename is null)
11975 		filename = decodeUriComponent(urlPrefix[1 .. $]); // FIXME is this actually correct?
11976 	if(contentType is null) {
11977 		contentType = contentTypeFromFileExtension(filename);
11978 	}
11979 
11980 	static struct DispatcherDetails {
11981 		string filename;
11982 		string contentType;
11983 	}
11984 
11985 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
11986 		if(details.contentType.indexOf("image/") == 0 || details.contentType.indexOf("audio/") == 0)
11987 			cgi.setCache(true);
11988 		cgi.setResponseContentType(details.contentType);
11989 		cgi.gzipResponse = gzipRecommendedForContentType(details.contentType);
11990 		cgi.write(std.file.read(details.filename), true);
11991 		return true;
11992 	}
11993 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(filename, contentType));
11994 }
11995 
11996 /++
11997 	Serves static data. To be used with [dispatcher].
11998 
11999 	History:
12000 		Added October 31, 2021
12001 +/
12002 auto serveStaticData(string urlPrefix, immutable(void)[] data, string contentType = null) {
12003 	assert(urlPrefix[0] == '/');
12004 	if(contentType is null) {
12005 		contentType = contentTypeFromFileExtension(urlPrefix);
12006 	}
12007 
12008 	static struct DispatcherDetails {
12009 		immutable(void)[] data;
12010 		string contentType;
12011 	}
12012 
12013 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12014 		cgi.setCache(true);
12015 		cgi.setResponseContentType(details.contentType);
12016 		cgi.write(details.data, true);
12017 		return true;
12018 	}
12019 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType));
12020 }
12021 
12022 string contentTypeFromFileExtension(string filename) {
12023 	import arsd.core;
12024 	return FilePath(filename).contentTypeFromFileExtension();
12025 }
12026 
12027 /// This serves a directory full of static files, figuring out the content-types from file extensions.
12028 /// It does not let you to descend into subdirectories (or ascend out of it, of course)
12029 auto serveStaticFileDirectory(string urlPrefix, string directory = null, bool recursive = false) {
12030 	assert(urlPrefix[0] == '/');
12031 	assert(urlPrefix[$-1] == '/');
12032 
12033 	static struct DispatcherDetails {
12034 		string directory;
12035 		bool recursive;
12036 	}
12037 
12038 	if(directory is null)
12039 		directory = urlPrefix[1 .. $];
12040 
12041 	if(directory.length == 0)
12042 		directory = "./";
12043 
12044 	assert(directory[$-1] == '/');
12045 
12046 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12047 		auto file = decodeUriComponent(cgi.pathInfo[urlPrefix.length .. $]); // FIXME: is this actually correct
12048 
12049 		if(details.recursive) {
12050 			// never allow a backslash since it isn't in a typical url anyway and makes the following checks easier
12051 			if(file.indexOf("\\") != -1)
12052 				return false;
12053 
12054 			import std.path;
12055 
12056 			file = std.path.buildNormalizedPath(file);
12057 			enum upOneDir = ".." ~ std.path.dirSeparator;
12058 
12059 			// also no point doing any kind of up directory things since that makes it more likely to break out of the parent
12060 			if(file == ".." || file.startsWith(upOneDir))
12061 				return false;
12062 			if(std.path.isAbsolute(file))
12063 				return false;
12064 
12065 			// FIXME: if it has slashes and stuff, should we redirect to the canonical resource? or what?
12066 
12067 			// once it passes these filters it is probably ok.
12068 		} else {
12069 			if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
12070 				return false;
12071 		}
12072 
12073 		if(file.length == 0)
12074 			return false;
12075 
12076 		auto contentType = contentTypeFromFileExtension(file);
12077 
12078 		auto fn = details.directory ~ file;
12079 		if(std.file.exists(fn)) {
12080 			//if(contentType.indexOf("image/") == 0)
12081 				//cgi.setCache(true);
12082 			//else if(contentType.indexOf("audio/") == 0)
12083 				cgi.setCache(true);
12084 			cgi.setResponseContentType(contentType);
12085 			cgi.gzipResponse = gzipRecommendedForContentType(contentType);
12086 			cgi.write(std.file.read(fn), true);
12087 			return true;
12088 		} else {
12089 			return false;
12090 		}
12091 	}
12092 
12093 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, false, DispatcherDetails(directory, recursive));
12094 }
12095 
12096 /++
12097 	Redirects one url to another
12098 
12099 	See_Also: [dispatcher], [serveStaticFile]
12100 +/
12101 auto serveRedirect(string urlPrefix, string redirectTo, int code = 303) {
12102 	assert(urlPrefix[0] == '/');
12103 	static struct DispatcherDetails {
12104 		string redirectTo;
12105 		string code;
12106 	}
12107 
12108 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12109 		cgi.setResponseLocation(details.redirectTo, true, details.code);
12110 		return true;
12111 	}
12112 
12113 
12114 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(redirectTo, getHttpCodeText(code)));
12115 }
12116 
12117 /// Used exclusively with `dispatchTo`
12118 struct DispatcherData(Presenter) {
12119 	Cgi cgi; /// You can use this cgi object.
12120 	Presenter presenter; /// This is the presenter from top level, and will be forwarded to the sub-dispatcher.
12121 	size_t pathInfoStart; /// This is forwarded to the sub-dispatcher. It may be marked private later, or at least read-only.
12122 }
12123 
12124 /++
12125 	Dispatches the URL to a specific function.
12126 +/
12127 auto handleWith(alias handler)(string urlPrefix) {
12128 	// cuz I'm too lazy to do it better right now
12129 	static class Hack : WebObject {
12130 		static import std.traits;
12131 		@UrlName("")
12132 		auto handle(std.traits.Parameters!handler args) {
12133 			return handler(args);
12134 		}
12135 	}
12136 
12137 	return urlPrefix.serveApiInternal!Hack;
12138 }
12139 
12140 /++
12141 	Dispatches the URL (and anything under it) to another dispatcher function. The function should look something like this:
12142 
12143 	---
12144 	bool other(DD)(DD dd) {
12145 		return dd.dispatcher!(
12146 			"/whatever".serveRedirect("/success"),
12147 			"/api/".serveApi!MyClass
12148 		);
12149 	}
12150 	---
12151 
12152 	The `DD` in there will be an instance of [DispatcherData] which you can inspect, or forward to another dispatcher
12153 	here. It is a template to account for any Presenter type, so you can do compile-time analysis in your presenters.
12154 	Or, of course, you could just use the exact type in your own code.
12155 
12156 	You return true if you handle the given url, or false if not. Just returning the result of [dispatcher] will do a
12157 	good job.
12158 
12159 
12160 +/
12161 auto dispatchTo(alias handler)(string urlPrefix) {
12162 	assert(urlPrefix[0] == '/');
12163 	assert(urlPrefix[$-1] != '/');
12164 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
12165 		return handler(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
12166 	}
12167 
12168 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
12169 }
12170 
12171 /++
12172 	See [serveStaticFile] if you want to serve a file off disk.
12173 
12174 	History:
12175 		Added January 28, 2023 (dub v11.0)
12176 +/
12177 auto serveStaticData(string urlPrefix, immutable(ubyte)[] data, string contentType, string filenameToSuggestAsDownload = null) {
12178 	assert(urlPrefix[0] == '/');
12179 
12180 	static struct DispatcherDetails {
12181 		immutable(ubyte)[] data;
12182 		string contentType;
12183 		string filenameToSuggestAsDownload;
12184 	}
12185 
12186 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12187 		cgi.setCache(true);
12188 		cgi.setResponseContentType(details.contentType);
12189 		if(details.filenameToSuggestAsDownload.length)
12190     			cgi.header("Content-Disposition: attachment; filename=\""~details.filenameToSuggestAsDownload~"\"");
12191 		cgi.gzipResponse = gzipRecommendedForContentType(details.contentType);
12192 		cgi.write(details.data, true);
12193 		return true;
12194 	}
12195 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType, filenameToSuggestAsDownload));
12196 }
12197 
12198 /++
12199 	Placeholder for use with [dispatchSubsection]'s `NewPresenter` argument to indicate you want to keep the parent's presenter.
12200 
12201 	History:
12202 		Added January 28, 2023 (dub v11.0)
12203 +/
12204 alias KeepExistingPresenter = typeof(null);
12205 
12206 /++
12207 	For use with [dispatchSubsection]. Calls your filter with the request and if your filter returns false,
12208 	this issues the given errorCode and stops processing.
12209 
12210 	---
12211 		bool hasAdminPermissions(Cgi cgi) {
12212 			return true;
12213 		}
12214 
12215 		mixin DispatcherMain!(
12216 			"/admin".dispatchSubsection!(
12217 				passFilterOrIssueError!(hasAdminPermissions, 403),
12218 				KeepExistingPresenter,
12219 				"/".serveApi!AdminFunctions
12220 			)
12221 		);
12222 	---
12223 
12224 	History:
12225 		Added January 28, 2023 (dub v11.0)
12226 +/
12227 template passFilterOrIssueError(alias filter, int errorCode) {
12228 	bool passFilterOrIssueError(DispatcherDetails)(DispatcherDetails dd) {
12229 		if(filter(dd.cgi))
12230 			return true;
12231 		dd.presenter.renderBasicError(dd.cgi, errorCode);
12232 		return false;
12233 	}
12234 }
12235 
12236 /++
12237 	Allows for a subsection of your dispatched urls to be passed through other a pre-request filter, optionally pick up an new presenter class,
12238 	and then be dispatched to their own handlers.
12239 
12240 	---
12241 	/+
12242 	// a long-form filter function
12243 	bool permissionCheck(DispatcherData)(DispatcherData dd) {
12244 		// you are permitted to call mutable methods on the Cgi object
12245 		// Note if you use a Cgi subclass, you can try dynamic casting it back to your custom type to attach per-request data
12246 		// though much of the request is immutable so there's only so much you're allowed to do to modify it.
12247 
12248 		if(checkPermissionOnRequest(dd.cgi)) {
12249 			return true; // OK, allow processing to continue
12250 		} else {
12251 			dd.presenter.renderBasicError(dd.cgi, 403); // reply forbidden to the requester
12252 			return false; // and stop further processing into this subsection
12253 		}
12254 	}
12255 	+/
12256 
12257 	// but you can also do short-form filters:
12258 
12259 	bool permissionCheck(Cgi cgi) {
12260 		return ("ok" in cgi.get) !is null;
12261 	}
12262 
12263 	// handler for the subsection
12264 	class AdminClass : WebObject {
12265 		int foo() { return 5; }
12266 	}
12267 
12268 	// handler for the main site
12269 	class TheMainSite : WebObject {}
12270 
12271 	mixin DispatcherMain!(
12272 		"/admin".dispatchSubsection!(
12273 			// converts our short-form filter into a long-form filter
12274 			passFilterOrIssueError!(permissionCheck, 403),
12275 			// can use a new presenter if wanted for the subsection
12276 			KeepExistingPresenter,
12277 			// and then provide child route dispatchers
12278 			"/".serveApi!AdminClass
12279 		),
12280 		// and back to the top level
12281 		"/".serveApi!TheMainSite
12282 	);
12283 	---
12284 
12285 	Note you can encapsulate sections in files like this:
12286 
12287 	---
12288 	auto adminDispatcher(string urlPrefix) {
12289 		return urlPrefix.dispatchSubsection!(
12290 			....
12291 		);
12292 	}
12293 
12294 	mixin DispatcherMain!(
12295 		"/admin".adminDispatcher,
12296 		// and so on
12297 	)
12298 	---
12299 
12300 	If you want no filter, you can pass `(cgi) => true` as the filter to approve all requests.
12301 
12302 	If you want to keep the same presenter as the parent, use [KeepExistingPresenter] as the presenter argument.
12303 
12304 
12305 	History:
12306 		Added January 28, 2023 (dub v11.0)
12307 +/
12308 auto dispatchSubsection(alias PreRequestFilter, NewPresenter, definitions...)(string urlPrefix) {
12309 	assert(urlPrefix[0] == '/');
12310 	assert(urlPrefix[$-1] != '/');
12311 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
12312 		static if(!is(PreRequestFilter == typeof(null))) {
12313 			if(!PreRequestFilter(DispatcherData!Presenter(cgi, presenter, urlPrefix.length)))
12314 				return true; // we handled it by rejecting it
12315 		}
12316 
12317 		static if(is(NewPresenter == Presenter) || is(NewPresenter == typeof(null))) {
12318 			return dispatcher!definitions(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
12319 		} else {
12320 			auto newPresenter = new NewPresenter();
12321 			return dispatcher!(definitions(DispatcherData!NewPresenter(cgi, presenter, urlPrefix.length)));
12322 		}
12323 	}
12324 
12325 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
12326 }
12327 
12328 /++
12329 	A URL dispatcher.
12330 
12331 	---
12332 	if(cgi.dispatcher!(
12333 		"/api/".serveApi!MyApiClass,
12334 		"/objects/lol".serveRestObject!MyRestObject,
12335 		"/file.js".serveStaticFile,
12336 		"/admin/".dispatchTo!adminHandler
12337 	)) return;
12338 	---
12339 
12340 
12341 	You define a series of url prefixes followed by handlers.
12342 
12343 	You may want to do different pre- and post- processing there, for example,
12344 	an authorization check and different page layout. You can use different
12345 	presenters and different function chains. See [dispatchSubsection] for details.
12346 
12347 	[dispatchTo] will send the request to another function for handling.
12348 +/
12349 template dispatcher(definitions...) {
12350 	bool dispatcher(Presenter)(Cgi cgi, Presenter presenterArg = null) {
12351 		static if(is(Presenter == typeof(null))) {
12352 			static class GenericWebPresenter : WebPresenter!(GenericWebPresenter) {}
12353 			auto presenter = new GenericWebPresenter();
12354 		} else
12355 			alias presenter = presenterArg;
12356 
12357 		return dispatcher(DispatcherData!(typeof(presenter))(cgi, presenter, 0));
12358 	}
12359 
12360 	bool dispatcher(DispatcherData)(DispatcherData dispatcherData) if(!is(DispatcherData : Cgi)) {
12361 		// I can prolly make this more efficient later but meh.
12362 		foreach(definition; definitions) {
12363 			if(definition.rejectFurther) {
12364 				if(dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $] == definition.urlPrefix) {
12365 					auto ret = definition.handler(
12366 						dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
12367 						dispatcherData.cgi, dispatcherData.presenter, definition.details);
12368 					if(ret)
12369 						return true;
12370 				}
12371 			} else if(
12372 				dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $].startsWith(definition.urlPrefix) &&
12373 				// cgi.d dispatcher urls must be complete or have a /;
12374 				// "foo" -> thing should NOT match "foobar", just "foo" or "foo/thing"
12375 				(definition.urlPrefix[$-1] == '/' || (dispatcherData.pathInfoStart + definition.urlPrefix.length) == dispatcherData.cgi.pathInfo.length
12376 				|| dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart + definition.urlPrefix.length] == '/')
12377 				) {
12378 				auto ret = definition.handler(
12379 					dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
12380 					dispatcherData.cgi, dispatcherData.presenter, definition.details);
12381 				if(ret)
12382 					return true;
12383 			}
12384 		}
12385 		return false;
12386 	}
12387 }
12388 
12389 });
12390 
12391 private struct StackBuffer {
12392 	char[1024] initial = void;
12393 	char[] buffer;
12394 	size_t position;
12395 
12396 	this(int a) {
12397 		buffer = initial[];
12398 		position = 0;
12399 	}
12400 
12401 	void add(in char[] what) {
12402 		if(position + what.length > buffer.length)
12403 			buffer.length = position + what.length + 1024; // reallocate with GC to handle special cases
12404 		buffer[position .. position + what.length] = what[];
12405 		position += what.length;
12406 	}
12407 
12408 	void add(in char[] w1, in char[] w2, in char[] w3 = null) {
12409 		add(w1);
12410 		add(w2);
12411 		add(w3);
12412 	}
12413 
12414 	void add(long v) {
12415 		char[16] buffer = void;
12416 		auto pos = buffer.length;
12417 		bool negative;
12418 		if(v < 0) {
12419 			negative = true;
12420 			v = -v;
12421 		}
12422 		do {
12423 			buffer[--pos] = cast(char) (v % 10 + '0');
12424 			v /= 10;
12425 		} while(v);
12426 
12427 		if(negative)
12428 			buffer[--pos] = '-';
12429 
12430 		auto res = buffer[pos .. $];
12431 
12432 		add(res[]);
12433 	}
12434 
12435 	char[] get() @nogc {
12436 		return buffer[0 .. position];
12437 	}
12438 }
12439 
12440 // duplicated in http2.d
12441 private static string getHttpCodeText(int code) pure nothrow @nogc {
12442 	switch(code) {
12443 		case 200: return "200 OK";
12444 		case 201: return "201 Created";
12445 		case 202: return "202 Accepted";
12446 		case 203: return "203 Non-Authoritative Information";
12447 		case 204: return "204 No Content";
12448 		case 205: return "205 Reset Content";
12449 		case 206: return "206 Partial Content";
12450 		//
12451 		case 300: return "300 Multiple Choices";
12452 		case 301: return "301 Moved Permanently";
12453 		case 302: return "302 Found";
12454 		case 303: return "303 See Other";
12455 		case 304: return "304 Not Modified";
12456 		case 305: return "305 Use Proxy";
12457 		case 307: return "307 Temporary Redirect";
12458 		case 308: return "308 Permanent Redirect";
12459 
12460 		//
12461 		case 400: return "400 Bad Request";
12462 		case 401: return "401 Unauthorized";
12463 		case 402: return "402 Payment Required";
12464 		case 403: return "403 Forbidden";
12465 		case 404: return "404 Not Found";
12466 		case 405: return "405 Method Not Allowed";
12467 		case 406: return "406 Not Acceptable";
12468 		case 407: return "407 Proxy Authentication Required";
12469 		case 408: return "408 Request Timeout";
12470 		case 409: return "409 Conflict";
12471 		case 410: return "410 Gone";
12472 		case 411: return "411 Length Required";
12473 		case 412: return "412 Precondition Failed";
12474 		case 413: return "413 Payload Too Large";
12475 		case 414: return "414 URI Too Long";
12476 		case 415: return "415 Unsupported Media Type";
12477 		case 416: return "416 Range Not Satisfiable";
12478 		case 417: return "417 Expectation Failed";
12479 		case 418: return "418 I'm a teapot";
12480 		case 421: return "421 Misdirected Request";
12481 		case 422: return "422 Unprocessable Entity (WebDAV)";
12482 		case 423: return "423 Locked (WebDAV)";
12483 		case 424: return "424 Failed Dependency (WebDAV)";
12484 		case 425: return "425 Too Early";
12485 		case 426: return "426 Upgrade Required";
12486 		case 428: return "428 Precondition Required";
12487 		case 431: return "431 Request Header Fields Too Large";
12488 		case 451: return "451 Unavailable For Legal Reasons";
12489 
12490 		case 500: return "500 Internal Server Error";
12491 		case 501: return "501 Not Implemented";
12492 		case 502: return "502 Bad Gateway";
12493 		case 503: return "503 Service Unavailable";
12494 		case 504: return "504 Gateway Timeout";
12495 		case 505: return "505 HTTP Version Not Supported";
12496 		case 506: return "506 Variant Also Negotiates";
12497 		case 507: return "507 Insufficient Storage (WebDAV)";
12498 		case 508: return "508 Loop Detected (WebDAV)";
12499 		case 510: return "510 Not Extended";
12500 		case 511: return "511 Network Authentication Required";
12501 		//
12502 		default: assert(0, "Unsupported http code");
12503 	}
12504 }
12505 
12506 
12507 /+
12508 /++
12509 	This is the beginnings of my web.d 2.0 - it dispatches web requests to a class object.
12510 
12511 	It relies on jsvar.d and dom.d.
12512 
12513 
12514 	You can get javascript out of it to call. The generated functions need to look
12515 	like
12516 
12517 	function name(a,b,c,d,e) {
12518 		return _call("name", {"realName":a,"sds":b});
12519 	}
12520 
12521 	And _call returns an object you can call or set up or whatever.
12522 +/
12523 bool apiDispatcher()(Cgi cgi) {
12524 	import arsd.jsvar;
12525 	import arsd.dom;
12526 }
12527 +/
12528 version(linux)
12529 private extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
12530 /*
12531 Copyright: Adam D. Ruppe, 2008 - 2023
12532 License:   [http://www.boost.org/LICENSE_1_0.txt|Boost License 1.0].
12533 Authors: Adam D. Ruppe
12534 
12535 	Copyright Adam D. Ruppe 2008 - 2023.
12536 Distributed under the Boost Software License, Version 1.0.
12537    (See accompanying file LICENSE_1_0.txt or copy at
12538 	http://www.boost.org/LICENSE_1_0.txt)
12539 */