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