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