Skip to content

Commit 9c961c5

Browse files
committed
Certain server variables need to be passed in the SG(request_info) struct.
Rework how server variables are passed between contexts,to ensure that we can fill out SG(request_info) before calling php_request_startup. This fixes query string passing from JS to PHP.
1 parent 7fa1e96 commit 9c961c5

File tree

2 files changed

+85
-19
lines changed

2 files changed

+85
-19
lines changed

lib/index.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,9 @@ exports.request = function(options, cb) {
122122
source = 'require ' + addslashes(options.file) + ';';
123123
}
124124
var stream = new StreamWrapper(options.stream || process.stdout);
125-
var context = options.context;
126-
var args = options.args || [];
127-
// This function initializes $_SERVER inside the PHP request.
128-
var initServer = function(server, cb) {
129-
server.CONTEXT = context;
125+
var buildServerVars = function() {
126+
var server = Object.create(null);
127+
server.CONTEXT = options.context;
130128
if (options.file) {
131129
server.PHP_SELF = options.file;
132130
server.SCRIPT_FILENAME = options.file;
@@ -148,13 +146,13 @@ exports.request = function(options, cb) {
148146
server.GATEWAY_INTERFACE =
149147
'CGI/1.1';
150148
server.REQUEST_SCHEME =
151-
'http'; // XXX?
149+
'http'; // XXX Is it possible to determine this from req object?
152150
server.REQUEST_METHOD =
153151
options.request.method;
154152
server.REQUEST_URI =
155153
options.request.url;
156154
server.SERVER_ADMIN =
157-
'webmaster@localhost'; // XXX?
155+
'webmaster@localhost'; // Bogus value: user can override.
158156
var parsedUrl = url.parse(options.request.url);
159157
server.QUERY_STRING =
160158
(parsedUrl.search || '').replace(/^[?]/, '');
@@ -165,14 +163,26 @@ exports.request = function(options, cb) {
165163
server.SERVER_ADDR = socket.localAddress;
166164
server.SERVER_PORT = socket.localPort;
167165
}
166+
server.DOCUMENT_ROOT =
167+
'/var/www'; // Bogus value: user can override.
168+
}
169+
if (options.serverInitFunc) {
170+
options.serverInitFunc(server);
168171
}
169-
if (typeof (options.serverInitFunc) === 'function') {
170-
options.serverInitFunc(server, cb);
171-
} else {
172-
cb();
172+
return server;
173+
};
174+
var args = options.args || [];
175+
var serverVars = buildServerVars();
176+
// This function initializes $_SERVER inside the PHP request.
177+
var initServer = function(server, cb) {
178+
// This loop deliberately doesn't use "hasOwnProperty" in order to
179+
// allow serverVars to be inherited from an object with defaults.
180+
for (var f in serverVars) {
181+
server[f] = serverVars[f];
173182
}
183+
cb();
174184
};
175-
return request(source, stream, args, initServer).tap(function() {
185+
return request(source, stream, args, serverVars, initServer).tap(function() {
176186
// Ensure the stream is flushed before promise is resolved.
177187
return new Promise(function(resolve, reject) {
178188
stream.write(new Buffer(0), function(e) {

src/node_php_embed.cc

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
// Copyright (c) 2015 C. Scott Ananian <cscott@cscott.net>
66
#include "src/node_php_embed.h"
77

8+
#include <string>
9+
#include <unordered_map>
10+
811
#include "nan.h"
912

1013
extern "C" {
@@ -52,9 +55,11 @@ class node_php_embed::PhpRequestWorker : public AsyncMessageWorker {
5255
public:
5356
PhpRequestWorker(Nan::Callback *callback, v8::Local<v8::String> source,
5457
v8::Local<v8::Object> stream, v8::Local<v8::Array> args,
58+
v8::Local<v8::Object> server_vars,
5559
v8::Local<v8::Value> init_func)
5660
: AsyncMessageWorker(callback), result_(), stream_(), init_func_(),
57-
argc_(args->Length()), argv_(new char*[args->Length()]) {
61+
argc_(args->Length()), argv_(new char*[args->Length()]),
62+
server_vars_() {
5863
JsStartupMapper mapper(this);
5964
source_.Set(&mapper, source);
6065
stream_.Set(&mapper, stream);
@@ -66,6 +71,18 @@ class node_php_embed::PhpRequestWorker : public AsyncMessageWorker {
6671
.FromMaybe(static_cast< v8::Local<v8::Value> >(Nan::EmptyString())));
6772
argv_[i] = strdup(*s ? *s : "");
6873
}
74+
// Turn the server_vars object into a c++ string->string map.
75+
v8::Local<v8::Array> names = Nan::GetPropertyNames(server_vars)
76+
.FromMaybe(Nan::New<v8::Array>(0));
77+
for (uint32_t i = 0; i < names->Length(); i++) {
78+
v8::Local<v8::Value> key = Nan::Get(names, i).ToLocalChecked();
79+
v8::Local<v8::Value> value = Nan::Get(server_vars, key)
80+
.FromMaybe(static_cast<v8::Local<v8::Value> >(Nan::Undefined()));
81+
if (!value->IsString()) { continue; }
82+
Nan::Utf8String k(key), v(value);
83+
if (!(*k && *v)) { continue; }
84+
server_vars_.emplace(*k, *v);
85+
}
6986
}
7087
virtual ~PhpRequestWorker() {
7188
for (uint32_t i = 0; i < argc_; i++) {
@@ -81,8 +98,29 @@ class node_php_embed::PhpRequestWorker : public AsyncMessageWorker {
8198
// should go on `this`.
8299
void Execute(MapperChannel *channel TSRMLS_DC) override {
83100
TRACE("> PhpRequestWorker");
101+
// Certain fields in request_info need to be set up before
102+
// php_request_startup is invoked.
84103
SG(request_info).argc = argc_;
85104
SG(request_info).argv = argv_;
105+
#define SET_REQUEST_INFO(envvar, requestvar) \
106+
if (server_vars_.count(envvar)) { \
107+
SG(request_info).requestvar = estrdup((server_vars_[envvar]).c_str()); \
108+
} else { \
109+
SG(request_info).requestvar = nullptr; \
110+
}
111+
#define FREE_REQUEST_INFO(requestvar) /* for later */ \
112+
if (SG(request_info).requestvar) { \
113+
efree(const_cast<char*>(SG(request_info).requestvar)); \
114+
SG(request_info).requestvar = nullptr; \
115+
}
116+
SET_REQUEST_INFO("REQUEST_METHOD", request_method);
117+
SET_REQUEST_INFO("QUERY_STRING", query_string);
118+
SET_REQUEST_INFO("PATH_TRANSLATED", path_translated);
119+
SET_REQUEST_INFO("REQUEST_URI", request_uri);
120+
// xxx set proto_num ?
121+
// xxx set cookie_data ?
122+
server_vars_.clear(); // We don't need to keep this around any more.
123+
86124
if (php_request_startup(TSRMLS_C) == FAILURE) {
87125
Nan::ThrowError("can't create request");
88126
return;
@@ -132,9 +170,13 @@ class node_php_embed::PhpRequestWorker : public AsyncMessageWorker {
132170
NODE_PHP_EMBED_G(worker) = nullptr;
133171
NODE_PHP_EMBED_G(channel) = nullptr;
134172
TRACE("- request shutdown");
135-
php_request_shutdown(nullptr);
136173
SG(request_info).argc = 0;
137174
SG(request_info).argv = nullptr;
175+
FREE_REQUEST_INFO(request_method);
176+
FREE_REQUEST_INFO(query_string);
177+
FREE_REQUEST_INFO(path_translated);
178+
FREE_REQUEST_INFO(request_uri);
179+
php_request_shutdown(nullptr);
138180
TRACE("< PhpRequestWorker");
139181
}
140182
// Executed when the async work is complete.
@@ -158,6 +200,7 @@ class node_php_embed::PhpRequestWorker : public AsyncMessageWorker {
158200
Value init_func_;
159201
uint32_t argc_;
160202
char **argv_;
203+
std::unordered_map<std::string, std::string> server_vars_;
161204
};
162205

163206
/* PHP extension metadata */
@@ -314,21 +357,25 @@ NAN_METHOD(request) {
314357
if (!info[2]->IsArray()) {
315358
return Nan::ThrowTypeError("argument array expected");
316359
}
317-
if (!info[3]->IsFunction()) {
318-
return Nan::ThrowTypeError("init function expected");
360+
if (!info[3]->IsObject()) {
361+
return Nan::ThrowTypeError("server vars object expected");
319362
}
320363
if (!info[4]->IsFunction()) {
364+
return Nan::ThrowTypeError("init function expected");
365+
}
366+
if (!info[5]->IsFunction()) {
321367
return Nan::ThrowTypeError("callback expected");
322368
}
323369
v8::Local<v8::String> source = info[0].As<v8::String>();
324370
v8::Local<v8::Object> stream = info[1].As<v8::Object>();
325371
v8::Local<v8::Array> args = info[2].As<v8::Array>();
326-
v8::Local<v8::Value> init_func = info[3];
327-
Nan::Callback *callback = new Nan::Callback(info[4].As<v8::Function>());
372+
v8::Local<v8::Object> server_vars = info[3].As<v8::Object>();
373+
v8::Local<v8::Value> init_func = info[4];
374+
Nan::Callback *callback = new Nan::Callback(info[5].As<v8::Function>());
328375

329376
node_php_embed_ensure_init();
330377
Nan::AsyncQueueWorker(new PhpRequestWorker(callback, source, stream,
331-
args, init_func));
378+
args, server_vars, init_func));
332379
TRACE("<");
333380
}
334381

@@ -395,6 +442,15 @@ static void node_php_embed_ensure_init(void) {
395442
// Shutdown the initially-created request; we'll create our own request
396443
// objects inside PhpRequestWorker.
397444
php_request_shutdown(nullptr);
445+
#define CHECK_NULL(requestvar) \
446+
if (SG(request_info).requestvar) { \
447+
NPE_ERROR("OOPS! " #requestvar " is set!"); \
448+
SG(request_info).requestvar = nullptr; \
449+
}
450+
CHECK_NULL(request_method);
451+
CHECK_NULL(query_string);
452+
CHECK_NULL(path_translated);
453+
CHECK_NULL(request_uri);
398454
node::AtExit(ModuleShutdown, nullptr);
399455
TRACE("<");
400456
}

0 commit comments

Comments
 (0)