1 /**
2 	A HTTP 1.1/1.0 server implementation.
3 
4 	Copyright: © 2012-2017 RejectedSoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Jan Krüger, Ilya Shipunov
7 */
8 module vibe.http.server;
9 
10 public import vibe.core.net;
11 public import vibe.http.common;
12 public import vibe.http.session;
13 
14 import vibe.core.file;
15 import vibe.core.log;
16 import vibe.data.json;
17 import vibe.http.dist;
18 import vibe.http.log;
19 import vibe.inet.message;
20 import vibe.inet.url;
21 import vibe.inet.webform;
22 import vibe.internal.interfaceproxy : InterfaceProxy;
23 import vibe.stream.counting;
24 import vibe.stream.operations;
25 import vibe.stream.tls;
26 import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
27 import vibe.stream.zlib;
28 import vibe.textfilter.urlencode;
29 import vibe.utils.array;
30 import vibe.internal.allocator;
31 import vibe.internal.freelistref;
32 import vibe.utils..string;
33 
34 import core.atomic;
35 import core.vararg;
36 import diet.traits : SafeFilterCallback, dietTraits;
37 import std.algorithm : canFind;
38 import std.array;
39 import std.conv;
40 import std.datetime;
41 import std.encoding : sanitize;
42 import std.exception;
43 import std.format;
44 import std.functional : toDelegate;
45 import std..string;
46 import std.traits : ReturnType;
47 import std.typecons;
48 import std.uri;
49 
50 
51 version (VibeNoSSL) version = HaveNoTLS;
52 else version (Have_botan) {}
53 else version (Have_openssl) {}
54 else version = HaveNoTLS;
55 
56 /**************************************************************************************************/
57 /* Public functions                                                                               */
58 /**************************************************************************************************/
59 
60 /**
61 	Starts a HTTP server listening on the specified port.
62 
63 	request_handler will be called for each HTTP request that is made. The
64 	res parameter of the callback then has to be filled with the response
65 	data.
66 
67 	request_handler can be either HTTPServerRequestDelegate/HTTPServerRequestFunction
68 	or a class/struct with a member function 'handleRequest' that has the same
69 	signature.
70 
71 	Note that if the application has been started with the --disthost command line
72 	switch, listenHTTP() will automatically listen on the specified VibeDist host
73 	instead of locally. This allows for a seamless switch from single-host to
74 	multi-host scenarios without changing the code. If you need to listen locally,
75 	use listenHTTPPlain() instead.
76 
77 	Params:
78 		settings = Customizes the HTTP servers functionality (host string or HTTPServerSettings object)
79 		request_handler = This callback is invoked for each incoming request and is responsible
80 			for generating the response.
81 
82 	Returns:
83 		A handle is returned that can be used to stop listening for further HTTP
84 		requests with the supplied settings. Another call to `listenHTTP` can be
85 		used afterwards to start listening again.
86 */
87 HTTPListener listenHTTP(Settings)(Settings _settings, HTTPServerRequestDelegate request_handler)
88 @safe
89 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
90 	// auto-construct HTTPServerSettings
91 	static if (is(Settings == string))
92 		auto settings = new HTTPServerSettings(_settings);
93 	else
94 		alias settings = _settings;
95 
96 	enforce(settings.bindAddresses.length, "Must provide at least one bind address for a HTTP server.");
97 
98 	// if a VibeDist host was specified on the command line, register there instead of listening
99 	// directly.
100 	if (s_distHost.length && !settings.disableDistHost) {
101 		return listenHTTPDist(settings, request_handler, s_distHost, s_distPort);
102 	} else {
103 		return listenHTTPPlain(settings, request_handler);
104 	}
105 }
106 /// ditto
107 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestFunction request_handler)
108 @safe
109 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
110 	return listenHTTP(settings, () @trusted { return toDelegate(request_handler); } ());
111 }
112 /// ditto
113 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestHandler request_handler)
114 @safe
115 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
116 	return listenHTTP(settings, &request_handler.handleRequest);
117 }
118 /// ditto
119 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestDelegateS request_handler)
120 @safe
121 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
122 	return listenHTTP(settings, cast(HTTPServerRequestDelegate)request_handler);
123 }
124 /// ditto
125 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestFunctionS request_handler)
126 @safe
127 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
128 	return listenHTTP(settings, () @trusted { return toDelegate(request_handler); } ());
129 }
130 /// ditto
131 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestHandlerS request_handler)
132 @safe
133 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
134 	return listenHTTP(settings, &request_handler.handleRequest);
135 }
136 
137 /// Scheduled for deprecation - use a `@safe` callback instead.
138 HTTPListener listenHTTP(Settings)(Settings settings, void delegate(HTTPServerRequest, HTTPServerResponse) @system request_handler)
139 @system
140 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
141 	return listenHTTP(settings, (req, res) @trusted => request_handler(req, res));
142 }
143 /// ditto
144 HTTPListener listenHTTP(Settings)(Settings settings, void function(HTTPServerRequest, HTTPServerResponse) @system request_handler)
145 @system
146 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
147 	return listenHTTP(settings, (req, res) @trusted => request_handler(req, res));
148 }
149 /// ditto
150 HTTPListener listenHTTP(Settings)(Settings settings, void delegate(scope HTTPServerRequest, scope HTTPServerResponse) @system request_handler)
151 @system
152 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
153 	return listenHTTP(settings, (scope req, scope res) @trusted => request_handler(req, res));
154 }
155 /// ditto
156 HTTPListener listenHTTP(Settings)(Settings settings, void function(scope HTTPServerRequest, scope HTTPServerResponse) @system request_handler)
157 @system
158 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
159 	return listenHTTP(settings, (scope req, scope res) @trusted => request_handler(req, res));
160 }
161 
162 unittest
163 {
164 	void test()
165 	{
166 		static void testSafeFunction(HTTPServerRequest req, HTTPServerResponse res) @safe {}
167 		listenHTTP("0.0.0.0:8080", &testSafeFunction);
168 		listenHTTP(":8080", new class HTTPServerRequestHandler {
169 			void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe {}
170 		});
171 		listenHTTP(":8080", (req, res) {});
172 
173 		static void testSafeFunctionS(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
174 		listenHTTP(":8080", &testSafeFunctionS);
175 		void testSafeDelegateS(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
176 		listenHTTP(":8080", &testSafeDelegateS);
177 		listenHTTP(":8080", new class HTTPServerRequestHandler {
178 			void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
179 		});
180 		listenHTTP(":8080", (scope req, scope res) {});
181 	}
182 }
183 
184 
185 /** Treats an existing connection as an HTTP connection and processes incoming
186 	requests.
187 
188 	After all requests have been processed, the connection will be closed and
189 	the function returns to the caller.
190 
191 	Params:
192 		connections = The stream to treat as an incoming HTTP client connection.
193 		context = Information about the incoming listener and available
194 			virtual hosts
195 */
196 void handleHTTPConnection(TCPConnection connection, HTTPServerContext context)
197 @safe {
198 	InterfaceProxy!Stream http_stream;
199 	http_stream = connection;
200 
201 	scope (exit) connection.close();
202 
203 	// Set NODELAY to true, to avoid delays caused by sending the response
204 	// header and body in separate chunks. Note that to avoid other performance
205 	// issues (caused by tiny packets), this requires using an output buffer in
206 	// the event driver, which is the case at least for the default libevent
207 	// based driver.
208 	connection.tcpNoDelay = true;
209 
210 	version(HaveNoTLS) {} else {
211 		TLSStreamType tls_stream;
212 	}
213 
214 	if (!connection.waitForData(10.seconds())) {
215 		logDebug("Client didn't send the initial request in a timely manner. Closing connection.");
216 		return;
217 	}
218 
219 	// If this is a HTTPS server, initiate TLS
220 	if (context.tlsContext) {
221 		version (HaveNoTLS) assert(false, "No TLS support compiled in.");
222 		else {
223 			logDebug("Accept TLS connection: %s", context.tlsContext.kind);
224 			// TODO: reverse DNS lookup for peer_name of the incoming connection for TLS client certificate verification purposes
225 			tls_stream = createTLSStreamFL(http_stream, context.tlsContext, TLSStreamState.accepting, null, connection.remoteAddress);
226 			http_stream = tls_stream;
227 		}
228 	}
229 
230 	while (!connection.empty) {
231 		HTTPServerSettings settings;
232 		bool keep_alive;
233 
234 		() @trusted {
235 			import vibe.internal.utilallocator: RegionListAllocator;
236 
237 			version (VibeManualMemoryManagement)
238 				scope request_allocator = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
239 			else
240 				scope request_allocator = new RegionListAllocator!(shared(GCAllocator), true)(1024, GCAllocator.instance);
241 
242 			handleRequest(http_stream, connection, context, settings, keep_alive, request_allocator);
243 		} ();
244 		if (!keep_alive) { logTrace("No keep-alive - disconnecting client."); break; }
245 
246 		logTrace("Waiting for next request...");
247 		// wait for another possible request on a keep-alive connection
248 		if (!connection.waitForData(settings.keepAliveTimeout)) {
249 			if (!connection.connected) logTrace("Client disconnected.");
250 			else logDebug("Keep-alive connection timed out!");
251 			break;
252 		}
253 	}
254 
255 	logTrace("Done handling connection.");
256 }
257 
258 
259 /**
260 	Provides a HTTP request handler that responds with a static Diet template.
261 */
262 @property HTTPServerRequestDelegateS staticTemplate(string template_file)()
263 {
264 	return (scope HTTPServerRequest req, scope HTTPServerResponse res){
265 		res.render!(template_file, req);
266 	};
267 }
268 
269 /**
270 	Provides a HTTP request handler that responds with a static redirection to the specified URL.
271 
272 	Params:
273 		url = The URL to redirect to
274 		status = Redirection status to use $(LPAREN)by default this is $(D HTTPStatus.found)$(RPAREN).
275 
276 	Returns:
277 		Returns a $(D HTTPServerRequestDelegate) that performs the redirect
278 */
279 HTTPServerRequestDelegate staticRedirect(string url, HTTPStatus status = HTTPStatus.found)
280 @safe {
281 	return (HTTPServerRequest req, HTTPServerResponse res){
282 		res.redirect(url, status);
283 	};
284 }
285 /// ditto
286 HTTPServerRequestDelegate staticRedirect(URL url, HTTPStatus status = HTTPStatus.found)
287 @safe {
288 	return (HTTPServerRequest req, HTTPServerResponse res){
289 		res.redirect(url, status);
290 	};
291 }
292 
293 ///
294 unittest {
295 	import vibe.http.router;
296 
297 	void test()
298 	{
299 		auto router = new URLRouter;
300 		router.get("/old_url", staticRedirect("http://example.org/new_url", HTTPStatus.movedPermanently));
301 
302 		listenHTTP(new HTTPServerSettings, router);
303 	}
304 }
305 
306 
307 /**
308 	Sets a VibeDist host to register with.
309 */
310 void setVibeDistHost(string host, ushort port)
311 @safe {
312 	s_distHost = host;
313 	s_distPort = port;
314 }
315 
316 
317 /**
318 	Renders the given Diet template and makes all ALIASES available to the template.
319 
320 	You can call this function as a pseudo-member of `HTTPServerResponse` using
321 	D's uniform function call syntax.
322 
323 	See_also: `diet.html.compileHTMLDietFile`
324 
325 	Examples:
326 		---
327 		string title = "Hello, World!";
328 		int pageNumber = 1;
329 		res.render!("mytemplate.dt", title, pageNumber);
330 		---
331 */
332 @property void render(string template_file, ALIASES...)(HTTPServerResponse res)
333 {
334 	res.contentType = "text/html; charset=UTF-8";
335 	version (VibeUseOldDiet)
336 		pragma(msg, "VibeUseOldDiet is not supported anymore. Please undefine in the package recipe.");
337 	import vibe.stream.wrapper : streamOutputRange;
338 	import diet.html : compileHTMLDietFile;
339 	auto output = streamOutputRange!1024(res.bodyWriter);
340 	compileHTMLDietFile!(template_file, ALIASES, DefaultDietFilters)(output);
341 }
342 
343 
344 /**
345 	Provides the default `css`, `javascript`, `markdown` and `htmlescape` filters
346  */
347 @dietTraits
348 struct DefaultDietFilters {
349 	import diet.html : HTMLOutputStyle;
350 	import diet.traits : SafeFilterCallback;
351 	import std..string : splitLines;
352 
353 	version (VibeOutputCompactHTML) enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.compact;
354 	else enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.pretty;
355 
356 	static string filterCss(I)(I text, size_t indent = 0)
357 	{
358 		auto lines = splitLines(text);
359 
360 		string indent_string = "\n";
361 		while (indent-- > 0) indent_string ~= '\t';
362 
363 		string ret = indent_string~"<style type=\"text/css\"><!--";
364 		indent_string = indent_string ~ '\t';
365 		foreach (ln; lines) ret ~= indent_string ~ ln;
366 		indent_string = indent_string[0 .. $-1];
367 		ret ~= indent_string ~ "--></style>";
368 
369 		return ret;
370 	}
371 
372 
373 	static string filterJavascript(I)(I text, size_t indent = 0)
374 	{
375 		auto lines = splitLines(text);
376 
377 		string indent_string = "\n";
378 		while (indent-- > 0) indent_string ~= '\t';
379 
380 		string ret = indent_string~"<script type=\"application/javascript\">";
381 		ret ~= indent_string~'\t' ~ "//<![CDATA[";
382 		foreach (ln; lines) ret ~= indent_string ~ '\t' ~ ln;
383 		ret ~= indent_string ~ '\t' ~ "//]]>" ~ indent_string ~ "</script>";
384 
385 		return ret;
386 	}
387 
388 	static string filterMarkdown(I)(I text)
389 	{
390 		import vibe.textfilter.markdown : markdown = filterMarkdown;
391 		// TODO: indent
392 		return markdown(text);
393 	}
394 
395 	static string filterHtmlescape(I)(I text)
396 	{
397 		import vibe.textfilter.html : htmlEscape;
398 		// TODO: indent
399 		return htmlEscape(text);
400 	}
401 
402 	static this()
403 	{
404 		filters["css"] = (input, scope output) { output(filterCss(input)); };
405 		filters["javascript"] = (input, scope output) { output(filterJavascript(input)); };
406 		filters["markdown"] = (input, scope output) { output(filterMarkdown(() @trusted { return cast(string)input; } ())); };
407 		filters["htmlescape"] = (input, scope output) { output(filterHtmlescape(input)); };
408 	}
409 
410 	static SafeFilterCallback[string] filters;
411 }
412 
413 
414 unittest {
415 	static string compile(string diet)() {
416 		import std.array : appender;
417 		import std..string : strip;
418 		import diet.html : compileHTMLDietString;
419 		auto dst = appender!string;
420 		dst.compileHTMLDietString!(diet, DefaultDietFilters);
421 		return strip(cast(string)(dst.data));
422 	}
423 
424 	assert(compile!":css .test" == "<style type=\"text/css\"><!--\n\t.test\n--></style>");
425 	assert(compile!":javascript test();" == "<script type=\"application/javascript\">\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
426 	assert(compile!":markdown **test**" == "<p><strong>test</strong>\n</p>");
427 	assert(compile!":htmlescape <test>" == "&lt;test&gt;");
428 	assert(compile!":css !{\".test\"}" == "<style type=\"text/css\"><!--\n\t.test\n--></style>");
429 	assert(compile!":javascript !{\"test();\"}" == "<script type=\"application/javascript\">\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
430 	assert(compile!":markdown !{\"**test**\"}" == "<p><strong>test</strong>\n</p>");
431 	assert(compile!":htmlescape !{\"<test>\"}" == "&lt;test&gt;");
432 	assert(compile!":javascript\n\ttest();" == "<script type=\"application/javascript\">\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
433 }
434 
435 
436 /**
437 	Creates a HTTPServerRequest suitable for writing unit tests.
438 */
439 HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method = HTTPMethod.GET, InputStream data = null)
440 @safe {
441 	InetHeaderMap headers;
442 	return createTestHTTPServerRequest(url, method, headers, data);
443 }
444 /// ditto
445 HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method, InetHeaderMap headers, InputStream data = null)
446 @safe {
447 	auto tls = url.schema == "https";
448 	auto ret = new HTTPServerRequest(Clock.currTime(UTC()), url.port ? url.port : tls ? 443 : 80);
449 	ret.requestPath = url.path;
450 	ret.queryString = url.queryString;
451 	ret.username = url.username;
452 	ret.password = url.password;
453 	ret.requestURI = url.localURI;
454 	ret.method = method;
455 	ret.tls = tls;
456 	ret.headers = headers;
457 	ret.bodyReader = data;
458 	return ret;
459 }
460 
461 /**
462 	Creates a HTTPServerResponse suitable for writing unit tests.
463 */
464 HTTPServerResponse createTestHTTPServerResponse(OutputStream data_sink = null, SessionStore session_store = null)
465 @safe {
466 	import vibe.stream.wrapper;
467 
468 	HTTPServerSettings settings;
469 	if (session_store) {
470 		settings = new HTTPServerSettings;
471 		settings.sessionStore = session_store;
472 	}
473 	if (!data_sink) data_sink = new NullOutputStream;
474 	auto stream = createProxyStream(Stream.init, data_sink);
475 	auto ret = new HTTPServerResponse(stream, null, settings, () @trusted { return vibeThreadAllocator(); } ());
476 	return ret;
477 }
478 
479 
480 /**************************************************************************************************/
481 /* Public types                                                                                   */
482 /**************************************************************************************************/
483 
484 /// Delegate based request handler
485 alias HTTPServerRequestDelegate = void delegate(HTTPServerRequest req, HTTPServerResponse res) @safe;
486 /// Static function based request handler
487 alias HTTPServerRequestFunction = void function(HTTPServerRequest req, HTTPServerResponse res) @safe;
488 /// Interface for class based request handlers
489 interface HTTPServerRequestHandler {
490 	/// Handles incoming HTTP requests
491 	void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe ;
492 }
493 
494 /// Delegate based request handler with scoped parameters
495 alias HTTPServerRequestDelegateS = void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
496 /// Static function based request handler with scoped parameters
497 alias HTTPServerRequestFunctionS  = void function(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
498 /// Interface for class based request handlers with scoped parameters
499 interface HTTPServerRequestHandlerS {
500 	/// Handles incoming HTTP requests
501 	void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
502 }
503 
504 unittest {
505 	static assert(is(HTTPServerRequestDelegateS : HTTPServerRequestDelegate));
506 	static assert(is(HTTPServerRequestFunctionS : HTTPServerRequestFunction));
507 }
508 
509 /// Aggregates all information about an HTTP error status.
510 final class HTTPServerErrorInfo {
511 	/// The HTTP status code
512 	int code;
513 	/// The error message
514 	string message;
515 	/// Extended error message with debug information such as a stack trace
516 	string debugMessage;
517 	/// The error exception, if any
518 	Throwable exception;
519 }
520 
521 /// Delegate type used for user defined error page generator callbacks.
522 alias HTTPServerErrorPageHandler = void delegate(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) @safe;
523 
524 
525 private enum HTTPServerOptionImpl {
526 	none                      = 0,
527 	errorStackTraces          = 1<<7,
528 	reusePort                 = 1<<8,
529 	distribute                = 1<<9 // deprecated
530 }
531 
532 // TODO: Should be turned back into an enum once the deprecated symbols can be removed
533 /**
534 	Specifies optional features of the HTTP server.
535 
536 	Disabling unneeded features can speed up the server or reduce its memory usage.
537 
538 	Note that the options `parseFormBody`, `parseJsonBody` and `parseMultiPartBody`
539 	will also drain the `HTTPServerRequest.bodyReader` stream whenever a request
540 	body with form or JSON data is encountered.
541 */
542 struct HTTPServerOption {
543 	static enum none                      = HTTPServerOptionImpl.none;
544 	deprecated("This is done lazily. It will be removed in 0.9.")
545 	static enum parseURL                  = none;
546 	deprecated("This is done lazily. It will be removed in 0.9.")
547 	static enum parseQueryString          = none;
548 	deprecated("This is done lazily. It will be removed in 0.9.")
549 	static enum parseFormBody             = none;
550 	deprecated("This is done lazily. It will be removed in 0.9.")
551 	static enum parseJsonBody             = none;
552 	deprecated("This is done lazily. It will be removed in 0.9.")
553 	static enum parseMultiPartBody        = none;
554 	/** Deprecated: Distributes request processing among worker threads
555 
556 		Note that this functionality assumes that the request handler
557 		is implemented in a thread-safe way. However, the D type system
558 		is bypassed, so that no static verification takes place.
559 
560 		For this reason, it is recommended to instead use
561 		`vibe.core.core.runWorkerTaskDist` and call `listenHTTP`
562 		from each task/thread individually. If the `reusePort` option
563 		is set, then all threads will be able to listen on the same port,
564 		with the operating system distributing the incoming connections.
565 
566 		If possible, instead of threads, the use of separate processes
567 		is more robust and often faster. The `reusePort` option works
568 		the same way in this scenario.
569 	*/
570 	deprecated("Use runWorkerTaskDist or start threads separately. It will be removed in 0.9.")
571 	static enum distribute                = HTTPServerOptionImpl.distribute;
572 	/** Enables stack traces (`HTTPServerErrorInfo.debugMessage`).
573 
574 		Note that generating the stack traces are generally a costly
575 		operation that should usually be avoided in production
576 		environments. It can also reveal internal information about
577 		the application, such as function addresses, which can
578 		help an attacker to abuse possible security holes.
579 	*/
580 	static enum errorStackTraces          = HTTPServerOptionImpl.errorStackTraces;
581 	/// Enable port reuse in `listenTCP()`
582 	static enum reusePort                 = HTTPServerOptionImpl.reusePort;
583 
584 	/** The default set of options.
585 
586 		Includes all parsing options, as well as the `errorStackTraces`
587 		option if the code is compiled in debug mode.
588 	*/
589 	static enum defaults = () { debug return HTTPServerOptionImpl.errorStackTraces; else return HTTPServerOptionImpl.none; } ().HTTPServerOption;
590 
591 	deprecated("None has been renamed to none.")
592 	static enum None = none;
593 	deprecated("This is done lazily. It will be removed in 0.9.")
594 	static enum ParseURL = none;
595 	deprecated("This is done lazily. It will be removed in 0.9.")
596 	static enum ParseQueryString = none;
597 	deprecated("This is done lazily. It will be removed in 0.9.")
598 	static enum ParseFormBody = none;
599 	deprecated("This is done lazily. It will be removed in 0.9.")
600 	static enum ParseJsonBody = none;
601 	deprecated("This is done lazily. It will be removed in 0.9.")
602 	static enum ParseMultiPartBody = none;
603 	deprecated("This is done lazily. It will be removed in 0.9.")
604 	static enum ParseCookies = none;
605 
606 	HTTPServerOptionImpl x;
607 	alias x this;
608 }
609 
610 
611 /**
612 	Contains all settings for configuring a basic HTTP server.
613 
614 	The defaults are sufficient for most normal uses.
615 */
616 final class HTTPServerSettings {
617 	/** The port on which the HTTP server is listening.
618 
619 		The default value is 80. If you are running a TLS enabled server you may want to set this
620 		to 443 instead.
621 
622 		Using a value of `0` instructs the server to use any available port on
623 		the given `bindAddresses` the actual addresses and ports can then be
624 		queried with `TCPListener.bindAddresses`.
625 	*/
626 	ushort port = 80;
627 
628 	/** The interfaces on which the HTTP server is listening.
629 
630 		By default, the server will listen on all IPv4 and IPv6 interfaces.
631 	*/
632 	string[] bindAddresses = ["::", "0.0.0.0"];
633 
634 	/** Determines the server host name.
635 
636 		If multiple servers are listening on the same port, the host name will determine which one
637 		gets a request.
638 	*/
639 	string hostName;
640 
641 	/** Configures optional features of the HTTP server
642 
643 		Disabling unneeded features can improve performance or reduce the server
644 		load in case of invalid or unwanted requests (DoS). By default,
645 		HTTPServerOption.defaults is used.
646 	*/
647 	HTTPServerOptionImpl options = HTTPServerOption.defaults;
648 
649 	/** Time of a request after which the connection is closed with an error; not supported yet
650 
651 		The default limit of 0 means that the request time is not limited.
652 	*/
653 	Duration maxRequestTime = 0.seconds;
654 
655 	/** Maximum time between two request on a keep-alive connection
656 
657 		The default value is 10 seconds.
658 	*/
659 	Duration keepAliveTimeout = 10.seconds;
660 
661 	/// Maximum number of transferred bytes per request after which the connection is closed with
662 	/// an error
663 	ulong maxRequestSize = 2097152;
664 
665 
666 	///	Maximum number of transferred bytes for the request header. This includes the request line
667 	/// the url and all headers.
668 	ulong maxRequestHeaderSize = 8192;
669 
670 	/// Sets a custom handler for displaying error pages for HTTP errors
671 	@property HTTPServerErrorPageHandler errorPageHandler() @safe { return errorPageHandler_; }
672 	/// ditto
673 	@property void errorPageHandler(HTTPServerErrorPageHandler del) @safe { errorPageHandler_ = del; }
674 	/// Scheduled for deprecation - use a `@safe` callback instead.
675 	@property void errorPageHandler(void delegate(HTTPServerRequest, HTTPServerResponse, HTTPServerErrorInfo) @system del)
676 	@system {
677 		this.errorPageHandler = (req, res, err) @trusted { del(req, res, err); };
678 	}
679 
680 	private HTTPServerErrorPageHandler errorPageHandler_ = null;
681 
682 	/// If set, a HTTPS server will be started instead of plain HTTP.
683 	TLSContext tlsContext;
684 
685 	/// Session management is enabled if a session store instance is provided
686 	SessionStore sessionStore;
687 	string sessionIdCookie = "vibe.session_id";
688 
689 	///
690 	import vibe.core.core : vibeVersionString;
691 	string serverString = "vibe.d/" ~ vibeVersionString;
692 
693 	/** Specifies the format used for the access log.
694 
695 		The log format is given using the Apache server syntax. By default NCSA combined is used.
696 
697 		---
698 		"%h - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""
699 		---
700 	*/
701 	string accessLogFormat = "%h - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"";
702 
703 	/// Spefifies the name of a file to which access log messages are appended.
704 	string accessLogFile = "";
705 
706 	/// If set, access log entries will be output to the console.
707 	bool accessLogToConsole = false;
708 
709 	/** Specifies a custom access logger instance.
710 	*/
711 	HTTPLogger accessLogger;
712 
713 	/// Returns a duplicate of the settings object.
714 	@property HTTPServerSettings dup()
715 	@safe {
716 		auto ret = new HTTPServerSettings;
717 		foreach (mem; __traits(allMembers, HTTPServerSettings)) {
718 			static if (mem == "sslContext") {}
719 			else static if (mem == "bindAddresses") ret.bindAddresses = bindAddresses.dup;
720 			else static if (__traits(compiles, __traits(getMember, ret, mem) = __traits(getMember, this, mem)))
721 				__traits(getMember, ret, mem) = __traits(getMember, this, mem);
722 		}
723 		return ret;
724 	}
725 
726 	/// Disable support for VibeDist and instead start listening immediately.
727 	bool disableDistHost = false;
728 
729 	/** Responds to "Accept-Encoding" by using compression if possible.
730 
731 		Compression can also be manually enabled by setting the
732 		"Content-Encoding" header of the HTTP response appropriately before
733 		sending the response body.
734 
735 		This setting is disabled by default. Also note that there are still some
736 		known issues with the GZIP compression code.
737 	*/
738 	bool useCompressionIfPossible = false;
739 
740 
741 	/** Interval between WebSocket ping frames.
742 
743 		The default value is 60 seconds; set to Duration.zero to disable pings.
744 	*/
745 	Duration webSocketPingInterval = 60.seconds;
746 
747 	/** Constructs a new settings object with default values.
748 	*/
749 	this() @safe {}
750 
751 	/** Constructs a new settings object with a custom bind interface and/or port.
752 
753 		The syntax of `bind_string` is `[<IP address>][:<port>]`, where either of
754 		the two parts can be left off. IPv6 addresses must be enclosed in square
755 		brackets, as they would within a URL.
756 
757 		Throws:
758 			An exception is thrown if `bind_string` is malformed.
759 	*/
760 	this(string bind_string)
761 	@safe {
762 		this();
763 
764 		if (bind_string.startsWith('[')) {
765 			auto idx = bind_string.indexOf(']');
766 			enforce(idx > 0, "Missing closing bracket for IPv6 address.");
767 			bindAddresses = [bind_string[1 .. idx]];
768 			bind_string = bind_string[idx+1 .. $];
769 
770 			enforce(bind_string.length == 0 || bind_string.startsWith(':'),
771 				"Only a colon may follow the IPv6 address.");
772 		}
773 
774 		auto idx = bind_string.indexOf(':');
775 		if (idx < 0) {
776 			if (bind_string.length > 0) bindAddresses = [bind_string];
777 		} else {
778 			if (idx > 0) bindAddresses = [bind_string[0 .. idx]];
779 			port = bind_string[idx+1 .. $].to!ushort;
780 		}
781 	}
782 
783 	///
784 	unittest {
785 		auto s = new HTTPServerSettings(":8080");
786 		assert(s.bindAddresses == ["::", "0.0.0.0"]); // default bind addresses
787 		assert(s.port == 8080);
788 
789 		s = new HTTPServerSettings("123.123.123.123");
790 		assert(s.bindAddresses == ["123.123.123.123"]);
791 		assert(s.port == 80);
792 
793 		s = new HTTPServerSettings("[::1]:443");
794 		assert(s.bindAddresses == ["::1"]);
795 		assert(s.port == 443);
796 	}
797 }
798 
799 
800 /**
801 	Options altering how sessions are created.
802 
803 	Multiple values can be or'ed together.
804 
805 	See_Also: HTTPServerResponse.startSession
806 */
807 enum SessionOption {
808 	/// No options.
809 	none = 0,
810 
811 	/** Instructs the browser to disallow accessing the session ID from JavaScript.
812 
813 		See_Also: Cookie.httpOnly
814 	*/
815 	httpOnly = 1<<0,
816 
817 	/** Instructs the browser to disallow sending the session ID over
818 		unencrypted connections.
819 
820 		By default, the type of the connection on which the session is started
821 		will be used to determine if secure or noSecure is used.
822 
823 		See_Also: noSecure, Cookie.secure
824 	*/
825 	secure = 1<<1,
826 
827 	/** Instructs the browser to allow sending the session ID over unencrypted
828 		connections.
829 
830 		By default, the type of the connection on which the session is started
831 		will be used to determine if secure or noSecure is used.
832 
833 		See_Also: secure, Cookie.secure
834 	*/
835 	noSecure = 1<<2
836 }
837 
838 
839 /**
840 	Represents a HTTP request as received by the server side.
841 */
842 final class HTTPServerRequest : HTTPRequest {
843 	private {
844 		SysTime m_timeCreated;
845 		HTTPServerSettings m_settings;
846 		ushort m_port;
847 		string m_peer;
848 	}
849 
850 	public {
851 		/// The IP address of the client
852 		@property string peer()
853 		@safe nothrow {
854 			if (!m_peer) {
855 				version (Have_vibe_core) {} else scope (failure) assert(false);
856 				// store the IP address (IPv4 addresses forwarded over IPv6 are stored in IPv4 format)
857 				auto peer_address_string = this.clientAddress.toString();
858 				if (peer_address_string.startsWith("::ffff:") && peer_address_string[7 .. $].indexOf(':') < 0)
859 					m_peer = peer_address_string[7 .. $];
860 				else m_peer = peer_address_string;
861 			}
862 			return m_peer;
863 		}
864 		/// ditto
865 		NetworkAddress clientAddress;
866 
867 		/// Determines if the request should be logged to the access log file.
868 		bool noLog;
869 
870 		/// Determines if the request was issued over an TLS encrypted channel.
871 		bool tls;
872 
873 		/** Information about the TLS certificate provided by the client.
874 
875 			Remarks: This field is only set if `tls` is true, and the peer
876 			presented a client certificate.
877 		*/
878 		TLSCertificateInformation clientCertificate;
879 
880 		/** Deprecated: The _path part of the URL.
881 
882 			Note that this function contains the decoded version of the
883 			requested path, which can yield incorrect results if the path
884 			contains URL encoded path separators. Use `requestPath` instead to
885 			get an encoding-aware representation.
886 		*/
887 		string path() @safe {
888 			if (_path.isNull) {
889 				_path = urlDecode(requestPath.toString);
890 			}
891 			return _path.get;
892 		}
893 
894 		private Nullable!string _path;
895 
896 		/** The path part of the requested URI.
897 		*/
898 		InetPath requestPath;
899 
900 		/** The user name part of the URL, if present.
901 		*/
902 		string username;
903 
904 		/** The _password part of the URL, if present.
905 		*/
906 		string password;
907 
908 		/** The _query string part of the URL.
909 		*/
910 		string queryString;
911 
912 		/** Contains the list of _cookies that are stored on the client.
913 
914 			Note that the a single cookie name may occur multiple times if multiple
915 			cookies have that name but different paths or domains that all match
916 			the request URI. By default, the first cookie will be returned, which is
917 			the or one of the cookies with the closest path match.
918 		*/
919 		@property ref CookieValueMap cookies() @safe {
920 			if (_cookies.isNull) {
921 				_cookies = CookieValueMap.init;
922 				if (auto pv = "cookie" in headers)
923 					parseCookies(*pv, _cookies);
924 			}
925 			return _cookies.get;
926 		}
927 		private Nullable!CookieValueMap _cookies;
928 
929 		/** Contains all _form fields supplied using the _query string.
930 
931 			The fields are stored in the same order as they are received.
932 		*/
933 		@property ref FormFields query() @safe {
934 			if (_query.isNull) {
935 				_query = FormFields.init;
936 				parseURLEncodedForm(queryString, _query);
937 			}
938 
939 			return _query.get;
940 		}
941 		Nullable!FormFields _query;
942 
943 		import vibe.utils.dictionarylist;
944 		/** A map of general parameters for the request.
945 
946 			This map is supposed to be used by middleware functionality to store
947 			information for later stages. For example vibe.http.router.URLRouter uses this map
948 			to store the value of any named placeholders.
949 		*/
950 		DictionaryList!(string, true, 8) params;
951 
952 		import std.variant : Variant;
953 		/** A map of context items for the request.
954 
955 			This is especially useful for passing application specific data down
956 			the chain of processors along with the request itself.
957 
958 			For example, a generic route may be defined to check user login status,
959 			if the user is logged in, add a reference to user specific data to the
960 			context.
961 
962 			This is implemented with `std.variant.Variant` to allow any type of data.
963 		*/
964 		DictionaryList!(Variant, true, 2) context;
965 
966 		/** Supplies the request body as a stream.
967 
968 			Note that when certain server options are set (such as
969 			HTTPServerOption.parseJsonBody) and a matching request was sent,
970 			the returned stream will be empty. If needed, remove those
971 			options and do your own processing of the body when launching
972 			the server. HTTPServerOption has a list of all options that affect
973 			the request body.
974 		*/
975 		InputStream bodyReader;
976 
977 		/** Contains the parsed Json for a JSON request.
978 
979 			A JSON request must have the Content-Type "application/json" or "application/vnd.api+json".
980 		*/
981 		@property ref Json json() @safe {
982 			if (_json.isNull) {
983 				if (icmp2(contentType, "application/json") == 0 || icmp2(contentType, "application/vnd.api+json") == 0 ) {
984 					auto bodyStr = bodyReader.readAllUTF8();
985 					if (!bodyStr.empty) _json = parseJson(bodyStr);
986 					else _json = Json.undefined;
987 				} else {
988 					_json = Json.undefined;
989 				}
990 			}
991 			return _json.get;
992 		}
993 
994 		private Nullable!Json _json;
995 
996 		/** Contains the parsed parameters of a HTML POST _form request.
997 
998 			The fields are stored in the same order as they are received.
999 
1000 			Remarks:
1001 				A form request must either have the Content-Type
1002 				"application/x-www-form-urlencoded" or "multipart/form-data".
1003 		*/
1004 		@property ref FormFields form() @safe {
1005 			if (_form.isNull)
1006 				parseFormAndFiles();
1007 
1008 			return _form.get;
1009 		}
1010 
1011 		private Nullable!FormFields _form;
1012 
1013 		private void parseFormAndFiles() @safe {
1014 			_form = FormFields.init;
1015 			parseFormData(_form, _files, headers.get("Content-Type", ""), bodyReader, MaxHTTPHeaderLineLength);
1016 		}
1017 
1018 		/** Contains information about any uploaded file for a HTML _form request.
1019 		*/
1020 		@property ref FilePartFormFields files() @safe {
1021 			// _form and _files are parsed in one step
1022 			if (_form.isNull) {
1023 				parseFormAndFiles();
1024 				assert(!_form.isNull);
1025 			}
1026 
1027             return _files;
1028 		}
1029 
1030 		private FilePartFormFields _files;
1031 
1032 		/** The current Session object.
1033 
1034 			This field is set if HTTPServerResponse.startSession() has been called
1035 			on a previous response and if the client has sent back the matching
1036 			cookie.
1037 
1038 			Remarks: Requires the HTTPServerOption.parseCookies option.
1039 		*/
1040 		Session session;
1041 	}
1042 
1043 	package {
1044 		/** The settings of the server serving this request.
1045 		 */
1046 		@property const(HTTPServerSettings) serverSettings() const @safe
1047 		{
1048 			return m_settings;
1049 		}
1050 	}
1051 
1052 	this(SysTime time, ushort port)
1053 	@safe {
1054 		m_timeCreated = time.toUTC();
1055 		m_port = port;
1056 	}
1057 
1058 	/** Time when this request started processing.
1059 	*/
1060 	@property SysTime timeCreated() const @safe { return m_timeCreated; }
1061 
1062 
1063 	/** The full URL that corresponds to this request.
1064 
1065 		The host URL includes the protocol, host and optionally the user
1066 		and password that was used for this request. This field is useful to
1067 		construct self referencing URLs.
1068 
1069 		Note that the port is currently not set, so that this only works if
1070 		the standard port is used.
1071 	*/
1072 	@property URL fullURL()
1073 	const @safe {
1074 		URL url;
1075 
1076 		auto xfh = this.headers.get("X-Forwarded-Host");
1077 		auto xfp = this.headers.get("X-Forwarded-Port");
1078 		auto xfpr = this.headers.get("X-Forwarded-Proto");
1079 
1080 		// Set URL host segment.
1081 		if (xfh.length) {
1082 			url.host = xfh;
1083 		} else if (!this.host.empty) {
1084 			url.host = this.host;
1085 		} else if (!m_settings.hostName.empty) {
1086 			url.host = m_settings.hostName;
1087 		} else {
1088 			url.host = m_settings.bindAddresses[0];
1089 		}
1090 
1091 		// Set URL schema segment.
1092 		if (xfpr.length) {
1093 			url.schema = xfpr;
1094 		} else if (this.tls) {
1095 			url.schema = "https";
1096 		} else {
1097 			url.schema = "http";
1098 		}
1099 
1100 		// Set URL port segment.
1101 		if (xfp.length) {
1102 			try {
1103 				url.port = xfp.to!ushort;
1104 			} catch (ConvException) {
1105 				// TODO : Consider responding with a 400/etc. error from here.
1106 				logWarn("X-Forwarded-Port header was not valid port (%s)", xfp);
1107 			}
1108 		} else if (!xfh) {
1109 			if (url.schema == "https") {
1110 				if (m_port != 443U) url.port = m_port;
1111 			} else {
1112 				if (m_port != 80U)  url.port = m_port;
1113 			}
1114 		}
1115 
1116 		if (url.host.startsWith('[')) { // handle IPv6 address
1117 			auto idx = url.host.indexOf(']');
1118 			if (idx >= 0 && idx+1 < url.host.length && url.host[idx+1] == ':')
1119 				url.host = url.host[1 .. idx];
1120 		} else { // handle normal host names or IPv4 address
1121 			auto idx = url.host.indexOf(':');
1122 			if (idx >= 0) url.host = url.host[0 .. idx];
1123 		}
1124 
1125 		url.username = this.username;
1126 		url.password = this.password;
1127 		url.localURI = this.requestURI;
1128 
1129 		return url;
1130 	}
1131 
1132 	/** The relative path to the root folder.
1133 
1134 		Using this function instead of absolute URLs for embedded links can be
1135 		useful to avoid dead link when the site is piped through a
1136 		reverse-proxy.
1137 
1138 		The returned string always ends with a slash.
1139 	*/
1140 	@property string rootDir()
1141 	const @safe {
1142 		import std.algorithm.searching : count;
1143 		auto depth = requestPath.bySegment.count!(s => s.name.length > 0);
1144 		if (depth > 0 && !requestPath.endsWithSlash) depth--;
1145 		return depth == 0 ? "./" : replicate("../", depth);
1146 	}
1147 
1148 	unittest {
1149 		assert(createTestHTTPServerRequest(URL("http://localhost/")).rootDir == "./");
1150 		assert(createTestHTTPServerRequest(URL("http://localhost/foo")).rootDir == "./");
1151 		assert(createTestHTTPServerRequest(URL("http://localhost/foo/")).rootDir == "../");
1152 		assert(createTestHTTPServerRequest(URL("http://localhost/foo/bar")).rootDir == "../");
1153 		assert(createTestHTTPServerRequest(URL("http://localhost")).rootDir == "./");
1154 	}
1155 }
1156 
1157 
1158 /**
1159 	Represents a HTTP response as sent from the server side.
1160 */
1161 final class HTTPServerResponse : HTTPResponse {
1162 	private {
1163 		InterfaceProxy!Stream m_conn;
1164 		InterfaceProxy!ConnectionStream m_rawConnection;
1165 		InterfaceProxy!OutputStream m_bodyWriter;
1166 		IAllocator m_requestAlloc;
1167 		FreeListRef!ChunkedOutputStream m_chunkedBodyWriter;
1168 		FreeListRef!CountingOutputStream m_countingWriter;
1169 		FreeListRef!ZlibOutputStream m_zlibOutputStream;
1170 		HTTPServerSettings m_settings;
1171 		Session m_session;
1172 		bool m_headerWritten = false;
1173 		bool m_isHeadResponse = false;
1174 		bool m_tls;
1175 		bool m_requiresConnectionClose;
1176 		SysTime m_timeFinalized;
1177 	}
1178 
1179 	static if (!is(Stream == InterfaceProxy!Stream)) {
1180 		this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc)
1181 		@safe {
1182 			this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc);
1183 		}
1184 	}
1185 
1186 	this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc)
1187 	@safe {
1188 		m_conn = conn;
1189 		m_rawConnection = raw_connection;
1190 		m_countingWriter = createCountingOutputStreamFL(conn);
1191 		m_settings = settings;
1192 		m_requestAlloc = req_alloc;
1193 	}
1194 
1195 	/** Returns the time at which the request was finalized.
1196 
1197 		Note that this field will only be set after `finalize` has been called.
1198 	*/
1199 	@property SysTime timeFinalized() const @safe { return m_timeFinalized; }
1200 
1201 	/** Determines if the HTTP header has already been written.
1202 	*/
1203 	@property bool headerWritten() const @safe { return m_headerWritten; }
1204 
1205 	/** Determines if the response does not need a body.
1206 	*/
1207 	bool isHeadResponse() const @safe { return m_isHeadResponse; }
1208 
1209 	/** Determines if the response is sent over an encrypted connection.
1210 	*/
1211 	bool tls() const @safe { return m_tls; }
1212 
1213 	/** Writes the entire response body at once.
1214 
1215 		Params:
1216 			data = The data to write as the body contents
1217 			status = Optional response status code to set
1218 			content_tyoe = Optional content type to apply to the response.
1219 				If no content type is given and no "Content-Type" header is
1220 				set in the response, this will default to
1221 				`"application/octet-stream"`.
1222 
1223 		See_Also: `HTTPStatusCode`
1224 	*/
1225 	void writeBody(in ubyte[] data, string content_type = null)
1226 	@safe {
1227 		if (content_type.length) headers["Content-Type"] = content_type;
1228 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1229 		headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", data.length);
1230 		bodyWriter.write(data);
1231 	}
1232 	/// ditto
1233 	void writeBody(in ubyte[] data, int status, string content_type = null)
1234 	@safe {
1235 		statusCode = status;
1236 		writeBody(data, content_type);
1237 	}
1238 	/// ditto
1239 	void writeBody(scope InputStream data, string content_type = null)
1240 	@safe {
1241 		if (content_type.length) headers["Content-Type"] = content_type;
1242 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1243 		data.pipe(bodyWriter);
1244 	}
1245 
1246 	/** Writes the entire response body as a single string.
1247 
1248 		Params:
1249 			data = The string to write as the body contents
1250 			status = Optional response status code to set
1251 			content_type = Optional content type to apply to the response.
1252 				If no content type is given and no "Content-Type" header is
1253 				set in the response, this will default to
1254 				`"text/plain; charset=UTF-8"`.
1255 
1256 		See_Also: `HTTPStatusCode`
1257 	*/
1258 	/// ditto
1259 	void writeBody(string data, string content_type = null)
1260 	@safe {
1261 		if (!content_type.length && "Content-Type" !in headers)
1262 			content_type = "text/plain; charset=UTF-8";
1263 		writeBody(cast(const(ubyte)[])data, content_type);
1264 	}
1265 	/// ditto
1266 	void writeBody(string data, int status, string content_type = null)
1267 	@safe {
1268 		statusCode = status;
1269 		writeBody(data, content_type);
1270 	}
1271 
1272 	/** Writes the whole response body at once, without doing any further encoding.
1273 
1274 		The caller has to make sure that the appropriate headers are set correctly
1275 		(i.e. Content-Type and Content-Encoding).
1276 
1277 		Note that the version taking a RandomAccessStream may perform additional
1278 		optimizations such as sending a file directly from the disk to the
1279 		network card using a DMA transfer.
1280 
1281 	*/
1282 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream) @safe
1283 		if (isRandomAccessStream!RandomAccessStream)
1284 	{
1285 		assert(!m_headerWritten, "A body was already written!");
1286 		writeHeader();
1287 		if (m_isHeadResponse) return;
1288 
1289 		auto bytes = stream.size - stream.tell();
1290 		stream.pipe(m_conn);
1291 		m_countingWriter.increment(bytes);
1292 	}
1293 	/// ditto
1294 	void writeRawBody(InputStream)(InputStream stream, size_t num_bytes = 0) @safe
1295 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1296 	{
1297 		assert(!m_headerWritten, "A body was already written!");
1298 		writeHeader();
1299 		if (m_isHeadResponse) return;
1300 
1301 		if (num_bytes > 0) {
1302 			stream.pipe(m_conn, num_bytes);
1303 			m_countingWriter.increment(num_bytes);
1304 		} else stream.pipe(m_countingWriter, num_bytes);
1305 	}
1306 	/// ditto
1307 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream, int status) @safe
1308 		if (isRandomAccessStream!RandomAccessStream)
1309 	{
1310 		statusCode = status;
1311 		writeRawBody(stream);
1312 	}
1313 	/// ditto
1314 	void writeRawBody(InputStream)(InputStream stream, int status, size_t num_bytes = 0) @safe
1315 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1316 	{
1317 		statusCode = status;
1318 		writeRawBody(stream, num_bytes);
1319 	}
1320 
1321 
1322 	/// Writes a JSON message with the specified status
1323 	void writeJsonBody(T)(T data, int status, bool allow_chunked = false)
1324 	{
1325 		statusCode = status;
1326 		writeJsonBody(data, allow_chunked);
1327 	}
1328 	/// ditto
1329 	void writeJsonBody(T)(T data, int status, string content_type, bool allow_chunked = false)
1330 	{
1331 		statusCode = status;
1332 		writeJsonBody(data, content_type, allow_chunked);
1333 	}
1334 
1335 	/// ditto
1336 	void writeJsonBody(T)(T data, string content_type, bool allow_chunked = false)
1337 	{
1338 		headers["Content-Type"] = content_type;
1339 		writeJsonBody(data, allow_chunked);
1340 	}
1341 	/// ditto
1342 	void writeJsonBody(T)(T data, bool allow_chunked = false)
1343 	{
1344 		doWriteJsonBody!(T, false)(data, allow_chunked);
1345 	}
1346 	/// ditto
1347 	void writePrettyJsonBody(T)(T data, bool allow_chunked = false)
1348 	{
1349 		doWriteJsonBody!(T, true)(data, allow_chunked);
1350 	}
1351 
1352 	private void doWriteJsonBody(T, bool PRETTY)(T data, bool allow_chunked = false)
1353 	{
1354 		import std.traits;
1355 		import vibe.stream.wrapper;
1356 
1357 		static if (!is(T == Json) && is(typeof(data.data())) && isArray!(typeof(data.data()))) {
1358 			static assert(!is(T == Appender!(typeof(data.data()))), "Passed an Appender!T to writeJsonBody - this is most probably not doing what's indended.");
1359 		}
1360 
1361 		if ("Content-Type" !in headers)
1362 			headers["Content-Type"] = "application/json; charset=UTF-8";
1363 
1364 
1365 		// set an explicit content-length field if chunked encoding is not allowed
1366 		if (!allow_chunked) {
1367 			import vibe.internal.rangeutil;
1368 			long length = 0;
1369 			auto counter = RangeCounter(() @trusted { return &length; } ());
1370 			static if (PRETTY) serializeToPrettyJson(counter, data);
1371 			else serializeToJson(counter, data);
1372 			headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length);
1373 		}
1374 
1375 		auto rng = streamOutputRange!1024(bodyWriter);
1376 		static if (PRETTY) serializeToPrettyJson(() @trusted { return &rng; } (), data);
1377 		else serializeToJson(() @trusted { return &rng; } (), data);
1378 	}
1379 
1380 	/**
1381 	 * Writes the response with no body.
1382 	 *
1383 	 * This method should be used in situations where no body is
1384 	 * requested, such as a HEAD request. For an empty body, just use writeBody,
1385 	 * as this method causes problems with some keep-alive connections.
1386 	 */
1387 	void writeVoidBody()
1388 	@safe {
1389 		if (!m_isHeadResponse) {
1390 			assert("Content-Length" !in headers);
1391 			assert("Transfer-Encoding" !in headers);
1392 		}
1393 		assert(!headerWritten);
1394 		writeHeader();
1395 		m_conn.flush();
1396 	}
1397 
1398 	/** A stream for writing the body of the HTTP response.
1399 
1400 		Note that after 'bodyWriter' has been accessed for the first time, it
1401 		is not allowed to change any header or the status code of the response.
1402 	*/
1403 	@property InterfaceProxy!OutputStream bodyWriter()
1404 	@safe {
1405 		assert(!!m_conn);
1406 		if (m_bodyWriter) return m_bodyWriter;
1407 
1408 		assert(!m_headerWritten, "A void body was already written!");
1409 
1410 		if (m_isHeadResponse) {
1411 			// for HEAD requests, we define a NullOutputWriter for convenience
1412 			// - no body will be written. However, the request handler should call writeVoidBody()
1413 			// and skip writing of the body in this case.
1414 			if ("Content-Length" !in headers)
1415 				headers["Transfer-Encoding"] = "chunked";
1416 			writeHeader();
1417 			m_bodyWriter = nullSink;
1418 			return m_bodyWriter;
1419 		}
1420 
1421 		if ("Content-Encoding" in headers && "Content-Length" in headers) {
1422 			// we do not known how large the compressed body will be in advance
1423 			// so remove the content-length and use chunked transfer
1424 			headers.remove("Content-Length");
1425 		}
1426 
1427 		if (auto pcl = "Content-Length" in headers) {
1428 			writeHeader();
1429 			m_countingWriter.writeLimit = (*pcl).to!ulong;
1430 			m_bodyWriter = m_countingWriter;
1431 		} else if (httpVersion <= HTTPVersion.HTTP_1_0) {
1432 			if ("Connection" in headers)
1433 				headers.remove("Connection"); // default to "close"
1434 			writeHeader();
1435 			m_bodyWriter = m_conn;
1436 		} else {
1437 			headers["Transfer-Encoding"] = "chunked";
1438 			writeHeader();
1439 			m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter);
1440 			m_bodyWriter = m_chunkedBodyWriter;
1441 		}
1442 
1443 		if (auto pce = "Content-Encoding" in headers) {
1444 			if (icmp2(*pce, "gzip") == 0) {
1445 				m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter);
1446 				m_bodyWriter = m_zlibOutputStream;
1447 			} else if (icmp2(*pce, "deflate") == 0) {
1448 				m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter);
1449 				m_bodyWriter = m_zlibOutputStream;
1450 			} else {
1451 				logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'");
1452 			}
1453 		}
1454 
1455 		return m_bodyWriter;
1456 	}
1457 
1458 	/** Sends a redirect request to the client.
1459 
1460 		Params:
1461 			url = The URL to redirect to
1462 			status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found)
1463 	*/
1464 	void redirect(string url, int status = HTTPStatus.Found)
1465 	@safe {
1466 		// Disallow any characters that may influence the header parsing
1467 		enforce(!url.representation.canFind!(ch => ch < 0x20),
1468 			"Control character in redirection URL.");
1469 
1470 		statusCode = status;
1471 		headers["Location"] = url;
1472 		writeBody("redirecting...");
1473 	}
1474 	/// ditto
1475 	void redirect(URL url, int status = HTTPStatus.Found)
1476 	@safe {
1477 		redirect(url.toString(), status);
1478 	}
1479 
1480 	///
1481 	@safe unittest {
1482 		import vibe.http.router;
1483 
1484 		void request_handler(HTTPServerRequest req, HTTPServerResponse res)
1485 		{
1486 			res.redirect("http://example.org/some_other_url");
1487 		}
1488 
1489 		void test()
1490 		{
1491 			auto router = new URLRouter;
1492 			router.get("/old_url", &request_handler);
1493 
1494 			listenHTTP(new HTTPServerSettings, router);
1495 		}
1496 	}
1497 
1498 
1499 	/** Special method sending a SWITCHING_PROTOCOLS response to the client.
1500 
1501 		Notice: For the overload that returns a `ConnectionStream`, it must be
1502 			ensured that the returned instance doesn't outlive the request
1503 			handler callback.
1504 
1505 		Params:
1506 			protocol = The protocol set in the "Upgrade" header of the response.
1507 				Use an empty string to skip setting this field.
1508 	*/
1509 	ConnectionStream switchProtocol(string protocol)
1510 	@safe {
1511 		statusCode = HTTPStatus.SwitchingProtocols;
1512 		if (protocol.length) headers["Upgrade"] = protocol;
1513 		writeVoidBody();
1514 		m_requiresConnectionClose = true;
1515 		return createConnectionProxyStream(m_conn, m_rawConnection);
1516 	}
1517 	/// ditto
1518 	void switchProtocol(string protocol, scope void delegate(scope ConnectionStream) @safe del)
1519 	@safe {
1520 		statusCode = HTTPStatus.SwitchingProtocols;
1521 		if (protocol.length) headers["Upgrade"] = protocol;
1522 		writeVoidBody();
1523 		m_requiresConnectionClose = true;
1524 		() @trusted {
1525 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1526 			del(conn);
1527 		} ();
1528 		finalize();
1529 	}
1530 
1531 	/** Special method for handling CONNECT proxy tunnel
1532 
1533 		Notice: For the overload that returns a `ConnectionStream`, it must be
1534 			ensured that the returned instance doesn't outlive the request
1535 			handler callback.
1536 	*/
1537 	ConnectionStream connectProxy()
1538 	@safe {
1539 		return createConnectionProxyStream(m_conn, m_rawConnection);
1540 	}
1541 	/// ditto
1542 	void connectProxy(scope void delegate(scope ConnectionStream) @safe del)
1543 	@safe {
1544 		() @trusted {
1545 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1546 			del(conn);
1547 		} ();
1548 		finalize();
1549 	}
1550 
1551 	/** Sets the specified cookie value.
1552 
1553 		Params:
1554 			name = Name of the cookie
1555 			value = New cookie value - pass null to clear the cookie
1556 			path = Path (as seen by the client) of the directory tree in which the cookie is visible
1557 	*/
1558 	Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url)
1559 	@safe {
1560 		auto cookie = new Cookie();
1561 		cookie.path = path;
1562 		cookie.setValue(value, encoding);
1563 		if (value is null) {
1564 			cookie.maxAge = 0;
1565 			cookie.expires = "Thu, 01 Jan 1970 00:00:00 GMT";
1566 		}
1567 		cookies[name] = cookie;
1568 		return cookie;
1569 	}
1570 
1571 	/**
1572 		Initiates a new session.
1573 
1574 		The session is stored in the SessionStore that was specified when
1575 		creating the server. Depending on this, the session can be persistent
1576 		or temporary and specific to this server instance.
1577 	*/
1578 	Session startSession(string path = "/", SessionOption options = SessionOption.httpOnly)
1579 	@safe {
1580 		assert(m_settings.sessionStore, "no session store set");
1581 		assert(!m_session, "Try to start a session, but already started one.");
1582 
1583 		bool secure;
1584 		if (options & SessionOption.secure) secure = true;
1585 		else if (options & SessionOption.noSecure) secure = false;
1586 		else secure = this.tls;
1587 
1588 		m_session = m_settings.sessionStore.create();
1589 		m_session.set("$sessionCookiePath", path);
1590 		m_session.set("$sessionCookieSecure", secure);
1591 		auto cookie = setCookie(m_settings.sessionIdCookie, m_session.id, path);
1592 		cookie.secure = secure;
1593 		cookie.httpOnly = (options & SessionOption.httpOnly) != 0;
1594 		return m_session;
1595 	}
1596 
1597 	/**
1598 		Terminates the current session (if any).
1599 	*/
1600 	void terminateSession()
1601 	@safe {
1602 		if (!m_session) return;
1603 		auto cookie = setCookie(m_settings.sessionIdCookie, null, m_session.get!string("$sessionCookiePath"));
1604 		cookie.secure = m_session.get!bool("$sessionCookieSecure");
1605 		m_session.destroy();
1606 		m_session = Session.init;
1607 	}
1608 
1609 	@property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; }
1610 
1611 	/**
1612 		Waits until either the connection closes, data arrives, or until the
1613 		given timeout is reached.
1614 
1615 		Returns:
1616 			$(D true) if the connection was closed and $(D false) if either the
1617 			timeout was reached, or if data has arrived for consumption.
1618 
1619 		See_Also: `connected`
1620 	*/
1621 	bool waitForConnectionClose(Duration timeout = Duration.max)
1622 	@safe {
1623 		if (!m_rawConnection || !m_rawConnection.connected) return true;
1624 		m_rawConnection.waitForData(timeout);
1625 		return !m_rawConnection.connected;
1626 	}
1627 
1628 	/**
1629 		Determines if the underlying connection is still alive.
1630 
1631 		Returns $(D true) if the remote peer is still connected and $(D false)
1632 		if the remote peer closed the connection.
1633 
1634 		See_Also: `waitForConnectionClose`
1635 	*/
1636 	@property bool connected()
1637 	@safe const {
1638 		if (!m_rawConnection) return false;
1639 		return m_rawConnection.connected;
1640 	}
1641 
1642 	/**
1643 		Finalizes the response. This is usually called automatically by the server.
1644 
1645 		This method can be called manually after writing the response to force
1646 		all network traffic associated with the current request to be finalized.
1647 		After the call returns, the `timeFinalized` property will be set.
1648 	*/
1649 	void finalize()
1650 	@safe {
1651 		if (m_zlibOutputStream) {
1652 			m_zlibOutputStream.finalize();
1653 			m_zlibOutputStream.destroy();
1654 		}
1655 		if (m_chunkedBodyWriter) {
1656 			m_chunkedBodyWriter.finalize();
1657 			m_chunkedBodyWriter.destroy();
1658 		}
1659 
1660 		// ignore exceptions caused by an already closed connection - the client
1661 		// may have closed the connection already and this doesn't usually indicate
1662 		// a problem.
1663 		if (m_rawConnection && m_rawConnection.connected) {
1664 			try if (m_conn) m_conn.flush();
1665 			catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg);
1666 			if (!isHeadResponse && bytesWritten < headers.get("Content-Length", "0").to!long) {
1667 				logDebug("HTTP response only written partially before finalization. Terminating connection.");
1668 				m_requiresConnectionClose = true;
1669 			}
1670 
1671 			m_rawConnection = InterfaceProxy!ConnectionStream.init;
1672 		}
1673 
1674 		if (m_conn) {
1675 			m_conn = InterfaceProxy!Stream.init;
1676 			m_timeFinalized = Clock.currTime(UTC());
1677 		}
1678 	}
1679 
1680 	private void writeHeader()
1681 	@safe {
1682 		import vibe.stream.wrapper;
1683 
1684 		assert(!m_bodyWriter && !m_headerWritten, "Try to write header after body has already begun.");
1685 		m_headerWritten = true;
1686 		auto dst = streamOutputRange!1024(m_conn);
1687 
1688 		void writeLine(T...)(string fmt, T args)
1689 		@safe {
1690 			formattedWrite(() @trusted { return &dst; } (), fmt, args);
1691 			dst.put("\r\n");
1692 			logTrace(fmt, args);
1693 		}
1694 
1695 		logTrace("---------------------");
1696 		logTrace("HTTP server response:");
1697 		logTrace("---------------------");
1698 
1699 		// write the status line
1700 		writeLine("%s %d %s",
1701 			getHTTPVersionString(this.httpVersion),
1702 			this.statusCode,
1703 			this.statusPhrase.length ? this.statusPhrase : httpStatusText(this.statusCode));
1704 
1705 		// write all normal headers
1706 		foreach (k, v; this.headers) {
1707 			dst.put(k);
1708 			dst.put(": ");
1709 			dst.put(v);
1710 			dst.put("\r\n");
1711 			logTrace("%s: %s", k, v);
1712 		}
1713 
1714 		logTrace("---------------------");
1715 
1716 		// write cookies
1717 		foreach (n, cookie; this.cookies) {
1718 			dst.put("Set-Cookie: ");
1719 			cookie.writeString(() @trusted { return &dst; } (), n);
1720 			dst.put("\r\n");
1721 		}
1722 
1723 		// finalize response header
1724 		dst.put("\r\n");
1725 	}
1726 }
1727 
1728 /**
1729 	Represents the request listener for a specific `listenHTTP` call.
1730 
1731 	This struct can be used to stop listening for HTTP requests at runtime.
1732 */
1733 struct HTTPListener {
1734 	private {
1735 		size_t[] m_virtualHostIDs;
1736 	}
1737 
1738 	private this(size_t[] ids) @safe { m_virtualHostIDs = ids; }
1739 
1740 	@property NetworkAddress[] bindAddresses()
1741 	{
1742 		NetworkAddress[] ret;
1743 		foreach (l; s_listeners)
1744 			if (l.m_virtualHosts.canFind!(v => m_virtualHostIDs.canFind(v.id))) {
1745 				NetworkAddress a;
1746 				a = resolveHost(l.bindAddress);
1747 				a.port = l.bindPort;
1748 				ret ~= a;
1749 			}
1750 		return ret;
1751 	}
1752 
1753 	/** Stops handling HTTP requests and closes the TCP listening port if
1754 		possible.
1755 	*/
1756 	void stopListening()
1757 	@safe {
1758 		import std.algorithm : countUntil;
1759 
1760 		foreach (vhid; m_virtualHostIDs) {
1761 			foreach (lidx, l; s_listeners) {
1762 				if (l.removeVirtualHost(vhid)) {
1763 					if (!l.hasVirtualHosts) {
1764 						l.m_listener.stopListening();
1765 						logInfo("Stopped to listen for HTTP%s requests on %s:%s", l.tlsContext ? "S": "", l.bindAddress, l.bindPort);
1766 						s_listeners = s_listeners[0 .. lidx] ~ s_listeners[lidx+1 .. $];
1767 					}
1768 				}
1769 				break;
1770 			}
1771 		}
1772 	}
1773 }
1774 
1775 
1776 /** Represents a single HTTP server port.
1777 
1778 	This class defines the incoming interface, port, and TLS configuration of
1779 	the public server port. The public server port may differ from the local
1780 	one if a reverse proxy of some kind is facing the public internet and
1781 	forwards to this HTTP server.
1782 
1783 	Multiple virtual hosts can be configured to be served from the same port.
1784 	Their TLS settings must be compatible and each virtual host must have a
1785 	unique name.
1786 */
1787 final class HTTPServerContext {
1788 	private struct VirtualHost {
1789 		HTTPServerRequestDelegate requestHandler;
1790 		HTTPServerSettings settings;
1791 		HTTPLogger[] loggers;
1792 		size_t id;
1793 	}
1794 
1795 	private {
1796 		TCPListener m_listener;
1797 		VirtualHost[] m_virtualHosts;
1798 		string m_bindAddress;
1799 		ushort m_bindPort;
1800 		TLSContext m_tlsContext;
1801 		static size_t s_vhostIDCounter = 1;
1802 	}
1803 
1804 	@safe:
1805 
1806 	this(string bind_address, ushort bind_port)
1807 	{
1808 		m_bindAddress = bind_address;
1809 		m_bindPort = bind_port;
1810 	}
1811 
1812 	/** Returns the TLS context associated with the listener.
1813 
1814 		For non-HTTPS listeners, `null` will be returned. Otherwise, if only a
1815 		single virtual host has been added, the TLS context of that host's
1816 		settings is returned. For multiple virtual hosts, an SNI context is
1817 		returned, which forwards to the individual contexts based on the
1818 		requested host name.
1819 	*/
1820 	@property TLSContext tlsContext() { return m_tlsContext; }
1821 
1822 	/// The local network interface IP address associated with this listener
1823 	@property string bindAddress() const { return m_bindAddress; }
1824 
1825 	/// The local port associated with this listener
1826 	@property ushort bindPort() const { return m_bindPort; }
1827 
1828 	/// Determines if any virtual hosts have been addded
1829 	@property bool hasVirtualHosts() const { return m_virtualHosts.length > 0; }
1830 
1831 	/** Adds a single virtual host.
1832 
1833 		Note that the port and bind address defined in `settings` must match the
1834 		ones for this listener. The `settings.host` field must be unique for
1835 		all virtual hosts.
1836 
1837 		Returns: Returns a unique ID for the new virtual host
1838 	*/
1839 	size_t addVirtualHost(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
1840 	{
1841 		assert(settings.port == 0 || settings.port == m_bindPort, "Virtual host settings do not match bind port.");
1842 		assert(settings.bindAddresses.canFind(m_bindAddress), "Virtual host settings do not match bind address.");
1843 
1844 		VirtualHost vhost;
1845 		vhost.id = s_vhostIDCounter++;
1846 		vhost.settings = settings;
1847 		vhost.requestHandler = request_handler;
1848 
1849 		if (settings.accessLogger) vhost.loggers ~= settings.accessLogger;
1850 		if (settings.accessLogToConsole)
1851 			vhost.loggers ~= new HTTPConsoleLogger(settings, settings.accessLogFormat);
1852 		if (settings.accessLogFile.length)
1853 			vhost.loggers ~= new HTTPFileLogger(settings, settings.accessLogFormat, settings.accessLogFile);
1854 
1855 		if (!m_virtualHosts.length) m_tlsContext = settings.tlsContext;
1856 
1857 		enforce((m_tlsContext !is null) == (settings.tlsContext !is null),
1858 			"Cannot mix HTTP and HTTPS virtual hosts within the same listener.");
1859 
1860 		if (m_tlsContext) addSNIHost(settings);
1861 
1862 		m_virtualHosts ~= vhost;
1863 
1864 		if (settings.hostName.length) {
1865 			auto proto = settings.tlsContext ? "https" : "http";
1866 			auto port = settings.tlsContext && settings.port == 443 || !settings.tlsContext && settings.port == 80 ? "" : ":" ~ settings.port.to!string;
1867 			logInfo("Added virtual host %s://%s:%s/ (%s)", proto, settings.hostName, m_bindPort, m_bindAddress);
1868 		}
1869 
1870 		return vhost.id;
1871 	}
1872 
1873 	/// Removes a previously added virtual host using its ID.
1874 	bool removeVirtualHost(size_t id)
1875 	{
1876 		import std.algorithm.searching : countUntil;
1877 
1878 		auto idx = m_virtualHosts.countUntil!(c => c.id == id);
1879 		if (idx < 0) return false;
1880 
1881 		auto ctx = m_virtualHosts[idx];
1882 		m_virtualHosts = m_virtualHosts[0 .. idx] ~ m_virtualHosts[idx+1 .. $];
1883 		return true;
1884 	}
1885 
1886 	private void addSNIHost(HTTPServerSettings settings)
1887 	{
1888 		if (settings.tlsContext !is m_tlsContext && m_tlsContext.kind != TLSContextKind.serverSNI) {
1889 			logDebug("Create SNI TLS context for %s, port %s", bindAddress, bindPort);
1890 			m_tlsContext = createTLSContext(TLSContextKind.serverSNI);
1891 			m_tlsContext.sniCallback = &onSNI;
1892 		}
1893 
1894 		foreach (ctx; m_virtualHosts) {
1895 			/*enforce(ctx.settings.hostName != settings.hostName,
1896 				"A server with the host name '"~settings.hostName~"' is already "
1897 				"listening on "~addr~":"~to!string(settings.port)~".");*/
1898 		}
1899 	}
1900 
1901 	private TLSContext onSNI(string servername)
1902 	{
1903 		foreach (vhost; m_virtualHosts)
1904 			if (vhost.settings.hostName.icmp(servername) == 0) {
1905 				logDebug("Found context for SNI host '%s'.", servername);
1906 				return vhost.settings.tlsContext;
1907 			}
1908 		logDebug("No context found for SNI host '%s'.", servername);
1909 		return null;
1910 	}
1911 }
1912 
1913 /**************************************************************************************************/
1914 /* Private types                                                                                  */
1915 /**************************************************************************************************/
1916 
1917 private enum MaxHTTPHeaderLineLength = 4096;
1918 
1919 private final class LimitedHTTPInputStream : LimitedInputStream {
1920 @safe:
1921 
1922 	this(InterfaceProxy!InputStream stream, ulong byte_limit, bool silent_limit = false) {
1923 		super(stream, byte_limit, silent_limit, true);
1924 	}
1925 	override void onSizeLimitReached() {
1926 		throw new HTTPStatusException(HTTPStatus.requestEntityTooLarge);
1927 	}
1928 }
1929 
1930 private final class TimeoutHTTPInputStream : InputStream {
1931 @safe:
1932 
1933 	private {
1934 		long m_timeref;
1935 		long m_timeleft;
1936 		InterfaceProxy!InputStream m_in;
1937 	}
1938 
1939 	this(InterfaceProxy!InputStream stream, Duration timeleft, SysTime reftime)
1940 	{
1941 		enforce(timeleft > 0.seconds, "Timeout required");
1942 		m_in = stream;
1943 		m_timeleft = timeleft.total!"hnsecs"();
1944 		m_timeref = reftime.stdTime();
1945 	}
1946 
1947 	@property bool empty() { enforce(m_in, "InputStream missing"); return m_in.empty(); }
1948 	@property ulong leastSize() { enforce(m_in, "InputStream missing"); return m_in.leastSize();  }
1949 	@property bool dataAvailableForRead() {  enforce(m_in, "InputStream missing"); return m_in.dataAvailableForRead; }
1950 	const(ubyte)[] peek() { return m_in.peek(); }
1951 
1952 	size_t read(scope ubyte[] dst, IOMode mode)
1953 	{
1954 		enforce(m_in, "InputStream missing");
1955 		size_t nread = 0;
1956 		checkTimeout();
1957 		// FIXME: this should use ConnectionStream.waitForData to enforce the timeout during the
1958 		// read operation
1959 		return m_in.read(dst, mode);
1960 	}
1961 
1962 	alias read = InputStream.read;
1963 
1964 	private void checkTimeout()
1965 	@safe {
1966 		auto curr = Clock.currStdTime();
1967 		auto diff = curr - m_timeref;
1968 		if (diff > m_timeleft) throw new HTTPStatusException(HTTPStatus.RequestTimeout);
1969 		m_timeleft -= diff;
1970 		m_timeref = curr;
1971 	}
1972 }
1973 
1974 /**************************************************************************************************/
1975 /* Private functions                                                                              */
1976 /**************************************************************************************************/
1977 
1978 private {
1979 	import core.sync.mutex;
1980 
1981 	shared string s_distHost;
1982 	shared ushort s_distPort = 11000;
1983 
1984 	HTTPServerContext[] s_listeners;
1985 }
1986 
1987 /**
1988 	[private] Starts a HTTP server listening on the specified port.
1989 
1990 	This is the same as listenHTTP() except that it does not use a VibeDist host for
1991 	remote listening, even if specified on the command line.
1992 */
1993 private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
1994 @safe {
1995 	import vibe.core.core : runWorkerTaskDist;
1996 	import std.algorithm : canFind, find;
1997 
1998 	static TCPListener doListen(HTTPServerContext listen_info, bool dist, bool reusePort, bool is_tls)
1999 	@safe {
2000 		try {
2001 			TCPListenOptions options = TCPListenOptions.defaults;
2002 			if(reusePort) options |= TCPListenOptions.reusePort; else options &= ~TCPListenOptions.reusePort;
2003 			auto ret = listenTCP(listen_info.bindPort, (TCPConnection conn) nothrow @safe {
2004 					try handleHTTPConnection(conn, listen_info);
2005 					catch (Exception e) {
2006 						logError("HTTP connection handler has thrown: %s", e.msg);
2007 						debug logDebug("Full error: %s", () @trusted { return e.toString().sanitize(); } ());
2008 						try conn.close();
2009 						catch (Exception e) logError("Failed to close connection: %s", e.msg);
2010 					}
2011 				}, listen_info.bindAddress, options);
2012 
2013 			// support port 0 meaning any available port
2014 			if (listen_info.bindPort == 0)
2015 				listen_info.m_bindPort = ret.bindAddress.port;
2016 
2017 			auto proto = is_tls ? "https" : "http";
2018 			auto urladdr = listen_info.bindAddress;
2019 			if (urladdr.canFind(':')) urladdr = "["~urladdr~"]";
2020 			logInfo("Listening for requests on %s://%s:%s/", proto, urladdr, listen_info.bindPort);
2021 			return ret;
2022 		} catch( Exception e ) {
2023 			logWarn("Failed to listen on %s:%s", listen_info.bindAddress, listen_info.bindPort);
2024 			return TCPListener.init;
2025 		}
2026 	}
2027 
2028 	size_t[] vid;
2029 
2030 	// Check for every bind address/port, if a new listening socket needs to be created and
2031 	// check for conflicting servers
2032 	foreach (addr; settings.bindAddresses) {
2033 		HTTPServerContext linfo;
2034 
2035 		auto l = s_listeners.find!(l => l.bindAddress == addr && l.bindPort == settings.port);
2036 		if (!l.empty) linfo = l.front;
2037 		else {
2038 			auto li = new HTTPServerContext(addr, settings.port);
2039 			if (auto tcp_lst = doListen(li,
2040 					(settings.options & HTTPServerOptionImpl.distribute) != 0,
2041 					(settings.options & HTTPServerOption.reusePort) != 0,
2042 					settings.tlsContext !is null)) // DMD BUG 2043
2043 			{
2044 				li.m_listener = tcp_lst;
2045 				s_listeners ~= li;
2046 				linfo = li;
2047 			}
2048 		}
2049 
2050 		if (linfo) vid ~= linfo.addVirtualHost(settings, request_handler);
2051 	}
2052 
2053 	enforce(vid.length > 0, "Failed to listen for incoming HTTP connections on any of the supplied interfaces.");
2054 
2055 	return HTTPListener(vid);
2056 }
2057 
2058 private alias TLSStreamType = ReturnType!(createTLSStreamFL!(InterfaceProxy!Stream));
2059 
2060 
2061 private bool handleRequest(InterfaceProxy!Stream http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, ref HTTPServerSettings settings, ref bool keep_alive, scope IAllocator request_allocator)
2062 @safe {
2063 	import std.algorithm.searching : canFind;
2064 
2065 	SysTime reqtime = Clock.currTime(UTC());
2066 
2067 	// some instances that live only while the request is running
2068 	FreeListRef!HTTPServerRequest req = FreeListRef!HTTPServerRequest(reqtime, listen_info.bindPort);
2069 	FreeListRef!TimeoutHTTPInputStream timeout_http_input_stream;
2070 	FreeListRef!LimitedHTTPInputStream limited_http_input_stream;
2071 	FreeListRef!ChunkedInputStream chunked_input_stream;
2072 
2073 	// store the IP address
2074 	req.clientAddress = tcp_connection.remoteAddress;
2075 
2076 	if (!listen_info.hasVirtualHosts) {
2077 		logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
2078 		keep_alive = false;
2079 		return false;
2080 	}
2081 
2082 	// Default to the first virtual host for this listener
2083 	HTTPServerContext.VirtualHost context = listen_info.m_virtualHosts[0];
2084 	HTTPServerRequestDelegate request_task = context.requestHandler;
2085 	settings = context.settings;
2086 
2087 	// temporarily set to the default settings, the virtual host specific settings will be set further down
2088 	req.m_settings = settings;
2089 
2090 	// Create the response object
2091 	InterfaceProxy!ConnectionStream cproxy = tcp_connection;
2092 	auto res = FreeListRef!HTTPServerResponse(http_stream, cproxy, settings, request_allocator/*.Scoped_payload*/);
2093 	req.tls = res.m_tls = listen_info.tlsContext !is null;
2094 	if (req.tls) {
2095 		version (HaveNoTLS) assert(false);
2096 		else {
2097 			static if (is(InterfaceProxy!ConnectionStream == ConnectionStream))
2098 				req.clientCertificate = (cast(TLSStream)http_stream).peerCertificate;
2099 			else
2100 				req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate;
2101 		}
2102 	}
2103 
2104 	// Error page handler
2105 	void errorOut(int code, string msg, string debug_msg, Throwable ex)
2106 	@safe {
2107 		assert(!res.headerWritten);
2108 
2109 		// stack traces sometimes contain random bytes - make sure they are replaced
2110 		debug_msg = sanitizeUTF8(cast(const(ubyte)[])debug_msg);
2111 
2112 		res.statusCode = code;
2113 		if (settings && settings.errorPageHandler) {
2114 			/*scope*/ auto err = new HTTPServerErrorInfo;
2115 			err.code = code;
2116 			err.message = msg;
2117 			err.debugMessage = debug_msg;
2118 			err.exception = ex;
2119 			settings.errorPageHandler_(req, res, err);
2120 		} else {
2121 			if (debug_msg.length)
2122 				res.writeBody(format("%s - %s\n\n%s\n\nInternal error information:\n%s", code, httpStatusText(code), msg, debug_msg));
2123 			else res.writeBody(format("%s - %s\n\n%s", code, httpStatusText(code), msg));
2124 		}
2125 		assert(res.headerWritten);
2126 	}
2127 
2128 	bool parsed = false;
2129 	/*bool*/ keep_alive = false;
2130 
2131 	// parse the request
2132 	try {
2133 		logTrace("reading request..");
2134 
2135 		// limit the total request time
2136 		InterfaceProxy!InputStream reqReader = http_stream;
2137 		if (settings.maxRequestTime > dur!"seconds"(0) && settings.maxRequestTime != Duration.max) {
2138 			timeout_http_input_stream = FreeListRef!TimeoutHTTPInputStream(reqReader, settings.maxRequestTime, reqtime);
2139 			reqReader = timeout_http_input_stream;
2140 		}
2141 
2142 		// basic request parsing
2143 		parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize);
2144 		logTrace("Got request header.");
2145 
2146 		// find the matching virtual host
2147 		string reqhost;
2148 		ushort reqport = 0;
2149 		{
2150 			string s = req.host;
2151 			enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
2152 			if (s.startsWith('[')) { // IPv6 address
2153 				auto idx = s.indexOf(']');
2154 				enforce(idx > 0, "Missing closing ']' for IPv6 address.");
2155 				reqhost = s[1 .. idx];
2156 				s = s[idx+1 .. $];
2157 			} else if (s.length) { // host name or IPv4 address
2158 				auto idx = s.indexOf(':');
2159 				if (idx < 0) idx = s.length;
2160 				enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
2161 				reqhost = s[0 .. idx];
2162 				s = s[idx .. $];
2163 			}
2164 			if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
2165 		}
2166 
2167 		foreach (ctx; listen_info.m_virtualHosts)
2168 			if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
2169 				(!reqport || reqport == ctx.settings.port))
2170 			{
2171 				context = ctx;
2172 				settings = ctx.settings;
2173 				request_task = ctx.requestHandler;
2174 				break;
2175 			}
2176 		req.m_settings = settings;
2177 		res.m_settings = settings;
2178 
2179 		// setup compressed output
2180 		if (settings.useCompressionIfPossible) {
2181 			if (auto pae = "Accept-Encoding" in req.headers) {
2182 				if (canFind(*pae, "gzip")) {
2183 					res.headers["Content-Encoding"] = "gzip";
2184 				} else if (canFind(*pae, "deflate")) {
2185 					res.headers["Content-Encoding"] = "deflate";
2186 				}
2187 			}
2188 		}
2189 
2190 		// limit request size
2191 		if (auto pcl = "Content-Length" in req.headers) {
2192 			string v = *pcl;
2193 			auto contentLength = parse!ulong(v); // DMDBUG: to! thinks there is a H in the string
2194 			enforceBadRequest(v.length == 0, "Invalid content-length");
2195 			enforceBadRequest(settings.maxRequestSize <= 0 || contentLength <= settings.maxRequestSize, "Request size too big");
2196 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, contentLength);
2197 		} else if (auto pt = "Transfer-Encoding" in req.headers) {
2198 			enforceBadRequest(icmp(*pt, "chunked") == 0);
2199 			chunked_input_stream = createChunkedInputStreamFL(reqReader);
2200 			InterfaceProxy!InputStream ciproxy = chunked_input_stream;
2201 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(ciproxy, settings.maxRequestSize, true);
2202 		} else {
2203 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, 0);
2204 		}
2205 		req.bodyReader = limited_http_input_stream;
2206 
2207 		// handle Expect header
2208 		if (auto pv = "Expect" in req.headers) {
2209 			if (icmp2(*pv, "100-continue") == 0) {
2210 				logTrace("sending 100 continue");
2211 				http_stream.write("HTTP/1.1 100 Continue\r\n\r\n");
2212 			}
2213 		}
2214 
2215         // eagerly parse the URL as its lightweight and defacto @nogc
2216 		auto url = URL.parse(req.requestURI);
2217 		req.queryString = url.queryString;
2218 		req.username = url.username;
2219 		req.password = url.password;
2220 		req.requestPath = url.path;
2221 
2222 		// lookup the session
2223 		if (settings.sessionStore) {
2224 			// use the first cookie that contains a valid session ID in case
2225 			// of multiple matching session cookies
2226 			foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
2227 				req.session = settings.sessionStore.open(val);
2228 				res.m_session = req.session;
2229 				if (req.session) break;
2230 			}
2231 		}
2232 
2233 		// write default headers
2234 		if (req.method == HTTPMethod.HEAD) res.m_isHeadResponse = true;
2235 		if (settings.serverString.length)
2236 			res.headers["Server"] = settings.serverString;
2237 		res.headers["Date"] = formatRFC822DateAlloc(request_allocator, reqtime);
2238 		if (req.persistent) res.headers["Keep-Alive"] = formatAlloc(request_allocator, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
2239 
2240 		// finished parsing the request
2241 		parsed = true;
2242 		logTrace("persist: %s", req.persistent);
2243 		keep_alive = req.persistent;
2244 
2245 		// handle the request
2246 		logTrace("handle request (body %d)", req.bodyReader.leastSize);
2247 		res.httpVersion = req.httpVersion;
2248 		request_task(req, res);
2249 
2250 		// if no one has written anything, return 404
2251 		if (!res.headerWritten) {
2252 			string dbg_msg;
2253 			logDiagnostic("No response written for %s", req.requestURI);
2254 			if (settings.options & HTTPServerOption.errorStackTraces)
2255 				dbg_msg = format("No routes match path '%s'", req.requestURI);
2256 			errorOut(HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound), dbg_msg, null);
2257 		}
2258 	} catch (HTTPStatusException err) {
2259 		if (!res.headerWritten) errorOut(err.status, err.msg, err.debugMessage, err);
2260 		else logDiagnostic("HTTPSterrorOutatusException while writing the response: %s", err.msg);
2261 		debug logDebug("Exception while handling request %s %s: %s", req.method, req.requestURI, () @trusted { return err.toString().sanitize; } ());
2262 		if (!parsed || res.headerWritten || justifiesConnectionClose(err.status))
2263 			keep_alive = false;
2264 	} catch (UncaughtException e) {
2265 		auto status = parsed ? HTTPStatus.internalServerError : HTTPStatus.badRequest;
2266 		string dbg_msg;
2267 		if (settings.options & HTTPServerOption.errorStackTraces) dbg_msg = () @trusted { return e.toString().sanitize; } ();
2268 		if (!res.headerWritten && tcp_connection.connected) errorOut(status, httpStatusText(status), dbg_msg, e);
2269 		else logDiagnostic("Error while writing the response: %s", e.msg);
2270 		debug logDebug("Exception while handling request %s %s: %s", req.method, req.requestURI, () @trusted { return e.toString().sanitize(); } ());
2271 		if (!parsed || res.headerWritten || !cast(Exception)e) keep_alive = false;
2272 	}
2273 
2274 	if (tcp_connection.connected && keep_alive) {
2275 		if (req.bodyReader && !req.bodyReader.empty) {
2276 			req.bodyReader.pipe(nullSink);
2277 			logTrace("dropped body");
2278 		}
2279 	}
2280 
2281 	// finalize (e.g. for chunked encoding)
2282 	res.finalize();
2283 
2284 	if (res.m_requiresConnectionClose)
2285 		keep_alive = false;
2286 
2287 	foreach (k, v ; req._files) {
2288 		if (existsFile(v.tempPath)) {
2289 			removeFile(v.tempPath);
2290 			logDebug("Deleted upload tempfile %s", v.tempPath.toString());
2291 		}
2292 	}
2293 
2294 	if (!req.noLog) {
2295 		// log the request to access log
2296 		foreach (log; context.loggers)
2297 			log.log(req, res);
2298 	}
2299 
2300 	//logTrace("return %s (used pool memory: %s/%s)", keep_alive, request_allocator.allocatedSize, request_allocator.totalSize);
2301 	logTrace("return %s", keep_alive);
2302 	return keep_alive != false;
2303 }
2304 
2305 
2306 private void parseRequestHeader(InputStream)(HTTPServerRequest req, InputStream http_stream, IAllocator alloc, ulong max_header_size)
2307 	if (isInputStream!InputStream)
2308 {
2309 	auto stream = FreeListRef!LimitedHTTPInputStream(http_stream, max_header_size);
2310 
2311 	logTrace("HTTP server reading status line");
2312 	auto reqln = () @trusted { return cast(string)stream.readLine(MaxHTTPHeaderLineLength, "\r\n", alloc); }();
2313 
2314 	logTrace("--------------------");
2315 	logTrace("HTTP server request:");
2316 	logTrace("--------------------");
2317 	logTrace("%s", reqln);
2318 
2319 	//Method
2320 	auto pos = reqln.indexOf(' ');
2321 	enforceBadRequest(pos >= 0, "invalid request method");
2322 
2323 	req.method = httpMethodFromString(reqln[0 .. pos]);
2324 	reqln = reqln[pos+1 .. $];
2325 	//Path
2326 	pos = reqln.indexOf(' ');
2327 	enforceBadRequest(pos >= 0, "invalid request path");
2328 
2329 	req.requestURI = reqln[0 .. pos];
2330 	reqln = reqln[pos+1 .. $];
2331 
2332 	req.httpVersion = parseHTTPVersion(reqln);
2333 
2334 	//headers
2335 	parseRFC5322Header(stream, req.headers, MaxHTTPHeaderLineLength, alloc, false);
2336 
2337 	foreach (k, v; req.headers)
2338 		logTrace("%s: %s", k, v);
2339 	logTrace("--------------------");
2340 }
2341 
2342 private void parseCookies(string str, ref CookieValueMap cookies)
2343 @safe {
2344 	import std.encoding : sanitize;
2345 	import std.array : split;
2346 	import std..string : strip;
2347 	import std.algorithm.iteration : map, filter, each;
2348 	import vibe.http.common : Cookie;
2349 	() @trusted { return str.sanitize; } ()
2350 		.split(";")
2351 		.map!(kv => kv.strip.split("="))
2352 		.filter!(kv => kv.length == 2) //ignore illegal cookies
2353 		.each!(kv => cookies.add(kv[0], kv[1], Cookie.Encoding.raw) );
2354 }
2355 
2356 unittest
2357 {
2358   auto cvm = CookieValueMap();
2359   parseCookies("foo=bar;; baz=zinga; öö=üü   ;   møøse=was=sacked;    onlyval1; =onlyval2; onlykey=", cvm);
2360   assert(cvm["foo"] == "bar");
2361   assert(cvm["baz"] == "zinga");
2362   assert(cvm["öö"] == "üü");
2363   assert( "møøse" ! in cvm); //illegal cookie gets ignored
2364   assert( "onlyval1" ! in cvm); //illegal cookie gets ignored
2365   assert(cvm["onlykey"] == "");
2366   assert(cvm[""] == "onlyval2");
2367   assert(cvm.length() == 5);
2368   cvm = CookieValueMap();
2369   parseCookies("", cvm);
2370   assert(cvm.length() == 0);
2371   cvm = CookieValueMap();
2372   parseCookies(";;=", cvm);
2373   assert(cvm.length() == 1);
2374   assert(cvm[""] == "");
2375 }
2376 
2377 shared static this()
2378 {
2379 	version (VibeNoDefaultArgs) {}
2380 	else {
2381 		string disthost = s_distHost;
2382 		ushort distport = s_distPort;
2383 		import vibe.core.args : readOption;
2384 		readOption("disthost|d", () @trusted { return &disthost; } (), "Sets the name of a vibedist server to use for load balancing.");
2385 		readOption("distport", () @trusted { return &distport; } (), "Sets the port used for load balancing.");
2386 		setVibeDistHost(disthost, distport);
2387 	}
2388 }
2389 
2390 private string formatRFC822DateAlloc(IAllocator alloc, SysTime time)
2391 @safe {
2392 	auto app = AllocAppender!string(alloc);
2393 	writeRFC822DateTimeString(app, time);
2394 	return () @trusted { return app.data; } ();
2395 }
2396 
2397 version (VibeDebugCatchAll) private alias UncaughtException = Throwable;
2398 else private alias UncaughtException = Exception;