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