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 <ae@cy.md> 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, DataVec; 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 = DataVec(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 /// Write the response headers from a HTTP response in CGI format. 76 void writeCGIHeaders(Writer)(HttpResponse r, ref Writer writer) 77 { 78 auto headers = r.headers; 79 if (r.status) 80 headers.require("Status", text(ushort(r.status), " ", r.statusMessage)); 81 82 static immutable string[] headerOrder = ["Location", "Content-Type", "Status"]; 83 foreach (name; headerOrder) 84 if (auto p = name in headers) 85 writer.put(name, ": ", *p, "\n"); 86 87 foreach (name, value; headers) 88 if (!headerOrder.canFind(name.normalizeHeaderName)) 89 writer.put(name, ": ", value, "\n"); 90 writer.put("\n"); 91 } 92 93 /// Write the response headers from a HTTP response in CGI NPH format. 94 void writeNPHHeaders(Writer)(HttpResponse r, ref Writer writer) 95 { 96 char[5] statusBuf; 97 writer.put("HTTP/1.0 ", toDec(ushort(r.status), statusBuf), " ", r.statusMessage, "\n"); 98 foreach (string header, string value; r.headers) 99 writer.put(header, ": ", value, "\n"); 100 writer.put("\n"); 101 } 102 103 /// Write a HTTP response in CGI format. 104 void writeCGIResponse(HttpResponse r) 105 { 106 auto writer = FileWriter(stdout); 107 writeCGIHeaders(r, writer); 108 109 foreach (datum; r.data) 110 stdout.rawWrite(datum.contents); 111 } 112 113 /// Write a HTTP response in CGI NPH format. 114 void writeNPHResponse(HttpResponse r) 115 { 116 auto writer = FileWriter(stdout); 117 writeNPHHeaders(r, writer); 118 119 foreach (datum; r.data) 120 stdout.rawWrite(datum.contents); 121 }