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