1 module dast.fcgi;
2 
3 // dfmt off
4 import
5 	dast.map,
6 	std.array,
7 	std.socket,
8 	std.string;
9 
10 size_t align8(size_t n) => (n + 7) & ~7;
11 
12 size_t intToStr(char* buf, size_t value) {
13 	char* p = buf;
14 	for (;;) {
15 		*(p++) = (value % 10) ^ '0';
16 		if (value < 10)
17 			break;
18 		value /= 10;
19 	}
20 	for(char* i = buf, j = p - 1; i < j; i++, j--) {
21 		char t = *i;
22 		*i = *j;
23 		*j = t;
24 	}
25 	return p - buf;
26 }
27 
28 unittest {
29 	char[10] buf = void;
30 	auto str = buf[];
31 	assert(intToStr(buf.ptr, 0));
32 	assert(str[0..1] == "0", str);
33 	assert(intToStr(buf.ptr, 255));
34 	assert(str[0..3] == "255", str);
35 	assert(intToStr(buf.ptr, 20170), str);
36 	assert(str[0..5] == "20170", str);
37 	assert(intToStr(buf.ptr, 4294967295), str);
38 	assert(str == "4294967295", str);
39 }
40 
41 enum {
42 	FCGI_LISTENSOCK_FILENO = 0,
43 	FCGI_KEEP_CONN = 1,
44 	FCGI_MAX_LENGTH = 0xffff,
45 	FCGI_HEADER_LEN = 8,
46 	FCGI_VERSION_1 = 1,
47 	FCGI_NULL_REQUEST_ID = 0
48 }
49 
50 enum Role {
51 	unknown,
52 	responder	= 1,
53 	authorizer	= 2,
54 	filter 		= 3
55 }
56 
57 enum RequestType {
58 	begin     = 1,
59 	Abort     = 2,
60 	End       = 3,
61 	Params    = 4,
62 	Stdin     = 5,
63 	Stdout    = 6,
64 	Stderr    = 7,
65 	Data      = 8,
66 	GetValues = 9,
67 	GetValuesResult = 10,
68 	UnknownType     = 11
69 }
70 
71 struct Header {
72 	ubyte
73 		ver,
74 		type,
75 		requestIdB1,
76 		requestIdB0,
77 		contentLengthB1,
78 		contentLengthB0,
79 		paddingLength,
80 		reserved;
81 
82 	this(RequestType reqType, size_t contentLength, int requestId, ubyte paddingLen)
83 	in (contentLength <= FCGI_MAX_LENGTH) {
84 		ver = FCGI_VERSION_1;
85 		type             = cast(ubyte) reqType;
86 		requestIdB1      = (requestId >> 8) & 0xff;
87 		requestIdB0      = (requestId     ) & 0xff;
88 		contentLengthB1  = (contentLength >> 8) & 0xff;
89 		contentLengthB0  = (contentLength     ) & 0xff;
90 		paddingLength    = paddingLen;
91 		//reserved         = 0;
92 	}
93 }
94 
95 struct BeginRequestBody {
96 	ubyte roleB1, roleB0,
97 		  flags;
98 	ubyte[5] reserved;
99 }
100 
101 // dfmt on
102 
103 struct EndRequestBody {
104 	int appStatus;
105 	ubyte protocolStatus;
106 	ubyte[3] reserved;
107 }
108 
109 class Request {
110 	enum bufferLen = 4 << 10;
111 	char[bufferLen] buffer = void;
112 	size_t contentLength;
113 	Map params;
114 
115 	protected Socket sock;
116 	// dfmt off
117 	ubyte	paddingLength;
118 	size_t	next,
119 			contentStop,
120 			stop;
121 	int requestId;
122 	protected bool* ipcSockClosed;
123 	// dfmt on
124 
125 	@property socket() => sock;
126 
127 	ubyte initialize(Socket socket, bool* sockClosed) {
128 		params.data.clear();
129 		sock = socket;
130 		ipcSockClosed = sockClosed;
131 		next = 0;
132 		stop = 0;
133 		contentStop = 0;
134 		fillBuffer();
135 		return processProtocol();
136 	}
137 
138 	protected bool fillBuffer() {
139 		if (next == stop) {
140 			auto readn = sock.receive(buffer[next .. $]);
141 			if (readn == Socket.ERROR)
142 				return false;
143 			if (readn == 0) {
144 				sock.close();
145 				*ipcSockClosed = true;
146 				return false;
147 			}
148 			stop += readn;
149 		}
150 		return true;
151 	}
152 
153 	protected ubyte processProtocol() {
154 		ubyte keepConnection;
155 		loop: for (;;) {
156 			// process Header
157 			if (next == contentStop)
158 				fillBuffer();
159 			auto header = cast(Header*)(buffer.ptr + next);
160 			next += Header.sizeof;
161 
162 			requestId = (header.requestIdB1 << 8) | header.requestIdB0;
163 			contentLength = (header.contentLengthB1 << 8) | header.contentLengthB0;
164 
165 			contentStop = next + contentLength;
166 			paddingLength = header.paddingLength;
167 			// process Body
168 			switch (header.type) {
169 			case RequestType.begin:
170 				auto rbody = cast(BeginRequestBody*)(buffer.ptr + next);
171 				next += BeginRequestBody.sizeof;
172 				keepConnection = rbody.flags & FCGI_KEEP_CONN;
173 				int role = rbody.roleB1 << 8 | rbody.roleB0;
174 				switch (role) {
175 				case Role.responder:
176 					params.FCGI_ROLE = "RESPONDER";
177 					break;
178 				case Role.authorizer:
179 					params.FCGI_ROLE = "AUTHORIZER";
180 					break;
181 				case Role.filter:
182 					params.FCGI_ROLE = "FILTER";
183 					break;
184 				default:
185 					params.FCGI_ROLE = "UNKNOWN";
186 				}
187 				break;
188 			case RequestType.Params:
189 				while (next < contentStop) {
190 					size_t nameLen = (buffer[next] & 0x80) ?
191 						((buffer[next++] & 0x7f) << 24)
192 						| buffer[next++] << 16
193 						| buffer[next++] << 8
194 						| buffer[next++] : buffer[next++],
195 
196 						valLen = (buffer[next] & 0x80) ?
197 						((buffer[next++] & 0x7f) << 24)
198 						| buffer[next++] << 16
199 						| buffer[next++] << 8
200 						| buffer[next++] : buffer[next++];
201 
202 					auto name = buffer[next .. next + nameLen].idup;
203 					next += nameLen;
204 					params[name] = buffer[next .. next + valLen].idup;
205 					next += valLen;
206 				}
207 				next += header.paddingLength;
208 				goto default;
209 			default:
210 				break loop;
211 			}
212 		}
213 		return keepConnection;
214 	}
215 
216 	int read(void[] buf) {
217 		if (next >= contentStop) {
218 			fillBuffer;
219 			processProtocol;
220 		}
221 		int len = cast(int)(contentStop - next);
222 		if (buf.length < len)
223 			len = cast(int)buf.length;
224 		buf[0 .. len] = buffer[next .. len + next];
225 		next += len;
226 		return len;
227 	}
228 }
229 
230 class Response {
231 	enum {
232 		bufferLen = 1024,
233 		maxHeaderLen = 1024
234 	}
235 	bool headerSent;
236 
237 	protected {
238 		import tame.buffer : Buffer = FixedBuffer;
239 
240 		bool* ipcSockClosed;
241 	}
242 	Buffer!maxHeaderLen head;
243 	Buffer!bufferLen buf;
244 
245 	this() {
246 		head = typeof(head)(&throwErr!"Headers");
247 		buf = typeof(buf)(&send);
248 	}
249 
250 	int requestId;
251 	ubyte keepConnection;
252 	protected Socket sock;
253 
254 	void initialize(Socket socket, bool* sockClosed, int reqId) pure @nogc nothrow @safe {
255 		sock = socket;
256 		ipcSockClosed = sockClosed;
257 		requestId = reqId;
258 		headerSent = false;
259 		head.clear();
260 		buf.clear();
261 	}
262 
263 	void writeHeader(in char[] header) {
264 		head ~= header;
265 		head ~= "\r\n";
266 	}
267 
268 	void write(in char[] data) {
269 		buf ~= data;
270 	}
271 
272 	alias put = write;
273 
274 	void flush() {
275 		buf.flush();
276 	}
277 
278 	void send(in char[] s) {
279 		if (!headerSent) {
280 			if (!head.length)
281 				writeHeader("Content-Type: text/html");
282 			head ~= "\r\n";
283 			putstr(head.data);
284 			head.clear();
285 			headerSent = true;
286 		}
287 		putstr(s);
288 	}
289 
290 	void finish() {
291 		buf.flush();
292 		auto endHeader = Header(RequestType.End, EndRequestBody.sizeof, requestId, 0);
293 		EndRequestBody endBody;
294 		buf ~= endHeader;
295 		buf ~= endBody;
296 		sock.send(buf.data);
297 		if (!keepConnection) {
298 			sock.close();
299 			*ipcSockClosed = true;
300 		}
301 	}
302 
303 	protected void throwErr(string obj)(in char[]) {
304 		throw new Exception(obj ~ " too long");
305 	}
306 
307 	protected int putstr(in char[] str) {
308 		size_t contentLength = str.length,
309 		alignLength = align8(contentLength);
310 		auto header = Header(RequestType.Stdout, contentLength, requestId, cast(ubyte)(
311 				alignLength - contentLength));
312 		return cast(int)(sock.send(
313 				(cast(char*)&header)[0 .. Header.sizeof]) +
314 				sock.send(str.ptr[0 .. alignLength]));
315 	}
316 }
317 
318 class FCGIServer {
319 	import core.thread;
320 	import std.concurrency;
321 
322 	alias Handler = void delegate(Request req, Response resp);
323 	protected Handler handler;
324 	void delegate(Exception e) onError;
325 	bool running;
326 	Socket listener;
327 	Socket sock;
328 	alias ipcSockClosed = running;
329 	protected Tid mainTid;
330 	Thread[] threads;
331 
332 	bool accept() {
333 		if (ipcSockClosed)
334 			synchronized (listener) {
335 				try
336 					sock = listener.accept();
337 				catch (SocketAcceptException)
338 					return false;
339 				ipcSockClosed = false;
340 			}
341 
342 		return true;
343 	}
344 
345 	this(Handler handler) {
346 		this.handler = handler;
347 	}
348 
349 	this(void function(Request req, Response resp) func) {
350 		import std.functional;
351 
352 		handler = toDelegate(func);
353 	}
354 
355 	void mainLoop() {
356 		scope req = new Request;
357 		scope resp = new Response;
358 
359 		while (accept()) {
360 			resp.keepConnection = req.initialize(sock, &ipcSockClosed);
361 			resp.initialize(sock, &ipcSockClosed, req.requestId);
362 			try
363 				handler(req, resp);
364 			catch (Exception e) {
365 				onError(e);
366 				resp.writeHeader("Status: 500");
367 				resp << e.toString;
368 			}
369 			resp.finish();
370 		}
371 	}
372 
373 	void start(ushort port = 9001, uint maxThread = 1) {
374 		if (threads.length)
375 			return;
376 
377 		listener = new TcpSocket;
378 		listener.bind(new InternetAddress(INADDR_LOOPBACK, port));
379 		listener.listen(128);
380 		running = true;
381 		mainTid = thisTid;
382 		if (!maxThread)
383 			return mainLoop();
384 
385 		threads = uninitializedArray!(Thread[])(maxThread);
386 		foreach (ref thread; threads) {
387 			thread = new Thread(&mainLoop);
388 			thread.start();
389 		}
390 	}
391 }