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