1 /**
2  * Support for implementing SCGI application servers.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.net.http.scgi.app;
15 
16 import std.algorithm.searching : findSplit;
17 import std.conv : to;
18 import std.exception;
19 import std..string;
20 
21 import ae.net.asockets;
22 import ae.net.http.cgi.common;
23 import ae.net.http.cgi.script;
24 import ae.net.http.common;
25 import ae.sys.log;
26 import ae.utils.array;
27 
28 final class SCGIConnection
29 {
30 	IConnection connection;
31 	Logger log;
32 	bool nph;
33 
34 	this(IConnection connection)
35 	{
36 		this.connection = connection;
37 		connection.handleReadData = &onReadData;
38 	}
39 
40 	private Data buffer;
41 
42 	void onReadData(Data data)
43 	{
44 		buffer ~= data;
45 
46 		while (true)
47 			try
48 			{
49 				auto bufferStr = cast(char[])buffer.contents;
50 				auto colonIdx = bufferStr.indexOf(':');
51 				if (colonIdx < 0)
52 					return;
53 
54 				auto headerLenStr = bufferStr[0 .. colonIdx];
55 				auto headerLen = headerLenStr.to!size_t;
56 				auto headerEnd = headerLenStr.length + 1 /*:*/ + headerLen + 1 /*,*/;
57 				if (buffer.length < headerEnd)
58 					return;
59 				enforce(bufferStr[headerEnd - 1] == ',', "Expected ','");
60 
61 				auto headersStr = bufferStr[headerLenStr.length + 1 .. headerEnd - 1];
62 				enum CONTENT_LENGTH = "CONTENT_LENGTH";
63 				enforce(headersStr.startsWith(CONTENT_LENGTH ~ "\0"), "Expected first header to be " ~ CONTENT_LENGTH);
64 				auto contentLength = headersStr[CONTENT_LENGTH.length + 1 .. $].findSplit("\0")[0].to!size_t;
65 				if (buffer.length < headerEnd + contentLength)
66 					return;
67 
68 				// We now know we have all the data in the request
69 
70 				auto headers = parseHeaders(headersStr.idup);
71 				enforce(headers.get("SCGI", null) == "1", "Unknown SCGI version");
72 				CGIRequest request;
73 				request.vars = CGIVars.fromAA(headers);
74 				request.headers = CGIRequest.decodeHeaders(headers, request.vars.serverProtocol ? request.vars.serverProtocol : "HTTP");
75 				request.data = [buffer[headerEnd .. headerEnd + contentLength]];
76 				buffer = buffer[headerEnd + contentLength .. $];
77 				handleRequest(request);
78 			}
79 			catch (Exception e)
80 			{
81 				if (log) log("Error handling request: " ~ e.toString());
82 				connection.disconnect(e.msg);
83 				return;
84 			}
85 	}
86 
87 	static string[string] parseHeaders(string s)
88 	{
89 		string[string] headers;
90 		while (s.length)
91 		{
92 			auto name = s.skipUntil('\0').enforce("Unterminated header name");
93 			auto value = s.skipUntil('\0').enforce("Unterminated header value");
94 			headers[name] = value;
95 		}
96 		return headers;
97 	}
98 
99 	void sendResponse(HttpResponse r)
100 	{
101 		FastAppender!char headers;
102 		if (nph)
103 			writeNPHHeaders(r, headers);
104 		else
105 			writeCGIHeaders(r, headers);
106 		connection.send([Data(headers.get)] ~ r.data);
107 		connection.disconnect("Response sent");
108 	}
109 
110 	void delegate(ref CGIRequest) handleRequest;
111 }