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