1 /**
2  * Common CGI declarations.
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.common;
15 
16 import std.algorithm.searching : skipOver, findSplit;
17 import std.array : replace;
18 import std.conv;
19 import std.exception : enforce;
20 
21 import ae.net.http.common : HttpRequest;
22 import ae.net.ietf.headers : Headers;
23 import ae.sys.data : Data;
24 import ae.utils.meta : getAttribute;
25 
26 /// CGI meta-variables
27 struct CGIVars
28 {
29 	@("AUTH_TYPE"        ) string authType;
30 	@("CONTENT_LENGTH"   ) string contentLength;
31 	@("CONTENT_TYPE"     ) string contentType;
32 	@("GATEWAY_INTERFACE") string gatewayInterface;
33 	@("PATH_INFO"        ) string pathInfo;
34 	@("PATH_TRANSLATED"  ) string pathTranslated;
35 	@("QUERY_STRING"     ) string queryString;
36 	@("REMOTE_ADDR"      ) string remoteAddr;
37 	@("REMOTE_HOST"      ) string remoteHost;
38 	@("REMOTE_IDENT"     ) string remoteIdent;
39 	@("REMOTE_USER"      ) string remoteUser;
40 	@("REQUEST_METHOD"   ) string requestMethod;
41 	@("SCRIPT_NAME"      ) string scriptName;
42 	@("SERVER_NAME"      ) string serverName;
43 	@("SERVER_PORT"      ) string serverPort;
44 	@("SERVER_PROTOCOL"  ) string serverProtocol;
45 	@("SERVER_SOFTWARE"  ) string serverSoftware;
46 
47 	// SCGI vars:
48 	@("REQUEST_URI"      ) string requestUri;
49 	@("DOCUMENT_URI"     ) string documentUri;
50 	@("DOCUMENT_ROOT"    ) string documentRoot;
51 
52 	static typeof(this) fromAA(string[string] env)
53 	{
54 		typeof(this) result;
55 		foreach (i, ref var; result.tupleof)
56 			var = env.get(getAttribute!(string, result.tupleof[i]), null);
57 		return result;
58 	}
59 }
60 
61 struct CGIRequest
62 {
63 	CGIVars vars;
64 	Headers headers;
65 	Data[] data;
66 
67 	static typeof(this) fromAA(string[string] env)
68 	{
69 		typeof(this) result;
70 		result.vars = CGIVars.fromAA(env);
71 
72 		// Missing `include /etc/nginx/fastcgi_params;` in nginx?
73 		enforce(result.vars.gatewayInterface, "GATEWAY_INTERFACE not set");
74 
75 		enforce(result.vars.gatewayInterface == "CGI/1.1",
76 			"Unknown CGI version: " ~ result.vars.gatewayInterface);
77 
78 		result.headers = decodeHeaders(env, result.vars.serverProtocol);
79 
80 		return result;
81 	}
82 
83 	static Headers decodeHeaders(string[string] env, string serverProtocol)
84 	{
85 		Headers headers;
86 		auto protocolPrefix = serverProtocol.findSplit("/")[0] ~ "_";
87 		foreach (name, value; env)
88 			if (name.skipOver(protocolPrefix))
89 				headers.add(name.replace("_", "-"), value);
90 		return headers;
91 	}
92 }
93 
94 class CGIHttpRequest : HttpRequest
95 {
96 	CGIVars cgiVars;
97 
98 	this(ref CGIRequest cgi)
99 	{
100 		cgiVars = cgi.vars;
101 		headers = cgi.headers;
102 		if (cgi.vars.requestUri)
103 			resource = cgi.vars.requestUri;
104 		else
105 		if (cgi.vars.documentUri)
106 			resource = cgi.vars.documentUri;
107 		else
108 			resource = cgi.vars.scriptName ~ cgi.vars.pathInfo;
109 		if (cgi.vars.queryString)
110 			queryString = cgi.vars.queryString;
111 
112 		if (cgi.vars.contentType)
113 			headers.require("Content-Type", cgi.vars.contentType);
114 		if (cgi.vars.serverName)
115 			headers.require("Host", cgi.vars.serverName);
116 		if (cgi.vars.serverPort)
117 			port = cgi.vars.serverPort.to!ushort;
118 		method = cgi.vars.requestMethod;
119 		protocol = cgi.vars.serverProtocol;
120 		data = cgi.data;
121 	}
122 }