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