This repository has been archived by the owner on May 27, 2020. It is now read-only.
/
cgi.js
186 lines (159 loc) · 5.98 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
178
179
180
181
182
183
184
185
186
/**
* Module dependencies.
*/
var url = require('url');
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 = options || {};
options.__proto__ = cgi.DEFAULTS;
return function layer(req, res, next) {
if (!next) {
// define a default "next" handler if none was passed
next = function(err) {
debug('"next" called:', 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: %j', req.url);
var host = (req.headers.host || '').split(':');
var address = host[0];
var port = host[1];
if ((!address || !port) && typeof this.address == 'function') {
debug('determining server address and port via address()');
var serverAddress = this.address();
if (!address) address = serverAddress.address;
if (!port) port = serverAddress.port;
}
var env = {};
// Take environment variables from the current server process
extend(process.env, env);
// These meta-variables below can be overwritten by a
// user's 'env' object in options
extend({
GATEWAY_INTERFACE: GATEWAY_INTERFACE,
SCRIPT_NAME: options.mountPoint,
PATH_INFO: req.uri.pathname.substring(options.mountPoint.length),
SERVER_NAME: address || 'unknown',
SERVER_PORT: port || 80,
SERVER_PROTOCOL: SERVER_PROTOCOL,
SERVER_SOFTWARE: SERVER_SOFTWARE
}, env);
// 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
extend(options.env, 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(':');
}
// Now we can spawn the CGI executable
debug('env: %j', env);
var envDoc = {env: env};
if (options.cwd !== undefined) envDoc.cwd = options.cwd;
var cgiSpawn = spawn(cgiBin, options.args, envDoc);
debug('cgi spawn (pid: %d)', 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.
var onData;
if (options.stderr) {
onData = function (chunk) {
options.stderr.write(chunk);
};
cgiSpawn.stderr.on('data', onData);
}
// 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 %d "exit" event (code %s) (signal %s)', 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 %d stdout "end" event', cgiSpawn.pid);
if (cgiResult) {
cgiResult.cleanup();
}
if (onData) {
options.stderr.removeListener('data', onData);
}
});
};
}
// The default config options to use for each `cgi()` call.
cgi.DEFAULTS = {
// The 'cgi' process working directory. If undefined current path is used
cwd: undefined,
// 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: []
};
// TODO: Remove this function, and use the prototype of the env instead
// Copies the values from source onto destination
function extend(source, destination) {
for (var i in source) {
destination[i] = source[i];
}
return destination;
}