1 /++
2 	Old magic web wrapper - one of my first applications of CT reflection. Given a class of fairly ordinary D code, it automatically creates HTML pages and forms, a Javascript file to access the functions from the client, and JSON based api responses. I do $(I not) recommend it for new projects though, as a replacement is now built into [arsd.cgi].
3 
4 	History:
5 		Written some time prior to July 2011.
6 
7 		Retired in June 2019. I will still maintain it until
8 		that becomes a hassle. But since I don't actively use
9 		any of my projects using it anymore, it is possible it
10 		will break without me noticing.
11 +/
12 module arsd.web;
13 
14 
15 static if(__VERSION__ <= 2076) {
16 	// compatibility shims with gdc
17 	enum JSONType {
18 		object = JSON_TYPE.OBJECT,
19 		null_ = JSON_TYPE.NULL,
20 		false_ = JSON_TYPE.FALSE,
21 		true_ = JSON_TYPE.TRUE,
22 		integer = JSON_TYPE.INTEGER,
23 		float_ = JSON_TYPE.FLOAT,
24 		array = JSON_TYPE.ARRAY,
25 		string = JSON_TYPE.STRING,
26 		uinteger = JSON_TYPE.UINTEGER
27 	}
28 }
29 
30 
31 
32 // it would be nice to be able to add meta info to a returned envelope
33 
34 // with cookie sessions, you must commit your session yourself before writing any content
35 
36 enum RequirePost;
37 enum RequireHttps;
38 enum NoAutomaticForm;
39 
40 ///
41 struct GenericContainerType {
42 	string type; ///
43 }
44 
45 /// Attribute for the default formatting (html, table, json, etc)
46 struct DefaultFormat {
47 	string format;
48 }
49 
50 /// Sets the preferred request method, used by things like other code generators.
51 /// While this is preferred, the function is still callable from any request method.
52 ///
53 /// By default, the preferred method is GET if the name starts with "get" and POST otherwise.
54 ///
55 /// See also: RequirePost, ensureGoodPost, and using Cgi.RequestMethod as an attribute
56 struct PreferredMethod {
57 	Cgi.RequestMethod preferredMethod;
58 }
59 
60 /// With this attribute, the function is only called if the input data's
61 /// content type is what you specify here. Makes sense for POST and PUT
62 /// verbs.
63 struct IfInputContentType {
64 	string contentType;
65 	string dataGoesInWhichArgument;
66 }
67 
68 /**
69 	URL Mapping
70 
71 	By default, it is the method name OR the method name separated by dashes instead of camel case
72 */
73 
74 
75 /+
76 	Attributes
77 
78 	// this is different than calling ensureGoodPost because
79 	// it is only called on direct calls. ensureGoodPost is flow oriented
80 	enum RequirePost;
81 
82 	// path info? One could be the name of the current function, one could be the stuff past it...
83 
84 	// Incomplete form handler
85 
86 	// overrides the getGenericContainer
87 	struct DocumentContainer {}
88 
89 	// custom formatter for json and other user defined types
90 
91 	// custom title for the page
92 
93 	// do we prefill from url? something else? default?
94 	struct Prefill {}
95 
96 	// btw prefill should also take a function
97 	// perhaps a FormFinalizer
98 
99 	// for automatic form creation
100 	struct ParameterSuggestions {
101 		string[] suggestions;
102 		bool showDropdown; /* otherwise it is just autocomplete on a text box */
103 	}
104 
105 +/
106 
107 // FIXME: if a method has a default value of a non-primitive type,
108 // it's still liable to screw everything else.
109 
110 /*
111 	Reasonably easy CSRF plan:
112 
113 	A csrf token can be associated with the entire session, and
114 	saved in the session file.
115 
116 	Each form outputs the token, and it is added as a parameter to
117 	the script thingy somewhere.
118 
119 	It need only be sent on POST items. Your app should handle proper
120 	get and post separation.
121 */
122 
123 /*
124 	Future directions for web stuff:
125 
126 	an improved css:
127 		add definition nesting
128 		add importing things from another definition
129 
130 		Implemented: see html.d
131 
132 	All css improvements are done via simple text rewriting. Aside
133 	from the nesting, it'd just be a simple macro system.
134 
135 
136 	Struct input functions:
137 		static typeof(this) fromWebString(string fromUrl) {}
138 
139 	Automatic form functions:
140 		static Element makeFormElement(Document document) {}
141 
142 
143 	javascript:
144 		I'd like to add functions and do static analysis actually.
145 		I can't believe I just said that though.
146 
147 		But the stuff I'd analyze is checking it against the
148 		D functions, recognizing that JS is loosely typed.
149 
150 		So basically it can do a grep for simple stuff:
151 
152 			CoolApi.xxxxxxx
153 
154 			if xxxxxxx isn't a function in CoolApi (the name
155 			it knows from the server), it can flag a compile
156 			error.
157 
158 			Might not be able to catch usage all the time
159 			but could catch typo names.
160 
161 */
162 
163 /*
164 	FIXME: in params on the wrapped functions generally don't work
165 		(can't modify const)
166 
167 	Running from the command line:
168 
169 	./myapp function positional args....
170 	./myapp --format=json function
171 
172 	./myapp --make-nested-call
173 
174 
175 	Formatting data:
176 
177 	CoolApi.myFunc().getFormat('Element', [...same as get...]);
178 
179 	You should also be able to ask for json, but with a particular format available as toString
180 
181 	format("json", "html") -- gets json, but each object has it's own toString. Actually, the object adds
182 		a member called formattedSecondarily that is the other thing.
183 	Note: the array itself cannot be changed in format, only it's members.
184 	Note: the literal string of the formatted object is often returned. This may more than double the bandwidth of the call
185 
186 	Note: BUG: it only works with built in formats right now when doing secondary
187 
188 
189 	// formats are: text, html, json, table, and xml
190 	// except json, they are all represented as strings in json values
191 
192 	string    toString        -> formatting as text
193 	Element   makeHtmlElement -> making it html (same as fragment)
194 	JSONValue makeJsonValue   -> formatting to json
195 	Table     makeHtmlTable   -> making a table
196 	(not implemented) toXml   -> making it into an xml document
197 
198 
199 	Arrays can be handled too:
200 
201 	static (converts to) string makeHtmlArray(typeof(this)[] arr);
202 
203 
204 	Envelope format:
205 
206 	document (default), json, none
207 */
208 
209 import std.exception;
210 static import std.uri;
211 public import arsd.dom;
212 public import arsd.cgi; // you have to import this in the actual usage file or else it won't link; surely a compiler bug
213 import arsd.sha;
214 
215 public import std.string;
216 public import std.array;
217 public import std.stdio : writefln;
218 public import std.conv;
219 import std.random;
220 import std.typetuple;
221 
222 import std.datetime;
223 
224 public import std.range;
225 
226 public import std.traits;
227 import std.json;
228 
229 /// This gets your site's base link. note it's really only good if you are using FancyMain.
230 string getSiteLink(Cgi cgi) {
231 	return cgi.requestUri[0.. cgi.requestUri.indexOf(cgi.scriptName) + cgi.scriptName.length + 1 /* for the slash at the end */];
232 }
233 
234 /// use this in a function parameter if you want the automatic form to render
235 /// it as a textarea
236 /// FIXME: this should really be an annotation on the parameter... somehow
237 struct Text {
238 	string content;
239 	alias content this;
240 }
241 
242 ///
243 struct URL {
244 	string url; ///
245 	string title; ///
246 	alias url this;
247 }
248 
249 /// This is the JSON envelope format
250 struct Envelope {
251 	bool success; /// did the call succeed? false if it threw an exception
252 	string type; /// static type of the return value
253 	string errorMessage; /// if !success, this is exception.msg
254 	string userData; /// null unless the user request included passedThroughUserData
255 
256 	// use result.str if the format was anything other than json
257 	JSONValue result; /// the return value of the function
258 
259 	debug string dFullString; /// exception.toString - includes stack trace, etc. Only available in debug mode for privacy reasons.
260 }
261 
262 /// Info about the current request - more specialized than the cgi object directly
263 struct RequestInfo {
264 	string mainSitePath; /// the bottom-most ApiProvider's path in this request
265 	string objectBasePath; /// the top-most resolved path in the current request
266 
267 	FunctionInfo currentFunction; /// what function is being called according to the url?
268 
269 	string requestedFormat; /// the format the returned data was requested to be sent
270 	string requestedEnvelopeFormat; /// the format the data is to be wrapped in
271 }
272 
273 /+
274 string linkTo(alias func, T...)(T args) {
275 	auto reflection = __traits(parent, func).reflection;
276 	assert(reflection !is null);
277 
278 	auto name = func.stringof;
279 	auto idx = name.indexOf("(");
280 	if(idx != -1)
281 		name = name[0 .. idx];
282 
283 	auto funinfo = reflection.functions[name];
284 
285 	return funinfo.originalName;
286 }
287 +/
288 
289 /// this is there so there's a common runtime type for all callables
290 class WebDotDBaseType {
291 	Cgi cgi; /// lower level access to the request
292 
293 	/// use this to look at exceptions and set up redirects and such. keep in mind it does NOT change the regular behavior
294 	void exceptionExaminer(Throwable e) {}
295 
296 	// HACK: to enable breaking up the path somehow
297 	int pathInfoStartingPoint() { return 0; }
298 
299 	/// Override this if you want to do something special to the document
300 	/// You should probably call super._postProcess at some point since I
301 	/// might add some default transformations here.
302 
303 	/// By default, it forwards the document root to _postProcess(Element).
304 	void _postProcess(Document document) {
305 		auto td = cast(TemplatedDocument) document;
306 		if(td !is null)
307 			td.vars["compile.timestamp"] = compiliationStamp;
308 
309 		if(document !is null && document.root !is null)
310 			_postProcessElement(document.root);
311 	}
312 
313 	/// Override this to do something special to returned HTML Elements.
314 	/// This is ONLY run if the return type is(: Element). It is NOT run
315 	/// if the return type is(: Document).
316 	void _postProcessElement(Element element) {} // why the fuck doesn't overloading actually work?
317 
318 	/// convenience function to enforce that the current method is POST.
319 	/// You should use this if you are going to commit to the database or something.
320 	void ensurePost() {
321 		assert(cgi !is null);
322 		enforce(cgi.requestMethod == Cgi.RequestMethod.POST);
323 	}
324 }
325 
326 /// This is meant to beautify and check links and javascripts to call web.d functions.
327 /// FIXME: this function sucks.
328 string linkCall(alias Func, Args...)(Args args) {
329 	static if(!__traits(compiles, Func(args))) {
330 		static assert(0, "Your function call doesn't compile. If you need client side dynamic data, try building the call as a string.");
331 	}
332 
333 	// FIXME: this link won't work from other parts of the site...
334 
335 	//string script = __traits(parent, Func).stringof;
336 	auto href = __traits(identifier, Func) ~ "?";
337 
338 	bool outputted = false;
339 	foreach(i, arg; args) {
340 		if(outputted) {
341 			href ~= "&";
342 		} else
343 			outputted = true;
344 
345 		href ~= std.uri.encodeComponent("positional-arg-" ~ to!string(i));
346 		href ~= "=";
347 		href ~= to!string(arg); // FIXME: this is wrong for all but the simplest types
348 	}
349 
350 	return href;
351 
352 }
353 
354 /// This is meant to beautify and check links and javascripts to call web.d functions.
355 /// This function works pretty ok. You're going to want to append a string to the return
356 /// value to actually call .get() or whatever; it only does the name and arglist.
357 string jsCall(alias Func, Args...)(Args args) /*if(is(__traits(parent, Func) : WebDotDBaseType))*/ {
358 	static if(!is(typeof(Func(args)))) { //__traits(compiles, Func(args))) {
359 		static assert(0, "Your function call doesn't compile. If you need client side dynamic data, try building the call as a string.");
360 	}
361 
362 	string script = __traits(parent, Func).stringof;
363 	script ~= "." ~ __traits(identifier, Func) ~ "(";
364 
365 	bool outputted = false;
366 	foreach(arg; args) {
367 		if(outputted) {
368 			script ~= ",";
369 		} else
370 			outputted = true;
371 
372 		script ~= toJson(arg);
373 	}
374 
375 	script ~= ")";
376 	return script;
377 }
378 
379 /// Everything should derive from this instead of the old struct namespace used before
380 /// Your class must provide a default constructor.
381 class ApiProvider : WebDotDBaseType {
382 	/*private*/ ApiProvider builtInFunctions;
383 
384 	Session session; // note: may be null
385 
386 	/// override this to change cross-site request forgery checks.
387 	///
388 	/// To perform a csrf check, call ensureGoodPost(); in your code.
389 	///
390 	/// It throws a PermissionDeniedException if the check fails.
391 	/// This might change later to make catching it easier.
392 	///
393 	/// If there is no session object, the test always succeeds. This lets you opt
394 	/// out of the system.
395 	///
396 	/// If the session is null, it does nothing. FancyMain makes a session for you.
397 	/// If you are doing manual run(), it is your responsibility to create a session
398 	/// and attach it to each primary object.
399 	///
400 	/// NOTE: it is important for you use ensureGoodPost() on any data changing things!
401 	/// This function alone is a no-op on non-POST methods, so there's no real protection
402 	/// without ensuring POST when making changes.
403 	///
404 	// FIXME: if someone is OAuth authorized, a csrf token should not really be necessary.
405 	// This check is done automatically right now, and doesn't account for that. I guess
406 	// people could override it in a subclass though. (Which they might have to since there's
407 	// no oauth integration at this level right now anyway. Nor may there ever be; it's kinda
408 	// high level. Perhaps I'll provide an oauth based subclass later on.)
409 	protected void checkCsrfToken() {
410 		assert(cgi !is null);
411 		if(cgi.requestMethod == Cgi.RequestMethod.POST) {
412 			auto tokenInfo = _getCsrfInfo();
413 			if(tokenInfo is null)
414 				return; // not doing checks
415 
416 			void fail() {
417 				throw new PermissionDeniedException("CSRF token test failed " ~ to!string(cgi.postArray));
418 				/*
419 				~ "::::::"~cgi.post[
420 				tokenInfo["key"]
421 				] ~ " != " ~
422 				tokenInfo["token"]);
423 				*/
424 			}
425 
426 			// expiration is handled by the session itself expiring (in the Session class)
427 
428 			if(tokenInfo["key"] !in cgi.post)
429 				fail();
430 			if(cgi.post[tokenInfo["key"]] != tokenInfo["token"])
431 				fail();
432 		}
433 	}
434 
435 	protected bool isCsrfTokenCorrect() {
436 		auto tokenInfo = _getCsrfInfo();
437 		if(tokenInfo is null)
438 			return false; // this means we aren't doing checks (probably because there is no session), but it is a failure nonetheless
439 
440 		auto token = tokenInfo["key"] ~ "=" ~ tokenInfo["token"];
441 		if("x-arsd-csrf-pair" in cgi.requestHeaders)
442 			return cgi.requestHeaders["x-arsd-csrf-pair"] == token;
443 		if(tokenInfo["key"] in cgi.post)
444 			return cgi.post[tokenInfo["key"]] == tokenInfo["token"];
445 		if(tokenInfo["key"] in cgi.get)
446 			return cgi.get[tokenInfo["key"]] == tokenInfo["token"];
447 
448 		return false;
449 	}
450 
451 	/// Shorthand for ensurePost and checkCsrfToken. You should use this on non-indempotent
452 	/// functions. Override it if doing some custom checking.
453 	void ensureGoodPost() {
454 		if(_noCsrfChecks) return;
455 		ensurePost();
456 		checkCsrfToken();
457 	}
458 
459 	bool _noCsrfChecks; // this is a hack to let you use the functions internally more easily
460 
461 	// gotta make sure this isn't callable externally! Oh lol that'd defeat the point...
462 	/// Gets the CSRF info (an associative array with key and token inside at least) from the session.
463 	/// Note that the actual token is generated by the Session class.
464 	protected string[string] _getCsrfInfo() {
465 		if(session is null || this._noCsrfChecks)
466 			return null;
467 		return decodeVariablesSingle(session.csrfToken);
468 	}
469 
470 	/// Adds CSRF tokens to the document for use by script (required by the Javascript API)
471 	/// and then calls addCsrfTokens(document.root) to add them to all POST forms as well.
472 	protected void addCsrfTokens(Document document) {
473 		if(document is null)
474 			return;
475 		auto bod = document.mainBody;
476 		if(bod is null)
477 			return;
478 		if(!bod.hasAttribute("data-csrf-key")) {
479 			auto tokenInfo = _getCsrfInfo();
480 			if(tokenInfo is null)
481 				return;
482 			if(bod !is null) {
483 				bod.setAttribute("data-csrf-key", tokenInfo["key"]);
484 				bod.setAttribute("data-csrf-token", tokenInfo["token"]);
485 			}
486 
487 			addCsrfTokens(document.root);
488 		}
489 	}
490 
491 	/// we have to add these things to the document...
492 	override void _postProcess(Document document) {
493 		if(document !is null) {
494 			foreach(pp; documentPostProcessors)
495 				pp(document);
496 
497 			addCsrfTokens(document);
498 		}
499 		super._postProcess(document);
500 	}
501 
502 	/// This adds CSRF tokens to all forms in the tree
503 	protected void addCsrfTokens(Element element) {
504 		if(element is null)
505 			return;
506 		auto tokenInfo = _getCsrfInfo();
507 		if(tokenInfo is null)
508 			return;
509 
510 		foreach(formElement; element.getElementsByTagName("form")) {
511 			if(formElement.method != "POST" && formElement.method != "post")
512 				continue;
513 			auto form = cast(Form) formElement;
514 			assert(form !is null);
515 
516 			form.setValue(tokenInfo["key"], tokenInfo["token"]);
517 		}
518 	}
519 
520 	// and added to ajax forms..
521 	override void _postProcessElement(Element element) {
522 		foreach(pp; elementPostProcessors)
523 			pp(element);
524 
525 		addCsrfTokens(element);
526 		super._postProcessElement(element);
527 	}
528 
529 
530 	// FIXME: the static is meant to be a performance improvement, but it breaks child modules' reflection!
531 	/*static */immutable(ReflectionInfo)* reflection;
532 	string _baseUrl; // filled based on where this is called from on this request
533 
534 	RequestInfo currentRequest; // FIXME: actually fill this in
535 
536 	/// Override this if you have initialization work that must be done *after* cgi and reflection is ready.
537 	/// It should be used instead of the constructor for most work.
538 	void _initialize() {}
539 
540 	/// On each call, you can register another post processor for the generated html. If your delegate takes a Document, it will only run on document envelopes (full pages generated). If you take an Element, it will apply on almost any generated html.
541 	///
542 	/// Note: if you override _postProcess or _postProcessElement, be sure to call the superclass version for these registered functions to run.
543 	void _registerPostProcessor(void delegate(Document) pp) {
544 		documentPostProcessors ~= pp;
545 	}
546 
547 	/// ditto
548 	void _registerPostProcessor(void delegate(Element) pp) {
549 		elementPostProcessors ~= pp;
550 	}
551 
552 	/// ditto
553 	void _registerPostProcessor(void function(Document) pp) {
554 		documentPostProcessors ~= delegate void(Document d) { pp(d); };
555 	}
556 
557 	/// ditto
558 	void _registerPostProcessor(void function(Element) pp) {
559 		elementPostProcessors ~= delegate void(Element d) { pp(d); };
560 	}
561 
562 	// these only work for one particular call
563 	private void delegate(Document d)[] documentPostProcessors;
564 	private void delegate(Element d)[] elementPostProcessors;
565 	/*private*/ void _initializePerCallInternal() {
566 		documentPostProcessors = null;
567 		elementPostProcessors = null;
568 
569 		_initializePerCall();
570 	}
571 
572 	/// This one is called at least once per call. (_initialize is only called once per process)
573 	void _initializePerCall() {}
574 
575 	/// Returns the stylesheet for this module. Use it to encapsulate the needed info for your output so the module is more easily reusable
576 	/// Override this to provide your own stylesheet. (of course, you can always provide it via _catchAll or any standard css file/style element too.)
577 	string _style() const {
578 		return null;
579 	}
580 
581 	/// Returns the combined stylesheet of all child modules and this module
582 	string stylesheet() const {
583 		string ret;
584 		foreach(i; reflection.objects) {
585 			if(i.instantiation !is null)
586 				ret ~= i.instantiation.stylesheet();
587 		}
588 
589 		ret ~= _style();
590 		return ret;
591 	}
592 
593 	int redirectsSuppressed;
594 
595 	/// Temporarily disables the redirect() call.
596 	void disableRedirects() {
597 		redirectsSuppressed++;
598 	}
599 
600 	/// Re-enables redirects. Call this once for every call to disableRedirects.
601 	void enableRedirects() {
602 		if(redirectsSuppressed)
603 			redirectsSuppressed--;
604 	}
605 
606 	/// This tentatively redirects the user - depends on the envelope fomat
607 	/// You can temporarily disable this using disableRedirects()
608 	string redirect(string location, bool important = false, string status = null) {
609 		if(redirectsSuppressed)
610 			return location;
611 		auto f = cgi.request("envelopeFormat", "document");
612 		if(f == "document" || f == "redirect" || f == "json_enable_redirects")
613 			cgi.setResponseLocation(location, important, status);
614 		return location;
615 	}
616 
617 	/// Returns a list of links to all functions in this class or sub-classes
618 	/// You can expose it publicly with alias: "alias _sitemap sitemap;" for example.
619 	Element _sitemap() {
620 		auto container = Element.make("div", "", "sitemap");
621 
622 		void writeFunctions(Element list, in ReflectionInfo* reflection, string base) {
623 			string[string] handled;
624 			foreach(key, func; reflection.functions) {
625 				if(func.originalName in handled)
626 					continue;
627 				handled[func.originalName] = func.originalName;
628 
629 				// skip these since the root is what this is there for
630 				if(func.originalName == "GET" || func.originalName == "POST")
631 					continue;
632 
633 				// the builtins aren't interesting either
634 				if(key.startsWith("builtin."))
635 					continue;
636 
637 				if(func.originalName.length)
638 					list.addChild("li", new Link(base ~ func.name, beautify(func.originalName)));
639 			}
640 
641 			handled = null;
642 			foreach(obj; reflection.objects) {
643 				if(obj.name in handled)
644 					continue;
645 				handled[obj.name] = obj.name;
646 
647 				auto li = list.addChild("li", new Link(base ~ obj.name, obj.name));
648 
649 				auto ul = li.addChild("ul");
650 				writeFunctions(ul, obj, base ~ obj.name ~ "/");
651 			}
652 		}
653 
654 		auto list = container.addChild("ul");
655 		auto starting = _baseUrl;
656 		if(starting is null)
657 			starting = cgi.logicalScriptName ~ cgi.pathInfo; // FIXME
658 		writeFunctions(list, reflection, starting ~ "/");
659 
660 		return container;
661 	}
662 
663 	/// If the user goes to your program without specifying a path, this function is called.
664 	// FIXME: should it return document? That's kinda a pain in the butt.
665 	Document _defaultPage() {
666 		throw new Exception("no default");
667 	}
668 
669 	/// forwards to [_getGenericContainer]("default")
670 	Element _getGenericContainer() {
671 		return _getGenericContainer("default");
672 	}
673 
674 	/// When the html document envelope is used, this function is used to get a html element
675 	/// where the return value is appended.
676 
677 	/// It's the main function to override to provide custom HTML templates.
678 	///
679 	/// The default document provides a default stylesheet, our default javascript, and some timezone cookie handling (which you must handle on the server. Eventually I'll open source my date-time helpers that do this, but the basic idea is it sends an hour offset, and you can add that to any UTC time you have to get a local time).
680 	Element _getGenericContainer(string containerName)
681 	out(ret) {
682 		assert(ret !is null);
683 	}
684 	do {
685 		auto document = new TemplatedDocument(
686 "<!DOCTYPE html>
687 <html>
688 <head>
689 	<title></title>
690 	<link rel=\"stylesheet\" id=\"webd-styles-css\" href=\"styles.css?"~compiliationStamp~"\" />
691 	<script> var delayedExecutionQueue = []; </script> <!-- FIXME do some better separation -->
692 	<script>
693 		if(document.cookie.indexOf(\"timezone=\") == -1) {
694 			var d = new Date();
695 			var tz = -d.getTimezoneOffset() / 60;
696 			document.cookie = \"timezone=\" + tz + \"; path=/\";
697 		}
698 	</script>
699 	<style>
700 		.format-row { display: none; }
701 		.validation-failed { background-color: #ffe0e0; }
702 	</style>
703 </head>
704 <body>
705 	<div id=\"body\"></div>
706 	<script id=\"webd-functions-js\" src=\"functions.js?"~compiliationStamp~"\"></script>
707 	" ~ deqFoot ~ "
708 </body>
709 </html>");
710 		if(this.reflection !is null)
711 			document.title = this.reflection.name;
712 		auto container = document.requireElementById("body");
713 		return container;
714 	}
715 
716 	// FIXME: set a generic container for a particular call
717 
718 	/// If the given url path didn't match a function, it is passed to this function
719 	/// for further handling. By default, it throws a NoSuchFunctionException.
720 
721 	/// Overriding it might be useful if you want to serve generic filenames or an opDispatch kind of thing.
722 	/// (opDispatch itself won't work because it's name argument needs to be known at compile time!)
723 	///
724 	/// Note that you can return Documents here as they implement
725 	/// the FileResource interface too.
726 	FileResource _catchAll(string path) {
727 		throw new NoSuchFunctionException(_errorMessageForCatchAll);
728 	}
729 
730 	private string _errorMessageForCatchAll;
731 	/*private*/ FileResource _catchallEntry(string path, string funName, string errorMessage) {
732 		if(!errorMessage.length) {
733 			/*
734 			string allFuncs, allObjs;
735 			foreach(n, f; reflection.functions)
736 				allFuncs ~= n ~ "\n";
737 			foreach(n, f; reflection.objects)
738 				allObjs ~= n ~ "\n";
739 			errorMessage =  "no such function " ~ funName ~ "\n functions are:\n" ~ allFuncs ~ "\n\nObjects are:\n" ~ allObjs;
740 			*/
741 
742 			errorMessage = "No such page: " ~ funName;
743 		}
744 
745 		_errorMessageForCatchAll = errorMessage;
746 
747 		return _catchAll(path);
748 	}
749 
750 	/// When in website mode, you can use this to beautify the error message
751 	Document delegate(Throwable) _errorFunction;
752 }
753 
754 enum string deqFoot = "
755 	<script>delayedExecutionQueue.runCode = function() {
756 		var a = 0;
757 		for(a = 0; a < this.length; a++) {
758 			try {
759 				this[a]();
760 			} catch(e) {/*ignore*/}
761 		}
762 		this.length = 0;
763 	}; delayedExecutionQueue.runCode();</script>
764 ";
765 
766 /// Implement subclasses of this inside your main provider class to do a more object
767 /// oriented site.
768 class ApiObject : WebDotDBaseType {
769 	/* abstract this(ApiProvider parent, string identifier) */
770 
771 	/// Override this to make json out of this object
772 	JSONValue makeJsonValue() {
773 		return toJsonValue(null);
774 	}
775 }
776 
777 class DataFile : FileResource {
778 	this(string contentType, immutable(void)[] contents) {
779 		_contentType = contentType;
780 		_content = contents;
781 	}
782 
783 	private string _contentType;
784 	private immutable(void)[] _content;
785 
786 	string contentType() const {
787 		return _contentType;
788 	}
789 
790 	@property string filename() const { return null; }
791 
792 	immutable(ubyte)[] getData() const {
793 		return cast(immutable(ubyte)[]) _content;
794 	}
795 }
796 
797 /// Describes the info collected about your class
798 struct ReflectionInfo {
799 	immutable(FunctionInfo)*[string] functions; /// the methods
800 	EnumInfo[string] enums; /// .
801 	StructInfo[string] structs; ///.
802 	const(ReflectionInfo)*[string] objects; /// ApiObjects and ApiProviders
803 
804 	bool needsInstantiation; // internal - does the object exist or should it be new'd before referenced?
805 
806 	ApiProvider instantiation; // internal (for now) - reference to the actual object being described
807 
808 	WebDotDBaseType delegate(string) instantiate;
809 
810 	// the overall namespace
811 	string name; /// this is also used as the object name in the JS api
812 
813 
814 	// these might go away.
815 
816 	string defaultOutputFormat = "default";
817 	int versionOfOutputFormat = 2; // change this in your constructor if you still need the (deprecated) old behavior
818 	// bool apiMode = false; // no longer used - if format is json, apiMode behavior is assumed. if format is html, it is not.
819 				// FIXME: what if you want the data formatted server side, but still in a json envelope?
820 				// should add format-payload:
821 }
822 
823 /// describes an enum, iff based on int as the underlying type
824 struct EnumInfo {
825 	string name; ///.
826 	int[] values; ///.
827 	string[] names; ///.
828 }
829 
830 /// describes a plain data struct
831 struct StructInfo {
832 	string name; ///.
833 	// a struct is sort of like a function constructor...
834 	StructMemberInfo[] members; ///.
835 }
836 
837 ///.
838 struct StructMemberInfo {
839 	string name; ///.
840 	string staticType; ///.
841 	string defaultValue; ///.
842 }
843 
844 ///.
845 struct FunctionInfo {
846 	WrapperFunction dispatcher; /// this is the actual function called when a request comes to it - it turns a string[][string] into the actual args and formats the return value
847 
848 	const(ReflectionInfo)* parentObject;
849 
850 	// should I also offer dispatchers for other formats like Variant[]?
851 
852 	string name; /// the URL friendly name
853 	string originalName; /// the original name in code
854 
855 	//string uriPath;
856 
857 	Parameter[] parameters; ///.
858 
859 	string returnType; ///. static type to string
860 	bool returnTypeIsDocument; // internal used when wrapping
861 	bool returnTypeIsElement; // internal used when wrapping
862 
863 	bool requireHttps;
864 
865 	string genericContainerType = "default";
866 
867 	Document delegate(in string[string] args) createForm; /// This is used if you want a custom form - normally, on insufficient parameters, an automatic form is created. But if there's a functionName_Form method, it is used instead. FIXME: this used to work but not sure if it still does
868 }
869 
870 /// Function parameter
871 struct Parameter {
872 	string name; /// name (not always accurate)
873 	string value; // ???
874 
875 	string type; /// type of HTML element to create when asking
876 	string staticType; /// original type
877 	string validator; /// FIXME
878 
879 	bool hasDefault; /// if there was a default defined in the function
880 	string defaultValue; /// the default value defined in D, but as a string, if present
881 
882 	// for radio and select boxes
883 	string[] options; /// possible options for selects
884 	string[] optionValues; ///.
885 
886 	Element function(Document, string) makeFormElement;
887 }
888 
889 // these are all filthy hacks
890 
891 template isEnum(alias T) if(is(T)) {
892 	static if (is(T == enum))
893 		enum bool isEnum = true;
894 	else
895 		enum bool isEnum = false;
896 }
897 
898 template isEnum(alias T) if(!is(T)) {
899 	enum bool isEnum = false;
900 }
901 
902 // WTF, shouldn't is(T == xxx) already do this?
903 template isEnum(T) if(!is(T)) {
904 	enum bool isEnum = false;
905 }
906 
907 template isStruct(alias T) {
908 	static if (is(T == struct))
909 		enum bool isStruct = true;
910 	else
911 		enum bool isStruct = false;
912 }
913 
914 template isApiObject(alias T) {
915 	static if (is(T : ApiObject))
916 		enum bool isApiObject = true;
917 	else
918 		enum bool isApiObject = false;
919 }
920 
921 template isApiProvider(alias T) {
922 	static if (is(T : ApiProvider))
923 		enum bool isApiProvider = true;
924 	else
925 		enum bool isApiProvider = false;
926 }
927 
928 template Passthrough(T) {
929 	T Passthrough;
930 }
931 
932 template PassthroughType(T) {
933 	alias T PassthroughType;
934 }
935 
936 // sets up the reflection object. now called automatically so you probably don't have to mess with it
937 immutable(ReflectionInfo*) prepareReflection(alias PM)(PM instantiation) if(is(PM : ApiProvider) || is(PM: ApiObject) ) {
938 	return prepareReflectionImpl!(PM, PM)(instantiation);
939 }
940 
941 // FIXME: this doubles the compile time and can add megabytes to the executable.
942 immutable(ReflectionInfo*) prepareReflectionImpl(alias PM, alias Parent)(Parent instantiation)
943 	if(is(PM : WebDotDBaseType) && is(Parent : ApiProvider))
944 {
945 	assert(instantiation !is null);
946 
947 	ReflectionInfo* reflection = new ReflectionInfo;
948 	reflection.name = PM.stringof;
949 
950 	static if(is(PM: ApiObject)) {
951 		reflection.needsInstantiation = true;
952 		reflection.instantiate = delegate WebDotDBaseType(string i) {
953 			auto n = new PM(instantiation, i);
954 			return n;
955 		};
956 	} else {
957 		reflection.instantiation = instantiation;
958 
959 		static if(!is(PM : BuiltInFunctions)) {
960 			auto builtins = new BuiltInFunctions(instantiation, reflection);
961 			instantiation.builtInFunctions = builtins;
962 			foreach(k, v; builtins.reflection.functions)
963 				reflection.functions["builtin." ~ k] = v;
964 		}
965 	}
966 
967 	static if(is(PM : ApiProvider)) {{ // double because I want a new scope
968 		auto f = new FunctionInfo;
969 		f.parentObject = reflection;
970 		f.dispatcher = generateWrapper!(PM, "_defaultPage", PM._defaultPage)(reflection, instantiation);
971 		f.returnTypeIsDocument = true;
972 		reflection.functions["/"] = cast(immutable) f;
973 
974 		/+
975 		// catchAll here too
976 		f = new FunctionInfo;
977 		f.parentObject = reflection;
978 		f.dispatcher = generateWrapper!(PM, "_catchAll", PM._catchAll)(reflection, instantiation);
979 		f.returnTypeIsDocument = true;
980 		reflection.functions["/_catchAll"] = cast(immutable) f;
981 		+/
982 	}}
983 
984 	// derivedMembers is changed from allMembers
985 
986 	// FIXME: this seems to do the right thing with inheritance.... but I don't really understand why. Isn't the override done first, and thus overwritten by the base class version? you know maybe it is all because it still does a vtable lookup on the real object. eh idk, just confirm what it does eventually
987 	foreach(Class; TypeTuple!(PM, BaseClassesTuple!(PM)))
988 	static if((is(Class : ApiProvider) && !is(Class == ApiProvider)) || is(Class : ApiObject))
989 	foreach(member; __traits(derivedMembers, Class)) { // we do derived on a base class loop because we don't want interfaces (OR DO WE? seriously idk) and we definitely don't want stuff from Object, ApiProvider itself is out too but that might change.
990 	static if(member[0] != '_') {
991 		// FIXME: the filthiest of all hacks...
992 		static if(!__traits(compiles,
993 			!is(typeof(__traits(getMember, Class, member)) == function) &&
994 			isEnum!(__traits(getMember, Class, member))))
995 		continue; // must be a data member or something...
996 		else
997 		// DONE WITH FILTHIEST OF ALL HACKS
998 
999 		//if(member.length == 0)
1000 		//	continue;
1001 		static if(
1002 			!is(typeof(__traits(getMember, Class, member)) == function) &&
1003 			isEnum!(__traits(getMember, Class, member))
1004 		) {
1005 			EnumInfo i;
1006 			i.name = member;
1007 			foreach(m; __traits(allMembers, __traits(getMember, Class, member))) {
1008 				i.names  ~= m;
1009 				i.values ~= cast(int) __traits(getMember, __traits(getMember, Class, member), m);
1010 			}
1011 
1012 			reflection.enums[member] = i;
1013 
1014 		} else static if(
1015 			!is(typeof(__traits(getMember, Class, member)) == function) &&
1016 			isStruct!(__traits(getMember, Class, member))
1017 		) {
1018 			StructInfo i;
1019 			i.name = member;
1020 
1021 			typeof(Passthrough!(__traits(getMember, Class, member))) s;
1022 			foreach(idx, m; s.tupleof) {
1023 				StructMemberInfo mem;
1024 
1025 				mem.name = s.tupleof[idx].stringof[2..$];
1026 				mem.staticType = typeof(m).stringof;
1027 
1028 				mem.defaultValue = null; // FIXME
1029 
1030 				i.members ~= mem;
1031 			}
1032 
1033 			reflection.structs[member] = i;
1034 		} else static if(
1035 			is(typeof(__traits(getMember, Class, member)) == function)
1036 				&& __traits(getProtection, __traits(getMember, Class, member)) == "export"
1037 				&&
1038 				(
1039 				member.length < 5 ||
1040 				(
1041 				member[$ - 5 .. $] != "_Page" &&
1042 				member[$ - 5 .. $] != "_Form") &&
1043 				!(member.length > 16 && member[$ - 16 .. $] == "_PermissionCheck")
1044 		)) {
1045 			FunctionInfo* f = new FunctionInfo;
1046 			ParameterTypeTuple!(__traits(getMember, Class, member)) fargs;
1047 
1048 			f.requireHttps = hasAnnotation!(__traits(getMember, Class, member), RequireHttps);
1049 			f.returnType = ReturnType!(__traits(getMember, Class, member)).stringof;
1050 			f.returnTypeIsDocument = is(ReturnType!(__traits(getMember, Class, member)) : Document);
1051 			f.returnTypeIsElement = is(ReturnType!(__traits(getMember, Class, member)) : Element);
1052 			static if(hasValueAnnotation!(__traits(getMember, Class, member), GenericContainerType))
1053 				f.genericContainerType = getAnnotation!(__traits(getMember, Class, member), GenericContainerType).type;
1054 
1055 			f.parentObject = reflection;
1056 
1057 			f.name = toUrlName(member);
1058 			f.originalName = member;
1059 
1060 			assert(instantiation !is null);
1061 			f.dispatcher = generateWrapper!(Class, member, __traits(getMember, Class, member))(reflection, instantiation);
1062 
1063 			//f.uriPath = f.originalName;
1064 
1065 			auto namesAndDefaults = parameterInfoImpl!(__traits(getMember, Class, member));
1066 			auto names = namesAndDefaults[0];
1067 			auto defaults = namesAndDefaults[1];
1068 			assert(names.length == defaults.length);
1069 
1070 			foreach(idx, param; fargs) {
1071 				if(idx >= names.length)
1072 					assert(0, to!string(idx) ~ " " ~ to!string(names));
1073 
1074 				Parameter p = reflectParam!(typeof(param))();
1075 
1076 				p.name = names[idx];
1077 				auto d = defaults[idx];
1078 				p.defaultValue = d == "null" ? "" : d;
1079 				p.hasDefault = d.length > 0;
1080 
1081 				f.parameters ~= p;
1082 			}
1083 
1084 			static if(__traits(hasMember, Class, member ~ "_Form")) {
1085 				f.createForm = &__traits(getMember, instantiation, member ~ "_Form");
1086 			}
1087 
1088 			reflection.functions[f.name] = cast(immutable) (f);
1089 			// also offer the original name if it doesn't
1090 			// conflict
1091 			//if(f.originalName !in reflection.functions)
1092 			reflection.functions[f.originalName] = cast(immutable) (f);
1093 		}
1094 		else static if(
1095 			!is(typeof(__traits(getMember, Class, member)) == function) &&
1096 			isApiObject!(__traits(getMember, Class, member))
1097 		) {
1098 			reflection.objects[member] = prepareReflectionImpl!(
1099 				__traits(getMember, Class, member), Parent)
1100 				(instantiation);
1101 		} else static if( // child ApiProviders are like child modules
1102 			!is(typeof(__traits(getMember, Class, member)) == function) &&
1103 			isApiProvider!(__traits(getMember, Class, member))
1104 		) {
1105 			PassthroughType!(__traits(getMember, Class, member)) i;
1106 			static if(__traits(compiles, i = new typeof(i)(instantiation)))
1107 				i = new typeof(i)(instantiation);
1108 			else
1109 				i = new typeof(i)();
1110 			auto r = prepareReflectionImpl!(__traits(getMember, Class, member), typeof(i))(i);
1111 			i.reflection = cast(immutable) r;
1112 			reflection.objects[member] = r;
1113 			if(toLower(member) !in reflection.objects) // web filenames are often lowercase too
1114 				reflection.objects[member.toLower] = r;
1115 		}
1116 	}
1117 	}
1118 
1119 	return cast(immutable) reflection;
1120 }
1121 
1122 Parameter reflectParam(param)() {
1123 	Parameter p;
1124 
1125 	p.staticType = param.stringof;
1126 
1127 	static if( __traits(compiles, p.makeFormElement = &(param.makeFormElement))) {
1128 		p.makeFormElement = &(param.makeFormElement);
1129 	} else static if( __traits(compiles, PM.makeFormElement!(param)(null, null))) {
1130 		alias PM.makeFormElement!(param) LOL;
1131 		p.makeFormElement = &LOL;
1132 	} else static if( is( param == enum )) {
1133 		p.type = "select";
1134 
1135 		foreach(opt; __traits(allMembers, param)) {
1136 			p.options ~= opt;
1137 			p.optionValues ~= to!string(__traits(getMember, param, opt));
1138 		}
1139 	} else static if (is(param == bool)) {
1140 		p.type = "checkbox";
1141 	} else static if (is(Unqual!(param) == Cgi.UploadedFile)) {
1142 		p.type = "file";
1143 	} else static if(is(Unqual!(param) == Text)) {
1144 		p.type = "textarea";
1145 	} else {
1146 		p.type = "text";
1147 	}
1148 
1149 	return p;
1150 }
1151 
1152 struct CallInfo {
1153 	string objectIdentifier;
1154 	immutable(FunctionInfo)* func;
1155 	void delegate(Document)[] postProcessors;
1156 }
1157 
1158 class NonCanonicalUrlException : Exception {
1159 	this(CanonicalUrlOption option, string properUrl = null) {
1160 		this.howToFix = option;
1161 		this.properUrl = properUrl;
1162 		super("The given URL needs this fix: " ~ to!string(option) ~ " " ~ properUrl);
1163 	}
1164 
1165 	CanonicalUrlOption howToFix;
1166 	string properUrl;
1167 }
1168 
1169 enum CanonicalUrlOption {
1170 	cutTrailingSlash,
1171 	addTrailingSlash
1172 }
1173 
1174 
1175 CallInfo parseUrl(in ReflectionInfo* reflection, string url, string defaultFunction, in bool hasTrailingSlash) {
1176 	CallInfo info;
1177 
1178 	if(url.length && url[0] == '/')
1179 		url = url[1 .. $];
1180 
1181 	if(reflection.needsInstantiation) {
1182 		// FIXME: support object identifiers that span more than one slash... maybe
1183 		auto idx = url.indexOf("/");
1184 		if(idx != -1) {
1185 			info.objectIdentifier = url[0 .. idx];
1186 			url = url[idx + 1 .. $];
1187 		} else {
1188 			info.objectIdentifier = url;
1189 			url = null;
1190 		}
1191 	}
1192 
1193 	string name;
1194 	auto idx = url.indexOf("/");
1195 	if(idx != -1) {
1196 		name = url[0 .. idx];
1197 		url = url[idx + 1 .. $];
1198 	} else {
1199 		name = url;
1200 		url = null;
1201 	}
1202 
1203 	bool usingDefault = false;
1204 	if(name.length == 0) {
1205 		name = defaultFunction;
1206 		usingDefault = true;
1207 		if(name !in reflection.functions)
1208 			name = "/"; // should call _defaultPage
1209 	}
1210 
1211 	if(reflection.instantiation !is null)
1212 		info.postProcessors ~= &((cast()(reflection.instantiation))._postProcess);
1213 
1214 	if(name in reflection.functions) {
1215 		info.func = reflection.functions[name];
1216 
1217 		// if we're using a default thing, we need as slash on the end so relative links work
1218 		if(usingDefault) {
1219 			if(!hasTrailingSlash)
1220 				throw new NonCanonicalUrlException(CanonicalUrlOption.addTrailingSlash);
1221 		} else {
1222 			if(hasTrailingSlash)
1223 				throw new NonCanonicalUrlException(CanonicalUrlOption.cutTrailingSlash);
1224 		}
1225 	}
1226 
1227 	if(name in reflection.objects) {
1228 		info = parseUrl(reflection.objects[name], url, defaultFunction, hasTrailingSlash);
1229 	}
1230 
1231 	return info;
1232 }
1233 
1234 /// If you're not using FancyMain, this is the go-to function to do most the work.
1235 /// instantiation should be an object of your ApiProvider type.
1236 /// pathInfoStartingPoint is used to make a slice of it, incase you already consumed part of the path info before you called this.
1237 
1238 void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint = 0, bool handleAllExceptions = true, Session session = null) if(is(Provider : ApiProvider)) {
1239 	assert(instantiation !is null);
1240 
1241 	instantiation.cgi = cgi;
1242 
1243 	if(instantiation.reflection is null) {
1244 		instantiation.reflection = prepareReflection!(Provider)(instantiation);
1245 		instantiation._initialize();
1246 		// FIXME: what about initializing child objects?
1247 	}
1248 
1249 	auto reflection = instantiation.reflection;
1250 	instantiation._baseUrl = cgi.logicalScriptName ~ cgi.pathInfo[0 .. pathInfoStartingPoint];
1251 
1252 	// everything assumes the url isn't empty...
1253 	if(cgi.pathInfo.length < pathInfoStartingPoint + 1) {
1254 		cgi.setResponseLocation(cgi.logicalScriptName ~ cgi.pathInfo ~ "/" ~ (cgi.queryString.length ? "?" ~ cgi.queryString : ""));
1255 		return;
1256 	}
1257 
1258 	// kinda a hack, but this kind of thing should be available anyway
1259 	string funName = cgi.pathInfo[pathInfoStartingPoint + 1..$];
1260 	if(funName == "functions.js") {
1261 		cgi.gzipResponse = true;
1262 		cgi.setResponseContentType("text/javascript");
1263 		cgi.setCache(true);
1264 		cgi.write(makeJavascriptApi(reflection, replace(cast(string) cgi.pathInfo, "functions.js", "")), true);
1265 		cgi.close();
1266 		return;
1267 	}
1268 	if(funName == "styles.css") {
1269 		cgi.gzipResponse = true;
1270 		cgi.setResponseContentType("text/css");
1271 		cgi.setCache(true);
1272 		cgi.write(instantiation.stylesheet(), true);
1273 		cgi.close();
1274 		return;
1275 	}
1276 
1277 	CallInfo info;
1278 
1279 	try
1280 		info = parseUrl(reflection, cgi.pathInfo[pathInfoStartingPoint + 1 .. $], to!string(cgi.requestMethod), cgi.pathInfo[$-1] == '/');
1281 	catch(NonCanonicalUrlException e) {
1282 		final switch(e.howToFix) {
1283 			case CanonicalUrlOption.cutTrailingSlash:
1284 				cgi.setResponseLocation(cgi.logicalScriptName ~ cgi.pathInfo[0 .. $ - 1] ~
1285 					(cgi.queryString.length ? ("?" ~ cgi.queryString) : ""));
1286 			break;
1287 			case CanonicalUrlOption.addTrailingSlash:
1288 				cgi.setResponseLocation(cgi.logicalScriptName ~ cgi.pathInfo ~ "/" ~
1289 					(cgi.queryString.length ? ("?" ~ cgi.queryString) : ""));
1290 			break;
1291 		}
1292 
1293 		return;
1294 	}
1295 
1296 	auto fun = info.func;
1297 	auto instantiator = info.objectIdentifier;
1298 
1299 	Envelope result;
1300 	result.userData = cgi.request("passedThroughUserData");
1301 
1302 	auto envelopeFormat = cgi.request("envelopeFormat", "document");
1303 
1304 	WebDotDBaseType base = instantiation;
1305 	WebDotDBaseType realObject = instantiation;
1306 	if(instantiator.length == 0)
1307 	if(fun !is null && fun.parentObject !is null && fun.parentObject.instantiation !is null)
1308 		realObject = cast() fun.parentObject.instantiation; // casting away transitive immutable...
1309 
1310 	// FIXME
1311 	if(cgi.pathInfo.indexOf("builtin.") != -1 && instantiation.builtInFunctions !is null)
1312 		base = instantiation.builtInFunctions;
1313 
1314 	if(base !is realObject) {
1315 		auto hack1 = cast(ApiProvider) base;
1316 		auto hack2 = cast(ApiProvider) realObject;
1317 
1318 		if(hack1 !is null && hack2 !is null && hack2.session is null)
1319 			hack2.session = hack1.session;
1320 	}
1321 
1322 	bool returnedHoldsADocument = false;
1323 	string[][string] want;
1324 	string format, secondaryFormat;
1325 	void delegate(Document d) moreProcessing;
1326 	WrapperReturn ret;
1327 
1328 	try {
1329 		if(fun is null) {
1330 			auto d = instantiation._catchallEntry(
1331 				cgi.pathInfo[pathInfoStartingPoint + 1..$],
1332 				funName,
1333 				"");
1334 
1335 			result.success = true;
1336 
1337 			if(d !is null) {
1338 				auto doc = cast(Document) d;
1339 				if(doc)
1340 					instantiation._postProcess(doc);
1341 
1342 				cgi.setResponseContentType(d.contentType());
1343 				cgi.write(d.getData(), true);
1344 			}
1345 
1346 			// we did everything we need above...
1347 			envelopeFormat = "no-processing";
1348 			goto do_nothing_else;
1349 		}
1350 
1351 		assert(fun !is null);
1352 		assert(fun.dispatcher !is null);
1353 		assert(cgi !is null);
1354 
1355 		if(fun.requireHttps && !cgi.https) {
1356 			cgi.setResponseLocation("https://" ~ cgi.host ~ cgi.logicalScriptName ~ cgi.pathInfo ~
1357 				(cgi.queryString.length ? "?" : "") ~ cgi.queryString);
1358 			envelopeFormat = "no-processing";
1359 			goto do_nothing_else;
1360 		}
1361 
1362 		if(instantiator.length) {
1363 			assert(fun !is null);
1364 			assert(fun.parentObject !is null);
1365 			assert(fun.parentObject.instantiate !is null);
1366 			realObject = fun.parentObject.instantiate(instantiator);
1367 		}
1368 
1369 
1370 		result.type = fun.returnType;
1371 
1372 		format = cgi.request("format", reflection.defaultOutputFormat);
1373 		secondaryFormat = cgi.request("secondaryFormat", "");
1374 		if(secondaryFormat.length == 0) secondaryFormat = null;
1375 
1376 		{ // scope so we can goto over this
1377 		JSONValue res;
1378 
1379 		// FIXME: hackalicious garbage. kill.
1380 		want = cast(string[][string]) (cgi.requestMethod == Cgi.RequestMethod.POST ? cgi.postArray : cgi.getArray);
1381 		version(fb_inside_hack) {
1382 			if(cgi.referrer.indexOf("apps.facebook.com") != -1) {
1383 				auto idx = cgi.referrer.indexOf("?");
1384 				if(idx != -1 && cgi.referrer[idx + 1 .. $] != cgi.queryString) {
1385 					// so fucking broken
1386 					cgi.setResponseLocation(cgi.logicalScriptName ~ cgi.pathInfo ~ cgi.referrer[idx .. $]);
1387 					return;
1388 				}
1389 			}
1390 			if(cgi.requestMethod == Cgi.RequestMethod.POST) {
1391 				foreach(k, v; cgi.getArray)
1392 					want[k] = cast(string[]) v;
1393 				foreach(k, v; cgi.postArray)
1394 					want[k] = cast(string[]) v;
1395 			}
1396 		}
1397 
1398 		realObject.cgi = cgi;
1399 		ret = fun.dispatcher(cgi, realObject, want, format, secondaryFormat);
1400 		if(ret.completed) {
1401 			envelopeFormat = "no-processing";
1402 			goto do_nothing_else;
1403 		}
1404 
1405 		res = ret.value;
1406 
1407 				//if(cgi)
1408 				//	cgi.setResponseContentType("application/json");
1409 		result.success = true;
1410 		result.result = res;
1411 		}
1412 
1413 		do_nothing_else: {}
1414 
1415 	}
1416 	catch (Throwable e) {
1417 		result.success = false;
1418 		result.errorMessage = e.msg;
1419 		result.type = e.classinfo.name;
1420 		debug result.dFullString = e.toString();
1421 
1422 		realObject.exceptionExaminer(e);
1423 
1424 		if(envelopeFormat == "document" || envelopeFormat == "html") {
1425 			if(auto fve = cast(FormValidationException) e) {
1426 				auto thing = fve.formFunction;
1427 				if(thing is null)
1428 					thing = fun;
1429 				fun = thing;
1430 				ret = fun.dispatcher(cgi, realObject, want, format, secondaryFormat);
1431 				result.result = ret.value;
1432 
1433 				if(fun.returnTypeIsDocument)
1434 					returnedHoldsADocument = true; // we don't replace the success flag, so this ensures no double document
1435 
1436 				moreProcessing = (Document d) {
1437 					Form f;
1438 					if(fve.getForm !is null)
1439 						f = fve.getForm(d);
1440 					else
1441 						f = d.requireSelector!Form("form");
1442 
1443 					foreach(k, v; want)
1444 						f.setValue(k, v[$-1]);
1445 
1446 					foreach(idx, failure; fve.failed) {
1447 						auto ele = f.requireSelector("[name=\""~failure~"\"]");
1448 						ele.addClass("validation-failed");
1449 						ele.dataset.validationMessage = fve.messagesForUser[idx];
1450 						ele.parentNode.addChild("span", fve.messagesForUser[idx]).addClass("validation-message");
1451 					}
1452 
1453 					if(fve.postProcessor !is null)
1454 						fve.postProcessor(d, f, fve);
1455 				};
1456 			} else if(auto ipe = cast(InsufficientParametersException) e) {
1457 				assert(fun !is null);
1458 				Form form;
1459 				 if(fun.createForm !is null) {
1460 					// go ahead and use it to make the form page
1461 					auto doc = fun.createForm(cgi.requestMethod == Cgi.RequestMethod.POST ? cgi.post : cgi.get);
1462 
1463 					form = doc.requireSelector!Form("form.created-by-create-form, form.automatic-form, form");
1464 				} else {
1465 					Parameter[] params = (cast(Parameter[])fun.parameters).dup;
1466 					foreach(i, p; fun.parameters) {
1467 						string value = "";
1468 						if(p.name in cgi.get)
1469 							value = cgi.get[p.name];
1470 						if(p.name in cgi.post)
1471 							value = cgi.post[p.name];
1472 						params[i].value = value;
1473 					}
1474 
1475 					form = createAutomaticForm(new Document("<html></html>", true, true), fun);// params, beautify(fun.originalName));
1476 					foreach(k, v; cgi.get)
1477 						form.setValue(k, v);
1478 
1479 					instantiation.addCsrfTokens(form);
1480 					form.setValue("envelopeFormat", envelopeFormat);
1481 
1482 					auto n = form.getElementById("function-name");
1483 					if(n)
1484 						n.innerText = beautify(fun.originalName);
1485 
1486 					// FIXME: I like having something, but it needs to not
1487 					// show it on the first user load.
1488 					// form.prependChild(Element.make("p", ipe.msg));
1489 				}
1490 
1491 				assert(form !is null);
1492 
1493 				foreach(k, v; cgi.get)
1494 					form.setValue(k, v); // carry what we have for params over
1495 				foreach(k, v; cgi.post)
1496 					form.setValue(k, v); // carry what we have for params over
1497 
1498 				result.result.str = form.toString();
1499 			} else {
1500 				auto fourOhFour = cast(NoSuchPageException) e;
1501 				if(fourOhFour !is null)
1502 					cgi.setResponseStatus("404 File Not Found");
1503 
1504 				if(instantiation._errorFunction !is null) {
1505 					auto document = instantiation._errorFunction(e);
1506 					if(document is null)
1507 						goto gotnull;
1508 					result.result.str = (document.toString());
1509 					returnedHoldsADocument = true;
1510 				} else {
1511 				gotnull:
1512 					if(!handleAllExceptions) {
1513 						envelopeFormat = "internal";
1514 						throw e; // pass it up the chain
1515 					}
1516 					auto code = Element.make("div");
1517 					code.addClass("exception-error-message");
1518 					import arsd.characterencodings;
1519 					code.addChild("p", convertToUtf8Lossy(cast(immutable(ubyte)[]) e.msg, "utf8"));
1520 					debug code.addChild("pre", convertToUtf8Lossy(cast(immutable(ubyte)[]) e.toString(), "utf8"));
1521 
1522 					result.result.str = (code.toString());
1523 				}
1524 			}
1525 		}
1526 	} finally {
1527 		// the function must have done its own thing; we need to quit or else it will trigger an assert down here
1528 		version(webd_cookie_sessions) {
1529 			if(cgi.canOutputHeaders() && session !is null)
1530 				session.commit();
1531 		}
1532 		if(!cgi.isClosed())
1533 		switch(envelopeFormat) {
1534 			case "no-processing":
1535 			case "internal":
1536 				break;
1537 			case "redirect":
1538 				auto redirect = cgi.request("_arsd_redirect_location", cgi.referrer);
1539 
1540 				// FIXME: is this safe? it'd make XSS super easy
1541 				// add result to url
1542 
1543 				if(!result.success)
1544 					goto case "none";
1545 
1546 				cgi.setResponseLocation(redirect, false);
1547 			break;
1548 			case "json":
1549 			case "json_enable_redirects":
1550 				// this makes firefox ugly
1551 				//cgi.setResponseContentType("application/json");
1552 				auto json = toJsonValue(result);
1553 				cgi.write(toJSON(json), true);
1554 			break;
1555 			case "script":
1556 			case "jsonp":
1557 				bool securityPass = false;
1558 				version(web_d_unrestricted_jsonp) {
1559 					// unrestricted is opt-in because i worry about fetching user info from across sites
1560 					securityPass = true;
1561 				} else {
1562 					// we check this on both get and post to ensure they can't fetch user private data cross domain.
1563 					auto hack1 = cast(ApiProvider) base;
1564 					if(hack1)
1565 						securityPass = hack1.isCsrfTokenCorrect();
1566 				}
1567 
1568 				if(securityPass) {
1569 					if(envelopeFormat == "script")
1570 						cgi.setResponseContentType("text/html");
1571 					else
1572 						cgi.setResponseContentType("application/javascript");
1573 
1574 					auto json = cgi.request("jsonp", "throw new Error") ~ "(" ~ toJson(result) ~ ");";
1575 
1576 					if(envelopeFormat == "script")
1577 						json = "<script type=\"text/javascript\">" ~ json ~ "</script>";
1578 					cgi.write(json, true);
1579 				} else {
1580 					// if the security check fails, you just don't get anything at all data wise...
1581 					cgi.setResponseStatus("403 Forbidden");
1582 				}
1583 			break;
1584 			case "csv":
1585 				cgi.setResponseContentType("text/csv");
1586 				cgi.header("Content-Disposition: attachment; filename=\"export.csv\"");
1587 
1588 				if(result.result.type == JSONType..string) {
1589 					cgi.write(result.result.str, true);
1590 				} else assert(0);
1591 			break;
1592 			case "download":
1593     				cgi.header("Content-Disposition: attachment; filename=\"data.csv\"");
1594 			goto case;
1595 			case "none":
1596 				cgi.setResponseContentType("text/plain");
1597 
1598 				if(result.success) {
1599 					if(result.result.type == JSONType..string) {
1600 						cgi.write(result.result.str, true);
1601 					} else {
1602 						cgi.write(toJSON(result.result), true);
1603 					}
1604 				} else {
1605 					cgi.write(result.errorMessage, true);
1606 				}
1607 			break;
1608 			case "document":
1609 			case "html":
1610 			default:
1611 				cgi.setResponseContentType("text/html; charset=utf-8");
1612 
1613 				if(result.result.type == JSONType..string) {
1614 					auto returned = result.result.str;
1615 
1616 					if(envelopeFormat != "html") {
1617 						Document document;
1618 										// this big condition means the returned holds a document too
1619 						if(returnedHoldsADocument || (result.success && fun !is null && fun.returnTypeIsDocument && returned.length)) {
1620 							// probably not super efficient...
1621 							document = new TemplatedDocument(returned);
1622 						} else {
1623 							// auto e = instantiation._getGenericContainer();
1624 							Element e;
1625 							auto hack = cast(ApiProvider) realObject;
1626 							if(hack !is null)
1627 								e = hack._getGenericContainer(fun is null ? "default" : fun.genericContainerType);
1628 							else
1629 								e = instantiation._getGenericContainer(fun is null ? "default" : fun.genericContainerType);
1630 
1631 
1632 							document = e.parentDocument;
1633 							//assert(0, document.toString());
1634 							// FIXME: a wee bit slow, esp if func return element
1635 							e.innerHTML = returned;
1636 							if(fun !is null)
1637 							e.setAttribute("data-from-function", fun.originalName);
1638 						}
1639 
1640 						if(document !is null) {
1641 							if(envelopeFormat == "document") {
1642 								// forming a nice chain here...
1643 								// FIXME: this isn't actually a nice chain!
1644 								bool[void delegate(Document)] run;
1645 
1646 								auto postProcessors = info.postProcessors;
1647 								if(base !is instantiation)
1648 									postProcessors ~= &(instantiation._postProcess);
1649 								if(realObject !is null)
1650 									postProcessors ~= &(realObject._postProcess);
1651 								postProcessors ~= &(base._postProcess);
1652 
1653 								// FIXME: cgi is sometimes null in te post processor... wtf
1654 								foreach(pp; postProcessors) {
1655 									if(pp in run)
1656 										continue;
1657 									run[pp] = true;
1658 									pp(document);
1659 								}
1660 							}
1661 
1662 							if(moreProcessing !is null)
1663 								moreProcessing(document);
1664 
1665 							returned = document.toString;
1666 						}
1667 					}
1668 
1669 					cgi.write(returned, true);
1670 				} else
1671 					cgi.write(htmlEntitiesEncode(toJSON(result.result)), true);
1672 			break;
1673 		}
1674 
1675 		if(envelopeFormat != "internal")
1676 			cgi.close();
1677 	}
1678 }
1679 
1680 class BuiltInFunctions : ApiProvider {
1681 	const(ReflectionInfo)* workingFor;
1682 	ApiProvider basedOn;
1683 	this(ApiProvider basedOn, in ReflectionInfo* other) {
1684 		this.basedOn = basedOn;
1685 		workingFor = other;
1686 		if(this.reflection is null)
1687 			this.reflection = prepareReflection!(BuiltInFunctions)(this);
1688 
1689 		assert(this.reflection !is null);
1690 	}
1691 
1692 	Form getAutomaticForm(string method) {
1693 		if(method !in workingFor.functions)
1694 			throw new Exception("no such method " ~ method);
1695 		auto f = workingFor.functions[method];
1696 
1697 		Form form;
1698 		if(f.createForm !is null) {
1699 			form = f.createForm(null).requireSelector!Form("form");
1700 		} else
1701 			form = createAutomaticForm(new Document("<html></html>", true, true), f);
1702 		auto idx = basedOn.cgi.requestUri.indexOf("builtin.getAutomaticForm");
1703 		if(idx == -1)
1704 			idx = basedOn.cgi.requestUri.indexOf("builtin.get-automatic-form");
1705 		assert(idx != -1);
1706 		form.action = basedOn.cgi.requestUri[0 .. idx] ~ form.action; // make sure it works across the site
1707 
1708 		return form;
1709 	}
1710 }
1711 
1712 	// what about some built in functions?
1713 		/+
1714 		// Built-ins
1715 		// Basic integer operations
1716 		builtin.opAdd
1717 		builtin.opSub
1718 		builtin.opMul
1719 		builtin.opDiv
1720 
1721 		// Basic array operations
1722 		builtin.opConcat 			// use to combine calls easily
1723 		builtin.opIndex
1724 		builtin.opSlice
1725 		builtin.length
1726 
1727 		// Basic floating point operations
1728 		builtin.round
1729 		builtin.floor
1730 		builtin.ceil
1731 
1732 		// Basic object operations
1733 		builtin.getMember
1734 
1735 		// Basic functional operations
1736 		builtin.filter 				// use to slice down on stuff to transfer
1737 		builtin.map 				// call a server function on a whole array
1738 		builtin.reduce
1739 
1740 		// Access to the html items
1741 		builtin.getAutomaticForm(method)
1742 		+/
1743 
1744 
1745 /// fancier wrapper to cgi.d's GenericMain - does most the work for you, so you can just write your class and be done with it
1746 /// Note it creates a session for you too, and will write to the disk - a csrf token. Compile with -version=no_automatic_session
1747 /// to disable this.
1748 mixin template FancyMain(T, Args...) {
1749 	mixin CustomCgiFancyMain!(Cgi, T, Args);
1750 }
1751 
1752 /// Like FancyMain, but you can pass a custom subclass of Cgi
1753 mixin template CustomCgiFancyMain(CustomCgi, T, Args...) if(is(CustomCgi : Cgi)) {
1754 	void fancyMainFunction(Cgi cgi) { //string[] args) {
1755 		version(catch_segfault) {
1756 			import etc.linux.memoryerror;
1757 			// NOTE: this is private on stock dmd right now, just
1758 			// open the file (src/druntime/import/etc/linux/memoryerror.d) and make it public
1759 			registerMemoryErrorHandler();
1760 		}
1761 
1762 //		auto cgi = new Cgi;
1763 
1764 		// there must be a trailing slash for relative links..
1765 		if(cgi.pathInfo.length == 0) {
1766 			cgi.setResponseLocation(cgi.requestUri ~ "/");
1767 			cgi.close();
1768 			return;
1769 		}
1770 
1771 		// FIXME: won't work for multiple objects
1772 		T instantiation = new T();
1773 		instantiation.cgi = cgi;
1774 		auto reflection = prepareReflection!(T)(instantiation);
1775 
1776 		version(no_automatic_session) {}
1777 		else {
1778 			auto session = new Session(cgi);
1779 			version(webd_cookie_sessions) { } // cookies have to be outputted before here since they are headers
1780 			else {
1781 				scope(exit) {
1782 					// I only commit automatically on non-bots to avoid writing too many files
1783 					// looking for bot should catch most them without false positives...
1784 					// empty user agent is prolly a tester too so i'll let that slide
1785 					if(cgi.userAgent.length && cgi.userAgent.toLower.indexOf("bot") == -1)
1786 						session.commit();
1787 				}
1788 			}
1789 			instantiation.session = session;
1790 		}
1791 
1792 		version(webd_cookie_sessions)
1793 			run(cgi, instantiation, instantiation.pathInfoStartingPoint, true, session);
1794 		else
1795 			run(cgi, instantiation, instantiation.pathInfoStartingPoint);
1796 
1797 /+
1798 		if(args.length > 1) {
1799 			string[string][] namedArgs;
1800 			foreach(arg; args[2..$]) {
1801 				auto lol = arg.indexOf("=");
1802 				if(lol == -1)
1803 					throw new Exception("use named args for all params");
1804 				//namedArgs[arg[0..lol]] = arg[lol+1..$]; // FIXME
1805 			}
1806 
1807 			if(!(args[1] in reflection.functions)) {
1808 				throw new Exception("No such function");
1809 			}
1810 
1811 			//writefln("%s", reflection.functions[args[1]].dispatcher(null, namedArgs, "string"));
1812 		} else {
1813 +/
1814 //		}
1815 	}
1816 
1817 	mixin CustomCgiMain!(CustomCgi, fancyMainFunction, Args);
1818 }
1819 
1820 /// Given a function from reflection, build a form to ask for it's params
1821 Form createAutomaticForm(Document document, in FunctionInfo* func, string[string] fieldTypes = null) {
1822 	return createAutomaticForm(document, func.name, func.parameters, beautify(func.originalName), "POST", fieldTypes);
1823 }
1824 
1825 /// ditto
1826 // FIXME: should there be something to prevent the pre-filled options from url? It's convenient but
1827 // someone might use it to trick people into submitting badness too. I'm leaning toward meh.
1828 Form createAutomaticForm(Document document, string action, in Parameter[] parameters, string submitText = "Submit", string method = "POST", string[string] fieldTypes = null) {
1829 	auto form = cast(Form) Element.make("form");
1830 	form.parentDocument = document;
1831 	form.addClass("automatic-form");
1832 
1833 	form.action = action;
1834 
1835 	assert(form !is null);
1836 	form.method = method;
1837 
1838 
1839 	auto fieldset = form.addChild("fieldset");
1840 	auto legend = fieldset.addChild("legend", submitText);
1841 
1842 	auto table = cast(Table) fieldset.addChild("table");
1843 	assert(table !is null);
1844 
1845 	table.addChild("tbody");
1846 
1847 	static int count = 0;
1848 
1849 	foreach(param; parameters) {
1850 		Element input;
1851 		string type;
1852 
1853 		if(param.makeFormElement !is null) {
1854 			input = param.makeFormElement(document, param.name);
1855 			goto gotelement;
1856 		}
1857 
1858 		type = param.type;
1859 		if(param.name in fieldTypes)
1860 			type = fieldTypes[param.name];
1861 
1862 		if(type == "select") {
1863 			input = Element.make("select");
1864 
1865 			foreach(idx, opt; param.options) {
1866 				auto option = Element.make("option");
1867 				option.name = opt;
1868 				option.value = param.optionValues[idx];
1869 
1870 				option.innerText = beautify(opt);
1871 
1872 				if(option.value == param.value)
1873 					option.selected = "selected";
1874 
1875 				input.appendChild(option);
1876 			}
1877 
1878 			input.name = param.name;
1879 		} else if (type == "radio") {
1880 			assert(0, "FIXME");
1881 		} else {
1882 			if(type.startsWith("textarea")) {
1883 				input = Element.make("textarea");
1884 				input.name = param.name;
1885 				input.innerText = param.value;
1886 
1887 				input.attrs.rows = "7";
1888 
1889 				auto idx = type.indexOf("-");
1890 				if(idx != -1) {
1891 					idx++;
1892 					input.attrs.rows = type[idx .. $];
1893 				}
1894 			} else {
1895 				input = Element.make("input");
1896 
1897 				// hack to support common naming convention
1898 				if(type == "text" && param.name.toLower.indexOf("password") != -1)
1899 					input.type = "password";
1900 				else
1901 					input.type = type;
1902 				input.name = param.name;
1903 				input.value = param.value;
1904 
1905 				if(type == "file") {
1906 					form.method = "POST";
1907 					form.enctype = "multipart/form-data";
1908 				}
1909 			}
1910 		}
1911 
1912 		gotelement:
1913 
1914 		string n = param.name ~ "_auto-form-" ~ to!string(count);
1915 
1916 		input.id = n;
1917 
1918 		if(type == "hidden") {
1919 			form.appendChild(input);
1920 		} else {
1921 			auto th = Element.make("th");
1922 			auto label = Element.make("label");
1923 			label.setAttribute("for", n);
1924 			label.innerText = beautify(param.name) ~ ": ";
1925 			th.appendChild(label);
1926 
1927 			table.appendRow(th, input);
1928 		}
1929 
1930 		count++;
1931 	}
1932 
1933 	auto fmt = Element.make("select");
1934 	fmt.name = "format";
1935 	fmt.addChild("option", "Automatic").setAttribute("value", "default");
1936 	fmt.addChild("option", "html").setAttribute("value", "html");
1937 	fmt.addChild("option", "table").setAttribute("value", "table");
1938 	fmt.addChild("option", "json").setAttribute("value", "json");
1939 	fmt.addChild("option", "string").setAttribute("value", "string");
1940 	auto th = table.th("");
1941 	th.addChild("label", "Format:");
1942 
1943 	table.appendRow(th, fmt).className = "format-row";
1944 
1945 
1946 	auto submit = Element.make("input");
1947 	submit.value = submitText;
1948 	submit.type = "submit";
1949 
1950 	table.appendRow(Html("&nbsp;"), submit);
1951 
1952 //	form.setValue("format", reflection.defaultOutputFormat);
1953 
1954 	return form;
1955 }
1956 
1957 
1958 /* *
1959  * Returns the parameter names of the given function
1960  *
1961  * Params:
1962  *     func = the function alias to get the parameter names of
1963  *
1964  * Returns: an array of strings containing the parameter names
1965  */
1966 /+
1967 string parameterNamesOf( alias fn )( ) {
1968     string fullName = typeof(&fn).stringof;
1969 
1970     int pos = fullName.lastIndexOf( ')' );
1971     int end = pos;
1972     int count = 0;
1973     do {
1974         if ( fullName[pos] == ')' ) {
1975             count++;
1976         } else if ( fullName[pos] == '(' ) {
1977             count--;
1978         }
1979         pos--;
1980     } while ( count > 0 );
1981 
1982     return fullName[pos+2..end];
1983 }
1984 +/
1985 
1986 
1987 template parameterNamesOf (alias func) {
1988         const parameterNamesOf = parameterInfoImpl!(func)[0];
1989 }
1990 
1991 // FIXME: I lost about a second on compile time after adding support for defaults :-(
1992 template parameterDefaultsOf (alias func) {
1993         const parameterDefaultsOf = parameterInfoImpl!(func)[1];
1994 }
1995 
1996 bool parameterHasDefault(alias func)(int p) {
1997         auto a = parameterDefaultsOf!(func);
1998 	if(a.length == 0)
1999 		return false;
2000 	return a[p].length > 0;
2001 }
2002 
2003 template parameterDefaultOf (alias func, int paramNum) {
2004 	alias parameterDefaultOf = ParameterDefaultValueTuple!func[paramNum];
2005         //auto a = parameterDefaultsOf!(func);
2006 	//return a[paramNum];
2007 }
2008 
2009 sizediff_t indexOfNew(string s, char a) {
2010 	foreach(i, c; s)
2011 		if(c == a)
2012 			return i;
2013 	return -1;
2014 }
2015 
2016 sizediff_t lastIndexOfNew(string s, char a) {
2017 	for(sizediff_t i = s.length; i > 0; i--)
2018 		if(s[i - 1] == a)
2019 			return i - 1;
2020 	return -1;
2021 }
2022 
2023 
2024 // FIXME: a problem here is the compiler only keeps one stringof
2025 // for a particular type
2026 //
2027 // so if you have void a(string a, string b); and void b(string b, string c),
2028 // both a() and b() will show up as params == ["a", "b"]!
2029 //
2030 //
2031 private string[][2] parameterInfoImpl (alias func) ()
2032 {
2033         string funcStr = typeof(func).stringof; // this might fix the fixme above...
2034 						// it used to be typeof(&func).stringof
2035 
2036         auto start = funcStr.indexOfNew('(');
2037         auto end = funcStr.lastIndexOfNew(')');
2038 
2039 	assert(start != -1);
2040 	assert(end != -1);
2041 
2042         const firstPattern = ' ';
2043         const secondPattern = ',';
2044 
2045         funcStr = funcStr[start + 1 .. end];
2046 
2047         if (funcStr == "") // no parameters
2048                 return [null, null];
2049 
2050         funcStr ~= secondPattern;
2051 
2052         string token;
2053         string[] arr;
2054 
2055         foreach (c ; funcStr)
2056         {
2057                 if (c != firstPattern && c != secondPattern)
2058                         token ~= c;
2059 
2060                 else
2061                 {
2062                         if (token)
2063                                 arr ~= token;
2064 
2065                         token = null;
2066                 }
2067         }
2068 
2069         if (arr.length == 1)
2070                 return [arr, [""]];
2071 
2072         string[] result;
2073 	string[] defaults;
2074         bool skip = false;
2075 
2076 	bool gettingDefault = false;
2077 
2078 	string currentName = "";
2079 	string currentDefault = "";
2080 
2081         foreach (str ; arr)
2082         {
2083 		if(str == "=") {
2084 			gettingDefault = true;
2085 			continue;
2086 		}
2087 
2088 		if(gettingDefault) {
2089 			assert(str.length);
2090 			currentDefault = str;
2091 			gettingDefault = false;
2092 			continue;
2093 		}
2094 
2095                 skip = !skip;
2096 
2097                 if (skip) {
2098 			if(currentName.length) {
2099 				result ~= currentName;
2100 				defaults ~= currentDefault;
2101 				currentName = null;
2102 			}
2103                         continue;
2104 		}
2105 
2106 		currentName = str;
2107         }
2108 
2109 	if(currentName !is null) {
2110 		result ~= currentName;
2111 		defaults ~= currentDefault;
2112 	}
2113 
2114 	assert(result.length == defaults.length);
2115 
2116         return [result, defaults];
2117 }
2118 /////////////////////////////////
2119 
2120 /// Formats any given type as HTML. In custom types, you can write Element makeHtmlElement(Document document = null); to provide
2121 /// custom html. (the default arg is important - it won't necessarily pass a Document in at all, and since it's silently duck typed,
2122 /// not having that means your function won't be called and you can be left wondering WTF is going on.)
2123 
2124 /// Alternatively, static Element makeHtmlArray(T[]) if you want to make a whole list of them. By default, it will just concat a bunch of individual
2125 /// elements though.
2126 string toHtml(T)(T a) {
2127 	string ret;
2128 
2129 	static if(is(T == typeof(null)))
2130 		ret = null;
2131 	else static if(is(T : Document)) {
2132 		if(a is null)
2133 			ret = null;
2134 		else
2135 			ret = a.toString();
2136 	} else
2137 	static if(isArray!(T) && !isSomeString!(T)) {
2138 		static if(__traits(compiles, typeof(a[0]).makeHtmlArray(a))) {
2139 			ret = to!string(typeof(a[0]).makeHtmlArray(a));
2140 		} else {
2141 			ret ~= "<ul>";
2142 			foreach(v; a)
2143 				ret ~= "<li>" ~ toHtml(v) ~ "</li>";
2144 			ret ~= "</ul>";
2145 		}
2146 	} else static if(is(T : Element))
2147 		ret = a.toString();
2148 	else static if(__traits(compiles, a.makeHtmlElement().toString()))
2149 		ret = a.makeHtmlElement().toString();
2150 	else static if(is(T == Html))
2151 		ret = a.source;
2152 	else {
2153 		auto str = to!string(a);
2154 		if(str.indexOf("\t") == -1)
2155 			ret = std.array.replace(htmlEntitiesEncode(str), "\n", "<br />\n");
2156 		else // if there's tabs in it, output it as code or something; the tabs are probably there for a reason.
2157 			ret = "<pre>" ~ htmlEntitiesEncode(str) ~ "</pre>";
2158 	}
2159 
2160 	return ret;
2161 }
2162 
2163 /// Translates a given type to a JSON string.
2164 
2165 /// TIP: if you're building a Javascript function call by strings, toJson("your string"); will build a nicely escaped string for you of any type.
2166 string toJson(T)(T a) {
2167 	auto v = toJsonValue(a);
2168 	return toJSON(v);
2169 }
2170 
2171 // FIXME: are the explicit instantiations of this necessary?
2172 /// like toHtml - it makes a json value of any given type.
2173 
2174 /// It can be used generically, or it can be passed an ApiProvider so you can do a secondary custom
2175 /// format. (it calls api.formatAs!(type)(typeRequestString)). Why would you want that? Maybe
2176 /// your javascript wants to do work with a proper object,but wants to append it to the document too.
2177 /// Asking for json with secondary format = html means the server will provide both to you.
2178 
2179 /// Implement JSONValue makeJsonValue() in your struct or class to provide 100% custom Json.
2180 
2181 /// Elements from DOM are turned into JSON strings of the element's html.
2182 JSONValue toJsonValue(T, R = ApiProvider)(T a, string formatToStringAs = null, R api = null)
2183 	if(is(R : ApiProvider))
2184 {
2185 	JSONValue val;
2186 	static if(is(T == typeof(null)) || is(T == void*)) {
2187 		/* void* goes here too because we don't know how to make it work... */
2188 		val.object = null;
2189 		//val.type = JSONType.null_;
2190 	} else static if(is(T == JSONValue)) {
2191 		val = a;
2192 	} else static if(__traits(compiles, val = a.makeJsonValue())) {
2193 		val = a.makeJsonValue();
2194 	// FIXME: free function to emulate UFCS?
2195 
2196 	// FIXME: should we special case something like struct Html?
2197 	} else static if(is(T : DateTime)) {
2198 		//val.type = JSONType.string;
2199 		val.str = a.toISOExtString();
2200 	} else static if(is(T : Element)) {
2201 		if(a is null) {
2202 			//val.type = JSONType.null_;
2203 			val = null;
2204 		} else {
2205 			//val.type = JSONType.string;
2206 			val.str = a.toString();
2207 		}
2208 	} else static if(is(T == long)) {
2209 		// FIXME: let's get a better range... I think it goes up to like 1 << 50 on positive and negative
2210 		// but this works for now
2211 		if(a < int.max && a > int.min) {
2212 			//val.type = JSONType.integer;
2213 			val.integer = to!long(a);
2214 		} else {
2215 			// WHY? because javascript can't actually store all 64 bit numbers correctly
2216 			//val.type = JSONType.string;
2217 			val.str = to!string(a);
2218 		}
2219 	} else static if(isIntegral!(T)) {
2220 		//val.type = JSONType.integer;
2221 		val.integer = to!long(a);
2222 	} else static if(isFloatingPoint!(T)) {
2223 		//val.type = JSONType.float_;
2224 		val.floating = to!real(a);
2225 	} else static if(isPointer!(T)) {
2226 		if(a is null) {
2227 			//val.type = JSONType.null_;
2228 			val = null;
2229 		} else {
2230 			val = toJsonValue!(typeof(*a), R)(*a, formatToStringAs, api);
2231 		}
2232 	} else static if(is(T == bool)) {
2233 		if(a == true)
2234 			val = true; // .type = JSONType.true_;
2235 		if(a == false)
2236 			val = false; // .type = JSONType.false_;
2237 	} else static if(isSomeString!(T)) {
2238 		//val.type = JSONType.string;
2239 		val.str = to!string(a);
2240 	} else static if(isAssociativeArray!(T)) {
2241 		//val.type = JSONType.object;
2242 		JSONValue[string] valo;
2243 		foreach(k, v; a) {
2244 			valo[to!string(k)] = toJsonValue!(typeof(v), R)(v, formatToStringAs, api);
2245 		}
2246 		val = valo;
2247 	} else static if(isArray!(T)) {
2248 		//val.type = JSONType.array;
2249 		JSONValue[] arr;
2250 		arr.length = a.length;
2251 		foreach(i, v; a) {
2252 			arr[i] = toJsonValue!(typeof(v), R)(v, formatToStringAs, api);
2253 		}
2254 
2255 		val.array = arr;
2256 	} else static if(is(T == struct)) { // also can do all members of a struct...
2257 		//val.type = JSONType.object;
2258 
2259 		JSONValue[string] valo;
2260 
2261 		foreach(i, member; a.tupleof) {
2262 			string name = a.tupleof[i].stringof[2..$];
2263 			static if(a.tupleof[i].stringof[2] != '_')
2264 				valo[name] = toJsonValue!(typeof(member), R)(member, formatToStringAs, api);
2265 		}
2266 			// HACK: bug in dmd can give debug members in a non-debug build
2267 			//static if(__traits(compiles, __traits(getMember, a, member)))
2268 		val = valo;
2269 	} else { /* our catch all is to just do strings */
2270 		//val.type = JSONType.string;
2271 		val.str = to!string(a);
2272 		// FIXME: handle enums
2273 	}
2274 
2275 
2276 	// don't want json because it could recurse
2277 	if(val.type == JSONType.object && formatToStringAs !is null && formatToStringAs != "json") {
2278 		JSONValue formatted;
2279 		//formatted.type = JSONType.string;
2280 		formatted.str = "";
2281 
2282 		formatAs!(T, R)(a, formatToStringAs, api, &formatted, null /* only doing one level of special formatting */);
2283 		assert(formatted.type == JSONType..string);
2284 		val.object["formattedSecondarily"] = formatted;
2285 	}
2286 
2287 	return val;
2288 }
2289 
2290 /+
2291 Document toXml(T)(T t) {
2292 	auto xml = new Document;
2293 	xml.parse(emptyTag(T.stringof), true, true);
2294 	xml.prolog = `<?xml version="1.0" encoding="UTF-8" ?>` ~ "\n";
2295 
2296 	xml.root = toXmlElement(xml, t);
2297 	return xml;
2298 }
2299 
2300 Element toXmlElement(T)(Document document, T t) {
2301 	Element val;
2302 	static if(is(T == Document)) {
2303 		val = t.root;
2304 	//} else static if(__traits(compiles, a.makeJsonValue())) {
2305 	//	val = a.makeJsonValue();
2306 	} else static if(is(T : Element)) {
2307 		if(t is null) {
2308 			val = document.createElement("value");
2309 			val.innerText = "null";
2310 			val.setAttribute("isNull", "true");
2311 		} else
2312 			val = t;
2313 	} else static if(is(T == void*)) {
2314 			val = document.createElement("value");
2315 			val.innerText = "null";
2316 			val.setAttribute("isNull", "true");
2317 	} else static if(isPointer!(T)) {
2318 		if(t is null) {
2319 			val = document.createElement("value");
2320 			val.innerText = "null";
2321 			val.setAttribute("isNull", "true");
2322 		} else {
2323 			val = toXmlElement(document, *t);
2324 		}
2325 	} else static if(isAssociativeArray!(T)) {
2326 		val = document.createElement("value");
2327 		foreach(k, v; t) {
2328 			auto e = document.createElement(to!string(k));
2329 			e.appendChild(toXmlElement(document, v));
2330 			val.appendChild(e);
2331 		}
2332 	} else static if(isSomeString!(T)) {
2333 		val = document.createTextNode(to!string(t));
2334 	} else static if(isArray!(T)) {
2335 		val = document.createElement("array");
2336 		foreach(i, v; t) {
2337 			auto e = document.createElement("item");
2338 			e.appendChild(toXmlElement(document, v));
2339 			val.appendChild(e);
2340 		}
2341 	} else static if(is(T == struct)) { // also can do all members of a struct...
2342 		val = document.createElement(T.stringof);
2343 		foreach(member; __traits(allMembers, T)) {
2344 			if(member[0] == '_') continue; // FIXME: skip member functions
2345 			auto e = document.createElement(member);
2346 			e.appendChild(toXmlElement(document, __traits(getMember, t, member)));
2347 			val.appendChild(e);
2348 		}
2349 	} else { /* our catch all is to just do strings */
2350 		val = document.createTextNode(to!string(t));
2351 		// FIXME: handle enums
2352 	}
2353 
2354 	return val;
2355 }
2356 +/
2357 
2358 
2359 /// throw this if your function needs something that is missing.
2360 
2361 /// Done automatically by the wrapper function
2362 class InsufficientParametersException : Exception {
2363 	this(string functionName, string msg, string file = __FILE__, size_t line = __LINE__) {
2364 		this.functionName = functionName;
2365 		super(functionName ~ ": " ~ msg, file, line);
2366 	}
2367 
2368 	string functionName;
2369 	string argumentName;
2370 	string formLocation;
2371 }
2372 
2373 /// helper for param checking
2374 bool isValidLookingEmailAddress(string e) {
2375 	import std.net.isemail;
2376 	return isEmail(e, CheckDns.no, EmailStatusCode.any).statusCode == EmailStatusCode.valid;
2377 }
2378 
2379 /// Looks for things like <a or [url - the kind of stuff I often see in blatantly obvious comment spam
2380 bool isFreeOfTypicalPlainTextSpamLinks(string txt) {
2381 	if(txt.indexOf("href=") != -1)
2382 		return false;
2383 	if(txt.indexOf("[url") != -1)
2384 		return false;
2385 	return true;
2386 }
2387 
2388 /**
2389 	---
2390 	auto checker = new ParamCheckHelper();
2391 
2392 	checker.finish(); // this will throw if any of the checks failed
2393 	// now go ahead and use the params
2394 	---
2395 */
2396 class ParamCheckHelper {
2397 	this(in Cgi cgi) {
2398 		this.cgi = cgi;
2399 	}
2400 
2401 	string[] failed;
2402 	string[] messagesForUser;
2403 	const(Cgi) cgi;
2404 
2405 	void failure(string name, string messageForUser = null) {
2406 		failed ~= name;
2407 		messagesForUser ~= messageForUser;
2408 	}
2409 
2410 	string checkParam(in string[string] among, in string name, bool delegate(string) ok, string messageForUser = null) {
2411 		return checkTypedParam!string(among, name, ok, messageForUser);
2412 	}
2413 
2414 	T checkTypedParam(T)(in string[string] among, in string name, bool delegate(T) ok, string messageForUser = null) {
2415 		T value;
2416 
2417 		bool isOk = false;
2418 		string genericErrorMessage = "Please complete this field";
2419 
2420 
2421 		try {
2422 			//auto ptr = "name" in among;
2423 			//if(ptr !is null) {
2424 			//	value = *ptr;
2425 			//} else {
2426 				// D's in operator is sometimes buggy, so let's confirm this with a linear search ugh)
2427 				// FIXME: fix D's AA
2428 				foreach(k, v; among)
2429 					if(k == name) {
2430 						value = fromUrlParam!T(v, name, null);
2431 						break;
2432 					}
2433 			//}
2434 
2435 			if(ok !is null)
2436 				isOk = ok(value);
2437 			else
2438 				isOk = true; // no checker means if we were able to convert above, we're ok
2439 		} catch(Exception e) {
2440 			genericErrorMessage = e.msg;
2441 			isOk = false;
2442 		}
2443 
2444 		if(!isOk) {
2445 			failure(name, messageForUser is null ? genericErrorMessage : messageForUser);
2446 		}
2447 
2448 		return value;
2449 	}
2450 
2451 	// int a = checkParam!int(cgi, "cool", (a) => a > 10);
2452 	T checkCgiParam(T)(string name, T defaultValue, bool delegate(T) ok, string messageForUser = null) {
2453 		auto value = cgi.request(name, defaultValue);
2454 		if(!ok(value)) {
2455 			failure(name, messageForUser);
2456 		}
2457 
2458 		return value;
2459 	}
2460 
2461 	void finish(
2462 		immutable(FunctionInfo)* formFunction,
2463 		Form delegate(Document) getForm,
2464 		void delegate(Document, Form, FormValidationException) postProcessor,
2465 		string file = __FILE__, size_t line = __LINE__)
2466 	{
2467 		if(failed.length)
2468 			throw new FormValidationException(
2469 				formFunction, getForm, postProcessor,
2470 				failed, messagesForUser,
2471 				to!string(failed), file, line);
2472 	}
2473 }
2474 
2475 auto check(alias field)(ParamCheckHelper helper, bool delegate(typeof(field)) ok, string messageForUser = null) {
2476 	if(!ok(field)) {
2477 		helper.failure(field.stringof, messageForUser);
2478 	}
2479 
2480 	return field;
2481 }
2482 
2483 bool isConvertableTo(T)(string v) {
2484 	try {
2485 		auto t = fromUrlParam!(T)(v, null, null);
2486 		return true;
2487 	} catch(Exception e) {
2488 		return false;
2489 	}
2490 }
2491 
2492 class FormValidationException : Exception {
2493 	this(
2494 		immutable(FunctionInfo)* formFunction,
2495 		Form delegate(Document) getForm,
2496 		void delegate(Document, Form, FormValidationException) postProcessor,
2497 		string[] failed, string[] messagesForUser,
2498 		string msg, string file = __FILE__, size_t line = __LINE__)
2499 	{
2500 		this.formFunction = formFunction;
2501 		this.getForm = getForm;
2502 		this.postProcessor = postProcessor;
2503 		this.failed = failed;
2504 		this.messagesForUser = messagesForUser;
2505 
2506 		super(msg, file, line);
2507 	}
2508 
2509 	// this will be called by the automatic catch
2510 	// it goes: Document d = formatAs(formFunction, document);
2511 	// then   : Form f = getForm(d);
2512 	// it will add the values used in the current call to the form with the error conditions
2513 	// and finally, postProcessor(d, f, this);
2514 	immutable(FunctionInfo)* formFunction;
2515 	Form delegate(Document) getForm;
2516 	void delegate(Document, Form, FormValidationException) postProcessor;
2517 	string[] failed;
2518 	string[] messagesForUser;
2519 }
2520 
2521 /// throw this if a paramater is invalid. Automatic forms may present this to the user in a new form. (FIXME: implement that)
2522 class InvalidParameterException : Exception {
2523 	this(string param, string value, string expected, string file = __FILE__, size_t line = __LINE__) {
2524 		this.param = param;
2525 		super("bad param: " ~ param ~ ". got: " ~ value ~ ". Expected: " ~expected, file, line);
2526 	}
2527 
2528 	/*
2529 		The way these are handled automatically is if something fails, web.d will
2530 		redirect the user to
2531 
2532 		formLocation ~ "?" ~ encodeVariables(cgi.get|postArray)
2533 	*/
2534 
2535 	string functionName;
2536 	string param;
2537 	string formLocation;
2538 }
2539 
2540 /// convenience for throwing InvalidParameterExceptions
2541 void badParameter(alias T)(string expected = "") {
2542 	throw new InvalidParameterException(T.stringof, T, expected);
2543 }
2544 
2545 /// throw this if the user's access is denied
2546 class PermissionDeniedException : Exception {
2547 	this(string msg, string file = __FILE__, int line = __LINE__) {
2548 		super(msg, file, line);
2549 	}
2550 }
2551 
2552 /// throw if the request path is not found. Done automatically by the default catch all handler.
2553 class NoSuchPageException : Exception {
2554 	this(string msg, string file = __FILE__, int line = __LINE__) {
2555 		super(msg, file, line);
2556 	}
2557 }
2558 
2559 class NoSuchFunctionException : NoSuchPageException {
2560 	this(string msg, string file = __FILE__, int line = __LINE__) {
2561 		super(msg, file, line);
2562 	}
2563 }
2564 
2565 type fromUrlParam(type)(in string ofInterest, in string name, in string[][string] all) {
2566 	type ret;
2567 
2568 	static if(!is(type == enum) && isArray!(type) && !isSomeString!(type)) {
2569 		// how do we get an array out of a simple string?
2570 		// FIXME
2571 		static assert(0);
2572 	} else static if(__traits(compiles, ret = type.fromWebString(ofInterest))) { // for custom object handling...
2573 		ret = type.fromWebString(ofInterest);
2574 	} else static if(is(type : Element)) {
2575 		auto doc = new Document(ofInterest, true, true);
2576 
2577 		ret = doc.root;
2578 	} else static if(is(type : Text)) {
2579 		ret = ofInterest;
2580 	} else static if(is(type : Html)) {
2581 		ret.source = ofInterest;
2582 	} else static if(is(type : TimeOfDay)) {
2583 		ret = TimeOfDay.fromISOExtString(ofInterest);
2584 	} else static if(is(type : Date)) {
2585 		ret = Date.fromISOExtString(ofInterest);
2586 	} else static if(is(type : DateTime)) {
2587 		ret = DateTime.fromISOExtString(ofInterest);
2588 	} else static if(is(type == struct)) {
2589 		auto n = name.length ? (name ~ ".")  : "";
2590 		foreach(idx, thing; ret.tupleof) {
2591 			enum fn = ret.tupleof[idx].stringof[4..$];
2592 			auto lol = n ~ fn;
2593 			if(lol in all)
2594 				ret.tupleof[idx] = fromUrlParam!(typeof(thing))(all[lol], lol, all);
2595 		}
2596 	} else static if(is(type == enum)) {
2597 		sw: switch(ofInterest) {
2598 			/*static*/ foreach(N; __traits(allMembers, type)) {
2599 			case N:
2600 				ret = __traits(getMember, type, N);
2601 				break sw;
2602 			}
2603 			default:
2604 				throw new InvalidParameterException(name, ofInterest, "");
2605 		}
2606 
2607 	}
2608 	/*
2609 	else static if(is(type : struct)) {
2610 		static assert(0, "struct not supported yet");
2611 	}
2612 	*/
2613 	else {
2614 		ret = to!type(ofInterest);
2615 	} // FIXME: can we support classes?
2616 
2617 	return ret;
2618 }
2619 
2620 /// turns a string array from the URL into a proper D type
2621 type fromUrlParam(type)(in string[] ofInterest, in string name, in string[][string] all) {
2622 	type ret;
2623 
2624 	// Arrays in a query string are sent as the name repeating...
2625 	static if(!is(type == enum) && isArray!(type) && !isSomeString!type) {
2626 		foreach(a; ofInterest) {
2627 			ret ~= fromUrlParam!(ElementType!(type))(a, name, all);
2628 		}
2629 	} else static if(isArray!(type) && isSomeString!(ElementType!type)) {
2630 		foreach(a; ofInterest) {
2631 			ret ~= fromUrlParam!(ElementType!(type))(a, name, all);
2632 		}
2633 	} else
2634 		ret = fromUrlParam!type(ofInterest[$-1], name, all);
2635 
2636 	return ret;
2637 }
2638 
2639 auto getMemberDelegate(alias ObjectType, string member)(ObjectType object) if(is(ObjectType : WebDotDBaseType)) {
2640 	if(object is null)
2641 		throw new NoSuchFunctionException("no such object " ~ ObjectType.stringof);
2642 	return &__traits(getMember, object, member);
2643 }
2644 
2645 /// generates the massive wrapper function for each of your class' methods.
2646 /// it is responsible for turning strings to params and return values back to strings.
2647 WrapperFunction generateWrapper(alias ObjectType, string funName, alias f, R)(ReflectionInfo* reflection, R api)
2648 	if(is(R: ApiProvider) && (is(ObjectType : WebDotDBaseType)) )
2649 {
2650 	WrapperReturn wrapper(Cgi cgi, WebDotDBaseType object, in string[][string] sargs, in string format, in string secondaryFormat = null) {
2651 
2652 		JSONValue returnValue;
2653 		returnValue.str = "";
2654 		//returnValue.type = JSONType.string;
2655 
2656 		auto instantiation = getMemberDelegate!(ObjectType, funName)(cast(ObjectType) object);
2657 
2658 		api._initializePerCallInternal();
2659 
2660 		ParameterTypeTuple!(f) args;
2661 
2662 		// Actually calling the function
2663 		// FIXME: default parameters
2664 		foreach(i, type; ParameterTypeTuple!(f)) {
2665 			string name = parameterNamesOf!(f)[i];
2666 
2667 			// We want to check the named argument first. If it's not there,
2668 			// try the positional arguments
2669 			string using = name;
2670 			if(name !in sargs)
2671 				using = "positional-arg-" ~ to!string(i);
2672 
2673 			// FIXME: if it's a struct, we should do it's pieces independently here
2674 
2675 			static if(is(type == bool)) {
2676 				// bool is special cased because HTML checkboxes don't send anything if it isn't checked
2677 				if(using in sargs) {
2678 					if(
2679 					sargs[using][$-1] != "false" &&
2680 					sargs[using][$-1] != "False" &&
2681 					sargs[using][$-1] != "FALSE" &&
2682 					sargs[using][$-1] != "no" &&
2683 					sargs[using][$-1] != "off" &&
2684 					sargs[using][$-1] != "0"
2685 					)
2686 					args[i] = true;
2687 					else
2688 					args[i] = false;
2689 				}
2690 				else {
2691 					static if(parameterHasDefault!(f)(i)) {
2692 						// args[i] = mixin(parameterDefaultOf!(f)(i));
2693 						args[i] = cast(type) parameterDefaultOf!(f, i);
2694 					} else
2695 						args[i] = false;
2696 				}
2697 
2698 				// FIXME: what if the default is true?
2699 			} else static if(is(Unqual!(type) == Cgi.UploadedFile)) {
2700 				if(using !in cgi.files)
2701 					throw new InsufficientParametersException(funName, "file " ~ using ~ " is not present");
2702 				args[i] = cast()  cgi.files[using]; // casting away const for the assignment to compile FIXME: shouldn't be needed
2703 			} else {
2704 				if(using !in sargs) {
2705 					static if(isArray!(type) && !isSomeString!(type)) {
2706 						args[i] = null;
2707 					} else 	static if(parameterHasDefault!(f)(i)) {
2708 						//args[i] = mixin(parameterDefaultOf!(f)(i));
2709 						args[i] = cast(type) parameterDefaultOf!(f, i);
2710 					} else 	static if(is(type == struct)) {
2711 						// try to load a struct as obj.members
2712 						args[i] = fromUrlParam!type(cast(string) null, name, sargs);
2713 					} else {
2714 						throw new InsufficientParametersException(funName, "arg " ~ name ~ " is not present");
2715 					}
2716 				} else {
2717 
2718 					// We now check the type reported by the client, if there is one
2719 					// Right now, only one type is supported: ServerResult, which means
2720 					// it's actually a nested function call
2721 
2722 					string[] ofInterest = cast(string[]) sargs[using]; // I'm changing the reference, but not the underlying stuff, so this cast is ok
2723 
2724 					if(using ~ "-type" in sargs) {
2725 						string reportedType = sargs[using ~ "-type"][$-1];
2726 						if(reportedType == "ServerResult") {
2727 
2728 							// FIXME: doesn't handle functions that return
2729 							// compound types (structs, arrays, etc)
2730 
2731 							ofInterest = null;
2732 
2733 							string str = sargs[using][$-1];
2734 							auto idx = str.indexOf("?");
2735 							string callingName, callingArguments;
2736 							if(idx == -1) {
2737 								callingName = str;
2738 							} else {
2739 								callingName = str[0..idx];
2740 								callingArguments = str[idx + 1 .. $];
2741 							}
2742 
2743 							// find it in reflection
2744 							ofInterest ~= reflection.functions[callingName].
2745 								dispatcher(cgi, object, decodeVariables(callingArguments), "string", null).value.str;
2746 						}
2747 					}
2748 
2749 
2750 					args[i] = fromUrlParam!type(ofInterest, using, sargs);
2751 				}
2752 			}
2753 		}
2754 
2755 		static if(!is(ReturnType!f == void))
2756 			ReturnType!(f) ret;
2757 		else
2758 			void* ret;
2759 
2760 		static if(!is(ReturnType!f == void))
2761 			ret = instantiation(args);
2762 		else
2763 			instantiation(args);
2764 
2765 		WrapperReturn r;
2766 
2767 		static if(is(ReturnType!f : Element)) {
2768 			if(ret is null) {
2769 				r.value = returnValue;
2770 				return r; // HACK to handle null returns
2771 			}
2772 			// we need to make sure that it's not called again when _postProcess(Document) is called!
2773 			// FIXME: is this right?
2774 			if(cgi.request("envelopeFormat", "document") != "document")
2775 				api._postProcessElement(ret); // need to post process the element here so it works in ajax modes.
2776 		}
2777 
2778 		static if(is(ReturnType!f : FileResource) && !is(ReturnType!f : Document)) {
2779 			if(ret !is null && cgi !is null) {
2780 				cgi.setResponseContentType(ret.contentType());
2781 				cgi.write(ret.getData(), true);
2782 				cgi.close();
2783 				r.completed = true;
2784 			}
2785 		}
2786 
2787 		formatAs(ret, format, api, &returnValue, secondaryFormat);
2788 
2789 		r.value = returnValue;
2790 
2791 		return r;
2792 	}
2793 
2794 	return &wrapper;
2795 }
2796 
2797 
2798 /// This is the function called to turn return values into strings.
2799 
2800 /// Implement a template called _customFormat in your apiprovider class to make special formats.
2801 
2802 /// Otherwise, this provides the defaults of html, table, json, etc.
2803 
2804 /// call it like so: JSONValue returnValue; formatAs(value, this, returnValue, "type");
2805 
2806 // FIXME: it's awkward to call manually due to the JSONValue ref thing. Returning a string would be mega nice.
2807 string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue = null, string formatJsonToStringAs = null) if(is(R : ApiProvider)) {
2808 
2809 	if(format == "default") {
2810 		static if(is(typeof(ret) : K[N][V], size_t N, K, V)) {
2811 			format = "table";
2812 		} else {
2813 			format = "html";
2814 		}
2815 
2816 		static if(is(typeof(ret) : K[], K)) {
2817 			static if(is(K == struct))
2818 				format = "table";
2819 		}
2820 	}
2821 
2822 	string retstr;
2823 	if(api !is null) {
2824 		static if(__traits(compiles, api._customFormat(ret, format))) {
2825 			auto customFormatted = api._customFormat(ret, format);
2826 			if(customFormatted !is null) {
2827 				if(returnValue !is null)
2828 					returnValue.str = customFormatted;
2829 				return customFormatted;
2830 			}
2831 		}
2832 	}
2833 	switch(format) {
2834 		case "html":
2835 			retstr = toHtml(ret);
2836 			if(returnValue !is null)
2837 				returnValue.str = retstr;
2838 		break;
2839 		case "string": // FIXME: this is the most expensive part of the compile! Two seconds in one of my apps.
2840 			static if(is(typeof(ret) == string)) {
2841 				returnValue.str = ret;
2842 				break;
2843 			} else
2844 		/+
2845 			static if(__traits(compiles, to!string(ret))) {
2846 				retstr = to!string(ret);
2847 				if(returnValue !is null)
2848 					returnValue.str = retstr;
2849 			}
2850 			else goto badType;
2851 		+/
2852 			goto badType; // FIXME
2853 		case "json":
2854 			assert(returnValue !is null);
2855 			*returnValue = toJsonValue!(typeof(ret), R)(ret, formatJsonToStringAs, api);
2856 		break;
2857 		case "table":
2858 		case "csv":
2859 			auto document = new Document("<root></root>");
2860 
2861 			void gotATable(Table table) {
2862 				if(format == "csv") {
2863 					retstr = tableToCsv(table);
2864 				} else if(format == "table") {
2865 					auto div = Element.make("div");
2866 					if(api !is null) {
2867 						auto cgi = api.cgi;
2868 						div.addChild("a", "Download as CSV", cgi.pathInfo ~ "?" ~ cgi.queryString ~ "&format=csv&envelopeFormat=csv");
2869 					}
2870 					div.appendChild(table);
2871 					retstr = div.toString();
2872 				} else assert(0);
2873 
2874 
2875 				if(returnValue !is null)
2876 					returnValue.str = retstr;
2877 			}
2878 
2879 			static if(__traits(compiles, structToTable(document, ret)))
2880 			{
2881 				auto table = structToTable(document, ret);
2882 				gotATable(table);
2883 				break;
2884 			} else static if(is(typeof(ret) : Element)) {
2885 				auto table = cast(Table) ret;
2886 				if(table is null)
2887 					goto badType;
2888 				gotATable(table);
2889 				break;
2890 			} else static if(is(typeof(ret) : K[N][V], size_t N, K, V)) {
2891 				auto table = cast(Table) Element.make("table");
2892 				table.addClass("data-display");
2893 				auto headerRow = table.addChild("tr");
2894 				foreach(n; 0 .. N)
2895 					table.addChild("th", "" ~ cast(char)(n  + 'A'));
2896 				foreach(k, v; ret) {
2897 					auto row = table.addChild("tr");
2898 					foreach(cell; v)
2899 						row.addChild("td", to!string(cell));
2900 				}
2901 				gotATable(table);
2902 				break;
2903 
2904 			} else
2905 				goto badType;
2906 		default:
2907 			badType:
2908 			throw new Exception("Couldn't get result as " ~ format);
2909 	}
2910 
2911 	return retstr;
2912 }
2913 
2914 string toCsv(string text) {
2915 	return `"`~text.replace(`"`, `""`)~`"`;
2916 }
2917 
2918 string tableToCsv(Table table) {
2919 	string csv;
2920 	foreach(tr; table.querySelectorAll("tr")) {
2921 		if(csv.length)
2922 			csv ~= "\r\n";
2923 
2924 		bool outputted = false;
2925 		foreach(item; tr.querySelectorAll("td, th")) {
2926 			if(outputted)
2927 				csv ~= ",";
2928 			else
2929 				outputted = true;
2930 
2931 			if(item.firstChild && item.firstChild.tagName == "ul") {
2932 				string c;
2933 				foreach(i, node; item.firstChild.childNodes) {
2934 					if(c.length) c ~= "; ";
2935 					c ~= node.innerText;
2936 				}
2937 				csv ~= toCsv(c);
2938 			} else {
2939 				csv ~= toCsv(item.innerText);
2940 			}
2941 		}
2942 	}
2943 
2944 	return csv;
2945 }
2946 
2947 
2948 private string emptyTag(string rootName) {
2949 	return ("<" ~ rootName ~ "></" ~ rootName ~ ">");
2950 }
2951 
2952 struct WrapperReturn {
2953 	JSONValue value;
2954 	bool completed;
2955 }
2956 
2957 /// The definition of the beastly wrapper function
2958 alias WrapperReturn delegate(Cgi cgi, WebDotDBaseType, in string[][string] args, in string format, in string secondaryFormat = null) WrapperFunction;
2959 
2960 /// tries to take a URL name and turn it into a human natural name. so get rid of slashes, capitalize, etc.
2961 string urlToBeauty(string url) {
2962 	string u = url.replace("/", "");
2963 
2964 	string ret;
2965 
2966 	bool capitalize = true;
2967 	foreach(c; u) {
2968 		if(capitalize) {
2969 			ret ~= ("" ~ c).toUpper;
2970 			capitalize = false;
2971 		} else {
2972 			if(c == '-') {
2973 				ret ~= " ";
2974 				capitalize = true;
2975 			} else
2976 				ret ~= c;
2977 		}
2978 	}
2979 
2980 	return ret;
2981 }
2982 
2983 /// turns camelCase into dash-separated
2984 string toUrlName(string name) {
2985 	string res;
2986 	foreach(c; name) {
2987 		if(c >= 'a' && c <= 'z')
2988 			res ~= c;
2989 		else {
2990 			res ~= '-';
2991 			if(c >= 'A' && c <= 'Z')
2992 				res ~= c + 0x20;
2993 			else
2994 				res ~= c;
2995 		}
2996 	}
2997 	return res;
2998 }
2999 
3000 /// turns camelCase into human presentable capitalized words with spaces
3001 string beautify(string name) {
3002 	string n;
3003 
3004 	// really if this is cap and the following is lower, we want a space.
3005 	// or in other words, if this is lower and previous is cap, we want a space injected before previous
3006 
3007 				// all caps names shouldn't get spaces
3008 	if(name.length == 0 || name.toUpper() == name)
3009 		return name;
3010 
3011 	n ~= toUpper(name[0..1]);
3012 
3013 	dchar last;
3014 	foreach(idx, dchar c; name[1..$]) {
3015 		if(c >= 'A' && c <= 'Z') {
3016 			if(idx + 1 < name[1 .. $].length && name[1 + idx + 1] >= 'a' && name[1 + idx + 1] <= 'z')
3017 				n ~= " ";
3018 		} else if(c >= '0' && c <= '9') {
3019 			if(last != ' ')
3020 				n ~= " ";
3021 		}
3022 
3023 		if(c == '_')
3024 			n ~= " ";
3025 		else
3026 			n ~= c;
3027 		last = c;
3028 	}
3029 	return n;
3030 }
3031 
3032 
3033 
3034 
3035 
3036 
3037 import core.stdc.stdlib;
3038 import core.stdc.time;
3039 import std.file;
3040 
3041 /// meant to give a generic useful hook for sessions. kinda sucks at this point.
3042 /// use the Session class instead. If you just construct it, the sessionId property
3043 /// works fine. Don't set any data and it won't save any file.
3044 version(none)
3045 deprecated string getSessionId(Cgi cgi) {
3046 	string token; // FIXME: should this actually be static? it seems wrong
3047 	if(token is null) {
3048 		if("_sess_id" in cgi.cookies)
3049 			token = cgi.cookies["_sess_id"];
3050 		else {
3051 			auto tmp = uniform(0, int.max);
3052 			token = to!string(tmp);
3053 
3054 			cgi.setCookie("_sess_id", token, /*60 * 8 * 1000*/ 0, "/", null, true);
3055 		}
3056 	}
3057 
3058 	import std.md5;
3059 	return getDigestString(cgi.remoteAddress ~ "\r\n" ~ cgi.userAgent ~ "\r\n" ~ token);
3060 }
3061 
3062 version(Posix) {
3063 	static import linux = core.sys.linux.unistd;
3064 	static import sys_stat = core.sys.posix.sys.stat;
3065 }
3066 
3067 /// This is cookie parameters for the Session class. The default initializers provide some simple default
3068 /// values for a site-wide session cookie.
3069 struct CookieParams {
3070 	string name    = "_sess_id";
3071 	string host    = null;
3072 	string path    = "/";
3073 	long expiresIn = 0;
3074 	bool httpsOnly = false;
3075 }
3076 
3077 /// Provides some persistent storage, kinda like PHP
3078 /// But, you have to manually commit() the data back to a file.
3079 /// You might want to put this in a scope(exit) block or something like that.
3080 class Session {
3081 	static Session loadReadOnly(Cgi cgi, CookieParams cookieParams = CookieParams(), bool useFile = true) {
3082 		return new Session(cgi, cookieParams, useFile, true);
3083 	}
3084 
3085 	version(webd_memory_sessions) {
3086 		// FIXME: make this a full fledged option, maybe even with an additional
3087 		// process to hold the information
3088 		__gshared static string[string][string] sessions;
3089 	}
3090 
3091 	/// Loads the session if available, and creates one if not.
3092 	/// May write a session id cookie to the passed cgi object.
3093 	this(Cgi cgi, CookieParams cookieParams = CookieParams(), bool useFile = true, bool readOnly = false) {
3094 		// uncomment these two to render session useless (it has no backing)
3095 		// but can be good for benchmarking the rest of your app
3096 		//useFile = false;
3097 		//_readOnly = true;
3098 
3099 
3100 		// assert(cgi.https); // you want this for best security, but I won't be an ass and require it.
3101 		this.cookieParams = cookieParams;
3102 		this.cgi = cgi;
3103 		this._readOnly = readOnly;
3104 
3105 		bool isNew = false;
3106 		// string token; // using a member, see the note below
3107 		if(cookieParams.name in cgi.cookies && cgi.cookies[cookieParams.name].length) {
3108 			token = cgi.cookies[cookieParams.name];
3109 		} else {
3110 			if("x-arsd-session-override" in cgi.requestHeaders) {
3111 				loadSpecialSession(cgi);
3112 				return;
3113 			} else {
3114 				// there is no session; make a new one.
3115 				token = makeNewCookie();
3116 				isNew = true;
3117 			}
3118 		}
3119 
3120 		makeSessionId(token);
3121 
3122 		if(useFile)
3123 			reload();
3124 		if(isNew)
3125 			addDefaults();
3126 	}
3127 
3128 	/// This loads a session that the user requests, without the normal
3129 	/// checks. The idea is to allow debugging or local request sharing.
3130 	///
3131 	/// It is private because you never have to call it yourself, but read on
3132 	/// to understand how it works and some potential security concerns.
3133 	///
3134 	/// It loads the requested session read-only (it does not commit),
3135 	/// if and only if the request asked for the correct hash and id.
3136 	///
3137 	/// If they have enough info to build the correct hash, they must
3138 	/// already know the contents of the file, so there should be no
3139 	/// risk of data contamination here. (A traditional session hijack
3140 	/// is surely much easier.)
3141 	///
3142 	/// It is possible for them to forge a request as a particular user
3143 	/// if they can read the file, but otherwise not write. For that reason,
3144 	/// especially with this functionality here, it is very important for you
3145 	/// to lock down your session files. If on a shared host, be sure each user's
3146 	/// processes run as separate operating system users, so the file permissions
3147 	/// set in commit() actually help you.
3148 	///
3149 	/// If you can't reasonably protect the session file, compile this out with
3150 	/// -version=no_session_override and only access unauthenticated functions
3151 	/// from other languages. They can still read your sessions, and potentially
3152 	/// hijack it, but it will at least be a little harder.
3153 	///
3154 	/// Also, don't use this over the open internet at all. It's supposed
3155 	/// to be local only. If someone sniffs the request, hijacking it
3156 	/// becomes very easy; even easier than a normal session id since they just reply it.
3157 	/// (you should really ssl encrypt all sessions for any real protection btw)
3158 	private void loadSpecialSession(Cgi cgi) {
3159 		// Note: this won't work with memory sessions
3160 		version(webd_memory_sessions)
3161 			throw new Exception("You cannot access sessions this way.");
3162 		else version(webd_cookie_sessions) {
3163 			// FIXME: implement
3164 		} else {
3165 			version(no_session_override)
3166 				throw new Exception("You cannot access sessions this way.");
3167 			else {
3168 				// the header goes full-session-id;file-contents-hash
3169 				auto info = split(cgi.requestHeaders["x-arsd-session-override"], ";");
3170 
3171 				_sessionId = info[0];
3172 				auto hash = info[1];
3173 
3174 				if(_sessionId.length == 0 || !std.file.exists(getFilePath())) {
3175 					// there is no session
3176 					_readOnly = true;
3177 					return;
3178 				}
3179 
3180 				// FIXME: race condition if the session changes?
3181 				auto file = getFilePath();
3182 				auto contents = readText(file);
3183 				auto ourhash = hashToString(SHA256(contents));
3184 				enforce(ourhash == hash);//, ourhash);
3185 				_readOnly = true;
3186 				reload();
3187 			}
3188 		}
3189 	}
3190 
3191 	/// Call this periodically to clean up old session files. The finalizer param can cancel the deletion
3192 	/// of a file by returning false.
3193 	public static void garbageCollect(bool delegate(string[string] data) finalizer = null, Duration maxAge = dur!"hours"(4)) {
3194 		version(webd_memory_sessions)
3195 			return; // blargh. FIXME really, they should be null so the gc can free them
3196 		version(webd_cookie_sessions)
3197 			return; // nothing needed to be done here
3198 
3199 		auto ctime = Clock.currTime();
3200 		foreach(DirEntry e; dirEntries(getTempDirectory(), "arsd_session_file_*", SpanMode.shallow)) {
3201 			try {
3202 				if(ctime - e.timeLastAccessed() > maxAge) {
3203 					auto data = Session.loadData(e.name);
3204 
3205 					if(finalizer is null || !finalizer(data))
3206 						std.file.remove(e.name);
3207 				}
3208 			} catch(Exception except) {
3209 				// if it is bad, kill it
3210 				if(std.file.exists(e.name))
3211 					std.file.remove(e.name);
3212 			}
3213 		}
3214 	}
3215 
3216 	private void addDefaults() {
3217 		set("csrfToken", generateCsrfToken());
3218 		set("creationTime", Clock.currTime().toISOExtString());
3219 
3220 		// this is there to help control access to someone requesting a specific session id (helpful for debugging or local access from other languages)
3221 		// the idea is if there's some random stuff in there that you can only know if you have access to the file, it doesn't hurt to load that
3222 		// session, since they have to be able to read it to know this value anyway, so you aren't giving them anything they don't already have.
3223 		set("randomRandomness", to!string(uniform(0, ulong.max)));
3224 	}
3225 
3226 	private string makeSessionId(string cookieToken) {
3227 		// the remote address changes too much on some ISPs to be a useful check;
3228 		// using it means sessions get lost regularly and users log out :(
3229 		_sessionId = hashToString(SHA256(/*cgi.remoteAddress ~ "\r\n" ~*/ cgi.userAgent ~ "\r\n" ~ cookieToken));
3230 		return _sessionId;
3231 	}
3232 
3233 	private string generateCsrfToken() {
3234 		string[string] csrf;
3235 
3236 		csrf["key"] = to!string(uniform(0, ulong.max));
3237 		csrf["token"] = to!string(uniform(0, ulong.max));
3238 
3239 		return encodeVariables(csrf);
3240 	}
3241 
3242 	private CookieParams cookieParams;
3243 
3244 	// don't forget to make the new session id and set a new csrfToken after this too.
3245 	private string makeNewCookie() {
3246 		auto tmp = uniform(0, ulong.max);
3247 		auto token = to!string(tmp);
3248 
3249 		version(webd_cookie_sessions) {}
3250 		else
3251 			setOurCookie(token);
3252 
3253 		return token;
3254 	}
3255 
3256 	// FIXME: hack, see note on member string token
3257 	// don't use this, it is meant to be private (...probably)
3258 	/*private*/ void setOurCookie(string data) {
3259 		this.token = data;
3260 		if(!_readOnly)
3261 		cgi.setCookie(cookieParams.name, data,
3262 			cookieParams.expiresIn, cookieParams.path, cookieParams.host, true, cookieParams.httpsOnly);
3263 	}
3264 
3265 	/// Kill the current session. It wipes out the disk file and memory, and
3266 	/// changes the session ID.
3267 	///
3268 	/// You should do this if the user's authentication info changes
3269 	/// at all.
3270 	void invalidate() {
3271 		setOurCookie("");
3272 		clear();
3273 
3274 		regenerateId();
3275 	}
3276 
3277 	/// Creates a new cookie, session id, and csrf token, deleting the old disk data.
3278 	/// If you call commit() after doing this, it will save your existing data back to disk.
3279 	/// (If you don't commit, the data will be lost when this object is deleted.)
3280 	void regenerateId() {
3281 		// we want to clean up the old file, but keep the data we have in memory.
3282 
3283 		version(webd_memory_sessions) {
3284 			synchronized {
3285 				if(sessionId in sessions)
3286 					sessions.remove(sessionId);
3287 			}
3288 		} else version(webd_cookie_sessions) {
3289 			// intentionally blank; the cookie will replace the old one
3290 		} else {
3291 			if(std.file.exists(getFilePath()))
3292 				std.file.remove(getFilePath());
3293 		}
3294 
3295 		// and new cookie -> new session id -> new csrf token
3296 		makeSessionId(makeNewCookie());
3297 		addDefaults();
3298 
3299 		if(hasData)
3300 			changed = true;
3301 
3302 	}
3303 
3304 	/// Clears the session data from both memory and disk.
3305 	/// The session id is not changed by this function. To change it,
3306 	/// use invalidate() if you want to clear data and change the ID
3307 	/// or regenerateId() if you want to change the session ID, but not change the data.
3308 	///
3309 	/// Odds are, invalidate() is what you really want.
3310 	void clear() {
3311 		assert(!_readOnly); // or should I throw an exception or just silently ignore it???
3312 		version(webd_memory_sessions) {
3313 			synchronized {
3314 				if(sessionId in sessions)
3315 					sessions.remove(sessionId);
3316 			}
3317 		} else version(webd_cookie_sessions) {
3318 			// nothing needed, setting data to null will do the trick
3319 		} else {
3320 			if(std.file.exists(getFilePath()))
3321 				std.file.remove(getFilePath());
3322 		}
3323 		data = null;
3324 		_hasData = false;
3325 		changed = false;
3326 	}
3327 
3328 	// FIXME: what about expiring a session id and perhaps issuing a new
3329 	// one? They should automatically invalidate at some point.
3330 
3331 	// Both an idle timeout and a forced reauth timeout is good to offer.
3332 	// perhaps a helper function to do it in js too?
3333 
3334 
3335 	// I also want some way to attach to a session if you have root
3336 	// on the server, for convenience of debugging...
3337 
3338 	string sessionId() const {
3339 		return _sessionId;
3340 	}
3341 
3342 	bool hasData() const {
3343 		return _hasData;
3344 	}
3345 
3346 	/// like opIn
3347 	bool hasKey(string key) const {
3348 		auto ptr = key in data;
3349 		if(ptr is null)
3350 			return false;
3351 		else
3352 			return true;
3353 	}
3354 
3355 	void removeKey(string key) {
3356 		data.remove(key);
3357 		_hasData = true;
3358 		changed = true;
3359 	}
3360 
3361 	/// get/set for strings
3362 	@property string opDispatch(string name)(string v = null) if(name != "popFront") {
3363 		if(v !is null)
3364 			set(name, v);
3365 		if(hasKey(name))
3366 			return get(name);
3367 		return null;
3368 	}
3369 
3370 	string opIndex(string key) const {
3371 		return get(key);
3372 	}
3373 
3374 	string opIndexAssign(string value, string field) {
3375 		set(field, value);
3376 		return value;
3377 	}
3378 
3379 	// FIXME: doesn't seem to work
3380 	string* opBinary(string op)(string key)  if(op == "in") {
3381 		return key in fields;
3382 	}
3383 
3384 	void set(string key, string value) {
3385 		data[key] = value;
3386 		_hasData = true;
3387 		changed = true;
3388 	}
3389 
3390 	string get(string key) const {
3391 		if(key !in data)
3392 			throw new Exception("No such key in session: " ~ key);
3393 		return data[key];
3394 	}
3395 
3396 	private string getFilePath() const {
3397 		string path = getTempDirectory();
3398 		path ~= "arsd_session_file_" ~ sessionId;
3399 
3400 		return path;
3401 	}
3402 
3403 	private static string[string] loadData(string path) {
3404 		auto json = std.file.readText(path);
3405 		return loadDataFromJson(json);
3406 	}
3407 
3408 	private static string[string] loadDataFromJson(string json) {
3409 		string[string] data = null;
3410 		auto obj = parseJSON(json);
3411 		enforce(obj.type == JSONType.object);
3412 		foreach(k, v; obj.object) {
3413 			string ret;
3414 			final switch(v.type) {
3415 				case JSONType..string:
3416 					ret = v.str;
3417 				break;
3418 				case JSONType.uinteger:
3419 					ret = to!string(v.integer);
3420 				break;
3421 				case JSONType.integer:
3422 					ret = to!string(v.integer);
3423 				break;
3424 				case JSONType.float_:
3425 					ret = to!string(v.floating);
3426 				break;
3427 				case JSONType.object:
3428 				case JSONType.array:
3429 					enforce(0, "invalid session data");
3430 				break;
3431 				case JSONType.true_:
3432 					ret = "true";
3433 				break;
3434 				case JSONType.false_:
3435 					ret = "false";
3436 				break;
3437 				case JSONType.null_:
3438 					ret = null;
3439 				break;
3440 			}
3441 
3442 			data[k] = ret;
3443 		}
3444 
3445 		return data;
3446 	}
3447 
3448 	// FIXME: there's a race condition here - if the user is using the session
3449 	// from two windows, one might write to it as we're executing, and we won't
3450 	// see the difference.... meaning we'll write the old data back.
3451 
3452 	/// Discards your changes, reloading the session data from the disk file.
3453 	void reload() {
3454 		data = null;
3455 
3456 		version(webd_memory_sessions) {
3457 			synchronized {
3458 				if(auto s = sessionId in sessions) {
3459 					foreach(k, v; *s)
3460 						data[k] = v;
3461 					_hasData = true;
3462 				} else {
3463 					_hasData = false;
3464 				}
3465 			}
3466 		} else version(webd_cookie_sessions) {
3467 			// FIXME
3468 			if(cookieParams.name in cgi.cookies) {
3469 				auto cookie = cgi.cookies[cookieParams.name];
3470 				if(cookie.length) {
3471 					import std.base64;
3472 					import std.zlib;
3473 
3474 					auto cd = Base64URL.decode(cookie);
3475 
3476 					if(cd[0] == 'Z')
3477 						cd = cast(ubyte[]) uncompress(cd[1 .. $]);
3478 
3479 					if(cd.length > 20) {
3480 						auto hash = cd[$ - 20 .. $];
3481 						auto json = cast(string) cd[0 .. $-20];
3482 
3483 						data = loadDataFromJson(json);
3484 					}
3485 				}
3486 			}
3487 		} else {
3488 			auto path = getFilePath();
3489 			try {
3490 				data = Session.loadData(path);
3491 				_hasData = true;
3492 			} catch(Exception e) {
3493 				// it's a bad session...
3494 				_hasData = false;
3495 				data = null;
3496 				if(std.file.exists(path))
3497 					std.file.remove(path);
3498 			}
3499 		}
3500 	}
3501 
3502 	version(webd_cookie_sessions)
3503 	private ubyte[20] getSignature(string jsonData) {
3504 		import arsd.hmac;
3505 		return hmac!SHA1(import("webd-cookie-signature-key.txt"), jsonData)[0 .. 20];
3506 	}
3507 
3508 	// FIXME: there's a race condition here - if the user is using the session
3509 	// from two windows, one might write to it as we're executing, and we won't
3510 	// see the difference.... meaning we'll write the old data back.
3511 
3512 	/// Commits your changes back to disk.
3513 	void commit(bool force = false) {
3514 		if(_readOnly)
3515 			return;
3516 		if(force || changed) {
3517 			version(webd_memory_sessions) {
3518 				synchronized {
3519 					sessions[sessionId] = data;
3520 					changed = false;
3521 				}
3522 			} else version(webd_cookie_sessions) {
3523 				immutable(ubyte)[] dataForCookie;
3524 				auto jsonData = toJson(data);
3525 				auto hash = getSignature(jsonData);
3526 				if(jsonData.length > 64) {
3527 					import std.zlib;
3528 					// file format: JSON ~ hash[20]
3529 					auto compressor = new Compress();
3530 					dataForCookie ~= "Z";
3531 					dataForCookie ~= cast(ubyte[]) compressor.compress(toJson(data));
3532 					dataForCookie ~= cast(ubyte[]) compressor.compress(hash);
3533 					dataForCookie ~= cast(ubyte[]) compressor.flush();
3534 				} else {
3535 					dataForCookie = cast(immutable(ubyte)[]) jsonData;
3536 					dataForCookie ~= hash;
3537 				}
3538 
3539 				import std.base64;
3540 				setOurCookie(Base64URL.encode(dataForCookie));
3541 				changed = false;
3542 			} else {
3543 				std.file.write(getFilePath(), toJson(data));
3544 				changed = false;
3545 				// We have to make sure that only we can read this file,
3546 				// since otherwise, on shared hosts, our session data might be
3547 				// easily stolen. Note: if your shared host doesn't have different
3548 				// users on the operating system for each user, it's still possible
3549 				// for them to access this file and hijack your session!
3550 				version(linux)
3551 					enforce(sys_stat.chmod(toStringz(getFilePath()), octal!600) == 0, "chmod failed");
3552 				// FIXME: ensure the file's read permissions are locked down
3553 				// on Windows too.
3554 			}
3555 		}
3556 	}
3557 
3558 	string[string] data;
3559 	private bool _hasData;
3560 	private bool changed;
3561 	private bool _readOnly;
3562 	private string _sessionId;
3563 	private Cgi cgi; // used to regenerate cookies, etc.
3564 
3565 	string token; // this isn't private, but don't use it FIXME this is a hack to allow cross domain session sharing on the same server....
3566 
3567 	//private Variant[string] data;
3568 	/*
3569 	Variant* opBinary(string op)(string key)  if(op == "in") {
3570 		return key in data;
3571 	}
3572 
3573 	T get(T)(string key) {
3574 		if(key !in data)
3575 			throw new Exception(key ~ " not in session data");
3576 
3577 		return data[key].coerce!T;
3578 	}
3579 
3580 	void set(T)(string key, T t) {
3581 		Variant v;
3582 		v = t;
3583 		data[key] = t;
3584 	}
3585 	*/
3586 }
3587 
3588 /// sets a site-wide cookie, meant to simplify login code. Note: you often might not want a side wide cookie, but I usually do since my projects need single sessions across multiple thingies, hence, this.
3589 void setLoginCookie(Cgi cgi, string name, string value) {
3590 	cgi.setCookie(name, value, 0, "/", null, true);
3591 }
3592 
3593 
3594 
3595 immutable(string[]) monthNames = [
3596 	null,
3597 	"January",
3598 	"February",
3599 	"March",
3600 	"April",
3601 	"May",
3602 	"June",
3603 	"July",
3604 	"August",
3605 	"September",
3606 	"October",
3607 	"November",
3608 	"December"
3609 ];
3610 
3611 immutable(string[]) weekdayNames = [
3612 	"Sunday",
3613 	"Monday",
3614 	"Tuesday",
3615 	"Wednesday",
3616 	"Thursday",
3617 	"Friday",
3618 	"Saturday"
3619 ];
3620 
3621 
3622 // this might be temporary
3623 struct TemplateFilters {
3624 	// arguments:
3625 	//                              args (space separated on pipe), context element, attribute name (if we're in an attribute)
3626 	// string (string replacement, string[], in Element, string)
3627 
3628 	string date(string replacement, string[], in Element, string) {
3629 		if(replacement.length == 0)
3630 			return replacement;
3631 		SysTime date;
3632 		if(replacement.isNumeric) {
3633 			auto dateTicks = to!long(replacement);
3634 			date = SysTime( unixTimeToStdTime(cast(time_t)(dateTicks/1_000)) );
3635 		} else {
3636 			date = cast(SysTime) DateTime.fromISOExtString(replacement);
3637 		}
3638 
3639 		auto day = date.day;
3640 		auto year = date.year;
3641 		assert(date.month < monthNames.length, to!string(date.month));
3642 		auto month = monthNames[date.month];
3643 		replacement = format("%s %d, %d", month, day, year);
3644 
3645 		return replacement;
3646 	}
3647 
3648 	string limitSize(string replacement, string[] args, in Element, string) {
3649 		auto limit = to!int(args[0]);
3650 
3651 		if(replacement.length > limit) {
3652 			replacement = replacement[0 .. limit];
3653 			while(replacement.length && replacement[$-1] > 127)
3654 				replacement = replacement[0 .. $-1];
3655 
3656 			if(args.length > 1)
3657 				replacement ~= args[1];
3658 		}
3659 
3660 		return replacement;
3661 	}
3662 
3663 	string uri(string replacement, string[], in Element, string) {
3664 		return std.uri.encodeComponent(replacement);
3665 	}
3666 
3667 	string js(string replacement, string[], in Element, string) {
3668 		return toJson(replacement);
3669 	}
3670 
3671 	string article(string replacement, string[], in Element, string) {
3672 		if(replacement.length && replacement[0].isVowel())
3673 			return "an " ~ replacement;
3674 		else if(replacement.length)
3675 			return "a " ~ replacement;
3676 		return replacement;
3677 	}
3678 
3679 	string capitalize(string replacement, string[], in Element, string) {
3680 		return std..string.capitalize(replacement);
3681 	}
3682 
3683 	string possessive(string replacement, string[], in Element, string) {
3684 		if(replacement.length && replacement[$ - 1] == 's')
3685 			return replacement ~ "'";
3686 		else if(replacement.length)
3687 			return replacement ~ "'s";
3688 		else
3689 			return replacement;
3690 	}
3691 
3692 	// {$count|plural singular plural}
3693 	string plural(string replacement, string[] args, in Element, string) {
3694 		return pluralHelper(replacement, args.length ? args[0] : null, args.length > 1 ? args[1] : null);
3695 	}
3696 
3697 	string pluralHelper(string number, string word, string pluralWord = null) {
3698 		if(word.length == 0)
3699 			return word;
3700 
3701 		int count = 0;
3702 		if(number.length && std..string.isNumeric(number))
3703 			count = to!int(number);
3704 
3705 		if(count == 1)
3706 			return word; // it isn't actually plural
3707 
3708 		if(pluralWord !is null)
3709 			return pluralWord;
3710 
3711 		switch(word[$ - 1]) {
3712 			case 's':
3713 			case 'a', 'i', 'o', 'u':
3714 				return word ~ "es";
3715 			case 'f':
3716 				return word[0 .. $-1] ~ "ves";
3717 			case 'y':
3718 				return word[0 .. $-1] ~ "ies";
3719 			default:
3720 				return word ~ "s";
3721 		}
3722 	}
3723 
3724 	// replacement is the number here, and args is some text to write
3725 	// it goes {$count|cnt thing(s)}
3726 	string cnt(string replacement, string[] args, in Element, string) {
3727 		string s = replacement;
3728 		foreach(arg; args) {
3729 			s ~= " ";
3730 			if(arg.endsWith("(s)"))
3731 				s ~= pluralHelper(replacement, arg[0 .. $-3]);
3732 			else
3733 				s ~= arg;
3734 		}
3735 
3736 		return s;
3737 	}
3738 
3739 	string stringArray(string replacement, string[] args, in Element, string) {
3740 		if(replacement.length == 0)
3741 			return replacement;
3742 		int idx = to!int(replacement);
3743 		if(idx < 0 || idx >= args.length)
3744 			return replacement;
3745 		return args[idx];
3746 	}
3747 
3748 	string boolean(string replacement, string[] args, in Element, string) {
3749 		if(replacement == "1")
3750 			return "yes";
3751 		return "no";
3752 	}
3753 
3754 	static auto defaultThings() {
3755 		string delegate(string, string[], in Element, string)[string] pipeFunctions;
3756 		TemplateFilters filters;
3757 
3758 		string delegate(string, string[], in Element, string) tmp;
3759 		foreach(member; __traits(allMembers, TemplateFilters)) {
3760 			static if(__traits(compiles, tmp = &__traits(getMember, filters, member))) {
3761 				if(member !in pipeFunctions)
3762 					pipeFunctions[member] = &__traits(getMember, filters, member);
3763 			}
3764 		}
3765 
3766 		return pipeFunctions;
3767 	}
3768 }
3769 
3770 
3771 string applyTemplateToText(
3772 	string text,
3773 	in string[string] vars,
3774 	in string delegate(string, string[], in Element, string)[string] pipeFunctions = TemplateFilters.defaultThings())
3775 {
3776 	// kinda hacky, but meh
3777 	auto element = Element.make("body");
3778 	element.innerText = text;
3779 	applyTemplateToElement(element, vars, pipeFunctions);
3780 	return element.innerText;
3781 }
3782 
3783 void applyTemplateToElement(
3784 	Element e,
3785 	in string[string] vars,
3786 	in string delegate(string, string[], in Element, string)[string] pipeFunctions = TemplateFilters.defaultThings())
3787 {
3788 
3789 	foreach(ele; e.tree) {
3790 		auto tc = cast(TextNode) ele;
3791 		if(tc !is null) {
3792 			// text nodes have no attributes, but they do have text we might replace.
3793 			tc.contents = htmlTemplateWithData(tc.contents, vars, pipeFunctions, false, tc, null);
3794 		} else {
3795 			if(ele.hasAttribute("data-html-from")) {
3796 				ele.innerHTML = htmlTemplateWithData(ele.dataset.htmlFrom, vars, pipeFunctions, false, ele, null);
3797 				ele.removeAttribute("data-html-from");
3798 			}
3799 
3800 			auto rs = cast(RawSource) ele;
3801 			if(rs !is null) {
3802 				bool isSpecial;
3803 				if(ele.parentNode)
3804 					isSpecial = ele.parentNode.tagName == "script" || ele.parentNode.tagName == "style";
3805 				rs.source = htmlTemplateWithData(rs.source, vars, pipeFunctions, !isSpecial, rs, null); /* FIXME: might be wrong... */
3806 			}
3807 			// if it is not a text node, it has no text where templating is valid, except the attributes
3808 			// note: text nodes have no attributes, which is why this is in the separate branch.
3809 			foreach(k, v; ele.attributes) {
3810 				if(k == "href" || k == "src") {
3811 					// FIXME: HACK this should be properly context sensitive..
3812 					v = v.replace("%7B%24", "{$");
3813 					v = v.replace("%7D", "}");
3814 				}
3815 				ele.attributes[k] = htmlTemplateWithData(v, vars, pipeFunctions, false, ele, k);
3816 			}
3817 		}
3818 	}
3819 }
3820 
3821 // this thing sucks a little less now.
3822 // set useHtml to false if you're working on internal data (such as TextNode.contents, or attribute);
3823 // it should only be set to true if you're doing input that has already been ran through toString or something.
3824 // NOTE: I'm probably going to change the pipe function thing a bit more, but I'm basically happy with it now.
3825 string htmlTemplateWithData(in string text, in string[string] vars, in string delegate(string, string[], in Element, string)[string] pipeFunctions, bool useHtml, Element contextElement = null, string contextAttribute = null) {
3826 	if(text is null)
3827 		return null;
3828 
3829 	int state = 0;
3830 
3831 	string newText = null;
3832 
3833 	size_t nameStart;
3834 	size_t replacementStart;
3835 	size_t lastAppend = 0;
3836 
3837 	string name = null;
3838 	bool replacementPresent = false;
3839 	string replacement = null;
3840 	string currentPipe = null;
3841 
3842 	foreach(i, c; text) {
3843 		void stepHandler() {
3844 			if(name is null)
3845 				name = text[nameStart .. i];
3846 			else if(nameStart != i)
3847 				currentPipe = text[nameStart .. i];
3848 
3849 			nameStart = i + 1;
3850 
3851 			if(!replacementPresent) {
3852 				auto it = name in vars;
3853 				if(it !is null) {
3854 					replacement = *it;
3855 					replacementPresent = true;
3856 				}
3857 			}
3858 		}
3859 
3860 		void pipeHandler() {
3861 			if(currentPipe is null || replacement is null)
3862 				return;
3863 
3864 			auto pieces = currentPipe.split(" ");
3865 			assert(pieces.length);
3866 			auto pipeName = pieces[0];
3867 			auto pipeArgs = pieces[1 ..$];
3868 
3869 			foreach(ref arg; pipeArgs) {
3870 				if(arg.length && arg[0] == '$') {
3871 					string n = arg[1 .. $];
3872 					auto idx = n.indexOf("(");
3873 					string moar;
3874 					if(idx != -1) {
3875 						moar = n[idx .. $];
3876 						n = n[0 .. idx];
3877 					}
3878 
3879 					if(n in vars)
3880 						arg = vars[n] ~ moar;
3881 				}
3882 			}
3883 
3884 			if(pipeName in pipeFunctions) {
3885 				replacement = pipeFunctions[pipeName](replacement, pipeArgs, contextElement, contextAttribute);
3886 				// string, string[], in Element, string
3887 			}
3888 
3889 			currentPipe = null;
3890 		}
3891 
3892 		switch(state) {
3893 			default: assert(0);
3894 			case 0:
3895 				if(c == '{') {
3896 					replacementStart = i;
3897 					state++;
3898 					replacementPresent = false;
3899 				}
3900 			break;
3901 			case 1:
3902 				if(c == '$')
3903 					state++;
3904 				else
3905 					state--; // not a variable
3906 			break;
3907 			case 2: // just started seeing a name
3908 				if(c == '}') {
3909 					state = 0; // empty names aren't allowed; ignore it
3910 				} else {
3911 					nameStart = i;
3912 					name = null;
3913 					state++;
3914 				}
3915 			break;
3916 			case 3: // reading a name
3917 				// the pipe operator lets us filter the text
3918 				if(c == '|') {
3919 					pipeHandler();
3920 					stepHandler();
3921 				} else if(c == '}') {
3922 					// just finished reading it, let's do our replacement.
3923 					pipeHandler(); // anything that was there
3924 					stepHandler(); // might make a new pipe if the first...
3925 					pipeHandler(); // new names/pipes since this is the last go
3926 					if(name !is null && replacementPresent /*&& replacement !is null*/) {
3927 						newText ~= text[lastAppend .. replacementStart];
3928 						if(useHtml)
3929 							replacement = htmlEntitiesEncode(replacement).replace("\n", "<br />");
3930 						newText ~= replacement;
3931 						lastAppend = i + 1;
3932 						replacement = null;
3933 					}
3934 
3935 					state = 0;
3936 				}
3937 			break;
3938 		}
3939 	}
3940 
3941 	if(newText is null)
3942 		newText = text; // nothing was found, so no need to risk allocating anything...
3943 	else
3944 		newText ~= text[lastAppend .. $]; // make sure we have everything here
3945 
3946 	return newText;
3947 }
3948 
3949 /// a specialization of Document that: a) is always in strict mode and b) provides some template variable text replacement, in addition to DOM manips. The variable text is valid in text nodes and attribute values. It takes the format of {$variable}, where variable is a key into the vars member.
3950 class TemplatedDocument : Document {
3951 	override string toString() const {
3952 		if(this.root !is null)
3953 			applyTemplateToElement(cast() this.root, vars, viewFunctions); /* FIXME: I shouldn't cast away const, since it's rude to modify an object in any toString.... but that's what I'm doing for now */
3954 
3955 		return super.toString();
3956 	}
3957 
3958 	public:
3959 		string[string] vars; /// use this to set up the string replacements. document.vars["name"] = "adam"; then in doc, <p>hellp, {$name}.</p>. Note the vars are converted lazily at toString time and are always HTML escaped.
3960 		/// In the html templates, you can write {$varname} or {$varname|func} (or {$varname|func arg arg|func} and so on). This holds the functions available these. The TemplatedDocument constructor puts in a handful of generic ones.
3961 		string delegate(string, string[], in Element, string)[string] viewFunctions;
3962 
3963 
3964 		this(string src) {
3965 			super();
3966 			viewFunctions = TemplateFilters.defaultThings();
3967 			parse(src, true, true);
3968 		}
3969 
3970 		this() {
3971 			viewFunctions = TemplateFilters.defaultThings();
3972 		}
3973 
3974 		void delegate(TemplatedDocument)[] preToStringFilters;
3975 		void delegate(ref string)[] postToStringFilters;
3976 }
3977 
3978 /// a convenience function to do filters on your doc and write it out. kinda useless still at this point.
3979 void writeDocument(Cgi cgi, TemplatedDocument document) {
3980 	foreach(f; document.preToStringFilters)
3981 		f(document);
3982 
3983 	auto s = document.toString();
3984 
3985 	foreach(f; document.postToStringFilters)
3986 		f(s);
3987 
3988 	cgi.write(s);
3989 }
3990 
3991 /* Password helpers */
3992 
3993 /// These added a dependency on arsd.sha, but hashing passwords is somewhat useful in a lot of apps so I figured it was worth it.
3994 /// use this to make the hash to put in the database...
3995 string makeSaltedPasswordHash(string userSuppliedPassword, string salt = null) {
3996 	if(salt is null)
3997 		salt = to!string(uniform(0, int.max));
3998 
3999 		// FIXME: sha256 is actually not ideal for this, but meh it's what i have.
4000 	return hashToString(SHA256(salt ~ userSuppliedPassword)) ~ ":" ~ salt;
4001 }
4002 
4003 /// and use this to check it.
4004 bool checkPassword(string saltedPasswordHash, string userSuppliedPassword) {
4005 	auto parts = saltedPasswordHash.split(":");
4006 
4007 	return makeSaltedPasswordHash(userSuppliedPassword, parts[1]) == saltedPasswordHash;
4008 }
4009 
4010 
4011 /// implements the "table" format option. Works on structs and associative arrays (string[string][])
4012 Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) if(isArray!(T) && !isAssociativeArray!(T)) {
4013 	auto t = cast(Table) document.createElement("table");
4014 	t.attrs.border = "1";
4015 
4016 	static if(is(T == string[string][])) {
4017 			string[string] allKeys;
4018 			foreach(row; arr) {
4019 				foreach(k; row.keys)
4020 					allKeys[k] = k;
4021 			}
4022 
4023 			auto sortedKeys = allKeys.keys.sort;
4024 			Element tr;
4025 
4026 			auto thead = t.addChild("thead");
4027 			auto tbody = t.addChild("tbody");
4028 
4029 			tr = thead.addChild("tr");
4030 			foreach(key; sortedKeys)
4031 				tr.addChild("th", key);
4032 
4033 			bool odd = true;
4034 			foreach(row; arr) {
4035 				tr = tbody.addChild("tr");
4036 				foreach(k; sortedKeys) {
4037 					tr.addChild("td", k in row ? row[k] : "");
4038 				}
4039 				if(odd)
4040 					tr.addClass("odd");
4041 
4042 				odd = !odd;
4043 			}
4044 	} else static if(is(typeof(arr[0]) == struct)) {
4045 		{
4046 			auto thead = t.addChild("thead");
4047 			auto tr = thead.addChild("tr");
4048 			if(arr.length) {
4049 				auto s = arr[0];
4050 				foreach(idx, member; s.tupleof)
4051 					tr.addChild("th", s.tupleof[idx].stringof[2..$]);
4052 			}
4053 		}
4054 
4055 		bool odd = true;
4056 		auto tbody = t.addChild("tbody");
4057 		foreach(s; arr) {
4058 			auto tr = tbody.addChild("tr");
4059 			foreach(member; s.tupleof) {
4060 				static if(is(typeof(member) == URL[])) {
4061 					auto td = tr.addChild("td");
4062 					foreach(i, link; member) {
4063 						td.addChild("a", link.title.length ? link.title : to!string(i), link.url);
4064 						td.appendText(" ");
4065 					}
4066 
4067 				} else {
4068 					tr.addChild("td", Html(toHtml(member)));
4069 				}
4070 			}
4071 
4072 			if(odd)
4073 				tr.addClass("odd");
4074 
4075 			odd = !odd;
4076 		}
4077 	} else static assert(0, T.stringof);
4078 
4079 	return t;
4080 }
4081 
4082 // this one handles horizontal tables showing just one item
4083 /// does a name/field table for just a singular object
4084 Table structToTable(T)(Document document, T s, string[] fieldsToSkip = null) if(!isArray!(T) || isAssociativeArray!(T)) {
4085 	static if(__traits(compiles, s.makeHtmlTable(document)))
4086 		return s.makeHtmlTable(document);
4087 	else {
4088 
4089 		auto t = cast(Table) document.createElement("table");
4090 
4091 		static if(is(T == struct)) {
4092 			main: foreach(i, member; s.tupleof) {
4093 				string name = s.tupleof[i].stringof[2..$];
4094 				foreach(f; fieldsToSkip)
4095 					if(name == f)
4096 						continue main;
4097 
4098 				string nameS = name.idup;
4099 				name = "";
4100 				foreach(idx, c; nameS) {
4101 					if(c >= 'A' && c <= 'Z')
4102 						name ~= " " ~ c;
4103 					else if(c == '_')
4104 						name ~= " ";
4105 					else
4106 						name ~= c;
4107 				}
4108 
4109 				t.appendRow(t.th(name.capitalize),
4110 					to!string(member));
4111 			}
4112 		} else static if(is(T == string[string])) {
4113 			foreach(k, v; s){
4114 				t.appendRow(t.th(k), v);
4115 			}
4116 		} else static assert(0);
4117 
4118 		return t;
4119 	}
4120 }
4121 
4122 /// This adds a custom attribute to links in the document called qsa which modifies the values on the query string
4123 void translateQsa(Document document, Cgi cgi, string logicalScriptName = null) {
4124 	if(document is null || cgi is null)
4125 		return;
4126 
4127 	if(logicalScriptName is null)
4128 		logicalScriptName = cgi.logicalScriptName;
4129 
4130 	foreach(a; document.querySelectorAll("a[qsa]")) {
4131 		string href = logicalScriptName ~ cgi.pathInfo ~ "?";
4132 
4133 		int matches, possibilities;
4134 
4135 		string[][string] vars;
4136 		foreach(k, v; cgi.getArray)
4137 			vars[k] = cast(string[]) v;
4138 		foreach(k, v; decodeVariablesSingle(a.attrs.qsa)) {
4139 			if(k in cgi.get && cgi.get[k] == v)
4140 				matches++;
4141 			possibilities++;
4142 
4143 			if(k !in vars || vars[k].length <= 1)
4144 				vars[k] = [v];
4145 			else
4146 				assert(0, "qsa doesn't work here");
4147 		}
4148 
4149 		string[] clear = a.getAttribute("qsa-clear").split("&");
4150 		clear ~= "ajaxLoading";
4151 		if(a.parentNode !is null)
4152 			clear ~= a.parentNode.getAttribute("qsa-clear").split("&");
4153 
4154 		bool outputted = false;
4155 		varskip: foreach(k, varr; vars) {
4156 			foreach(item; clear)
4157 				if(k == item)
4158 					continue varskip;
4159 			foreach(v; varr) {
4160 				if(outputted)
4161 					href ~= "&";
4162 				else
4163 					outputted = true;
4164 
4165 				href ~= std.uri.encodeComponent(k) ~ "=" ~ std.uri.encodeComponent(v);
4166 			}
4167 		}
4168 
4169 		a.href = href;
4170 
4171 		a.removeAttribute("qsa");
4172 
4173 		if(matches == possibilities)
4174 			a.addClass("current");
4175 	}
4176 }
4177 
4178 /// This uses reflection info to generate Javascript that can call the server with some ease.
4179 /// Also includes javascript base (see bottom of this file)
4180 string makeJavascriptApi(const ReflectionInfo* mod, string base, bool isNested = false) {
4181 	assert(mod !is null);
4182 
4183 	string script;
4184 
4185 	if(0 && isNested)
4186 		script = `'`~mod.name~`': {
4187 	"_apiBase":'`~base~`',`;
4188 	else
4189 		script = `var `~mod.name~` = {
4190 	"_apiBase":'`~base~`',`;
4191 
4192 	script ~= javascriptBase;
4193 
4194 	script ~= "\n\t";
4195 
4196 	bool[string] alreadyDone;
4197 
4198 	bool outp = false;
4199 
4200 	foreach(s; mod.enums) {
4201 		if(outp)
4202 			script ~= ",\n\t";
4203 		else
4204 			outp = true;
4205 
4206 		script ~= "'"~s.name~"': {\n";
4207 
4208 		bool outp2 = false;
4209 		foreach(i, n; s.names) {
4210 			if(outp2)
4211 				script ~= ",\n";
4212 			else
4213 				outp2 = true;
4214 
4215 			// auto v = s.values[i];
4216 			auto v = "'" ~ n ~ "'"; // we actually want to use the name here because to!enum() uses member name.
4217 
4218 			script ~= "\t\t'"~n~"':" ~ to!string(v);
4219 		}
4220 
4221 		script ~= "\n\t}";
4222 	}
4223 
4224 	foreach(s; mod.structs) {
4225 		if(outp)
4226 			script ~= ",\n\t";
4227 		else
4228 			outp = true;
4229 
4230 		script ~= "'"~s.name~"': function(";
4231 
4232 		bool outp2 = false;
4233 		foreach(n; s.members) {
4234 			if(outp2)
4235 				script ~= ", ";
4236 			else
4237 				outp2 = true;
4238 
4239 			script ~= n.name;
4240 
4241 		}
4242 		script ~= ") { return {\n";
4243 
4244 		outp2 = false;
4245 
4246 		script ~= "\t\t'_arsdTypeOf':'"~s.name~"'";
4247 		if(s.members.length)
4248 			script ~= ",";
4249 		script ~= " // metadata, ought to be read only\n";
4250 
4251 		// outp2 is still false because I put the comma above
4252 		foreach(n; s.members) {
4253 			if(outp2)
4254 				script ~= ",\n";
4255 			else
4256 				outp2 = true;
4257 
4258 			auto v = n.defaultValue;
4259 
4260 			script ~= "\t\t'"~n.name~"': (typeof "~n.name~" == 'undefined') ? "~n.name~" : '" ~ to!string(v) ~ "'";
4261 		}
4262 
4263 		script ~= "\n\t}; }";
4264 	}
4265 
4266 	foreach(key, func; mod.functions) {
4267 		if(func.originalName in alreadyDone)
4268 			continue; // there's url friendly and code friendly, only need one
4269 
4270 		alreadyDone[func.originalName] = true;
4271 
4272 		if(outp)
4273 			script ~= ",\n\t";
4274 		else
4275 			outp = true;
4276 
4277 
4278 		string args;
4279 		string obj;
4280 		bool outputted = false;
4281 		/+
4282 		foreach(i, arg; func.parameters) {
4283 			if(outputted) {
4284 				args ~= ",";
4285 				obj ~= ",";
4286 			} else
4287 				outputted = true;
4288 
4289 			args ~= arg.name;
4290 
4291 			// FIXME: we could probably do better checks here too like on type
4292 			obj ~= `'`~arg.name~`':(typeof `~arg.name ~ ` == "undefined" ? this._raiseError('InsufficientParametersException', '`~func.originalName~`: argument `~to!string(i) ~ " (" ~ arg.staticType~` `~arg.name~`) is not present') : `~arg.name~`)`;
4293 		}
4294 		+/
4295 
4296 		/*
4297 		if(outputted)
4298 			args ~= ",";
4299 		args ~= "callback";
4300 		*/
4301 
4302 		script ~= `'` ~ func.originalName ~ `'`;
4303 		script ~= ":";
4304 		script ~= `function(`~args~`) {`;
4305 		if(obj.length)
4306 		script ~= `
4307 		var argumentsObject = {
4308 			`~obj~`
4309 		};
4310 		return this._serverCall('`~key~`', argumentsObject, '`~func.returnType~`');`;
4311 		else
4312 		script ~= `
4313 		return this._serverCall('`~key~`', arguments, '`~func.returnType~`');`;
4314 
4315 		script ~= `
4316 	}`;
4317 	}
4318 
4319 	script ~= "\n}";
4320 
4321 	// some global stuff to put in
4322 	if(!isNested)
4323 	script ~= `
4324 		if(typeof arsdGlobalStuffLoadedForWebDotD == "undefined") {
4325 			arsdGlobalStuffLoadedForWebDotD = true;
4326 			var oldObjectDotPrototypeDotToString = Object.prototype.toString;
4327 			Object.prototype.toString = function() {
4328 				if(this.formattedSecondarily)
4329 					return this.formattedSecondarily;
4330 
4331 				return  oldObjectDotPrototypeDotToString.call(this);
4332 			}
4333 		}
4334 	`;
4335 
4336 	// FIXME: it should output the classes too
4337 	// FIXME: hax hax hax
4338 	foreach(n, obj; mod.objects) {
4339 		script ~= ";";
4340 		//if(outp)
4341 		//	script ~= ",\n\t";
4342 		//else
4343 		//	outp = true;
4344 
4345 		script ~= makeJavascriptApi(obj, base ~ n ~ "/", true);
4346 	}
4347 
4348 	return script;
4349 }
4350 
4351 bool isVowel(char c) {
4352 	return (
4353 		c == 'a' || c == 'A' ||
4354 		c == 'e' || c == 'E' ||
4355 		c == 'i' || c == 'I' ||
4356 		c == 'o' || c == 'O' ||
4357 		c == 'u' || c == 'U'
4358 	);
4359 }
4360 
4361 
4362 debug string javascriptBase = `
4363 	// change this in your script to get fewer error popups
4364 	"_debugMode":true,` ~ javascriptBaseImpl;
4365 else string javascriptBase = `
4366 	// change this in your script to get more details in errors
4367 	"_debugMode":false,` ~ javascriptBaseImpl;
4368 
4369 /// The Javascript code used in the generated JS API.
4370 /**
4371 	It provides the foundation to calling the server via background requests
4372 	and handling the response in callbacks. (ajax style stuffs).
4373 
4374 	The names with a leading underscore are meant to be private.
4375 
4376 
4377 	Generally:
4378 
4379 	YourClassName.yourMethodName(args...).operation(args);
4380 
4381 
4382 	CoolApi.getABox("red").useToReplace(document.getElementById("playground"));
4383 
4384 	for example.
4385 
4386 	When you call a method, it doesn't make the server request. Instead, it returns
4387 	an object describing the call. This means you can manipulate it (such as requesting
4388 	a custom format), pass it as an argument to other functions (thus saving http requests)
4389 	and finally call it at the end.
4390 
4391 	The operations are:
4392 		get(callback, args to callback...);
4393 
4394 		See below.
4395 
4396 		useToReplace(element) // pass an element reference. Example: useToReplace(document.querySelector(".name"));
4397 		useToReplace(element ID : string) // you pass a string, it calls document.getElementById for you
4398 
4399 		useToReplace sets the given element's innerHTML to the return value. The return value is automatically requested
4400 		to be formatted as HTML.
4401 
4402 		appendTo(element)
4403 		appendTo(element ID : String)
4404 
4405 		Adds the return value, as HTML, to the given element's inner html.
4406 
4407 		useToReplaceElement(element)
4408 
4409 		Replaces the given element entirely with the return value. (basically element.outerHTML = returnValue;)
4410 
4411 		useToFillForm(form)
4412 
4413 		Takes an object. Loop through the members, setting the form.elements[key].value = value.
4414 
4415 		Does not work if the return value is not a javascript object (so use it if your function returns a struct or string[string])
4416 
4417 		getSync()
4418 
4419 		Does a synchronous get and returns the server response. Not recommended.
4420 
4421 	get() :
4422 
4423 		The generic get() function is the most generic operation to get a response. It's arguments implement
4424 		partial application for you, so you can pass just about any callback to it.
4425 
4426 		Despite the name, the underlying operation may be HTTP GET or HTTP POST. This is determined from the
4427 		function's server side attributes. (FIXME: implement smarter thing. Currently it actually does it by name - if
4428 		the function name starts with get, do get. Else, do POST.)
4429 
4430 
4431 		Usage:
4432 
4433 		CoolApi.getABox('red').get(alert); // calls alert(returnedValue);  so pops up the returned value
4434 
4435 		CoolApi.getABox('red').get(fadeOut, this); // calls fadeOut(this, returnedValue);
4436 
4437 
4438 		Since JS functions generally ignore extra params, this lets you call just about anything:
4439 
4440 		CoolApi.getABox('red').get(alert, "Success"); // pops a box saying "Success", ignoring the actual return value
4441 
4442 
4443 		Passing arguments to the functions let you reuse a lot of things that might not have been designed with this in mind.
4444 		If you use arsd.js, there's other little functions that let you turn properties into callbacks too.
4445 
4446 
4447 		Passing "this" to a callback via get is useful too since inside the callback, this probably won't refer to what you
4448 		wanted. As an argument though, it all remains sane.
4449 
4450 
4451 
4452 
4453 	Error Handling:
4454 
4455 		D exceptions are translated into Javascript exceptions by the serverCall function. They are thrown, but since it's
4456 		async, catching them is painful.
4457 
4458 		It will probably show up in your browser's error console, or you can set the returned object's onerror function
4459 		to something to handle it callback style. FIXME: not sure if this actually works right!
4460 */
4461 // FIXME: this should probably be rewritten to make a constructable prototype object instead of a literal.
4462 enum string javascriptBaseImpl = q{
4463 	"_doRequest": function(url, args, callback, method, async) {
4464 		var xmlHttp;
4465 		try {
4466 			xmlHttp=new XMLHttpRequest();
4467 		}
4468 		catch (e) {
4469 			try {
4470 				xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
4471 			}
4472 			catch (e) {
4473 				xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
4474 			}
4475 		}
4476 
4477 		if(async)
4478 		xmlHttp.onreadystatechange=function() {
4479 			if(xmlHttp.readyState==4) {
4480 				// either if the function is nor available or if it returns a good result, we're set.
4481 				// it might get to this point without the headers if the request was aborted
4482 				if(callback && (!xmlHttp.getAllResponseHeaders || xmlHttp.getAllResponseHeaders())) {
4483 					callback(xmlHttp.responseText, xmlHttp.responseXML);
4484 				}
4485 			}
4486 		}
4487 
4488 		var argString = this._getArgString(args);
4489 		if(method == "GET" && url.indexOf("?") == -1)
4490 			url = url + "?" + argString;
4491 
4492 		xmlHttp.open(method, url, async);
4493 
4494 		var a = "";
4495 
4496 		var csrfKey = document.body.getAttribute("data-csrf-key");
4497 		var csrfToken = document.body.getAttribute("data-csrf-token");
4498 		var csrfPair = "";
4499 		if(csrfKey && csrfKey.length > 0 && csrfToken && csrfToken.length > 0) {
4500 			csrfPair = encodeURIComponent(csrfKey) + "=" + encodeURIComponent(csrfToken);
4501 			// we send this so it can be easily verified for things like restricted jsonp
4502 			xmlHttp.setRequestHeader("X-Arsd-Csrf-Pair", csrfPair);
4503 		}
4504 
4505 		if(method == "POST") {
4506 			xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
4507 			a = argString;
4508 			// adding the CSRF stuff, if necessary
4509 			if(csrfPair.length) {
4510 				if(a.length > 0)
4511 					a += "&";
4512 				a += csrfPair;
4513 			}
4514 		} else {
4515 			xmlHttp.setRequestHeader("Content-Type", "text/plain");
4516 		}
4517 
4518 		xmlHttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
4519 		xmlHttp.send(a);
4520 
4521 		if(!async && callback) {
4522 			xmlHttp.timeout = 500;
4523 			return callback(xmlHttp.responseText, xmlHttp.responseXML);
4524 		}
4525 		return xmlHttp;
4526 	},
4527 
4528 	"_raiseError":function(type, message) {
4529 		var error = new Error(message);
4530 		error.name = type;
4531 		throw error;
4532 	},
4533 
4534 	"_getUriRelativeToBase":function(name, args) {
4535 		var str = name;
4536 		var argsStr = this._getArgString(args);
4537 		if(argsStr.length)
4538 			str += "?" + argsStr;
4539 
4540 		return str;
4541 	},
4542 
4543 	"_getArgString":function(args) {
4544 		var a = "";
4545 		var outputted = false;
4546 		var i; // wow Javascript sucks! god damned global loop variables
4547 		for(i in args) {
4548 			if(outputted) {
4549 				a += "&";
4550 			} else outputted = true;
4551 			var arg = args[i];
4552 			var argType = "";
4553 			// Make sure the types are all sane
4554 
4555 			if(arg && arg._arsdTypeOf && arg._arsdTypeOf == "ServerResult") {
4556 				argType = arg._arsdTypeOf;
4557 				arg = this._getUriRelativeToBase(arg._serverFunction, arg._serverArguments);
4558 
4559 				// this arg is a nested server call
4560 				a += encodeURIComponent(i) + "=";
4561 				a += encodeURIComponent(arg);
4562 			} else if(arg && arg.length && typeof arg != "string") {
4563 				// FIXME: are we sure this is actually an array? It might be an object with a length property...
4564 
4565 				var outputtedHere = false;
4566 				for(var idx = 0; idx < arg.length; idx++) {
4567 					if(outputtedHere) {
4568 						a += "&";
4569 					} else outputtedHere = true;
4570 
4571 					// FIXME: ought to be recursive
4572 					a += encodeURIComponent(i) + "=";
4573 					a += encodeURIComponent(arg[idx]);
4574 				}
4575 			} else {
4576 				// a regular argument
4577 				a += encodeURIComponent(i) + "=";
4578 				a += encodeURIComponent(arg);
4579 			}
4580 			// else if: handle arrays and objects too
4581 
4582 			if(argType.length > 0) {
4583 				a += "&";
4584 				a += encodeURIComponent(i + "-type") + "=";
4585 				a += encodeURIComponent(argType);
4586 			}
4587 		}
4588 
4589 		return a;
4590 	},
4591 
4592 	"_onError":function(error) {
4593 		throw error;
4594 	},
4595 
4596 	/// returns an object that can be used to get the actual response from the server
4597  	"_serverCall": function (name, passedArgs, returnType) {
4598 		var me = this; // this is the Api object
4599 		var args;
4600 		// FIXME: is there some way to tell arguments apart from other objects? dynamic languages suck.
4601 		if(!passedArgs.length)
4602 			args = passedArgs;
4603 		else {
4604 			args = new Object();
4605 			for(var a = 0; a < passedArgs.length; a++)
4606 				args["positional-arg-" + a] = passedArgs[a];
4607 		}
4608 		return {
4609 			// type info metadata
4610 			"_arsdTypeOf":"ServerResult",
4611 			"_staticType":(typeof returnType == "undefined" ? null : returnType),
4612 
4613 			// Info about the thing
4614 			"_serverFunction":name,
4615 			"_serverArguments":args,
4616 			"_moreArguments":{},
4617 			"_methodOverride":null,
4618 
4619 			// lower level implementation
4620 			"_get":function(callback, onError, async) {
4621 				var resObj = this; // the request/response object. var me is the ApiObject.
4622 				if(args == null)
4623 					args = {};
4624 				if(!args.format)
4625 					args.format = "json";
4626 				args.envelopeFormat = "json";
4627 
4628 				for(i in this._moreArguments)
4629 					args[i] = this._moreArguments[i];
4630 
4631 				return me._doRequest(me._apiBase + name, args, function(t, xml) {
4632 					/*
4633 					if(me._debugMode) {
4634 						try {
4635 							var obj = eval("(" + t + ")");
4636 						} catch(e) {
4637 							alert("Bad server json: " + e +
4638 								"\nOn page: " + (me._apiBase + name) +
4639 								"\nGot:\n" + t);
4640 						}
4641 					} else {
4642 					*/
4643 						var obj;
4644 						if(JSON && JSON.parse)
4645 							obj = JSON.parse(t);
4646 						else
4647 							obj = eval("(" + t + ")");
4648 					//}
4649 
4650 					var returnValue;
4651 
4652 					if(obj.success) {
4653 						if(typeof callback == "function")
4654 							callback(obj.result);
4655 						else if(typeof resObj.onSuccess == "function") {
4656 							resObj.onSuccess(obj.result);
4657 						} else if(typeof me.onSuccess == "function") { // do we really want this?
4658 							me.onSuccess(obj.result);
4659 						} else {
4660 							// can we automatically handle it?
4661 							// If it's an element, we should replace innerHTML by ID if possible
4662 							// if a callback is given and it's a string, that's an id. Return type of element
4663 							// should replace that id. return type of string should be appended
4664 							// FIXME: meh just do something here.
4665 						}
4666 
4667 						returnValue = obj.result;
4668 					} else {
4669 						// how should we handle the error? I guess throwing is better than nothing
4670 						// but should there be an error callback too?
4671 						var error = new Error(obj.errorMessage);
4672 						error.name = obj.type;
4673 						error.functionUrl = me._apiBase + name;
4674 						error.functionArgs = args;
4675 						error.errorMessage = obj.errorMessage;
4676 
4677 						// myFunction.caller should be available and checked too
4678 						// btw arguments.callee is like this for functions
4679 
4680 						if(me._debugMode) {
4681 							var ourMessage = obj.type + ": " + obj.errorMessage +
4682 								"\nOn: " + me._apiBase + name;
4683 							if(args.toSource)
4684 								ourMessage += args.toSource();
4685 							if(args.stack)
4686 								ourMessage += "\n" + args.stack;
4687 
4688 							error.message = ourMessage;
4689 
4690 							// alert(ourMessage);
4691 						}
4692 
4693 						if(onError) // local override first...
4694 							returnValue = onError(error);
4695 						else if(resObj.onError) // then this object
4696 							returnValue = resObj.onError(error);
4697 						else if(me._onError) // then the global object
4698 							returnValue = me._onError(error);
4699 						else
4700 							throw error; // if all else fails...
4701 					}
4702 
4703 					if(typeof resObj.onComplete == "function") {
4704 						resObj.onComplete();
4705 					}
4706 
4707 					if(typeof me._onComplete == "function") {
4708 						me._onComplete(resObj);
4709 					}
4710 
4711 					return returnValue;
4712 
4713 					// assert(0); // not reached
4714 				}, this._methodOverride === null ? ((name.indexOf("get") == 0) ? "GET" : "POST") : this._methodOverride, async); // FIXME: hack: naming convention used to figure out method to use
4715 			},
4716 
4717 			// should pop open the thing in HTML format
4718 			// "popup":null, // FIXME not implemented
4719 
4720 			"onError":null, // null means call the global one
4721 
4722 			"onSuccess":null, // a generic callback. generally pass something to get instead.
4723 
4724 			"formatSet":false, // is the format overridden?
4725 
4726 			// gets the result. Works automatically if you don't pass a callback.
4727 			// You can also curry arguments to your callback by listing them here. The
4728 			// result is put on the end of the arg list to the callback
4729 			"get":function(callbackObj) {
4730 				var callback = null;
4731 				var errorCb = null;
4732 				var callbackThis = null;
4733 				if(callbackObj) {
4734 					if(typeof callbackObj == "function")
4735 						callback = callbackObj;
4736 					else {
4737 						if(callbackObj.length) {
4738 							// array
4739 							callback = callbackObj[0];
4740 
4741 							if(callbackObj.length >= 2)
4742 								errorCb = callbackObj[1];
4743 						} else {
4744 							if(callbackObj.onSuccess)
4745 								callback = callbackObj.onSuccess;
4746 							if(callbackObj.onError)
4747 								errorCb = callbackObj.onError;
4748 							if(callbackObj.self)
4749 								callbackThis = callbackObj.self;
4750 							else
4751 								callbackThis = callbackObj;
4752 						}
4753 					}
4754 				}
4755 				if(arguments.length > 1) {
4756 					var ourArguments = [];
4757 					for(var a = 1; a < arguments.length; a++)
4758 						ourArguments.push(arguments[a]);
4759 
4760 					function cb(obj, xml) {
4761 						ourArguments.push(obj);
4762 						ourArguments.push(xml);
4763 
4764 						// that null is the this object inside the function... can
4765 						// we make that work?
4766 						return callback.apply(callbackThis, ourArguments);
4767 					}
4768 
4769 					function cberr(err) {
4770 						ourArguments.unshift(err);
4771 
4772 						// that null is the this object inside the function... can
4773 						// we make that work?
4774 						return errorCb.apply(callbackThis, ourArguments);
4775 					}
4776 
4777 
4778 					this._get(cb, errorCb ? cberr : null, true);
4779 				} else {
4780 					this._get(callback, errorCb, true);
4781 				}
4782 			},
4783 
4784 			// If you need a particular format, use this.
4785 			"getFormat":function(format /* , same args as get... */) {
4786 				this.format(format);
4787 				var forwardedArgs = [];
4788 				for(var a = 1; a < arguments.length; a++)
4789 					forwardedArgs.push(arguments[a]);
4790 				this.get.apply(this, forwardedArgs);
4791 			},
4792 
4793 			// sets the format of the request so normal get uses it
4794 			// myapi.someFunction().format('table').get(...);
4795 			// see also: getFormat and getHtml
4796 			// the secondaryFormat only makes sense if format is json. It
4797 			// sets the format returned by object.toString() in the returned objects.
4798 			"format":function(format, secondaryFormat) {
4799 				if(args == null)
4800 					args = {};
4801 				args.format = format;
4802 
4803 				if(typeof secondaryFormat == "string" && secondaryFormat) {
4804 					if(format != "json")
4805 						me._raiseError("AssertError", "secondaryFormat only works if format == json");
4806 					args.secondaryFormat = secondaryFormat;
4807 				}
4808 
4809 				this.formatSet = true;
4810 				return this;
4811 			},
4812 
4813 			"getHtml":function(/* args to get... */) {
4814 				this.format("html");
4815 				this.get.apply(this, arguments);
4816 			},
4817 
4818 			// FIXME: add post aliases
4819 
4820 			// don't use unless you're deploying to localhost or something
4821 			"getSync":function() {
4822 				function cb(obj) {
4823 					// no nothing, we're returning the value below
4824 				}
4825 
4826 				return this._get(cb, null, false);
4827 			},
4828 			// takes the result and appends it as html to the given element
4829 
4830 			// FIXME: have a type override
4831 			"appendTo":function(what) {
4832 				if(!this.formatSet)
4833 					this.format("html");
4834 				this.get(me._appendContent(what));
4835 			},
4836 			// use it to replace the content of the given element
4837 			"useToReplace":function(what) {
4838 				if(!this.formatSet)
4839 					this.format("html");
4840 				this.get(me._replaceContent(what));
4841 			},
4842 			// use to replace the given element altogether
4843 			"useToReplaceElement":function(what) {
4844 				if(!this.formatSet)
4845 					this.format("html");
4846 				this.get(me._replaceElement(what));
4847 			},
4848 			"useToFillForm":function(what) {
4849 				this.get(me._fillForm(what));
4850 			},
4851 			"setValue":function(key, value) {
4852 				this._moreArguments[key] = value;
4853 				return this;
4854 			},
4855 			"setMethod":function(method) {
4856 				this._methodOverride = method;
4857 				return this;
4858 			}
4859 			// runAsScript has been removed, use get(eval) instead
4860 			// FIXME: might be nice to have an automatic popin function too
4861 		};
4862 	},
4863 
4864 	"_fillForm": function(what) {
4865 		var e = this._getElement(what);
4866 		if(this._isListOfNodes(e))
4867 			alert("FIXME: list of forms not implemented");
4868 		else return function(obj) {
4869 			if(e.elements && typeof obj == "object") {
4870 				for(i in obj)
4871 					if(e.elements[i])
4872 						e.elements[i].value = obj[i]; // FIXME: what about checkboxes, selects, etc?
4873 			} else
4874 				throw new Error("unsupported response");
4875 		};
4876 	},
4877 
4878 	"_getElement": function(what) {
4879 		// FIXME: what about jQuery users? If they do useToReplace($("whatever")), we ought to do what we can with it for the most seamless experience even if I loathe that bloat.
4880 		// though I guess they should be ok in doing $("whatever")[0] or maybe $("whatever").get() so not too awful really.
4881 		var e;
4882 		if(typeof what == "string")
4883 			e = document.getElementById(what);
4884 		else
4885 			e = what;
4886 
4887 		return e;
4888 	},
4889 
4890 	"_isListOfNodes": function(what) {
4891 		// length is on both arrays and lists, but some elements
4892 		// have it too. We disambiguate with getAttribute
4893 		return (what && (what.length && !what.getAttribute))
4894 	},
4895 
4896 	// These are some convenience functions to use as callbacks
4897 	"_replaceContent": function(what) {
4898 		var e = this._getElement(what);
4899 		var me = this;
4900 		if(this._isListOfNodes(e))
4901 			return function(obj) {
4902 				// I do not want scripts accidentally running here...
4903 				for(var a = 0; a < e.length; a++) {
4904 					if( (e[a].tagName.toLowerCase() == "input"
4905 						&&
4906 						e[a].getAttribute("type") == "text")
4907 						||
4908 						e[a].tagName.toLowerCase() == "textarea")
4909 					{
4910 						e[a].value = obj;
4911 					} else
4912 						e[a].innerHTML = obj;
4913 				}
4914 			}
4915 		else
4916 			return function(obj) {
4917 				var data = me._extractHtmlScript(obj);
4918 				if( (e.tagName.toLowerCase() == "input"
4919 					&&
4920 					e.getAttribute("type") == "text")
4921 					||
4922 					e.tagName.toLowerCase() == "textarea")
4923 				{
4924 					e.value = obj; // might want script looking thing as a value
4925 				} else
4926 					e.innerHTML = data[0];
4927 				if(me._wantScriptExecution && data[1].length)
4928 					eval(data[1]);
4929 			}
4930 	},
4931 
4932 	// note: what must be only a single element, FIXME: could check the static type
4933 	"_replaceElement": function(what) {
4934 		var e = this._getElement(what);
4935 		if(this._isListOfNodes(e))
4936 			throw new Error("Can only replace individual elements since removal from a list may be unstable.");
4937 		var me = this;
4938 		return function(obj) {
4939 			var data = me._extractHtmlScript(obj);
4940 			var n = document.createElement("div");
4941 			n.innerHTML = data[0];
4942 
4943 			if(n.firstChild) {
4944 				e.parentNode.replaceChild(n.firstChild, e);
4945 			} else {
4946 				e.parentNode.removeChild(e);
4947 			}
4948 			if(me._wantScriptExecution && data[1].length)
4949 				eval(data[1]);
4950 		}
4951 	},
4952 
4953 	"_appendContent": function(what) {
4954 		var e = this._getElement(what);
4955 		var me = this;
4956 		if(this._isListOfNodes(e)) // FIXME: repeating myself...
4957 			return function(obj) {
4958 				var data = me._extractHtmlScript(obj);
4959 				for(var a = 0; a < e.length; a++)
4960 					e[a].innerHTML += data[0];
4961 				if(me._wantScriptExecution && data[1].length)
4962 					eval(data[1]);
4963 			}
4964 		else
4965 			return function(obj) {
4966 				var data = me._extractHtmlScript(obj);
4967 				e.innerHTML += data[0];
4968 				if(me._wantScriptExecution && data[1].length)
4969 					eval(data[1]);
4970 			}
4971 	},
4972 
4973 	"_extractHtmlScript": function(response) {
4974 		var scriptRegex = new RegExp("<script>([\\s\\S]*?)<\\/script>", "g");
4975 		var scripts = "";
4976 		var match;
4977 		while(match = scriptRegex.exec(response)) {
4978 			scripts += match[1];
4979 		}
4980 		var html = response.replace(scriptRegex, "");
4981 
4982 		return [html, scripts];
4983 	},
4984 
4985 	// we say yes by default because these always come from your own domain anyway;
4986 	// it should be safe (as long as your app is sane). You can turn it off though if you want
4987 	// by setting this to false somewhere in your code.
4988 	"_wantScriptExecution" : true,
4989 };
4990 
4991 
4992 template hasAnnotation(alias f, Attr) {
4993 	static bool helper() {
4994 		foreach(attr; __traits(getAttributes, f))
4995 			static if(is(attr == Attr) || is(typeof(attr) == Attr))
4996 				return true;
4997 		return false;
4998 
4999 	}
5000 	enum bool hasAnnotation = helper;
5001 }
5002 
5003 template hasValueAnnotation(alias f, Attr) {
5004 	static bool helper() {
5005 		foreach(attr; __traits(getAttributes, f))
5006 			static if(is(typeof(attr) == Attr))
5007 				return true;
5008 		return false;
5009 
5010 	}
5011 	enum bool hasValueAnnotation = helper;
5012 }
5013 
5014 
5015 
5016 template getAnnotation(alias f, Attr) if(hasValueAnnotation!(f, Attr)) {
5017 	static auto helper() {
5018 		foreach(attr; __traits(getAttributes, f))
5019 			static if(is(typeof(attr) == Attr))
5020 				return attr;
5021 		assert(0);
5022 	}
5023 
5024 	enum getAnnotation = helper;
5025 }
5026 
5027 // use this as a query string param to all forever-cached resources
5028 string makeCompileTimestamp(string ts) {
5029 	string ret;
5030 	foreach(t; ts)
5031 		if((t >= '0' && t <= '9'))
5032 			ret ~= t;
5033 	return ret;
5034 }
5035 
5036 enum compiliationStamp = makeCompileTimestamp(__TIMESTAMP__);
5037 
5038 /*
5039 Copyright: Adam D. Ruppe, 2010 - 2012
5040 License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
5041 Authors: Adam D. Ruppe, with contributions by Nick Sabalausky
5042 
5043         Copyright Adam D. Ruppe 2010-2012.
5044 Distributed under the Boost Software License, Version 1.0.
5045    (See accompanying file LICENSE_1_0.txt or copy at
5046         http://www.boost.org/LICENSE_1_0.txt)
5047 */