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