nxt_php_sapi.c (1874:3d76ec426540) nxt_php_sapi.c (1980:43553aa72111)
1/*
2 * Copyright (C) Max Romanov
3 * Copyright (C) Valentin V. Bartenev
4 * Copyright (C) NGINX, Inc.
5 */
6
7#include "php.h"
8#include "SAPI.h"
9#include "php_main.h"
10#include "php_variables.h"
11#include "ext/standard/php_standard.h"
12
13#include <nxt_main.h>
14#include <nxt_router.h>
15#include <nxt_unit.h>
16#include <nxt_unit_request.h>
17
18
19#if PHP_VERSION_ID >= 50400
20#define NXT_HAVE_PHP_IGNORE_CWD 1
21#endif
22
23#if PHP_VERSION_ID >= 70100
24#define NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE 1
25#else
26#define NXT_HAVE_PHP_INTERRUPTS 1
27#endif
28
29#if PHP_VERSION_ID >= 70000
30#define NXT_PHP7 1
31#endif
32#if PHP_VERSION_ID >= 80000
33#define NXT_PHP8 1
34#endif
35
36/* PHP 8 */
37#ifndef TSRMLS_CC
38#define TSRMLS_CC
39#define TSRMLS_DC
40#define TSRMLS_D void
41#define TSRMLS_C
42#endif
43
44
45typedef struct {
46 nxt_str_t root;
47 nxt_str_t index;
48 nxt_str_t script_name;
49 nxt_str_t script_dirname;
50 nxt_str_t script_filename;
51} nxt_php_target_t;
52
53
54typedef struct {
55 char *cookie;
56 nxt_str_t *root;
57 nxt_str_t *index;
58 nxt_str_t path_info;
59 nxt_str_t script_name;
60 nxt_str_t script_filename;
61 nxt_str_t script_dirname;
62 nxt_unit_request_info_t *req;
63
64 uint8_t chdir; /* 1 bit */
65} nxt_php_run_ctx_t;
66
67
68#if NXT_PHP8
69typedef int (*nxt_php_disable_t)(const char *p, size_t size);
70#elif NXT_PHP7
71typedef int (*nxt_php_disable_t)(char *p, size_t size);
72#else
73typedef int (*nxt_php_disable_t)(char *p, uint TSRMLS_DC);
74#endif
75
76#if PHP_VERSION_ID < 70200
77typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS);
78#endif
79
80
81static nxt_int_t nxt_php_setup(nxt_task_t *task, nxt_process_t *process,
82 nxt_common_app_conf_t *conf);
83static nxt_int_t nxt_php_start(nxt_task_t *task, nxt_process_data_t *data);
84static nxt_int_t nxt_php_set_target(nxt_task_t *task, nxt_php_target_t *target,
85 nxt_conf_value_t *conf);
86static nxt_int_t nxt_php_set_ini_path(nxt_task_t *task, nxt_str_t *path,
87 char *workdir);
88static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options,
89 int type);
90static nxt_int_t nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value,
91 int type);
92#ifdef NXT_PHP8
93static void nxt_php_disable_functions(nxt_str_t *str);
94#endif
95static void nxt_php_disable(nxt_task_t *task, const char *type,
96 nxt_str_t *value, char **ptr, nxt_php_disable_t disable);
97
98static nxt_int_t nxt_php_dirname(const nxt_str_t *file, nxt_str_t *dir);
99static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t);
100static void nxt_php_str_trim_lead(nxt_str_t *str, u_char t);
101nxt_inline u_char *nxt_realpath(const void *c);
102
103static void nxt_php_request_handler(nxt_unit_request_info_t *req);
104static void nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx,
105 nxt_unit_request_t *r);
106static void nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r);
107nxt_inline void nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, u_char *dir);
108
109static int nxt_php_startup(sapi_module_struct *sapi_module);
110static int nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC);
111static void *nxt_php_hash_str_find_ptr(const HashTable *ht,
112 const nxt_str_t *str);
113static char *nxt_php_read_cookies(TSRMLS_D);
114static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name,
115 nxt_unit_sptr_t *v, uint32_t len, zval *track_vars_array TSRMLS_DC);
116nxt_inline void nxt_php_set_str(nxt_unit_request_info_t *req, const char *name,
117 nxt_str_t *s, zval *track_vars_array TSRMLS_DC);
118static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
119 const char *str, uint32_t len, zval *track_vars_array TSRMLS_DC);
120static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC);
121#if NXT_PHP8
122static void nxt_php_log_message(const char *message, int syslog_type_int);
123#else
124#ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
125static void nxt_php_log_message(char *message, int syslog_type_int);
126#else
127static void nxt_php_log_message(char *message TSRMLS_DC);
128#endif
129#endif
130
131#ifdef NXT_PHP7
132static size_t nxt_php_unbuffered_write(const char *str,
133 size_t str_length TSRMLS_DC);
134static size_t nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC);
135#else
136static int nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC);
137static int nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC);
138#endif
139
140
141#ifdef NXT_PHP7
142#if PHP_VERSION_ID < 70200
143ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fastcgi_finish_request, 0, 0,
144 _IS_BOOL, NULL, 0)
145#else
146ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fastcgi_finish_request, 0, 0,
147 _IS_BOOL, 0)
148#endif
149#else /* PHP5 */
150ZEND_BEGIN_ARG_INFO_EX(arginfo_fastcgi_finish_request, 0, 0, 0)
151#endif
152ZEND_END_ARG_INFO()
153
154ZEND_FUNCTION(fastcgi_finish_request);
155
156PHP_MINIT_FUNCTION(nxt_php_ext);
157ZEND_NAMED_FUNCTION(nxt_php_chdir);
158
159
160static const zend_function_entry nxt_php_ext_functions[] = {
161 ZEND_FE(fastcgi_finish_request, arginfo_fastcgi_finish_request)
162 ZEND_FE_END
163};
164
165
166zif_handler nxt_php_chdir_handler;
167zend_auto_global *nxt_php_server_ag;
168
169
170static zend_module_entry nxt_php_unit_module = {
171 STANDARD_MODULE_HEADER,
172 "unit",
173 nxt_php_ext_functions, /* function table */
174 PHP_MINIT(nxt_php_ext), /* initialization */
175 NULL, /* shutdown */
176 NULL, /* request initialization */
177 NULL, /* request shutdown */
178 NULL, /* information */
179 NXT_VERSION,
180 STANDARD_MODULE_PROPERTIES
181};
182
183
184PHP_MINIT_FUNCTION(nxt_php_ext)
185{
186 zend_function *func;
187
188 static const nxt_str_t chdir = nxt_string("chdir");
189
190 func = nxt_php_hash_str_find_ptr(CG(function_table), &chdir);
191 if (nxt_slow_path(func == NULL)) {
192 return FAILURE;
193 }
194
195 nxt_php_chdir_handler = func->internal_function.handler;
196 func->internal_function.handler = nxt_php_chdir;
197
198 return SUCCESS;
199}
200
201
202ZEND_NAMED_FUNCTION(nxt_php_chdir)
203{
204 nxt_php_run_ctx_t *ctx;
205
206 ctx = SG(server_context);
207 ctx->chdir = 1;
208
209 nxt_php_chdir_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
210}
211
212
213PHP_FUNCTION(fastcgi_finish_request)
214{
215 zend_auto_global *ag;
216 nxt_php_run_ctx_t *ctx;
217
218 if (nxt_slow_path(zend_parse_parameters_none() == FAILURE)) {
219#ifdef NXT_PHP8
220 RETURN_THROWS();
221#else
222 return;
223#endif
224 }
225
226 ctx = SG(server_context);
227
228 if (nxt_slow_path(ctx->req == NULL)) {
229 RETURN_FALSE;
230 }
231
232#ifdef NXT_PHP7
233 php_output_end_all();
234 php_header();
235#else
236#ifdef PHP_OUTPUT_NEWAPI
237 php_output_end_all(TSRMLS_C);
238#else
239 php_end_ob_buffers(1 TSRMLS_CC);
240#endif
241
242 php_header(TSRMLS_C);
243#endif
244
245 ag = nxt_php_server_ag;
246
247 if (ag->armed) {
248#ifdef NXT_PHP7
249 ag->armed = ag->auto_global_callback(ag->name);
250#else
251 ag->armed = ag->auto_global_callback(ag->name, ag->name_len TSRMLS_CC);
252#endif
253 }
254
255 nxt_unit_request_done(ctx->req, NXT_UNIT_OK);
256 ctx->req = NULL;
257
258 PG(connection_status) = PHP_CONNECTION_ABORTED;
259#ifdef NXT_PHP7
260 php_output_set_status(PHP_OUTPUT_DISABLED);
261#else
262#ifdef PHP_OUTPUT_NEWAPI
263 php_output_set_status(PHP_OUTPUT_DISABLED TSRMLS_CC);
264#else
265 php_output_set_status(0 TSRMLS_CC);
266#endif
267#endif
268
269 RETURN_TRUE;
270}
271
272
273static sapi_module_struct nxt_php_sapi_module =
274{
275 (char *) "cli-server",
276 (char *) "unit",
277
278 nxt_php_startup, /* startup */
279 php_module_shutdown_wrapper, /* shutdown */
280
281 NULL, /* activate */
282 NULL, /* deactivate */
283
284 nxt_php_unbuffered_write, /* unbuffered write */
285 NULL, /* flush */
286 NULL, /* get uid */
287 NULL, /* getenv */
288
289 php_error, /* error handler */
290
291 NULL, /* header handler */
292 nxt_php_send_headers, /* send headers handler */
293 NULL, /* send header handler */
294
295 nxt_php_read_post, /* read POST data */
296 nxt_php_read_cookies, /* read Cookies */
297
298 nxt_php_register_variables, /* register server variables */
299 nxt_php_log_message, /* log message */
300 NULL, /* get request time */
301 NULL, /* terminate process */
302
303 NULL, /* php_ini_path_override */
304#ifdef NXT_HAVE_PHP_INTERRUPTS
305 NULL, /* block_interruptions */
306 NULL, /* unblock_interruptions */
307#endif
308 NULL, /* default_post_reader */
309 NULL, /* treat_data */
310 NULL, /* executable_location */
311
312 0, /* php_ini_ignore */
313#ifdef NXT_HAVE_PHP_IGNORE_CWD
314 1, /* php_ini_ignore_cwd */
315#endif
316 NULL, /* get_fd */
317
318 NULL, /* force_http_10 */
319
320 NULL, /* get_target_uid */
321 NULL, /* get_target_gid */
322
323 NULL, /* input_filter */
324
325 NULL, /* ini_defaults */
326 0, /* phpinfo_as_text */
327
328 NULL, /* ini_entries */
329 NULL, /* additional_functions */
330 NULL /* input_filter_init */
331};
332
333
334static uint32_t compat[] = {
335 NXT_VERNUM, NXT_DEBUG,
336};
337
338
339NXT_EXPORT nxt_app_module_t nxt_app_module = {
340 sizeof(compat),
341 compat,
342 nxt_string("php"),
343 PHP_VERSION,
344 NULL,
345 0,
346 nxt_php_setup,
347 nxt_php_start,
348};
349
350
351static nxt_php_target_t *nxt_php_targets;
352static nxt_int_t nxt_php_last_target = -1;
353
354static nxt_unit_ctx_t *nxt_php_unit_ctx;
355#if defined(ZTS) && PHP_VERSION_ID < 70400
356static void ***tsrm_ls;
357#endif
358
359
360static nxt_int_t
361nxt_php_setup(nxt_task_t *task, nxt_process_t *process,
362 nxt_common_app_conf_t *conf)
363{
364 nxt_str_t ini_path;
365 nxt_int_t ret;
366 nxt_conf_value_t *value;
367 nxt_php_app_conf_t *c;
368
369 static nxt_str_t file_str = nxt_string("file");
370 static nxt_str_t user_str = nxt_string("user");
371 static nxt_str_t admin_str = nxt_string("admin");
372
373 c = &conf->u.php;
374
375#ifdef ZTS
376
377#if PHP_VERSION_ID >= 70400
378 php_tsrm_startup();
379#else
380 tsrm_startup(1, 1, 0, NULL);
381 tsrm_ls = ts_resource(0);
382#endif
383
384#endif
385
386#if defined(NXT_PHP7) && defined(ZEND_SIGNALS)
387
388#if (NXT_ZEND_SIGNAL_STARTUP)
389 zend_signal_startup();
390#elif defined(ZTS)
391#error PHP is built with thread safety and broken signals.
392#endif
393
394#endif
395
396 sapi_startup(&nxt_php_sapi_module);
397
398 if (c->options != NULL) {
399 value = nxt_conf_get_object_member(c->options, &file_str, NULL);
400
401 if (value != NULL) {
402 nxt_conf_get_string(value, &ini_path);
403
404 ret = nxt_php_set_ini_path(task, &ini_path,
405 conf->working_directory);
406
407 if (nxt_slow_path(ret != NXT_OK)) {
408 return NXT_ERROR;
409 }
410 }
411 }
412
413 if (nxt_slow_path(nxt_php_startup(&nxt_php_sapi_module) == FAILURE)) {
414 nxt_alert(task, "failed to initialize SAPI module and extension");
415 return NXT_ERROR;
416 }
417
418 if (c->options != NULL) {
419 value = nxt_conf_get_object_member(c->options, &admin_str, NULL);
420 nxt_php_set_options(task, value, ZEND_INI_SYSTEM);
421
422 value = nxt_conf_get_object_member(c->options, &user_str, NULL);
423 nxt_php_set_options(task, value, ZEND_INI_USER);
424 }
425
426#ifdef NXT_PHP7
427 nxt_php_server_ag = zend_hash_str_find_ptr(CG(auto_globals), "_SERVER",
428 nxt_length("_SERVER"));
429#else
430 zend_hash_quick_find(CG(auto_globals), "_SERVER", sizeof("_SERVER"),
431 zend_hash_func("_SERVER", sizeof("_SERVER")),
432 (void **) &nxt_php_server_ag);
433#endif
434 if (nxt_slow_path(nxt_php_server_ag == NULL)) {
435 nxt_alert(task, "failed to find $_SERVER auto global");
436 return NXT_ERROR;
437 }
438
439 return NXT_OK;
440}
441
442
443static nxt_int_t
444nxt_php_start(nxt_task_t *task, nxt_process_data_t *data)
445{
446 uint32_t next;
447 nxt_int_t ret;
448 nxt_str_t name;
449 nxt_uint_t n;
450 nxt_unit_ctx_t *unit_ctx;
451 nxt_unit_init_t php_init;
452 nxt_conf_value_t *value;
453 nxt_php_app_conf_t *c;
454 nxt_common_app_conf_t *conf;
455
456 conf = data->app;
457 c = &conf->u.php;
458
459 n = (c->targets != NULL) ? nxt_conf_object_members_count(c->targets) : 1;
460
461 nxt_php_targets = nxt_zalloc(sizeof(nxt_php_target_t) * n);
462 if (nxt_slow_path(nxt_php_targets == NULL)) {
463 return NXT_ERROR;
464 }
465
466 if (c->targets != NULL) {
467 next = 0;
468
469 for (n = 0; /* void */; n++) {
470 value = nxt_conf_next_object_member(c->targets, &name, &next);
471 if (value == NULL) {
472 break;
473 }
474
475 ret = nxt_php_set_target(task, &nxt_php_targets[n], value);
476 if (nxt_slow_path(ret != NXT_OK)) {
477 return NXT_ERROR;
478 }
479 }
480
481 } else {
482 ret = nxt_php_set_target(task, &nxt_php_targets[0], conf->self);
483 if (nxt_slow_path(ret != NXT_OK)) {
484 return NXT_ERROR;
485 }
486 }
487
1/*
2 * Copyright (C) Max Romanov
3 * Copyright (C) Valentin V. Bartenev
4 * Copyright (C) NGINX, Inc.
5 */
6
7#include "php.h"
8#include "SAPI.h"
9#include "php_main.h"
10#include "php_variables.h"
11#include "ext/standard/php_standard.h"
12
13#include <nxt_main.h>
14#include <nxt_router.h>
15#include <nxt_unit.h>
16#include <nxt_unit_request.h>
17
18
19#if PHP_VERSION_ID >= 50400
20#define NXT_HAVE_PHP_IGNORE_CWD 1
21#endif
22
23#if PHP_VERSION_ID >= 70100
24#define NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE 1
25#else
26#define NXT_HAVE_PHP_INTERRUPTS 1
27#endif
28
29#if PHP_VERSION_ID >= 70000
30#define NXT_PHP7 1
31#endif
32#if PHP_VERSION_ID >= 80000
33#define NXT_PHP8 1
34#endif
35
36/* PHP 8 */
37#ifndef TSRMLS_CC
38#define TSRMLS_CC
39#define TSRMLS_DC
40#define TSRMLS_D void
41#define TSRMLS_C
42#endif
43
44
45typedef struct {
46 nxt_str_t root;
47 nxt_str_t index;
48 nxt_str_t script_name;
49 nxt_str_t script_dirname;
50 nxt_str_t script_filename;
51} nxt_php_target_t;
52
53
54typedef struct {
55 char *cookie;
56 nxt_str_t *root;
57 nxt_str_t *index;
58 nxt_str_t path_info;
59 nxt_str_t script_name;
60 nxt_str_t script_filename;
61 nxt_str_t script_dirname;
62 nxt_unit_request_info_t *req;
63
64 uint8_t chdir; /* 1 bit */
65} nxt_php_run_ctx_t;
66
67
68#if NXT_PHP8
69typedef int (*nxt_php_disable_t)(const char *p, size_t size);
70#elif NXT_PHP7
71typedef int (*nxt_php_disable_t)(char *p, size_t size);
72#else
73typedef int (*nxt_php_disable_t)(char *p, uint TSRMLS_DC);
74#endif
75
76#if PHP_VERSION_ID < 70200
77typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS);
78#endif
79
80
81static nxt_int_t nxt_php_setup(nxt_task_t *task, nxt_process_t *process,
82 nxt_common_app_conf_t *conf);
83static nxt_int_t nxt_php_start(nxt_task_t *task, nxt_process_data_t *data);
84static nxt_int_t nxt_php_set_target(nxt_task_t *task, nxt_php_target_t *target,
85 nxt_conf_value_t *conf);
86static nxt_int_t nxt_php_set_ini_path(nxt_task_t *task, nxt_str_t *path,
87 char *workdir);
88static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options,
89 int type);
90static nxt_int_t nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value,
91 int type);
92#ifdef NXT_PHP8
93static void nxt_php_disable_functions(nxt_str_t *str);
94#endif
95static void nxt_php_disable(nxt_task_t *task, const char *type,
96 nxt_str_t *value, char **ptr, nxt_php_disable_t disable);
97
98static nxt_int_t nxt_php_dirname(const nxt_str_t *file, nxt_str_t *dir);
99static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t);
100static void nxt_php_str_trim_lead(nxt_str_t *str, u_char t);
101nxt_inline u_char *nxt_realpath(const void *c);
102
103static void nxt_php_request_handler(nxt_unit_request_info_t *req);
104static void nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx,
105 nxt_unit_request_t *r);
106static void nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r);
107nxt_inline void nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, u_char *dir);
108
109static int nxt_php_startup(sapi_module_struct *sapi_module);
110static int nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC);
111static void *nxt_php_hash_str_find_ptr(const HashTable *ht,
112 const nxt_str_t *str);
113static char *nxt_php_read_cookies(TSRMLS_D);
114static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name,
115 nxt_unit_sptr_t *v, uint32_t len, zval *track_vars_array TSRMLS_DC);
116nxt_inline void nxt_php_set_str(nxt_unit_request_info_t *req, const char *name,
117 nxt_str_t *s, zval *track_vars_array TSRMLS_DC);
118static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
119 const char *str, uint32_t len, zval *track_vars_array TSRMLS_DC);
120static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC);
121#if NXT_PHP8
122static void nxt_php_log_message(const char *message, int syslog_type_int);
123#else
124#ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
125static void nxt_php_log_message(char *message, int syslog_type_int);
126#else
127static void nxt_php_log_message(char *message TSRMLS_DC);
128#endif
129#endif
130
131#ifdef NXT_PHP7
132static size_t nxt_php_unbuffered_write(const char *str,
133 size_t str_length TSRMLS_DC);
134static size_t nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC);
135#else
136static int nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC);
137static int nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC);
138#endif
139
140
141#ifdef NXT_PHP7
142#if PHP_VERSION_ID < 70200
143ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fastcgi_finish_request, 0, 0,
144 _IS_BOOL, NULL, 0)
145#else
146ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fastcgi_finish_request, 0, 0,
147 _IS_BOOL, 0)
148#endif
149#else /* PHP5 */
150ZEND_BEGIN_ARG_INFO_EX(arginfo_fastcgi_finish_request, 0, 0, 0)
151#endif
152ZEND_END_ARG_INFO()
153
154ZEND_FUNCTION(fastcgi_finish_request);
155
156PHP_MINIT_FUNCTION(nxt_php_ext);
157ZEND_NAMED_FUNCTION(nxt_php_chdir);
158
159
160static const zend_function_entry nxt_php_ext_functions[] = {
161 ZEND_FE(fastcgi_finish_request, arginfo_fastcgi_finish_request)
162 ZEND_FE_END
163};
164
165
166zif_handler nxt_php_chdir_handler;
167zend_auto_global *nxt_php_server_ag;
168
169
170static zend_module_entry nxt_php_unit_module = {
171 STANDARD_MODULE_HEADER,
172 "unit",
173 nxt_php_ext_functions, /* function table */
174 PHP_MINIT(nxt_php_ext), /* initialization */
175 NULL, /* shutdown */
176 NULL, /* request initialization */
177 NULL, /* request shutdown */
178 NULL, /* information */
179 NXT_VERSION,
180 STANDARD_MODULE_PROPERTIES
181};
182
183
184PHP_MINIT_FUNCTION(nxt_php_ext)
185{
186 zend_function *func;
187
188 static const nxt_str_t chdir = nxt_string("chdir");
189
190 func = nxt_php_hash_str_find_ptr(CG(function_table), &chdir);
191 if (nxt_slow_path(func == NULL)) {
192 return FAILURE;
193 }
194
195 nxt_php_chdir_handler = func->internal_function.handler;
196 func->internal_function.handler = nxt_php_chdir;
197
198 return SUCCESS;
199}
200
201
202ZEND_NAMED_FUNCTION(nxt_php_chdir)
203{
204 nxt_php_run_ctx_t *ctx;
205
206 ctx = SG(server_context);
207 ctx->chdir = 1;
208
209 nxt_php_chdir_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
210}
211
212
213PHP_FUNCTION(fastcgi_finish_request)
214{
215 zend_auto_global *ag;
216 nxt_php_run_ctx_t *ctx;
217
218 if (nxt_slow_path(zend_parse_parameters_none() == FAILURE)) {
219#ifdef NXT_PHP8
220 RETURN_THROWS();
221#else
222 return;
223#endif
224 }
225
226 ctx = SG(server_context);
227
228 if (nxt_slow_path(ctx->req == NULL)) {
229 RETURN_FALSE;
230 }
231
232#ifdef NXT_PHP7
233 php_output_end_all();
234 php_header();
235#else
236#ifdef PHP_OUTPUT_NEWAPI
237 php_output_end_all(TSRMLS_C);
238#else
239 php_end_ob_buffers(1 TSRMLS_CC);
240#endif
241
242 php_header(TSRMLS_C);
243#endif
244
245 ag = nxt_php_server_ag;
246
247 if (ag->armed) {
248#ifdef NXT_PHP7
249 ag->armed = ag->auto_global_callback(ag->name);
250#else
251 ag->armed = ag->auto_global_callback(ag->name, ag->name_len TSRMLS_CC);
252#endif
253 }
254
255 nxt_unit_request_done(ctx->req, NXT_UNIT_OK);
256 ctx->req = NULL;
257
258 PG(connection_status) = PHP_CONNECTION_ABORTED;
259#ifdef NXT_PHP7
260 php_output_set_status(PHP_OUTPUT_DISABLED);
261#else
262#ifdef PHP_OUTPUT_NEWAPI
263 php_output_set_status(PHP_OUTPUT_DISABLED TSRMLS_CC);
264#else
265 php_output_set_status(0 TSRMLS_CC);
266#endif
267#endif
268
269 RETURN_TRUE;
270}
271
272
273static sapi_module_struct nxt_php_sapi_module =
274{
275 (char *) "cli-server",
276 (char *) "unit",
277
278 nxt_php_startup, /* startup */
279 php_module_shutdown_wrapper, /* shutdown */
280
281 NULL, /* activate */
282 NULL, /* deactivate */
283
284 nxt_php_unbuffered_write, /* unbuffered write */
285 NULL, /* flush */
286 NULL, /* get uid */
287 NULL, /* getenv */
288
289 php_error, /* error handler */
290
291 NULL, /* header handler */
292 nxt_php_send_headers, /* send headers handler */
293 NULL, /* send header handler */
294
295 nxt_php_read_post, /* read POST data */
296 nxt_php_read_cookies, /* read Cookies */
297
298 nxt_php_register_variables, /* register server variables */
299 nxt_php_log_message, /* log message */
300 NULL, /* get request time */
301 NULL, /* terminate process */
302
303 NULL, /* php_ini_path_override */
304#ifdef NXT_HAVE_PHP_INTERRUPTS
305 NULL, /* block_interruptions */
306 NULL, /* unblock_interruptions */
307#endif
308 NULL, /* default_post_reader */
309 NULL, /* treat_data */
310 NULL, /* executable_location */
311
312 0, /* php_ini_ignore */
313#ifdef NXT_HAVE_PHP_IGNORE_CWD
314 1, /* php_ini_ignore_cwd */
315#endif
316 NULL, /* get_fd */
317
318 NULL, /* force_http_10 */
319
320 NULL, /* get_target_uid */
321 NULL, /* get_target_gid */
322
323 NULL, /* input_filter */
324
325 NULL, /* ini_defaults */
326 0, /* phpinfo_as_text */
327
328 NULL, /* ini_entries */
329 NULL, /* additional_functions */
330 NULL /* input_filter_init */
331};
332
333
334static uint32_t compat[] = {
335 NXT_VERNUM, NXT_DEBUG,
336};
337
338
339NXT_EXPORT nxt_app_module_t nxt_app_module = {
340 sizeof(compat),
341 compat,
342 nxt_string("php"),
343 PHP_VERSION,
344 NULL,
345 0,
346 nxt_php_setup,
347 nxt_php_start,
348};
349
350
351static nxt_php_target_t *nxt_php_targets;
352static nxt_int_t nxt_php_last_target = -1;
353
354static nxt_unit_ctx_t *nxt_php_unit_ctx;
355#if defined(ZTS) && PHP_VERSION_ID < 70400
356static void ***tsrm_ls;
357#endif
358
359
360static nxt_int_t
361nxt_php_setup(nxt_task_t *task, nxt_process_t *process,
362 nxt_common_app_conf_t *conf)
363{
364 nxt_str_t ini_path;
365 nxt_int_t ret;
366 nxt_conf_value_t *value;
367 nxt_php_app_conf_t *c;
368
369 static nxt_str_t file_str = nxt_string("file");
370 static nxt_str_t user_str = nxt_string("user");
371 static nxt_str_t admin_str = nxt_string("admin");
372
373 c = &conf->u.php;
374
375#ifdef ZTS
376
377#if PHP_VERSION_ID >= 70400
378 php_tsrm_startup();
379#else
380 tsrm_startup(1, 1, 0, NULL);
381 tsrm_ls = ts_resource(0);
382#endif
383
384#endif
385
386#if defined(NXT_PHP7) && defined(ZEND_SIGNALS)
387
388#if (NXT_ZEND_SIGNAL_STARTUP)
389 zend_signal_startup();
390#elif defined(ZTS)
391#error PHP is built with thread safety and broken signals.
392#endif
393
394#endif
395
396 sapi_startup(&nxt_php_sapi_module);
397
398 if (c->options != NULL) {
399 value = nxt_conf_get_object_member(c->options, &file_str, NULL);
400
401 if (value != NULL) {
402 nxt_conf_get_string(value, &ini_path);
403
404 ret = nxt_php_set_ini_path(task, &ini_path,
405 conf->working_directory);
406
407 if (nxt_slow_path(ret != NXT_OK)) {
408 return NXT_ERROR;
409 }
410 }
411 }
412
413 if (nxt_slow_path(nxt_php_startup(&nxt_php_sapi_module) == FAILURE)) {
414 nxt_alert(task, "failed to initialize SAPI module and extension");
415 return NXT_ERROR;
416 }
417
418 if (c->options != NULL) {
419 value = nxt_conf_get_object_member(c->options, &admin_str, NULL);
420 nxt_php_set_options(task, value, ZEND_INI_SYSTEM);
421
422 value = nxt_conf_get_object_member(c->options, &user_str, NULL);
423 nxt_php_set_options(task, value, ZEND_INI_USER);
424 }
425
426#ifdef NXT_PHP7
427 nxt_php_server_ag = zend_hash_str_find_ptr(CG(auto_globals), "_SERVER",
428 nxt_length("_SERVER"));
429#else
430 zend_hash_quick_find(CG(auto_globals), "_SERVER", sizeof("_SERVER"),
431 zend_hash_func("_SERVER", sizeof("_SERVER")),
432 (void **) &nxt_php_server_ag);
433#endif
434 if (nxt_slow_path(nxt_php_server_ag == NULL)) {
435 nxt_alert(task, "failed to find $_SERVER auto global");
436 return NXT_ERROR;
437 }
438
439 return NXT_OK;
440}
441
442
443static nxt_int_t
444nxt_php_start(nxt_task_t *task, nxt_process_data_t *data)
445{
446 uint32_t next;
447 nxt_int_t ret;
448 nxt_str_t name;
449 nxt_uint_t n;
450 nxt_unit_ctx_t *unit_ctx;
451 nxt_unit_init_t php_init;
452 nxt_conf_value_t *value;
453 nxt_php_app_conf_t *c;
454 nxt_common_app_conf_t *conf;
455
456 conf = data->app;
457 c = &conf->u.php;
458
459 n = (c->targets != NULL) ? nxt_conf_object_members_count(c->targets) : 1;
460
461 nxt_php_targets = nxt_zalloc(sizeof(nxt_php_target_t) * n);
462 if (nxt_slow_path(nxt_php_targets == NULL)) {
463 return NXT_ERROR;
464 }
465
466 if (c->targets != NULL) {
467 next = 0;
468
469 for (n = 0; /* void */; n++) {
470 value = nxt_conf_next_object_member(c->targets, &name, &next);
471 if (value == NULL) {
472 break;
473 }
474
475 ret = nxt_php_set_target(task, &nxt_php_targets[n], value);
476 if (nxt_slow_path(ret != NXT_OK)) {
477 return NXT_ERROR;
478 }
479 }
480
481 } else {
482 ret = nxt_php_set_target(task, &nxt_php_targets[0], conf->self);
483 if (nxt_slow_path(ret != NXT_OK)) {
484 return NXT_ERROR;
485 }
486 }
487
488 ret = nxt_unit_default_init(task, &php_init);
488 ret = nxt_unit_default_init(task, &php_init, conf);
489 if (nxt_slow_path(ret != NXT_OK)) {
490 nxt_alert(task, "nxt_unit_default_init() failed");
491 return ret;
492 }
493
494 php_init.callbacks.request_handler = nxt_php_request_handler;
489 if (nxt_slow_path(ret != NXT_OK)) {
490 nxt_alert(task, "nxt_unit_default_init() failed");
491 return ret;
492 }
493
494 php_init.callbacks.request_handler = nxt_php_request_handler;
495 php_init.shm_limit = conf->shm_limit;
496
497 unit_ctx = nxt_unit_init(&php_init);
498 if (nxt_slow_path(unit_ctx == NULL)) {
499 return NXT_ERROR;
500 }
501
502 nxt_php_unit_ctx = unit_ctx;
503
504 nxt_unit_run(nxt_php_unit_ctx);
505 nxt_unit_done(nxt_php_unit_ctx);
506
507 exit(0);
508
509 return NXT_OK;
510}
511
512
513static nxt_int_t
514nxt_php_set_target(nxt_task_t *task, nxt_php_target_t *target,
515 nxt_conf_value_t *conf)
516{
517 u_char *tmp, *p;
518 nxt_str_t str;
519 nxt_int_t ret;
520 nxt_conf_value_t *value;
521
522 static nxt_str_t root_str = nxt_string("root");
523 static nxt_str_t script_str = nxt_string("script");
524 static nxt_str_t index_str = nxt_string("index");
525
526 value = nxt_conf_get_object_member(conf, &root_str, NULL);
527
528 nxt_conf_get_string(value, &str);
529
530 tmp = nxt_malloc(str.length + 1);
531 if (nxt_slow_path(tmp == NULL)) {
532 return NXT_ERROR;
533 }
534
535 p = tmp;
536
537 p = nxt_cpymem(p, str.start, str.length);
538 *p = '\0';
539
540 p = nxt_realpath(tmp);
541 if (nxt_slow_path(p == NULL)) {
542 nxt_alert(task, "root realpath(%s) failed %E", tmp, nxt_errno);
543 return NXT_ERROR;
544 }
545
546 nxt_free(tmp);
547
548 target->root.length = nxt_strlen(p);
549 target->root.start = p;
550
551 nxt_php_str_trim_trail(&target->root, '/');
552
553 value = nxt_conf_get_object_member(conf, &script_str, NULL);
554
555 if (value != NULL) {
556 nxt_conf_get_string(value, &str);
557
558 nxt_php_str_trim_lead(&str, '/');
559
560 tmp = nxt_malloc(target->root.length + 1 + str.length + 1);
561 if (nxt_slow_path(tmp == NULL)) {
562 return NXT_ERROR;
563 }
564
565 p = tmp;
566
567 p = nxt_cpymem(p, target->root.start, target->root.length);
568 *p++ = '/';
569
570 p = nxt_cpymem(p, str.start, str.length);
571 *p = '\0';
572
573 p = nxt_realpath(tmp);
574 if (nxt_slow_path(p == NULL)) {
575 nxt_alert(task, "script realpath(%s) failed %E", tmp, nxt_errno);
576 return NXT_ERROR;
577 }
578
579 nxt_free(tmp);
580
581 target->script_filename.length = nxt_strlen(p);
582 target->script_filename.start = p;
583
584 if (!nxt_str_start(&target->script_filename,
585 target->root.start, target->root.length))
586 {
587 nxt_alert(task, "script is not under php root");
588 return NXT_ERROR;
589 }
590
591 ret = nxt_php_dirname(&target->script_filename,
592 &target->script_dirname);
593 if (nxt_slow_path(ret != NXT_OK)) {
594 return NXT_ERROR;
595 }
596
597 target->script_name.length = target->script_filename.length
598 - target->root.length;
599 target->script_name.start = target->script_filename.start
600 + target->root.length;
601
602 } else {
603 value = nxt_conf_get_object_member(conf, &index_str, NULL);
604
605 if (value != NULL) {
606 nxt_conf_get_string(value, &str);
607
608 tmp = nxt_malloc(str.length);
609 if (nxt_slow_path(tmp == NULL)) {
610 return NXT_ERROR;
611 }
612
613 nxt_memcpy(tmp, str.start, str.length);
614
615 target->index.length = str.length;
616 target->index.start = tmp;
617
618 } else {
619 nxt_str_set(&target->index, "index.php");
620 }
621 }
622
623 return NXT_OK;
624}
625
626
627static nxt_int_t
628nxt_php_set_ini_path(nxt_task_t *task, nxt_str_t *ini_path, char *workdir)
629{
630 size_t wdlen;
631 u_char *p, *start;
632
633 if (ini_path->start[0] == '/' || workdir == NULL) {
634 p = nxt_malloc(ini_path->length + 1);
635 if (nxt_slow_path(p == NULL)) {
636 return NXT_ERROR;
637 }
638
639 start = p;
640
641 } else {
642 wdlen = nxt_strlen(workdir);
643
644 p = nxt_malloc(wdlen + ini_path->length + 2);
645 if (nxt_slow_path(p == NULL)) {
646 return NXT_ERROR;
647 }
648
649 start = p;
650
651 p = nxt_cpymem(p, workdir, wdlen);
652
653 if (workdir[wdlen - 1] != '/') {
654 *p++ = '/';
655 }
656 }
657
658 p = nxt_cpymem(p, ini_path->start, ini_path->length);
659 *p = '\0';
660
661 nxt_php_sapi_module.php_ini_path_override = (char *) start;
662
663 return NXT_OK;
664}
665
666
667static void
668nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, int type)
669{
670 uint32_t next;
671 nxt_str_t name, value;
672 nxt_conf_value_t *value_obj;
673
674 if (options != NULL) {
675 next = 0;
676
677 for ( ;; ) {
678 value_obj = nxt_conf_next_object_member(options, &name, &next);
679 if (value_obj == NULL) {
680 break;
681 }
682
683 nxt_conf_get_string(value_obj, &value);
684
685 if (nxt_php_alter_option(&name, &value, type) != NXT_OK) {
686 nxt_log(task, NXT_LOG_ERR,
687 "setting PHP option \"%V: %V\" failed", &name, &value);
688 continue;
689 }
690
691 if (nxt_str_eq(&name, "disable_functions", 17)) {
692#ifdef NXT_PHP8
693 nxt_php_disable_functions(&value);
694#else
695 nxt_php_disable(task, "function", &value,
696 &PG(disable_functions),
697 zend_disable_function);
698#endif
699 continue;
700 }
701
702 if (nxt_str_eq(&name, "disable_classes", 15)) {
703 nxt_php_disable(task, "class", &value,
704 &PG(disable_classes),
705 zend_disable_class);
706 continue;
707 }
708 }
709 }
710}
711
712
713#ifdef NXT_PHP7
714
715static nxt_int_t
716nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type)
717{
718 zend_string *zs;
719 zend_ini_entry *ini_entry;
720
721 ini_entry = nxt_php_hash_str_find_ptr(EG(ini_directives), name);
722 if (nxt_slow_path(ini_entry == NULL)) {
723 return NXT_ERROR;
724 }
725
726 /* PHP exits on memory allocation errors. */
727 zs = zend_string_init((char *) value->start, value->length, 1);
728
729 if (ini_entry->on_modify
730 && ini_entry->on_modify(ini_entry, zs, ini_entry->mh_arg1,
731 ini_entry->mh_arg2, ini_entry->mh_arg3,
732 ZEND_INI_STAGE_ACTIVATE)
733 != SUCCESS)
734 {
735 zend_string_release(zs);
736 return NXT_ERROR;
737 }
738
739 ini_entry->value = zs;
740 ini_entry->modifiable = type;
741
742 return NXT_OK;
743}
744
745#else /* PHP 5. */
746
747static nxt_int_t
748nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type)
749{
750 char *cstr;
751 zend_ini_entry *ini_entry;
752
753 ini_entry = nxt_php_hash_str_find_ptr(EG(ini_directives), name);
754 if (nxt_slow_path(ini_entry == NULL)) {
755 return NXT_ERROR;
756 }
757
758 cstr = nxt_malloc(value->length + 1);
759 if (nxt_slow_path(cstr == NULL)) {
760 return NXT_ERROR;
761 }
762
763 nxt_memcpy(cstr, value->start, value->length);
764 cstr[value->length] = '\0';
765
766 if (ini_entry->on_modify
767 && ini_entry->on_modify(ini_entry, cstr, value->length,
768 ini_entry->mh_arg1, ini_entry->mh_arg2,
769 ini_entry->mh_arg3, ZEND_INI_STAGE_ACTIVATE
770 TSRMLS_CC)
771 != SUCCESS)
772 {
773 nxt_free(cstr);
774 return NXT_ERROR;
775 }
776
777 ini_entry->value = cstr;
778 ini_entry->value_length = value->length;
779 ini_entry->modifiable = type;
780
781 return NXT_OK;
782}
783
784#endif
785
786
787#ifdef NXT_PHP8
788
789static void
790nxt_php_disable_functions(nxt_str_t *str)
791{
792 char *p;
793
794 p = nxt_malloc(str->length + 1);
795 if (nxt_slow_path(p == NULL)) {
796 return;
797 }
798
799 nxt_memcpy(p, str->start, str->length);
800 p[str->length] = '\0';
801
802 zend_disable_functions(p);
803
804 nxt_free(p);
805}
806
807#endif
808
809
810static void
811nxt_php_disable(nxt_task_t *task, const char *type, nxt_str_t *value,
812 char **ptr, nxt_php_disable_t disable)
813{
814 char c, *p, *start;
815
816 p = nxt_malloc(value->length + 1);
817 if (nxt_slow_path(p == NULL)) {
818 return;
819 }
820
821 /*
822 * PHP frees this memory on module shutdown.
823 * See core_globals_dtor() for details.
824 */
825 *ptr = p;
826
827 nxt_memcpy(p, value->start, value->length);
828 p[value->length] = '\0';
829
830 start = p;
831
832 do {
833 c = *p;
834
835 if (c == ' ' || c == ',' || c == '\0') {
836
837 if (p != start) {
838 *p = '\0';
839
840#ifdef NXT_PHP7
841 if (disable(start, p - start)
842#else
843 if (disable(start, p - start TSRMLS_CC)
844#endif
845 != SUCCESS)
846 {
847 nxt_log(task, NXT_LOG_ERR,
848 "PHP: failed to disable \"%s\": no such %s",
849 start, type);
850 }
851 }
852
853 start = p + 1;
854 }
855
856 p++;
857
858 } while (c != '\0');
859}
860
861
862static nxt_int_t
863nxt_php_dirname(const nxt_str_t *file, nxt_str_t *dir)
864{
865 size_t length;
866
867 if (file->length == 0 || file->start[0] != '/') {
868 nxt_unit_alert(NULL, "php_dirname: invalid file name "
869 "(not starts from '/')");
870 return NXT_ERROR;
871 }
872
873 length = file->length;
874
875 while (file->start[length - 1] != '/') {
876 length--;
877 }
878
879 dir->length = length;
880 dir->start = nxt_malloc(length + 1);
881 if (nxt_slow_path(dir->start == NULL)) {
882 return NXT_ERROR;
883 }
884
885 nxt_memcpy(dir->start, file->start, length);
886
887 dir->start[length] = '\0';
888
889 return NXT_OK;
890}
891
892
893static void
894nxt_php_str_trim_trail(nxt_str_t *str, u_char t)
895{
896 while (str->length > 0 && str->start[str->length - 1] == t) {
897 str->length--;
898 }
899
900 str->start[str->length] = '\0';
901}
902
903
904static void
905nxt_php_str_trim_lead(nxt_str_t *str, u_char t)
906{
907 while (str->length > 0 && str->start[0] == t) {
908 str->length--;
909 str->start++;
910 }
911}
912
913
914nxt_inline u_char *
915nxt_realpath(const void *c)
916{
917 return (u_char *) realpath(c, NULL);
918}
919
920
921static void
922nxt_php_request_handler(nxt_unit_request_info_t *req)
923{
924 nxt_php_target_t *target;
925 nxt_php_run_ctx_t ctx;
926 nxt_unit_request_t *r;
927
928 r = req->request;
929 target = &nxt_php_targets[r->app_target];
930
931 nxt_memzero(&ctx, sizeof(ctx));
932
933 ctx.req = req;
934 ctx.root = &target->root;
935 ctx.index = &target->index;
936
937 if (target->script_filename.length == 0) {
938 nxt_php_dynamic_request(&ctx, r);
939 return;
940 }
941
942 ctx.script_filename = target->script_filename;
943 ctx.script_dirname = target->script_dirname;
944 ctx.script_name = target->script_name;
945
946 ctx.chdir = (r->app_target != nxt_php_last_target);
947
948 nxt_php_execute(&ctx, r);
949
950 nxt_php_last_target = ctx.chdir ? -1 : r->app_target;
951}
952
953
954static void
955nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r)
956{
957 u_char *p;
958 nxt_str_t path, script_name;
959 nxt_int_t ret;
960
961 path.length = r->path_length;
962 path.start = nxt_unit_sptr_get(&r->path);
963
964 nxt_str_null(&script_name);
965
966 ctx->path_info.start = (u_char *) strstr((char *) path.start, ".php/");
967 if (ctx->path_info.start != NULL) {
968 ctx->path_info.start += 4;
969 path.length = ctx->path_info.start - path.start;
970
971 ctx->path_info.length = r->path_length - path.length;
972
973 } else if (path.start[path.length - 1] == '/') {
974 script_name = *ctx->index;
975
976 } else {
977 if (nxt_slow_path(path.length < 4
978 || nxt_memcmp(path.start + (path.length - 4),
979 ".php", 4)))
980 {
981 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
982
983 return;
984 }
985 }
986
987 ctx->script_filename.length = ctx->root->length
988 + path.length
989 + script_name.length;
990
991 p = nxt_malloc(ctx->script_filename.length + 1);
992 if (nxt_slow_path(p == NULL)) {
993 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
994
995 return;
996 }
997
998 ctx->script_filename.start = p;
999
1000 ctx->script_name.length = path.length + script_name.length;
1001 ctx->script_name.start = p + ctx->root->length;
1002
1003 p = nxt_cpymem(p, ctx->root->start, ctx->root->length);
1004 p = nxt_cpymem(p, path.start, path.length);
1005
1006 if (script_name.length > 0) {
1007 p = nxt_cpymem(p, script_name.start, script_name.length);
1008 }
1009
1010 *p = '\0';
1011
1012 ctx->chdir = 1;
1013
1014 ret = nxt_php_dirname(&ctx->script_filename, &ctx->script_dirname);
1015 if (nxt_slow_path(ret != NXT_OK)) {
1016 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
1017 nxt_free(ctx->script_filename.start);
1018
1019 return;
1020 }
1021
1022 nxt_php_execute(ctx, r);
1023
1024 nxt_free(ctx->script_filename.start);
1025 nxt_free(ctx->script_dirname.start);
1026
1027 nxt_php_last_target = -1;
1028}
1029
1030
1031static void
1032nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r)
1033{
1034#if (PHP_VERSION_ID < 50600)
1035 void *read_post;
1036#endif
1037 nxt_unit_field_t *f;
1038 zend_file_handle file_handle;
1039
1040 nxt_unit_req_debug(ctx->req, "PHP execute script %s",
1041 ctx->script_filename.start);
1042
1043 SG(server_context) = ctx;
1044 SG(options) |= SAPI_OPTION_NO_CHDIR;
1045 SG(request_info).request_uri = nxt_unit_sptr_get(&r->target);
1046 SG(request_info).request_method = nxt_unit_sptr_get(&r->method);
1047
1048 SG(request_info).proto_num = 1001;
1049
1050 SG(request_info).query_string = r->query.offset
1051 ? nxt_unit_sptr_get(&r->query) : NULL;
1052 SG(request_info).content_length = r->content_length;
1053
1054 if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
1055 f = r->fields + r->content_type_field;
1056
1057 SG(request_info).content_type = nxt_unit_sptr_get(&f->value);
1058 }
1059
1060 if (r->cookie_field != NXT_UNIT_NONE_FIELD) {
1061 f = r->fields + r->cookie_field;
1062
1063 ctx->cookie = nxt_unit_sptr_get(&f->value);
1064 }
1065
1066 if (r->authorization_field != NXT_UNIT_NONE_FIELD) {
1067 f = r->fields + r->authorization_field;
1068
1069#ifdef NXT_PHP7
1070 php_handle_auth_data(nxt_unit_sptr_get(&f->value));
1071#else
1072 php_handle_auth_data(nxt_unit_sptr_get(&f->value) TSRMLS_CC);
1073#endif
1074
1075 } else {
1076 SG(request_info).auth_digest = NULL;
1077 SG(request_info).auth_user = NULL;
1078 SG(request_info).auth_password = NULL;
1079 }
1080
1081 SG(sapi_headers).http_response_code = 200;
1082
1083 SG(request_info).path_translated = NULL;
1084
1085#ifdef NXT_PHP7
1086 if (nxt_slow_path(php_request_startup() == FAILURE)) {
1087#else
1088 if (nxt_slow_path(php_request_startup(TSRMLS_C) == FAILURE)) {
1089#endif
1090 nxt_unit_req_debug(ctx->req, "php_request_startup() failed");
1091
1092 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
1093 return;
1094 }
1095
1096 if (ctx->chdir) {
1097 ctx->chdir = 0;
1098 nxt_php_vcwd_chdir(ctx->req, ctx->script_dirname.start);
1099 }
1100
1101 nxt_memzero(&file_handle, sizeof(file_handle));
1102
1103 file_handle.type = ZEND_HANDLE_FILENAME;
1104#if (PHP_VERSION_ID >= 80100)
1105 file_handle.filename = zend_string_init((char *) ctx->script_filename.start,
1106 ctx->script_filename.length, 0);
1107 file_handle.primary_script = 1;
1108#else
1109 file_handle.filename = (char *) ctx->script_filename.start;
1110#endif
1111
1112 php_execute_script(&file_handle TSRMLS_CC);
1113
1114#if (PHP_VERSION_ID >= 80100)
1115 zend_destroy_file_handle(&file_handle);
1116#endif
1117
1118 /* Prevention of consuming possible unread request body. */
1119#if (PHP_VERSION_ID < 50600)
1120 read_post = sapi_module.read_post;
1121 sapi_module.read_post = NULL;
1122#else
1123 SG(post_read) = 1;
1124#endif
1125
1126 php_request_shutdown(NULL);
1127
1128 if (ctx->req != NULL) {
1129 nxt_unit_request_done(ctx->req, NXT_UNIT_OK);
1130 }
1131
1132#if (PHP_VERSION_ID < 50600)
1133 sapi_module.read_post = read_post;
1134#endif
1135}
1136
1137
1138nxt_inline void
1139nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, u_char *dir)
1140{
1141 if (nxt_slow_path(VCWD_CHDIR((char *) dir) != 0)) {
1142 nxt_unit_req_alert(req, "VCWD_CHDIR(%s) failed (%d: %s)",
1143 dir, errno, strerror(errno));
1144 }
1145}
1146
1147
1148static int
1149nxt_php_startup(sapi_module_struct *sapi_module)
1150{
1151 return php_module_startup(sapi_module, &nxt_php_unit_module, 1);
1152}
1153
1154
1155#ifdef NXT_PHP7
1156static size_t
1157nxt_php_unbuffered_write(const char *str, size_t str_length TSRMLS_DC)
1158#else
1159static int
1160nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC)
1161#endif
1162{
1163 int rc;
1164 nxt_php_run_ctx_t *ctx;
1165
1166 ctx = SG(server_context);
1167
1168 rc = nxt_unit_response_write(ctx->req, str, str_length);
1169 if (nxt_fast_path(rc == NXT_UNIT_OK)) {
1170 return str_length;
1171 }
1172
1173 php_handle_aborted_connection();
1174 return 0;
1175}
1176
1177
1178static int
1179nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
1180{
1181 int rc, fields_count;
1182 char *colon, *value;
1183 uint16_t status;
1184 uint32_t resp_size;
1185 nxt_php_run_ctx_t *ctx;
1186 sapi_header_struct *h;
1187 zend_llist_position zpos;
1188 nxt_unit_request_info_t *req;
1189
1190 ctx = SG(server_context);
1191 req = ctx->req;
1192
1193 nxt_unit_req_debug(req, "nxt_php_send_headers");
1194
1195 if (SG(request_info).no_headers == 1) {
1196 rc = nxt_unit_response_init(req, 200, 0, 0);
1197 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1198 return SAPI_HEADER_SEND_FAILED;
1199 }
1200
1201 return SAPI_HEADER_SENT_SUCCESSFULLY;
1202 }
1203
1204 resp_size = 0;
1205 fields_count = zend_llist_count(&sapi_headers->headers);
1206
1207 for (h = zend_llist_get_first_ex(&sapi_headers->headers, &zpos);
1208 h;
1209 h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos))
1210 {
1211 resp_size += h->header_len;
1212 }
1213
1214 status = SG(sapi_headers).http_response_code;
1215
1216 rc = nxt_unit_response_init(req, status, fields_count, resp_size);
1217 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1218 return SAPI_HEADER_SEND_FAILED;
1219 }
1220
1221 for (h = zend_llist_get_first_ex(&sapi_headers->headers, &zpos);
1222 h;
1223 h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos))
1224 {
1225 colon = memchr(h->header, ':', h->header_len);
1226 if (nxt_slow_path(colon == NULL)) {
1227 nxt_unit_req_warn(req, "colon not found in header '%.*s'",
1228 (int) h->header_len, h->header);
1229 continue;
1230 }
1231
1232 value = colon + 1;
1233 while(isspace(*value)) {
1234 value++;
1235 }
1236
1237 nxt_unit_response_add_field(req, h->header, colon - h->header,
1238 value,
1239 h->header_len - (value - h->header));
1240 }
1241
1242 rc = nxt_unit_response_send(req);
1243 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1244 nxt_unit_req_debug(req, "failed to send response");
1245
1246 return SAPI_HEADER_SEND_FAILED;
1247 }
1248
1249 return SAPI_HEADER_SENT_SUCCESSFULLY;
1250}
1251
1252
1253#ifdef NXT_PHP7
1254static size_t
1255nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC)
1256#else
1257static int
1258nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC)
1259#endif
1260{
1261 nxt_php_run_ctx_t *ctx;
1262
1263 ctx = SG(server_context);
1264
1265 nxt_unit_req_debug(ctx->req, "nxt_php_read_post %d", (int) count_bytes);
1266
1267 return nxt_unit_request_read(ctx->req, buffer, count_bytes);
1268}
1269
1270
1271static char *
1272nxt_php_read_cookies(TSRMLS_D)
1273{
1274 nxt_php_run_ctx_t *ctx;
1275
1276 ctx = SG(server_context);
1277
1278 nxt_unit_req_debug(ctx->req, "nxt_php_read_cookies");
1279
1280 return ctx->cookie;
1281}
1282
1283
1284static void
1285nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
1286{
1287 const char *name;
1288 nxt_unit_field_t *f, *f_end;
1289 nxt_php_run_ctx_t *ctx;
1290 nxt_unit_request_t *r;
1291 nxt_unit_request_info_t *req;
1292
1293 ctx = SG(server_context);
1294
1295 req = ctx->req;
1296 r = req->request;
1297
1298 nxt_unit_req_debug(req, "nxt_php_register_variables");
1299
1300 php_register_variable_safe((char *) "SERVER_SOFTWARE",
1301 (char *) nxt_server.start,
1302 nxt_server.length, track_vars_array TSRMLS_CC);
1303
1304 nxt_php_set_sptr(req, "SERVER_PROTOCOL", &r->version, r->version_length,
1305 track_vars_array TSRMLS_CC);
1306
1307 /*
1308 * 'PHP_SELF'
1309 * The filename of the currently executing script, relative to the document
1310 * root. For instance, $_SERVER['PHP_SELF'] in a script at the address
1311 * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__
1312 * constant contains the full path and filename of the current (i.e.
1313 * included) file. If PHP is running as a command-line processor this
1314 * variable contains the script name since PHP 4.3.0. Previously it was not
1315 * available.
1316 */
1317
1318 if (ctx->path_info.length != 0) {
1319 nxt_php_set_sptr(req, "PHP_SELF", &r->path, r->path_length,
1320 track_vars_array TSRMLS_CC);
1321
1322 nxt_php_set_str(req, "PATH_INFO", &ctx->path_info,
1323 track_vars_array TSRMLS_CC);
1324
1325 } else {
1326 nxt_php_set_str(req, "PHP_SELF", &ctx->script_name,
1327 track_vars_array TSRMLS_CC);
1328 }
1329
1330 /*
1331 * 'SCRIPT_NAME'
1332 * Contains the current script's path. This is useful for pages which need
1333 * to point to themselves. The __FILE__ constant contains the full path and
1334 * filename of the current (i.e. included) file.
1335 */
1336
1337 nxt_php_set_str(req, "SCRIPT_NAME", &ctx->script_name,
1338 track_vars_array TSRMLS_CC);
1339
1340 /*
1341 * 'SCRIPT_FILENAME'
1342 * The absolute pathname of the currently executing script.
1343 */
1344
1345 nxt_php_set_str(req, "SCRIPT_FILENAME", &ctx->script_filename,
1346 track_vars_array TSRMLS_CC);
1347
1348 /*
1349 * 'DOCUMENT_ROOT'
1350 * The document root directory under which the current script is executing,
1351 * as defined in the server's configuration file.
1352 */
1353
1354 nxt_php_set_str(req, "DOCUMENT_ROOT", ctx->root,
1355 track_vars_array TSRMLS_CC);
1356
1357 nxt_php_set_sptr(req, "REQUEST_METHOD", &r->method, r->method_length,
1358 track_vars_array TSRMLS_CC);
1359 nxt_php_set_sptr(req, "REQUEST_URI", &r->target, r->target_length,
1360 track_vars_array TSRMLS_CC);
1361 nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length,
1362 track_vars_array TSRMLS_CC);
1363
1364 nxt_php_set_sptr(req, "REMOTE_ADDR", &r->remote, r->remote_length,
1365 track_vars_array TSRMLS_CC);
1366 nxt_php_set_sptr(req, "SERVER_ADDR", &r->local, r->local_length,
1367 track_vars_array TSRMLS_CC);
1368
1369 nxt_php_set_sptr(req, "SERVER_NAME", &r->server_name, r->server_name_length,
1370 track_vars_array TSRMLS_CC);
1371 nxt_php_set_cstr(req, "SERVER_PORT", "80", 2, track_vars_array TSRMLS_CC);
1372
1373 if (r->tls) {
1374 nxt_php_set_cstr(req, "HTTPS", "on", 2, track_vars_array TSRMLS_CC);
1375 }
1376
1377 f_end = r->fields + r->fields_count;
1378 for (f = r->fields; f < f_end; f++) {
1379 name = nxt_unit_sptr_get(&f->name);
1380
1381 nxt_php_set_sptr(req, name, &f->value, f->value_length,
1382 track_vars_array TSRMLS_CC);
1383 }
1384
1385 if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
1386 f = r->fields + r->content_length_field;
1387
1388 nxt_php_set_sptr(req, "CONTENT_LENGTH", &f->value, f->value_length,
1389 track_vars_array TSRMLS_CC);
1390 }
1391
1392 if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
1393 f = r->fields + r->content_type_field;
1394
1395 nxt_php_set_sptr(req, "CONTENT_TYPE", &f->value, f->value_length,
1396 track_vars_array TSRMLS_CC);
1397 }
1398}
1399
1400
1401static void
1402nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name,
1403 nxt_unit_sptr_t *v, uint32_t len, zval *track_vars_array TSRMLS_DC)
1404{
1405 char *str;
1406
1407 str = nxt_unit_sptr_get(v);
1408
1409 nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, str);
1410
1411 php_register_variable_safe((char *) name, str, len,
1412 track_vars_array TSRMLS_CC);
1413}
1414
1415
1416nxt_inline void
1417nxt_php_set_str(nxt_unit_request_info_t *req, const char *name,
1418 nxt_str_t *s, zval *track_vars_array TSRMLS_DC)
1419{
1420 nxt_php_set_cstr(req, name, (char *) s->start, s->length,
1421 track_vars_array TSRMLS_CC);
1422}
1423
1424
1425#ifdef NXT_PHP7
1426
1427static void *
1428nxt_php_hash_str_find_ptr(const HashTable *ht, const nxt_str_t *str)
1429{
1430 return zend_hash_str_find_ptr(ht, (const char *) str->start, str->length);
1431}
1432
1433#else
1434
1435static void *
1436nxt_php_hash_str_find_ptr(const HashTable *ht, const nxt_str_t *str)
1437{
1438 int ret;
1439 void *entry;
1440 char buf[256];
1441
1442 if (nxt_slow_path(str->length >= (sizeof(buf) - 1))) {
1443 return NULL;
1444 }
1445
1446 nxt_memcpy(buf, str->start, str->length);
1447 buf[str->length] = '\0';
1448
1449 ret = zend_hash_find(ht, buf, str->length + 1, &entry);
1450 if (nxt_fast_path(ret == SUCCESS)) {
1451 return entry;
1452 }
1453
1454 return NULL;
1455}
1456
1457#endif
1458
1459
1460static void
1461nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
1462 const char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC)
1463{
1464 if (nxt_slow_path(cstr == NULL)) {
1465 return;
1466 }
1467
1468 nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, cstr);
1469
1470 php_register_variable_safe((char *) name, (char *) cstr, len,
1471 track_vars_array TSRMLS_CC);
1472}
1473
1474
1475#if NXT_PHP8
1476static void
1477nxt_php_log_message(const char *message, int syslog_type_int)
1478#else
1479#ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
1480static void
1481nxt_php_log_message(char *message, int syslog_type_int)
1482#else
1483static void
1484nxt_php_log_message(char *message TSRMLS_DC)
1485#endif
1486#endif
1487{
1488 nxt_php_run_ctx_t *ctx;
1489
1490 ctx = SG(server_context);
1491
1492 if (ctx != NULL) {
1493 nxt_unit_req_log(ctx->req, NXT_UNIT_LOG_NOTICE,
1494 "php message: %s", message);
1495
1496 } else {
1497 nxt_unit_log(nxt_php_unit_ctx, NXT_UNIT_LOG_NOTICE,
1498 "php message: %s", message);
1499 }
1500}
495
496 unit_ctx = nxt_unit_init(&php_init);
497 if (nxt_slow_path(unit_ctx == NULL)) {
498 return NXT_ERROR;
499 }
500
501 nxt_php_unit_ctx = unit_ctx;
502
503 nxt_unit_run(nxt_php_unit_ctx);
504 nxt_unit_done(nxt_php_unit_ctx);
505
506 exit(0);
507
508 return NXT_OK;
509}
510
511
512static nxt_int_t
513nxt_php_set_target(nxt_task_t *task, nxt_php_target_t *target,
514 nxt_conf_value_t *conf)
515{
516 u_char *tmp, *p;
517 nxt_str_t str;
518 nxt_int_t ret;
519 nxt_conf_value_t *value;
520
521 static nxt_str_t root_str = nxt_string("root");
522 static nxt_str_t script_str = nxt_string("script");
523 static nxt_str_t index_str = nxt_string("index");
524
525 value = nxt_conf_get_object_member(conf, &root_str, NULL);
526
527 nxt_conf_get_string(value, &str);
528
529 tmp = nxt_malloc(str.length + 1);
530 if (nxt_slow_path(tmp == NULL)) {
531 return NXT_ERROR;
532 }
533
534 p = tmp;
535
536 p = nxt_cpymem(p, str.start, str.length);
537 *p = '\0';
538
539 p = nxt_realpath(tmp);
540 if (nxt_slow_path(p == NULL)) {
541 nxt_alert(task, "root realpath(%s) failed %E", tmp, nxt_errno);
542 return NXT_ERROR;
543 }
544
545 nxt_free(tmp);
546
547 target->root.length = nxt_strlen(p);
548 target->root.start = p;
549
550 nxt_php_str_trim_trail(&target->root, '/');
551
552 value = nxt_conf_get_object_member(conf, &script_str, NULL);
553
554 if (value != NULL) {
555 nxt_conf_get_string(value, &str);
556
557 nxt_php_str_trim_lead(&str, '/');
558
559 tmp = nxt_malloc(target->root.length + 1 + str.length + 1);
560 if (nxt_slow_path(tmp == NULL)) {
561 return NXT_ERROR;
562 }
563
564 p = tmp;
565
566 p = nxt_cpymem(p, target->root.start, target->root.length);
567 *p++ = '/';
568
569 p = nxt_cpymem(p, str.start, str.length);
570 *p = '\0';
571
572 p = nxt_realpath(tmp);
573 if (nxt_slow_path(p == NULL)) {
574 nxt_alert(task, "script realpath(%s) failed %E", tmp, nxt_errno);
575 return NXT_ERROR;
576 }
577
578 nxt_free(tmp);
579
580 target->script_filename.length = nxt_strlen(p);
581 target->script_filename.start = p;
582
583 if (!nxt_str_start(&target->script_filename,
584 target->root.start, target->root.length))
585 {
586 nxt_alert(task, "script is not under php root");
587 return NXT_ERROR;
588 }
589
590 ret = nxt_php_dirname(&target->script_filename,
591 &target->script_dirname);
592 if (nxt_slow_path(ret != NXT_OK)) {
593 return NXT_ERROR;
594 }
595
596 target->script_name.length = target->script_filename.length
597 - target->root.length;
598 target->script_name.start = target->script_filename.start
599 + target->root.length;
600
601 } else {
602 value = nxt_conf_get_object_member(conf, &index_str, NULL);
603
604 if (value != NULL) {
605 nxt_conf_get_string(value, &str);
606
607 tmp = nxt_malloc(str.length);
608 if (nxt_slow_path(tmp == NULL)) {
609 return NXT_ERROR;
610 }
611
612 nxt_memcpy(tmp, str.start, str.length);
613
614 target->index.length = str.length;
615 target->index.start = tmp;
616
617 } else {
618 nxt_str_set(&target->index, "index.php");
619 }
620 }
621
622 return NXT_OK;
623}
624
625
626static nxt_int_t
627nxt_php_set_ini_path(nxt_task_t *task, nxt_str_t *ini_path, char *workdir)
628{
629 size_t wdlen;
630 u_char *p, *start;
631
632 if (ini_path->start[0] == '/' || workdir == NULL) {
633 p = nxt_malloc(ini_path->length + 1);
634 if (nxt_slow_path(p == NULL)) {
635 return NXT_ERROR;
636 }
637
638 start = p;
639
640 } else {
641 wdlen = nxt_strlen(workdir);
642
643 p = nxt_malloc(wdlen + ini_path->length + 2);
644 if (nxt_slow_path(p == NULL)) {
645 return NXT_ERROR;
646 }
647
648 start = p;
649
650 p = nxt_cpymem(p, workdir, wdlen);
651
652 if (workdir[wdlen - 1] != '/') {
653 *p++ = '/';
654 }
655 }
656
657 p = nxt_cpymem(p, ini_path->start, ini_path->length);
658 *p = '\0';
659
660 nxt_php_sapi_module.php_ini_path_override = (char *) start;
661
662 return NXT_OK;
663}
664
665
666static void
667nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, int type)
668{
669 uint32_t next;
670 nxt_str_t name, value;
671 nxt_conf_value_t *value_obj;
672
673 if (options != NULL) {
674 next = 0;
675
676 for ( ;; ) {
677 value_obj = nxt_conf_next_object_member(options, &name, &next);
678 if (value_obj == NULL) {
679 break;
680 }
681
682 nxt_conf_get_string(value_obj, &value);
683
684 if (nxt_php_alter_option(&name, &value, type) != NXT_OK) {
685 nxt_log(task, NXT_LOG_ERR,
686 "setting PHP option \"%V: %V\" failed", &name, &value);
687 continue;
688 }
689
690 if (nxt_str_eq(&name, "disable_functions", 17)) {
691#ifdef NXT_PHP8
692 nxt_php_disable_functions(&value);
693#else
694 nxt_php_disable(task, "function", &value,
695 &PG(disable_functions),
696 zend_disable_function);
697#endif
698 continue;
699 }
700
701 if (nxt_str_eq(&name, "disable_classes", 15)) {
702 nxt_php_disable(task, "class", &value,
703 &PG(disable_classes),
704 zend_disable_class);
705 continue;
706 }
707 }
708 }
709}
710
711
712#ifdef NXT_PHP7
713
714static nxt_int_t
715nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type)
716{
717 zend_string *zs;
718 zend_ini_entry *ini_entry;
719
720 ini_entry = nxt_php_hash_str_find_ptr(EG(ini_directives), name);
721 if (nxt_slow_path(ini_entry == NULL)) {
722 return NXT_ERROR;
723 }
724
725 /* PHP exits on memory allocation errors. */
726 zs = zend_string_init((char *) value->start, value->length, 1);
727
728 if (ini_entry->on_modify
729 && ini_entry->on_modify(ini_entry, zs, ini_entry->mh_arg1,
730 ini_entry->mh_arg2, ini_entry->mh_arg3,
731 ZEND_INI_STAGE_ACTIVATE)
732 != SUCCESS)
733 {
734 zend_string_release(zs);
735 return NXT_ERROR;
736 }
737
738 ini_entry->value = zs;
739 ini_entry->modifiable = type;
740
741 return NXT_OK;
742}
743
744#else /* PHP 5. */
745
746static nxt_int_t
747nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type)
748{
749 char *cstr;
750 zend_ini_entry *ini_entry;
751
752 ini_entry = nxt_php_hash_str_find_ptr(EG(ini_directives), name);
753 if (nxt_slow_path(ini_entry == NULL)) {
754 return NXT_ERROR;
755 }
756
757 cstr = nxt_malloc(value->length + 1);
758 if (nxt_slow_path(cstr == NULL)) {
759 return NXT_ERROR;
760 }
761
762 nxt_memcpy(cstr, value->start, value->length);
763 cstr[value->length] = '\0';
764
765 if (ini_entry->on_modify
766 && ini_entry->on_modify(ini_entry, cstr, value->length,
767 ini_entry->mh_arg1, ini_entry->mh_arg2,
768 ini_entry->mh_arg3, ZEND_INI_STAGE_ACTIVATE
769 TSRMLS_CC)
770 != SUCCESS)
771 {
772 nxt_free(cstr);
773 return NXT_ERROR;
774 }
775
776 ini_entry->value = cstr;
777 ini_entry->value_length = value->length;
778 ini_entry->modifiable = type;
779
780 return NXT_OK;
781}
782
783#endif
784
785
786#ifdef NXT_PHP8
787
788static void
789nxt_php_disable_functions(nxt_str_t *str)
790{
791 char *p;
792
793 p = nxt_malloc(str->length + 1);
794 if (nxt_slow_path(p == NULL)) {
795 return;
796 }
797
798 nxt_memcpy(p, str->start, str->length);
799 p[str->length] = '\0';
800
801 zend_disable_functions(p);
802
803 nxt_free(p);
804}
805
806#endif
807
808
809static void
810nxt_php_disable(nxt_task_t *task, const char *type, nxt_str_t *value,
811 char **ptr, nxt_php_disable_t disable)
812{
813 char c, *p, *start;
814
815 p = nxt_malloc(value->length + 1);
816 if (nxt_slow_path(p == NULL)) {
817 return;
818 }
819
820 /*
821 * PHP frees this memory on module shutdown.
822 * See core_globals_dtor() for details.
823 */
824 *ptr = p;
825
826 nxt_memcpy(p, value->start, value->length);
827 p[value->length] = '\0';
828
829 start = p;
830
831 do {
832 c = *p;
833
834 if (c == ' ' || c == ',' || c == '\0') {
835
836 if (p != start) {
837 *p = '\0';
838
839#ifdef NXT_PHP7
840 if (disable(start, p - start)
841#else
842 if (disable(start, p - start TSRMLS_CC)
843#endif
844 != SUCCESS)
845 {
846 nxt_log(task, NXT_LOG_ERR,
847 "PHP: failed to disable \"%s\": no such %s",
848 start, type);
849 }
850 }
851
852 start = p + 1;
853 }
854
855 p++;
856
857 } while (c != '\0');
858}
859
860
861static nxt_int_t
862nxt_php_dirname(const nxt_str_t *file, nxt_str_t *dir)
863{
864 size_t length;
865
866 if (file->length == 0 || file->start[0] != '/') {
867 nxt_unit_alert(NULL, "php_dirname: invalid file name "
868 "(not starts from '/')");
869 return NXT_ERROR;
870 }
871
872 length = file->length;
873
874 while (file->start[length - 1] != '/') {
875 length--;
876 }
877
878 dir->length = length;
879 dir->start = nxt_malloc(length + 1);
880 if (nxt_slow_path(dir->start == NULL)) {
881 return NXT_ERROR;
882 }
883
884 nxt_memcpy(dir->start, file->start, length);
885
886 dir->start[length] = '\0';
887
888 return NXT_OK;
889}
890
891
892static void
893nxt_php_str_trim_trail(nxt_str_t *str, u_char t)
894{
895 while (str->length > 0 && str->start[str->length - 1] == t) {
896 str->length--;
897 }
898
899 str->start[str->length] = '\0';
900}
901
902
903static void
904nxt_php_str_trim_lead(nxt_str_t *str, u_char t)
905{
906 while (str->length > 0 && str->start[0] == t) {
907 str->length--;
908 str->start++;
909 }
910}
911
912
913nxt_inline u_char *
914nxt_realpath(const void *c)
915{
916 return (u_char *) realpath(c, NULL);
917}
918
919
920static void
921nxt_php_request_handler(nxt_unit_request_info_t *req)
922{
923 nxt_php_target_t *target;
924 nxt_php_run_ctx_t ctx;
925 nxt_unit_request_t *r;
926
927 r = req->request;
928 target = &nxt_php_targets[r->app_target];
929
930 nxt_memzero(&ctx, sizeof(ctx));
931
932 ctx.req = req;
933 ctx.root = &target->root;
934 ctx.index = &target->index;
935
936 if (target->script_filename.length == 0) {
937 nxt_php_dynamic_request(&ctx, r);
938 return;
939 }
940
941 ctx.script_filename = target->script_filename;
942 ctx.script_dirname = target->script_dirname;
943 ctx.script_name = target->script_name;
944
945 ctx.chdir = (r->app_target != nxt_php_last_target);
946
947 nxt_php_execute(&ctx, r);
948
949 nxt_php_last_target = ctx.chdir ? -1 : r->app_target;
950}
951
952
953static void
954nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r)
955{
956 u_char *p;
957 nxt_str_t path, script_name;
958 nxt_int_t ret;
959
960 path.length = r->path_length;
961 path.start = nxt_unit_sptr_get(&r->path);
962
963 nxt_str_null(&script_name);
964
965 ctx->path_info.start = (u_char *) strstr((char *) path.start, ".php/");
966 if (ctx->path_info.start != NULL) {
967 ctx->path_info.start += 4;
968 path.length = ctx->path_info.start - path.start;
969
970 ctx->path_info.length = r->path_length - path.length;
971
972 } else if (path.start[path.length - 1] == '/') {
973 script_name = *ctx->index;
974
975 } else {
976 if (nxt_slow_path(path.length < 4
977 || nxt_memcmp(path.start + (path.length - 4),
978 ".php", 4)))
979 {
980 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
981
982 return;
983 }
984 }
985
986 ctx->script_filename.length = ctx->root->length
987 + path.length
988 + script_name.length;
989
990 p = nxt_malloc(ctx->script_filename.length + 1);
991 if (nxt_slow_path(p == NULL)) {
992 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
993
994 return;
995 }
996
997 ctx->script_filename.start = p;
998
999 ctx->script_name.length = path.length + script_name.length;
1000 ctx->script_name.start = p + ctx->root->length;
1001
1002 p = nxt_cpymem(p, ctx->root->start, ctx->root->length);
1003 p = nxt_cpymem(p, path.start, path.length);
1004
1005 if (script_name.length > 0) {
1006 p = nxt_cpymem(p, script_name.start, script_name.length);
1007 }
1008
1009 *p = '\0';
1010
1011 ctx->chdir = 1;
1012
1013 ret = nxt_php_dirname(&ctx->script_filename, &ctx->script_dirname);
1014 if (nxt_slow_path(ret != NXT_OK)) {
1015 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
1016 nxt_free(ctx->script_filename.start);
1017
1018 return;
1019 }
1020
1021 nxt_php_execute(ctx, r);
1022
1023 nxt_free(ctx->script_filename.start);
1024 nxt_free(ctx->script_dirname.start);
1025
1026 nxt_php_last_target = -1;
1027}
1028
1029
1030static void
1031nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r)
1032{
1033#if (PHP_VERSION_ID < 50600)
1034 void *read_post;
1035#endif
1036 nxt_unit_field_t *f;
1037 zend_file_handle file_handle;
1038
1039 nxt_unit_req_debug(ctx->req, "PHP execute script %s",
1040 ctx->script_filename.start);
1041
1042 SG(server_context) = ctx;
1043 SG(options) |= SAPI_OPTION_NO_CHDIR;
1044 SG(request_info).request_uri = nxt_unit_sptr_get(&r->target);
1045 SG(request_info).request_method = nxt_unit_sptr_get(&r->method);
1046
1047 SG(request_info).proto_num = 1001;
1048
1049 SG(request_info).query_string = r->query.offset
1050 ? nxt_unit_sptr_get(&r->query) : NULL;
1051 SG(request_info).content_length = r->content_length;
1052
1053 if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
1054 f = r->fields + r->content_type_field;
1055
1056 SG(request_info).content_type = nxt_unit_sptr_get(&f->value);
1057 }
1058
1059 if (r->cookie_field != NXT_UNIT_NONE_FIELD) {
1060 f = r->fields + r->cookie_field;
1061
1062 ctx->cookie = nxt_unit_sptr_get(&f->value);
1063 }
1064
1065 if (r->authorization_field != NXT_UNIT_NONE_FIELD) {
1066 f = r->fields + r->authorization_field;
1067
1068#ifdef NXT_PHP7
1069 php_handle_auth_data(nxt_unit_sptr_get(&f->value));
1070#else
1071 php_handle_auth_data(nxt_unit_sptr_get(&f->value) TSRMLS_CC);
1072#endif
1073
1074 } else {
1075 SG(request_info).auth_digest = NULL;
1076 SG(request_info).auth_user = NULL;
1077 SG(request_info).auth_password = NULL;
1078 }
1079
1080 SG(sapi_headers).http_response_code = 200;
1081
1082 SG(request_info).path_translated = NULL;
1083
1084#ifdef NXT_PHP7
1085 if (nxt_slow_path(php_request_startup() == FAILURE)) {
1086#else
1087 if (nxt_slow_path(php_request_startup(TSRMLS_C) == FAILURE)) {
1088#endif
1089 nxt_unit_req_debug(ctx->req, "php_request_startup() failed");
1090
1091 nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR);
1092 return;
1093 }
1094
1095 if (ctx->chdir) {
1096 ctx->chdir = 0;
1097 nxt_php_vcwd_chdir(ctx->req, ctx->script_dirname.start);
1098 }
1099
1100 nxt_memzero(&file_handle, sizeof(file_handle));
1101
1102 file_handle.type = ZEND_HANDLE_FILENAME;
1103#if (PHP_VERSION_ID >= 80100)
1104 file_handle.filename = zend_string_init((char *) ctx->script_filename.start,
1105 ctx->script_filename.length, 0);
1106 file_handle.primary_script = 1;
1107#else
1108 file_handle.filename = (char *) ctx->script_filename.start;
1109#endif
1110
1111 php_execute_script(&file_handle TSRMLS_CC);
1112
1113#if (PHP_VERSION_ID >= 80100)
1114 zend_destroy_file_handle(&file_handle);
1115#endif
1116
1117 /* Prevention of consuming possible unread request body. */
1118#if (PHP_VERSION_ID < 50600)
1119 read_post = sapi_module.read_post;
1120 sapi_module.read_post = NULL;
1121#else
1122 SG(post_read) = 1;
1123#endif
1124
1125 php_request_shutdown(NULL);
1126
1127 if (ctx->req != NULL) {
1128 nxt_unit_request_done(ctx->req, NXT_UNIT_OK);
1129 }
1130
1131#if (PHP_VERSION_ID < 50600)
1132 sapi_module.read_post = read_post;
1133#endif
1134}
1135
1136
1137nxt_inline void
1138nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, u_char *dir)
1139{
1140 if (nxt_slow_path(VCWD_CHDIR((char *) dir) != 0)) {
1141 nxt_unit_req_alert(req, "VCWD_CHDIR(%s) failed (%d: %s)",
1142 dir, errno, strerror(errno));
1143 }
1144}
1145
1146
1147static int
1148nxt_php_startup(sapi_module_struct *sapi_module)
1149{
1150 return php_module_startup(sapi_module, &nxt_php_unit_module, 1);
1151}
1152
1153
1154#ifdef NXT_PHP7
1155static size_t
1156nxt_php_unbuffered_write(const char *str, size_t str_length TSRMLS_DC)
1157#else
1158static int
1159nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC)
1160#endif
1161{
1162 int rc;
1163 nxt_php_run_ctx_t *ctx;
1164
1165 ctx = SG(server_context);
1166
1167 rc = nxt_unit_response_write(ctx->req, str, str_length);
1168 if (nxt_fast_path(rc == NXT_UNIT_OK)) {
1169 return str_length;
1170 }
1171
1172 php_handle_aborted_connection();
1173 return 0;
1174}
1175
1176
1177static int
1178nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
1179{
1180 int rc, fields_count;
1181 char *colon, *value;
1182 uint16_t status;
1183 uint32_t resp_size;
1184 nxt_php_run_ctx_t *ctx;
1185 sapi_header_struct *h;
1186 zend_llist_position zpos;
1187 nxt_unit_request_info_t *req;
1188
1189 ctx = SG(server_context);
1190 req = ctx->req;
1191
1192 nxt_unit_req_debug(req, "nxt_php_send_headers");
1193
1194 if (SG(request_info).no_headers == 1) {
1195 rc = nxt_unit_response_init(req, 200, 0, 0);
1196 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1197 return SAPI_HEADER_SEND_FAILED;
1198 }
1199
1200 return SAPI_HEADER_SENT_SUCCESSFULLY;
1201 }
1202
1203 resp_size = 0;
1204 fields_count = zend_llist_count(&sapi_headers->headers);
1205
1206 for (h = zend_llist_get_first_ex(&sapi_headers->headers, &zpos);
1207 h;
1208 h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos))
1209 {
1210 resp_size += h->header_len;
1211 }
1212
1213 status = SG(sapi_headers).http_response_code;
1214
1215 rc = nxt_unit_response_init(req, status, fields_count, resp_size);
1216 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1217 return SAPI_HEADER_SEND_FAILED;
1218 }
1219
1220 for (h = zend_llist_get_first_ex(&sapi_headers->headers, &zpos);
1221 h;
1222 h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos))
1223 {
1224 colon = memchr(h->header, ':', h->header_len);
1225 if (nxt_slow_path(colon == NULL)) {
1226 nxt_unit_req_warn(req, "colon not found in header '%.*s'",
1227 (int) h->header_len, h->header);
1228 continue;
1229 }
1230
1231 value = colon + 1;
1232 while(isspace(*value)) {
1233 value++;
1234 }
1235
1236 nxt_unit_response_add_field(req, h->header, colon - h->header,
1237 value,
1238 h->header_len - (value - h->header));
1239 }
1240
1241 rc = nxt_unit_response_send(req);
1242 if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1243 nxt_unit_req_debug(req, "failed to send response");
1244
1245 return SAPI_HEADER_SEND_FAILED;
1246 }
1247
1248 return SAPI_HEADER_SENT_SUCCESSFULLY;
1249}
1250
1251
1252#ifdef NXT_PHP7
1253static size_t
1254nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC)
1255#else
1256static int
1257nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC)
1258#endif
1259{
1260 nxt_php_run_ctx_t *ctx;
1261
1262 ctx = SG(server_context);
1263
1264 nxt_unit_req_debug(ctx->req, "nxt_php_read_post %d", (int) count_bytes);
1265
1266 return nxt_unit_request_read(ctx->req, buffer, count_bytes);
1267}
1268
1269
1270static char *
1271nxt_php_read_cookies(TSRMLS_D)
1272{
1273 nxt_php_run_ctx_t *ctx;
1274
1275 ctx = SG(server_context);
1276
1277 nxt_unit_req_debug(ctx->req, "nxt_php_read_cookies");
1278
1279 return ctx->cookie;
1280}
1281
1282
1283static void
1284nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
1285{
1286 const char *name;
1287 nxt_unit_field_t *f, *f_end;
1288 nxt_php_run_ctx_t *ctx;
1289 nxt_unit_request_t *r;
1290 nxt_unit_request_info_t *req;
1291
1292 ctx = SG(server_context);
1293
1294 req = ctx->req;
1295 r = req->request;
1296
1297 nxt_unit_req_debug(req, "nxt_php_register_variables");
1298
1299 php_register_variable_safe((char *) "SERVER_SOFTWARE",
1300 (char *) nxt_server.start,
1301 nxt_server.length, track_vars_array TSRMLS_CC);
1302
1303 nxt_php_set_sptr(req, "SERVER_PROTOCOL", &r->version, r->version_length,
1304 track_vars_array TSRMLS_CC);
1305
1306 /*
1307 * 'PHP_SELF'
1308 * The filename of the currently executing script, relative to the document
1309 * root. For instance, $_SERVER['PHP_SELF'] in a script at the address
1310 * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__
1311 * constant contains the full path and filename of the current (i.e.
1312 * included) file. If PHP is running as a command-line processor this
1313 * variable contains the script name since PHP 4.3.0. Previously it was not
1314 * available.
1315 */
1316
1317 if (ctx->path_info.length != 0) {
1318 nxt_php_set_sptr(req, "PHP_SELF", &r->path, r->path_length,
1319 track_vars_array TSRMLS_CC);
1320
1321 nxt_php_set_str(req, "PATH_INFO", &ctx->path_info,
1322 track_vars_array TSRMLS_CC);
1323
1324 } else {
1325 nxt_php_set_str(req, "PHP_SELF", &ctx->script_name,
1326 track_vars_array TSRMLS_CC);
1327 }
1328
1329 /*
1330 * 'SCRIPT_NAME'
1331 * Contains the current script's path. This is useful for pages which need
1332 * to point to themselves. The __FILE__ constant contains the full path and
1333 * filename of the current (i.e. included) file.
1334 */
1335
1336 nxt_php_set_str(req, "SCRIPT_NAME", &ctx->script_name,
1337 track_vars_array TSRMLS_CC);
1338
1339 /*
1340 * 'SCRIPT_FILENAME'
1341 * The absolute pathname of the currently executing script.
1342 */
1343
1344 nxt_php_set_str(req, "SCRIPT_FILENAME", &ctx->script_filename,
1345 track_vars_array TSRMLS_CC);
1346
1347 /*
1348 * 'DOCUMENT_ROOT'
1349 * The document root directory under which the current script is executing,
1350 * as defined in the server's configuration file.
1351 */
1352
1353 nxt_php_set_str(req, "DOCUMENT_ROOT", ctx->root,
1354 track_vars_array TSRMLS_CC);
1355
1356 nxt_php_set_sptr(req, "REQUEST_METHOD", &r->method, r->method_length,
1357 track_vars_array TSRMLS_CC);
1358 nxt_php_set_sptr(req, "REQUEST_URI", &r->target, r->target_length,
1359 track_vars_array TSRMLS_CC);
1360 nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length,
1361 track_vars_array TSRMLS_CC);
1362
1363 nxt_php_set_sptr(req, "REMOTE_ADDR", &r->remote, r->remote_length,
1364 track_vars_array TSRMLS_CC);
1365 nxt_php_set_sptr(req, "SERVER_ADDR", &r->local, r->local_length,
1366 track_vars_array TSRMLS_CC);
1367
1368 nxt_php_set_sptr(req, "SERVER_NAME", &r->server_name, r->server_name_length,
1369 track_vars_array TSRMLS_CC);
1370 nxt_php_set_cstr(req, "SERVER_PORT", "80", 2, track_vars_array TSRMLS_CC);
1371
1372 if (r->tls) {
1373 nxt_php_set_cstr(req, "HTTPS", "on", 2, track_vars_array TSRMLS_CC);
1374 }
1375
1376 f_end = r->fields + r->fields_count;
1377 for (f = r->fields; f < f_end; f++) {
1378 name = nxt_unit_sptr_get(&f->name);
1379
1380 nxt_php_set_sptr(req, name, &f->value, f->value_length,
1381 track_vars_array TSRMLS_CC);
1382 }
1383
1384 if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
1385 f = r->fields + r->content_length_field;
1386
1387 nxt_php_set_sptr(req, "CONTENT_LENGTH", &f->value, f->value_length,
1388 track_vars_array TSRMLS_CC);
1389 }
1390
1391 if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
1392 f = r->fields + r->content_type_field;
1393
1394 nxt_php_set_sptr(req, "CONTENT_TYPE", &f->value, f->value_length,
1395 track_vars_array TSRMLS_CC);
1396 }
1397}
1398
1399
1400static void
1401nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name,
1402 nxt_unit_sptr_t *v, uint32_t len, zval *track_vars_array TSRMLS_DC)
1403{
1404 char *str;
1405
1406 str = nxt_unit_sptr_get(v);
1407
1408 nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, str);
1409
1410 php_register_variable_safe((char *) name, str, len,
1411 track_vars_array TSRMLS_CC);
1412}
1413
1414
1415nxt_inline void
1416nxt_php_set_str(nxt_unit_request_info_t *req, const char *name,
1417 nxt_str_t *s, zval *track_vars_array TSRMLS_DC)
1418{
1419 nxt_php_set_cstr(req, name, (char *) s->start, s->length,
1420 track_vars_array TSRMLS_CC);
1421}
1422
1423
1424#ifdef NXT_PHP7
1425
1426static void *
1427nxt_php_hash_str_find_ptr(const HashTable *ht, const nxt_str_t *str)
1428{
1429 return zend_hash_str_find_ptr(ht, (const char *) str->start, str->length);
1430}
1431
1432#else
1433
1434static void *
1435nxt_php_hash_str_find_ptr(const HashTable *ht, const nxt_str_t *str)
1436{
1437 int ret;
1438 void *entry;
1439 char buf[256];
1440
1441 if (nxt_slow_path(str->length >= (sizeof(buf) - 1))) {
1442 return NULL;
1443 }
1444
1445 nxt_memcpy(buf, str->start, str->length);
1446 buf[str->length] = '\0';
1447
1448 ret = zend_hash_find(ht, buf, str->length + 1, &entry);
1449 if (nxt_fast_path(ret == SUCCESS)) {
1450 return entry;
1451 }
1452
1453 return NULL;
1454}
1455
1456#endif
1457
1458
1459static void
1460nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
1461 const char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC)
1462{
1463 if (nxt_slow_path(cstr == NULL)) {
1464 return;
1465 }
1466
1467 nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, cstr);
1468
1469 php_register_variable_safe((char *) name, (char *) cstr, len,
1470 track_vars_array TSRMLS_CC);
1471}
1472
1473
1474#if NXT_PHP8
1475static void
1476nxt_php_log_message(const char *message, int syslog_type_int)
1477#else
1478#ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
1479static void
1480nxt_php_log_message(char *message, int syslog_type_int)
1481#else
1482static void
1483nxt_php_log_message(char *message TSRMLS_DC)
1484#endif
1485#endif
1486{
1487 nxt_php_run_ctx_t *ctx;
1488
1489 ctx = SG(server_context);
1490
1491 if (ctx != NULL) {
1492 nxt_unit_req_log(ctx->req, NXT_UNIT_LOG_NOTICE,
1493 "php message: %s", message);
1494
1495 } else {
1496 nxt_unit_log(nxt_php_unit_ctx, NXT_UNIT_LOG_NOTICE,
1497 "php message: %s", message);
1498 }
1499}