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