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