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