Skip to content

Commit 6f67edb

Browse files
committed
cleanup and add tests
1 parent 62cba18 commit 6f67edb

File tree

6 files changed

+199
-157
lines changed

6 files changed

+199
-157
lines changed

main.js

Lines changed: 145 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,159 @@
11
// License: MIT
22
// Copyright (c) 2014-2016 Martin K. Schröder <[email protected]>
33

4-
var url = require("url");
4+
var URL = require("url");
55
var child = require("child_process");
66
var path = require("path");
77
var fs = require("fs");
8+
var shell = require("shelljs");
89

9-
function runPHP(req, response, next, phpdir){
10-
var parts = url.parse(req.url);
11-
var query = parts.query;
12-
13-
var file = path.join(phpdir, parts.pathname);
14-
15-
// get file stats asynchronously
16-
fs.stat(file, function(err,stat){
17-
18-
if(err){ // file does not exist
19-
file = path.join(phpdir, "index.php");
20-
} else if(stat.isDirectory()){
21-
file = path.join(file, "index.php");
22-
}
10+
var PHP_CGI = shell.which('php-cgi');
2311

24-
var pathinfo = "";
25-
var i = req.url.indexOf(".php");
26-
if(i > 0) pathinfo = parts.pathname.substring(i+4);
27-
else pathinfo = parts.pathname;
12+
function findFile(url, phpdir, callback) {
13+
var file = path.join(phpdir, url.pathname);
2814

29-
var env = {
30-
SERVER_SIGNATURE: "NodeJS server at localhost",
31-
PATH_INFO: pathinfo, //The extra path information, as given in the requested URL. In fact, scripts can be accessed by their virtual path, followed by extra information at the end of this path. The extra information is sent in PATH_INFO.
32-
PATH_TRANSLATED: "", //The virtual-to-real mapped version of PATH_INFO.
33-
SCRIPT_NAME: parts.pathname, //The virtual path of the script being executed.
34-
SCRIPT_FILENAME: file,
35-
REQUEST_FILENAME: file, //The real path of the script being executed.
36-
SCRIPT_URI: req.url, //The full URL to the current object requested by the client.
37-
URL: req.url, //The full URI of the current request. It is made of the concatenation of SCRIPT_NAME and PATH_INFO (if available.)
38-
SCRIPT_URL: req.url,
39-
REQUEST_URI: req.url, //The original request URI sent by the client.
40-
REQUEST_METHOD: req.method, //The method used by the current request; usually set to GET or POST.
41-
QUERY_STRING: parts.query||"", //The information which follows the ? character in the requested URL.
42-
CONTENT_TYPE: req.get("Content-type")||"", //"multipart/form-data", //"application/x-www-form-urlencoded", //The MIME type of the request body; set only for POST or PUT requests.
43-
CONTENT_LENGTH: req.get("Content-Length") || 0, //The length in bytes of the request body; set only for POST or PUT requests.
44-
AUTH_TYPE: "", //The authentication type if the client has authenticated itself to access the script.
45-
AUTH_USER: "",
46-
REMOTE_USER: "", //The name of the user as issued by the client when authenticating itself to access the script.
47-
ALL_HTTP: Object.keys(req.headers).map(function(x){return "HTTP_"+x.toUpperCase().replace("-", "_")+": "+req.headers[x];}).reduce(function(a, b){return a+b+"\n";}, ""), //All HTTP headers sent by the client. Headers are separated by carriage return characters (ASCII 13 - \n) and each header name is prefixed by HTTP_, transformed to upper cases, and - characters it contains are replaced by _ characters.
48-
ALL_RAW: Object.keys(req.headers).map(function(x){return x+": "+req.headers[x];}).reduce(function(a, b){return a+b+"\n";}, ""), //All HTTP headers as sent by the client in raw form. No transformation on the header names is applied.
49-
SERVER_SOFTWARE: "NodeJS", //The web server's software identity.
50-
SERVER_NAME: "localhost", //The host name or the IP address of the computer running the web server as given in the requested URL.
51-
SERVER_ADDR: "127.0.0.1", //The IP address of the computer running the web server.
52-
SERVER_PORT: 8011, //The port to which the request was sent.
53-
GATEWAY_INTERFACE: "CGI/1.1", //The CGI Specification version supported by the web server; always set to CGI/1.1.
54-
SERVER_PROTOCOL: "", //The HTTP protocol version used by the current request.
55-
REMOTE_ADDR: req.ip||"", //The IP address of the computer that sent the request.
56-
REMOTE_PORT: "", //The port from which the request was sent.
57-
DOCUMENT_ROOT: "", //The absolute path of the web site files. It has the same value as Documents Path.
58-
INSTANCE_ID: "", //The numerical identifier of the host which served the request. On Abyss Web Server X1, it is always set to 1 since there is only a single host.
59-
APPL_MD_PATH: "", //The virtual path of the deepest alias which contains the request URI. If no alias contains the request URI, the variable is set to /.
60-
APPL_PHYSICAL_PATH: "", //The real path of the deepest alias which contains the request URI. If no alias contains the request URI, the variable is set to the same value as DOCUMENT_ROOT.
61-
IS_SUBREQ: "", //It is set to true if the current request is a subrequest, i.e. a request not directly invoked by a client. Otherwise, it is set to true. Subrequests are generated by the server for internal processing. XSSI includes for example result in subrequests.
62-
REDIRECT_STATUS: 1
63-
};
64-
65-
Object.keys(req.headers).map(function(x){return env["HTTP_"+x.toUpperCase().replace("-", "_")] = req.headers[x];});
66-
67-
if(/.*?\.php$/.test(file)){
68-
var res = "", err = "";
69-
70-
var php = child.spawn("php-cgi", [], {
71-
env: env
72-
});
73-
74-
//php.stdin.resume();
75-
//console.log(req.rawBody);
76-
//(new Stream(req.rawBody)).pipe(php.stdin);
77-
php.stdin.on("error", function(){});
78-
req.pipe(php.stdin); // pipe request stream directly into the php process
79-
req.resume();
80-
//php.stdin.write("\n");
81-
82-
//php.stdin.end();
83-
84-
php.stdout.on("data", function(data){
85-
//console.log(data.toString());
86-
res += data.toString();
87-
});
88-
php.stderr.on("data", function(data){
89-
err += err.toString();
90-
});
91-
php.on("error", function(err){
92-
console.error(err);
93-
});
94-
php.on("exit", function(){
95-
// extract headers
96-
php.stdin.end();
97-
98-
var lines = res.split("\r\n");
99-
var line = 0;
100-
var html = "";
101-
if(lines.length){
102-
do {
103-
var m = lines[line].split(": ");
104-
if(m[0] === "") break;
105-
106-
//console.log("HEADER: "+m[0]+": "+m[1]);
107-
if(m[0] == "Status"){
108-
response.statusCode = parseInt(m[1]);
109-
}
110-
if(m.length == 2){
111-
response.setHeader(m[0], m[1]);
112-
}
113-
line++;
114-
} while(lines[line] !== "");
115-
116-
html = lines.splice(line+1).join("\n");
117-
} else {
118-
html = res;
119-
}
120-
//console.log("STATUS: "+response.statusCode);
121-
//console.log(html);
122-
response.status(response.statusCode).send(html);
123-
response.end();
124-
});
125-
126-
} else {
127-
response.sendFile(file);
128-
//response.end();
129-
//next();
15+
fs.stat(file, function(err, stat) {
16+
17+
// file does not exist
18+
if (err || stat.isDirectory()) {
19+
file = path.join(err ? phpdir : file, "index.php");
20+
fs.exists(file, function(exists) {
21+
callback(exists && file);
22+
});
23+
}
24+
// file found
25+
else {
26+
callback(file);
13027
}
28+
13129
});
13230
}
133-
exports.cgi = function(phproot){
134-
return function(req, res, next){
135-
req.pause(); // stop stream until child-process is opened
136-
runPHP(req, res, next, phproot);
31+
32+
function runPHP(req, response, next, url, file) {
33+
var query = url.query;
34+
35+
var pathinfo = "";
36+
var i = req.url.indexOf(".php");
37+
if (i > 0) pathinfo = url.pathname.substring(i + 4);
38+
else pathinfo = url.pathname;
39+
40+
var env = {
41+
SERVER_SIGNATURE: "NodeJS server at localhost",
42+
PATH_INFO: pathinfo, //The extra path information, as given in the requested URL. In fact, scripts can be accessed by their virtual path, followed by extra information at the end of this path. The extra information is sent in PATH_INFO.
43+
PATH_TRANSLATED: "", //The virtual-to-real mapped version of PATH_INFO.
44+
SCRIPT_NAME: url.pathname, //The virtual path of the script being executed.
45+
SCRIPT_FILENAME: file,
46+
REQUEST_FILENAME: file, //The real path of the script being executed.
47+
SCRIPT_URI: req.url, //The full URL to the current object requested by the client.
48+
URL: req.url, //The full URI of the current request. It is made of the concatenation of SCRIPT_NAME and PATH_INFO (if available.)
49+
SCRIPT_URL: req.url,
50+
REQUEST_URI: req.url, //The original request URI sent by the client.
51+
REQUEST_METHOD: req.method, //The method used by the current request; usually set to GET or POST.
52+
QUERY_STRING: url.query || "", //The information which follows the ? character in the requested URL.
53+
CONTENT_TYPE: req.get("Content-Type") || "", //"multipart/form-data", //"application/x-www-form-urlencoded", //The MIME type of the request body; set only for POST or PUT requests.
54+
CONTENT_LENGTH: req.get("Content-Length") || 0, //The length in bytes of the request body; set only for POST or PUT requests.
55+
AUTH_TYPE: "", //The authentication type if the client has authenticated itself to access the script.
56+
AUTH_USER: "",
57+
REMOTE_USER: "", //The name of the user as issued by the client when authenticating itself to access the script.
58+
ALL_HTTP: Object.keys(req.headers).map(function(x) { return "HTTP_" + x.toUpperCase().replace("-", "_") + ": " + req.headers[x]; }).reduce(function(a, b) { return a + b + "\n"; }, ""), //All HTTP headers sent by the client. Headers are separated by carriage return characters (ASCII 13 - \n) and each header name is prefixed by HTTP_, transformed to upper cases, and - characters it contains are replaced by _ characters.
59+
ALL_RAW: Object.keys(req.headers).map(function(x) { return x + ": " + req.headers[x]; }).reduce(function(a, b) { return a + b + "\n"; }, ""), //All HTTP headers as sent by the client in raw form. No transformation on the header names is applied.
60+
SERVER_SOFTWARE: "NodeJS", //The web server's software identity.
61+
SERVER_NAME: "localhost", //The host name or the IP address of the computer running the web server as given in the requested URL.
62+
SERVER_ADDR: "127.0.0.1", //The IP address of the computer running the web server.
63+
SERVER_PORT: 8011, //The port to which the request was sent.
64+
GATEWAY_INTERFACE: "CGI/1.1", //The CGI Specification version supported by the web server; always set to CGI/1.1.
65+
SERVER_PROTOCOL: "", //The HTTP protocol version used by the current request.
66+
REMOTE_ADDR: req.ip || "", //The IP address of the computer that sent the request.
67+
REMOTE_PORT: "", //The port from which the request was sent.
68+
DOCUMENT_ROOT: "", //The absolute path of the web site files. It has the same value as Documents Path.
69+
INSTANCE_ID: "", //The numerical identifier of the host which served the request. On Abyss Web Server X1, it is always set to 1 since there is only a single host.
70+
APPL_MD_PATH: "", //The virtual path of the deepest alias which contains the request URI. If no alias contains the request URI, the variable is set to /.
71+
APPL_PHYSICAL_PATH: "", //The real path of the deepest alias which contains the request URI. If no alias contains the request URI, the variable is set to the same value as DOCUMENT_ROOT.
72+
IS_SUBREQ: "", //It is set to true if the current request is a subrequest, i.e. a request not directly invoked by a client. Otherwise, it is set to true. Subrequests are generated by the server for internal processing. XSSI includes for example result in subrequests.
73+
REDIRECT_STATUS: 1
74+
};
75+
76+
Object.keys(req.headers).map(function(x) { return env["HTTP_" + x.toUpperCase().replace("-", "_")] = req.headers[x]; });
77+
78+
if (/.*?\.php$/.test(file)) {
79+
var res = "", err = "";
80+
81+
var php = child.spawn(PHP_CGI, [], {
82+
env: env
83+
});
84+
85+
// php.stdin.resume();
86+
// console.log(req.rawBody);
87+
// (new Stream(req.rawBody)).pipe(php.stdin);
88+
php.stdin.on("error", function() { });
89+
req.pipe(php.stdin); // pipe request stream directly into the php process
90+
req.resume();
91+
// php.stdin.write("\n");
92+
93+
// php.stdin.end();
94+
95+
php.stdout.on("data", function(data) {
96+
// console.log(data.toString());
97+
res += data.toString();
98+
});
99+
php.stderr.on("data", function(data) {
100+
err += err.toString();
101+
});
102+
php.on("error", function(err) {
103+
console.error(err);
104+
});
105+
php.on("exit", function() {
106+
// extract headers
107+
php.stdin.end();
108+
109+
var lines = res.split("\r\n");
110+
var line = 0;
111+
var html = "";
112+
if (lines.length) {
113+
do {
114+
var m = lines[line].split(": ");
115+
if (m[0] === "") break;
116+
117+
// console.log("HEADER: "+m[0]+": "+m[1]);
118+
if (m[0] == "Status") {
119+
response.statusCode = parseInt(m[1]);
120+
}
121+
if (m.length == 2) {
122+
response.setHeader(m[0], m[1]);
123+
}
124+
line++;
125+
} while (lines[line] !== "");
126+
127+
html = lines.splice(line + 1).join("\n");
128+
} else {
129+
html = res;
130+
}
131+
// console.log("STATUS: "+response.statusCode);
132+
// console.log(html);
133+
response.status(response.statusCode).send(html);
134+
response.end();
135+
});
136+
137+
} else {
138+
response.sendFile(file);
139+
//response.end();
140+
//next();
141+
}
142+
}
143+
144+
exports.cgi = function(phproot) {
145+
return function(req, res, next) {
146+
req.pause(); // stop stream until child-process is opened
147+
148+
var url = URL.parse(req.url);
149+
findFile(url, phproot, function(file) {
150+
151+
if (file) {
152+
runPHP(req, res, next, url, file);
153+
} else {
154+
next();
155+
}
156+
157+
});
137158
}
138159
}

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@
1111
"main": "./main.js",
1212
"bin": {},
1313
"dependencies": {
14+
"shelljs": "^0.6.0"
1415
},
1516
"devDependencies": {
16-
"cluster": "*",
17-
"express": "*",
18-
"request": "*"
19-
},
17+
"express": "*",
18+
"mocha": "*",
19+
"supertest": "*"
20+
},
2021
"optionalDependencies": {},
2122
"engines": {
2223
"node": "*"
2324
},
2425
"scripts": {
25-
"test": "nodeunit tests"
26+
"test": "mocha tests"
2627
}
2728
}

tests/express.js

Lines changed: 0 additions & 27 deletions
This file was deleted.

tests/php/index.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
header('Content-Type: application/json');
3+
echo json_encode(array('$_SERVER' => $_SERVER));
4+
?>

tests/test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
var request = require('supertest');
2+
var assert = require('chai').assert;
3+
var express = require('express');
4+
var php = require('../main');
5+
6+
var app = express();
7+
8+
app.use('/', php.cgi(__dirname + '/php'));
9+
10+
describe('GET /', function() {
11+
it('should respond with /index.php', function(done) {
12+
request(app)
13+
.get('/')
14+
.set('Accept', 'application/json')
15+
.expect('Content-Type', /json/)
16+
.expect(200)
17+
.end(function(err, res) {
18+
if (err) {
19+
done(err);
20+
} else {
21+
assert.match(res.body.$_SERVER.SCRIPT_FILENAME, /index.php$/);
22+
}
23+
done();
24+
});
25+
})
26+
});
27+
28+
describe('Get /index.php', function() {
29+
it('should return a valid $_SERVER variable', function(done) {
30+
request(app)
31+
.get('/index.php')
32+
.set('Accept', 'application/json')
33+
.expect('Content-Type', /json/)
34+
.expect(200)
35+
.end(function(err, res) {
36+
if (err) {
37+
done(err);
38+
} else {
39+
assert.match(res.body.$_SERVER.REMOTE_ADDR, /127.0.0.1|::1/);
40+
}
41+
done();
42+
});
43+
})
44+
});

tests/test.php

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)