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