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 <ae@cy.md>
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, DataVec;
24 import ae.utils.meta : getAttribute;
25 
26 /// Holds parsed CGI meta-variables.
27 struct CGIVars
28 {
29 	@("AUTH_TYPE"        ) string authType;         /// The CGI "AUTH_TYPE" meta-variable.
30 	@("CONTENT_LENGTH"   ) string contentLength;    /// The CGI "CONTENT_LENGTH" meta-variable.
31 	@("CONTENT_TYPE"     ) string contentType;      /// The CGI "CONTENT_TYPE" meta-variable.
32 	@("GATEWAY_INTERFACE") string gatewayInterface; /// The CGI "GATEWAY_INTERFACE" meta-variable.
33 	@("PATH_INFO"        ) string pathInfo;         /// The CGI "PATH_INFO" meta-variable.
34 	@("PATH_TRANSLATED"  ) string pathTranslated;   /// The CGI "PATH_TRANSLATED" meta-variable.
35 	@("QUERY_STRING"     ) string queryString;      /// The CGI "QUERY_STRING" meta-variable.
36 	@("REMOTE_ADDR"      ) string remoteAddr;       /// The CGI "REMOTE_ADDR" meta-variable.
37 	@("REMOTE_HOST"      ) string remoteHost;       /// The CGI "REMOTE_HOST" meta-variable.
38 	@("REMOTE_IDENT"     ) string remoteIdent;      /// The CGI "REMOTE_IDENT" meta-variable.
39 	@("REMOTE_USER"      ) string remoteUser;       /// The CGI "REMOTE_USER" meta-variable.
40 	@("REQUEST_METHOD"   ) string requestMethod;    /// The CGI "REQUEST_METHOD" meta-variable.
41 	@("SCRIPT_NAME"      ) string scriptName;       /// The CGI "SCRIPT_NAME" meta-variable.
42 	@("SERVER_NAME"      ) string serverName;       /// The CGI "SERVER_NAME" meta-variable.
43 	@("SERVER_PORT"      ) string serverPort;       /// The CGI "SERVER_PORT" meta-variable.
44 	@("SERVER_PROTOCOL"  ) string serverProtocol;   /// The CGI "SERVER_PROTOCOL" meta-variable.
45 	@("SERVER_SOFTWARE"  ) string serverSoftware;   /// The CGI "SERVER_SOFTWARE" meta-variable.
46 
47 	// SCGI vars:
48 	@("REQUEST_URI"      ) string requestUri;       /// The SCGI "REQUEST_URI" meta-variable.
49 	@("DOCUMENT_URI"     ) string documentUri;      /// The SCGI "DOCUMENT_URI" meta-variable.
50 	@("DOCUMENT_ROOT"    ) string documentRoot;     /// The SCGI "DOCUMENT_ROOT" meta-variable.
51 
52 	/// Parse from an environment block (represented as an associate array).
53 	static typeof(this) fromAA(string[string] env)
54 	{
55 		typeof(this) result;
56 		foreach (i, ref var; result.tupleof)
57 			var = env.get(getAttribute!(string, result.tupleof[i]), null);
58 		return result;
59 	}
60 }
61 
62 /// Holds a CGI request.
63 struct CGIRequest
64 {
65 	CGIVars vars;    /// CGI meta-variables.
66 	Headers headers; /// Request headers.
67 	DataVec data;    /// Request data.
68 
69 	/// Parse from an environment block (represented as an associate array).
70 	static typeof(this) fromAA(string[string] env)
71 	{
72 		typeof(this) result;
73 		result.vars = CGIVars.fromAA(env);
74 
75 		// Missing `include /etc/nginx/fastcgi_params;` in nginx?
76 		enforce(result.vars.gatewayInterface, "GATEWAY_INTERFACE not set");
77 
78 		enforce(result.vars.gatewayInterface == "CGI/1.1",
79 			"Unknown CGI version: " ~ result.vars.gatewayInterface);
80 
81 		result.headers = decodeHeaders(env, result.vars.serverProtocol);
82 
83 		return result;
84 	}
85 
86 	/// Extract request headers from an environment block (represented
87 	/// as an associate array).
88 	/// Params:
89 	///  env            = The environment block.
90 	///  serverProtocol = The protocol (as specified in
91 	///                   SERVER_PROTOCOL).
92 	static Headers decodeHeaders(string[string] env, string serverProtocol)
93 	{
94 		Headers headers;
95 		auto protocolPrefix = serverProtocol.findSplit("/")[0] ~ "_";
96 		foreach (name, value; env)
97 			if (name.skipOver(protocolPrefix))
98 				headers.add(name.replace("_", "-"), value);
99 		return headers;
100 	}
101 }
102 
103 /// Subclass of `HttpRequest` for HTTP requests received via CGI.
104 class CGIHttpRequest : HttpRequest
105 {
106 	CGIVars cgiVars; /// CGI meta-variables.
107 
108 	/// Construct the HTTP request from a CGI request.
109 	this(ref CGIRequest cgi)
110 	{
111 		cgiVars = cgi.vars;
112 		headers = cgi.headers;
113 		if (cgi.vars.requestUri)
114 			resource = cgi.vars.requestUri;
115 		else
116 		if (cgi.vars.documentUri)
117 			resource = cgi.vars.documentUri;
118 		else
119 			resource = cgi.vars.scriptName ~ cgi.vars.pathInfo;
120 		if (cgi.vars.queryString)
121 			queryString = cgi.vars.queryString;
122 
123 		if (cgi.vars.contentType)
124 			headers.require("Content-Type", cgi.vars.contentType);
125 		if (cgi.vars.serverName)
126 			headers.require("Host", cgi.vars.serverName);
127 		if (cgi.vars.serverPort)
128 			port = cgi.vars.serverPort.to!ushort;
129 		method = cgi.vars.requestMethod;
130 		protocol = cgi.vars.serverProtocol;
131 		data = cgi.data.dup;
132 	}
133 }