1 /**
2  * Support for implementing CGI scripts.
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.cgi.script;
15 
16 import core.runtime : Runtime;
17 
18 import std.algorithm.searching : startsWith, canFind;
19 import std.conv : to, text;
20 import std.exception : enforce;
21 import std.path : baseName;
22 import std.process : environment;
23 import std.stdio : stdin, stdout, File;
24 
25 import ae.net.http.cgi.common;
26 import ae.net.http.common;
27 import ae.net.ietf.headers : Headers, normalizeHeaderName;
28 import ae.sys.data : Data;
29 import ae.sys.file : readExactly;
30 import ae.utils.text.ascii : toDec;
31 
32 /// Return true if the current process was invoked as a CGI script.
33 bool inCGI()
34 {
35 	return !!environment.get("GATEWAY_INTERFACE", null);
36 }
37 
38 /// Return true if it seems likely that we are being invoked as an NPH
39 /// (non-parsed headers) script.
40 bool isNPH()
41 {
42 	// https://www.htmlhelp.com/faq/cgifaq.2.html#8
43 	return Runtime.args[0].baseName.startsWith("nph-");
44 }
45 
46 /// Load the CGI request from the environment / standard input.
47 CGIRequest readCGIRequest(
48 	string[string] env = environment.toAA(),
49 	File input = stdin,
50 )
51 {
52 	auto request = CGIRequest.fromAA(env);
53 
54 	if (request.vars.contentLength)
55 	{
56 		auto contentLength = request.vars.contentLength.to!size_t;
57 		if (contentLength)
58 		{
59 			auto data = Data(contentLength);
60 			input.readExactly(cast(ubyte[])data.contents)
61 				.enforce("EOF while reading content data");
62 			request.data = [data];
63 		}
64 	}
65 
66 	return request;
67 }
68 
69 private struct FileWriter
70 {
71 	File f;
72 	void put(T...)(auto ref T args) { f.write(args); }
73 }
74 
75 void writeCGIHeaders(Writer)(HttpResponse r, ref Writer writer)
76 {
77 	auto headers = r.headers;
78 	if (r.status)
79 		headers.require("Status", text(ushort(r.status), " ", r.statusMessage));
80 
81 	static immutable string[] headerOrder = ["Location", "Content-Type", "Status"];
82 	foreach (name; headerOrder)
83 		if (auto p = name in headers)
84 			writer.put(name, ": ", *p, "\n");
85 
86 	foreach (name, value; headers)
87 		if (!headerOrder.canFind(name.normalizeHeaderName))
88 			writer.put(name, ": ", value, "\n");
89 	writer.put("\n");
90 }
91 
92 void writeNPHHeaders(Writer)(HttpResponse r, ref Writer writer)
93 {
94 	char[5] statusBuf;
95 	writer.put("HTTP/1.0 ", toDec(ushort(r.status), statusBuf), " ", r.statusMessage, "\n");
96 	foreach (string header, string value; r.headers)
97 		writer.put(header, ": ", value, "\n");
98 	writer.put("\n");
99 }
100 
101 void writeCGIResponse(HttpResponse r)
102 {
103 	auto writer = FileWriter(stdout);
104 	writeCGIHeaders(r, writer);
105 
106 	foreach (datum; r.data)
107 		stdout.rawWrite(datum.contents);
108 }
109 
110 void writeNPHResponse(HttpResponse r)
111 {
112 	auto writer = FileWriter(stdout);
113 	writeNPHHeaders(r, writer);
114 
115 	foreach (datum; r.data)
116 		stdout.rawWrite(datum.contents);
117 }