-
Notifications
You must be signed in to change notification settings - Fork 31
/
cgi.js
177 lines (150 loc) · 5.84 KB
/
cgi.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/**
* Module dependencies.
*/
var url = require('url');
var extend = require('extend');
var debug = require('debug')('cgi');
var spawn = require('child_process').spawn;
var CGIParser = require('./parser');
/**
* Module exports.
*/
module.exports = cgi;
/**
* Constants.
*/
var SERVER_SOFTWARE = 'Node/' + process.version;
var SERVER_PROTOCOL = 'HTTP/1.1';
var GATEWAY_INTERFACE = 'CGI/1.1';
function cgi(cgiBin, options) {
options = extend({}, cgi.DEFAULTS, options);
return function layer(req, res, next) {
if (!next) {
// define a default "next" handler if none was passed
next = function(err) {
debug('"next" called: %o', err);
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found\n");
};
}
if (!req.hasOwnProperty("uri")) { req.uri = url.parse(req.url); }
if (req.uri.pathname.substring(0, options.mountPoint.length) !== options.mountPoint) return next();
debug('handling HTTP request: %o', req.url);
var host = (req.headers.host || '').split(':');
var address = host[0];
var port = host[1];
if ((!address || !port) && typeof this.address == 'function') {
var serverAddress = this.address();
debug('server address and port: %o', serverAddress);
if (!address) address = serverAddress.address;
if (!port) port = serverAddress.port;
}
// Take environment variables from the current server process
var env = extend({}, process.env);
// Determine the correct PATH_INFO variable.
// It must be prepended with a "/" char as per:
// https://tools.ietf.org/html/rfc3875#section-4.1.5
var pathInfo = req.uri.pathname.substring(options.mountPoint.length);
if ('/' !== pathInfo[0]) pathInfo = '/' + pathInfo;
debug('calculated PATH_INFO variable: %o', pathInfo);
// These meta-variables below can be overwritten by a
// user's 'env' object in options
extend(env, {
GATEWAY_INTERFACE: GATEWAY_INTERFACE,
SCRIPT_NAME: options.mountPoint,
PATH_INFO: pathInfo,
SERVER_NAME: address || 'unknown',
SERVER_PORT: port || 80,
SERVER_PROTOCOL: SERVER_PROTOCOL,
SERVER_SOFTWARE: SERVER_SOFTWARE
});
// The client HTTP request headers are attached to the env as well,
// in the format: "User-Agent" -> "HTTP_USER_AGENT"
for (var header in req.headers) {
var name = 'HTTP_' + header.toUpperCase().replace(/-/g, '_');
env[name] = req.headers[header];
}
// Now add the user-specified env variables
if (options.env) extend(env, options.env);
// These final environment variables take precedence over user-specified ones.
env.REQUEST_METHOD = req.method;
env.QUERY_STRING = req.uri.query || '';
env.REMOTE_ADDR = req.connection.remoteAddress;
env.REMOTE_PORT = req.connection.remotePort;
if ('content-length' in req.headers) {
env.CONTENT_LENGTH = req.headers['content-length'];
}
if ('content-type' in req.headers) {
env.CONTENT_TYPE = req.headers['content-type'];
}
if ('authorization' in req.headers) {
var auth = req.headers.authorization.split(' ');
env.AUTH_TYPE = auth[0];
//var unbase = new Buffer(auth[1], 'base64').toString().split(':');
}
var opts = extend({}, options);
// Now we can spawn the CGI executable
debug('env: %o', env);
opts.env = env;
var cgiSpawn = spawn(cgiBin, opts.args, opts);
debug('cgi spawn (pid: %o)', cgiSpawn.pid);
// The request body is piped to 'stdin' of the CGI spawn
req.pipe(cgiSpawn.stdin);
// If `options.stderr` is set to a Stream instance, then re-emit the
// 'data' events onto the stream.
if (options.stderr) {
cgiSpawn.stderr.pipe(options.stderr);
}
// A proper CGI script is supposed to print headers to 'stdout'
// followed by a blank line, then a response body.
var cgiResult;
if (!options.nph) {
cgiResult = new CGIParser(cgiSpawn.stdout);
// When the blank line after the headers has been parsed, then
// the 'headers' event is emitted with a Headers instance.
cgiResult.on('headers', function(headers) {
headers.forEach(function(header) {
// Don't set the 'Status' header. It's special, and should be
// used to set the HTTP response code below.
if (header.key === 'Status') return;
res.setHeader(header.key, header.value);
});
// set the response status code
res.statusCode = parseInt(headers.status, 10) || 200;
// The response body is piped to the response body of the HTTP request
cgiResult.pipe(res);
});
} else {
// If it's an NPH script, then responsibility of the HTTP response is
// completely passed off to the child process.
cgiSpawn.stdout.pipe(res.connection);
}
cgiSpawn.on('exit', function(code, signal) {
debug('cgi spawn %o "exit" event (code %o) (signal %o)', cgiSpawn.pid, code, signal);
// TODO: react on a failure status code (dump stderr to the response?)
});
cgiSpawn.stdout.on('end', function () {
// clean up event listeners upon the "end" event
debug('cgi spawn %o stdout "end" event', cgiSpawn.pid);
if (cgiResult) {
cgiResult.cleanup();
}
//if (options.stderr) {
// cgiSpawn.stderr.unpipe(options.stderr);
//}
});
};
}
// The default config options to use for each `cgi()` call.
cgi.DEFAULTS = {
// The 'cgi' handler will take effect when the req.url begins with "mountPoint"
mountPoint: '/',
// Any additional variables to insert into the CGI script's Environment
env: {},
// Set to 'true' if the CGI script is an NPH script
nph: false,
// Set to a `Stream` instance if you want to log stderr of the CGI script somewhere
stderr: null,
// A list of arguments for the cgi bin to be used by spawn
args: []
};