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