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