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 }