xref: /unit/src/nxt_php_sapi.c (revision 0:a63ceefd6ab0)
1 
2 /*
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_application.h>
14 
15 
16 typedef struct {
17     size_t     max_name;
18 
19     nxt_str_t  *cookie;
20     nxt_str_t  *content_type;
21     nxt_str_t  *content_length;
22 
23     nxt_str_t  script;
24     nxt_str_t  query;
25 
26     size_t     script_name_len;
27 
28     off_t      content_length_n;
29 } nxt_php_ctx_t;
30 
31 
32 nxt_int_t nxt_php_init(nxt_thread_t *thr);
33 nxt_int_t nxt_php_request_init(nxt_app_request_t *r);
34 nxt_int_t nxt_php_request_header(nxt_app_request_t *r,
35     nxt_app_header_field_t *field);
36 nxt_int_t nxt_php_handler(nxt_app_request_t *r);
37 
38 
39 nxt_int_t nxt_python_init();
40 
41 
42 static nxt_int_t nxt_php_opts(nxt_log_t *log);
43 
44 
45 static int nxt_php_startup(sapi_module_struct *sapi_module);
46 static int nxt_php_send_headers(sapi_headers_struct *sapi_headers);
47 static char *nxt_php_read_cookies(void);
48 static void nxt_php_register_variables(zval *track_vars_array);
49 static void nxt_php_log_message(char *message);
50 
51 #define NXT_PHP7 1
52 
53 #ifdef NXT_PHP7
54 static size_t nxt_php_unbuffered_write(const char *str,
55     size_t str_length TSRMLS_DC);
56 static size_t nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC);
57 #else
58 static int nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC);
59 static int nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC);
60 #endif
61 
62 
63 static sapi_module_struct  nxt_php_sapi_module =
64 {
65     (char *) "cli-server",
66     (char *) "nginman",
67 
68     nxt_php_startup,             /* startup */
69     php_module_shutdown_wrapper, /* shutdown */
70 
71     NULL,                        /* activate */
72     NULL,                        /* deactivate */
73 
74     nxt_php_unbuffered_write,    /* unbuffered write */
75     NULL,                        /* flush */
76     NULL,                        /* get uid */
77     NULL,                        /* getenv */
78 
79     php_error,                   /* error handler */
80 
81     NULL,                        /* header handler */
82     nxt_php_send_headers,        /* send headers handler */
83     NULL,                        /* send header handler */
84 
85     nxt_php_read_post,           /* read POST data */
86     nxt_php_read_cookies,        /* read Cookies */
87 
88     nxt_php_register_variables,  /* register server variables */
89     nxt_php_log_message,         /* log message */
90 
91     NULL, NULL, NULL, NULL, NULL, NULL,
92     NULL, NULL, 0, 0, NULL, NULL, NULL,
93     NULL, NULL, NULL, 0, NULL, NULL, NULL
94 };
95 
96 
97 static nxt_str_t nxt_php_path;
98 static nxt_str_t nxt_php_root;
99 static nxt_str_t nxt_php_script;
100 
101 
102 nxt_int_t
103 nxt_php_init(nxt_thread_t *thr)
104 {
105     if (nxt_php_opts(thr->log)) {
106         return NXT_ERROR;
107     }
108 
109     sapi_startup(&nxt_php_sapi_module);
110     nxt_php_startup(&nxt_php_sapi_module);
111 
112     return NXT_OK;
113 }
114 
115 
116 static nxt_int_t
117 nxt_php_opts(nxt_log_t *log)
118 {
119     char        **argv;
120     u_char      *p;
121     nxt_uint_t  i;
122 
123     argv = nxt_process_argv;
124 
125     while (*argv != NULL) {
126         p = (u_char *) *argv++;
127 
128         if (nxt_strcmp(p, "--php") == 0) {
129             if (*argv == NULL) {
130                 nxt_log_error(NXT_LOG_ERR, log,
131                               "no argument for option \"--php\"");
132                 return NXT_ERROR;
133             }
134 
135             p = (u_char *) *argv;
136 
137             nxt_php_root.data = p;
138             nxt_php_path.data = p;
139 
140             i = 0;
141 
142             for ( /* void */ ; p[i] != '\0'; i++) {
143                 if (p[i] == '/') {
144                     nxt_php_script.data = &p[i];
145                     nxt_php_root.len = i;
146                 }
147             }
148 
149             nxt_php_path.len = i;
150             nxt_php_script.len = i - nxt_php_root.len;
151 
152             nxt_log_error(NXT_LOG_INFO, log, "php script \"%V\" root: \"%V\"",
153                           &nxt_php_script, &nxt_php_root);
154 
155             return NXT_OK;
156         }
157     }
158 
159     nxt_log_error(NXT_LOG_ERR, log, "no option \"--php\" specified");
160 
161     return NXT_ERROR;
162 }
163 
164 
165 nxt_int_t
166 nxt_php_request_init(nxt_app_request_t *r)
167 {
168     nxt_php_ctx_t  *ctx;
169 
170     ctx = nxt_mem_zalloc(r->mem_pool, sizeof(nxt_php_ctx_t));
171     if (nxt_slow_path(ctx == NULL)) {
172         return NXT_ERROR;
173     }
174 
175     r->ctx = ctx;
176 
177     return NXT_OK;
178 }
179 
180 
181 
182 nxt_int_t
183 nxt_php_request_header(nxt_app_request_t *r, nxt_app_header_field_t *field)
184 {
185     nxt_php_ctx_t  *ctx;
186 
187     static const u_char cookie[6] = "Cookie";
188     static const u_char content_length[14] = "Content-Length";
189     static const u_char content_type[12] = "Content-Type";
190 
191     ctx = r->ctx;
192 
193     ctx->max_name = nxt_max(ctx->max_name, field->name.len);
194 
195     if (field->name.len == sizeof(cookie)
196         && nxt_memcasecmp(field->name.data, cookie, sizeof(cookie)) == 0)
197     {
198         ctx->cookie = &field->value;
199 
200     } else if (field->name.len == sizeof(content_length)
201                && nxt_memcasecmp(field->name.data, content_length,
202                                  sizeof(content_length)) == 0)
203     {
204         ctx->content_length = &field->value;
205         ctx->content_length_n = nxt_off_t_parse(field->value.data,
206                                                 field->value.len);
207 
208     } else if (field->name.len == sizeof(content_type)
209                && nxt_memcasecmp(field->name.data, content_type,
210                                  sizeof(content_type)) == 0)
211     {
212         ctx->content_type = &field->value;
213         field->value.data[field->value.len] = '\0';
214     }
215 
216     return NXT_OK;
217 }
218 
219 
220 #define ABS_MODE 1
221 
222 
223 #if !ABS_MODE
224 static const u_char root[] = "/home/vbart/Development/tests/php/wordpress";
225 #endif
226 
227 
228 nxt_int_t
229 nxt_php_handler(nxt_app_request_t *r)
230 {
231     u_char            *query;
232 #if !ABS_MODE
233     u_char            *p;
234 #endif
235     nxt_php_ctx_t     *ctx;
236     zend_file_handle  file_handle;
237 
238 #if ABS_MODE
239     if (nxt_php_path.len == 0) {
240         return NXT_ERROR;
241     }
242 #endif
243 
244     r->header.path.data[r->header.path.len] = '\0';
245     r->header.method.data[r->header.method.len] = '\0';
246 
247     ctx = r->ctx;
248 
249     query = nxt_memchr(r->header.path.data, '?', r->header.path.len);
250 
251     if (query != NULL) {
252         ctx->script_name_len = query - r->header.path.data;
253 
254         ctx->query.data = query + 1;
255         ctx->query.len = r->header.path.data + r->header.path.len
256                          - ctx->query.data;
257 
258     } else {
259         ctx->script_name_len = r->header.path.len;
260     }
261 
262 #if !ABS_MODE
263     ctx->script.len = sizeof(root) - 1 + ctx->script_name_len;
264     ctx->script.data = nxt_mem_nalloc(r->mem_pool, ctx->script.len + 1);
265 
266     if (nxt_slow_path(ctx->script.data == NULL)) {
267         return NXT_ERROR;
268     }
269 
270     p = nxt_cpymem(ctx->script.data, root, sizeof(root) - 1);
271     p = nxt_cpymem(p, r->header.path.data, ctx->script_name_len);
272     *p = '\0';
273 #endif
274 
275     SG(server_context) = r;
276     SG(request_info).request_uri = (char *) r->header.path.data;
277     SG(request_info).request_method = (char *) r->header.method.data;
278 
279     SG(request_info).proto_num = 1001;
280 
281     SG(request_info).query_string = (char *) ctx->query.data;
282     SG(request_info).content_length = ctx->content_length_n;
283 
284     if (ctx->content_type != NULL) {
285         SG(request_info).content_type = (char *) ctx->content_type->data;
286     }
287 
288     SG(sapi_headers).http_response_code = 200;
289 
290     SG(request_info).path_translated = NULL;
291 
292     file_handle.type = ZEND_HANDLE_FILENAME;
293 #if ABS_MODE
294     file_handle.filename = (char *) nxt_php_path.data;
295 #else
296     file_handle.filename = (char *) ctx->script.data;
297 #endif
298     file_handle.free_filename = 0;
299     file_handle.opened_path = NULL;
300 
301 #if ABS_MODE
302     nxt_log_debug(r->log, "run script %V in absolute mode", &nxt_php_path);
303 #else
304     nxt_log_debug(r->log, "run script %V", &ctx->script);
305 #endif
306 
307     if (nxt_slow_path(php_request_startup() == FAILURE)) {
308         return NXT_ERROR;
309     }
310 
311     php_execute_script(&file_handle TSRMLS_CC);
312     php_request_shutdown(NULL);
313 
314     return NXT_OK;
315 }
316 
317 
318 static int
319 nxt_php_startup(sapi_module_struct *sapi_module)
320 {
321    return php_module_startup(sapi_module, NULL, 0);
322 }
323 
324 
325 #ifdef NXT_PHP7
326 static size_t
327 nxt_php_unbuffered_write(const char *str, size_t str_length TSRMLS_DC)
328 #else
329 static int
330 nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC)
331 #endif
332 {
333     nxt_app_request_t  *r;
334 
335     r = SG(server_context);
336 
337     nxt_app_write(r, (u_char *) str, str_length);
338 
339     return str_length;
340 }
341 
342 
343 static int
344 nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
345 {
346     size_t               len;
347     nxt_app_request_t    *r;
348     sapi_header_struct   *h;
349     zend_llist_position  zpos;
350     u_char               *p, *status, buf[4096];
351 
352     static const u_char default_repsonse[]
353         = "HTTP/1.1 200 OK\r\n"
354           "Server: nginman/0.1\r\n"
355           "Content-Type: text/html; charset=UTF-8\r\n"
356           "Connection: close\r\n"
357           "\r\n";
358 
359     static const u_char default_headers[]
360         = "Server: nginman/0.1\r\n"
361           "Connection: close\r\n";
362 
363     r = SG(server_context);
364 
365     if (SG(request_info).no_headers == 1) {
366         nxt_app_write(r, default_repsonse, sizeof(default_repsonse) - 1);
367         return SAPI_HEADER_SENT_SUCCESSFULLY;
368     }
369 
370     if (SG(sapi_headers).http_status_line) {
371         status = (u_char *) SG(sapi_headers).http_status_line + 9;
372         len = nxt_strlen(status);
373 
374         p = nxt_cpymem(buf, "HTTP/1.1 ", sizeof("HTTP/1.1 ") - 1);
375         p = nxt_cpymem(p, status, len);
376         *p++ = '\r'; *p++ = '\n';
377 
378     } else if (SG(sapi_headers).http_response_code) {
379         p = nxt_cpymem(buf, "HTTP/1.1 ", sizeof("HTTP/1.1 ") - 1);
380         p = nxt_sprintf(p, buf + sizeof(buf), "%03d",
381                         SG(sapi_headers).http_response_code);
382         *p++ = '\r'; *p++ = '\n';
383 
384     } else {
385         p = nxt_cpymem(buf, "HTTP/1.1 200 OK\r\n",
386                        sizeof("HTTP/1.1 200 OK\r\n") - 1);
387     }
388 
389     p = nxt_cpymem(p, default_headers, sizeof(default_headers) - 1);
390 
391     h = zend_llist_get_first_ex(&sapi_headers->headers, &zpos);
392 
393     while (h) {
394         p = nxt_cpymem(p, h->header, h->header_len);
395         *p++ = '\r'; *p++ = '\n';
396 
397         h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos);
398     }
399 
400     *p++ = '\r'; *p++ = '\n';
401 
402     nxt_app_write(r, buf, p - buf);
403 
404     return SAPI_HEADER_SENT_SUCCESSFULLY;
405 }
406 
407 
408 #ifdef NXT_PHP7
409 static size_t
410 nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC)
411 #else
412 static int
413 nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC)
414 #endif
415 {
416     off_t              rest;
417     size_t             size;
418     ssize_t            n;
419     nxt_err_t          err;
420     nxt_php_ctx_t      *ctx;
421     nxt_app_request_t  *r;
422 
423     r = SG(server_context);
424     ctx = r->ctx;
425 
426     rest = ctx->content_length_n - SG(read_post_bytes);
427 
428     nxt_log_debug(r->log, "nxt_php_read_post %O", rest);
429 
430     if (rest == 0) {
431         return 0;
432     }
433 
434     size = 0;
435 #ifdef NXT_PHP7
436     count_bytes = (size_t) nxt_min(rest, (off_t) count_bytes);
437 #else
438     count_bytes = (uint) nxt_min(rest, (off_t) count_bytes);
439 #endif
440 
441     if (r->body_preread.len != 0) {
442         size = nxt_min(r->body_preread.len, count_bytes);
443 
444         nxt_memcpy(buffer, r->body_preread.data, size);
445 
446         r->body_preread.len -= size;
447         r->body_preread.data += size;
448 
449         if (size == count_bytes) {
450             return size;
451         }
452     }
453 
454     nxt_log_debug(r->log, "recv %z", (size_t) count_bytes - size);
455 
456     n = recv(r->event_conn->socket.fd, buffer + size, count_bytes - size, 0);
457 
458     if (nxt_slow_path(n <= 0)) {
459         err = (n == 0) ? 0 : nxt_socket_errno;
460 
461         nxt_log_error(NXT_LOG_ERR, r->log, "recv(%d, %uz) failed %E",
462                       r->event_conn->socket.fd, (size_t) count_bytes - size,
463                       err);
464 
465         return size;
466     }
467 
468     return size + n;
469 }
470 
471 
472 static char *
473 nxt_php_read_cookies(TSRMLS_D)
474 {
475     u_char             *p;
476     nxt_php_ctx_t      *ctx;
477     nxt_app_request_t  *r;
478 
479     r = SG(server_context);
480     ctx = r->ctx;
481 
482     if (ctx->cookie == NULL) {
483         return NULL;
484     }
485 
486     p = ctx->cookie->data;
487     p[ctx->cookie->len] = '\0';
488 
489     return (char *) p;
490 }
491 
492 
493 static void
494 nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
495 {
496     u_char                  *var, *p, ch;
497     nxt_uint_t              i, n;
498     nxt_php_ctx_t           *ctx;
499     nxt_app_request_t       *r;
500     nxt_app_header_field_t  *fld;
501 
502     static const u_char prefix[5] = "HTTP_";
503 
504     r = SG(server_context);
505     ctx = r->ctx;
506 
507     nxt_log_debug(r->log, "php register variables");
508 
509     php_register_variable_safe((char *) "PHP_SELF",
510                                (char *) r->header.path.data,
511                                ctx->script_name_len, track_vars_array TSRMLS_CC);
512 
513     php_register_variable_safe((char *) "SERVER_PROTOCOL",
514                                (char *) r->header.version.data,
515                                r->header.version.len, track_vars_array TSRMLS_CC);
516 
517 #if ABS_MODE
518     php_register_variable_safe((char *) "SCRIPT_NAME",
519                                (char *) nxt_php_script.data,
520                                nxt_php_script.len, track_vars_array TSRMLS_CC);
521 
522     php_register_variable_safe((char *) "SCRIPT_FILENAME",
523                                (char *) nxt_php_path.data,
524                                nxt_php_path.len, track_vars_array TSRMLS_CC);
525 
526     php_register_variable_safe((char *) "DOCUMENT_ROOT",
527                                (char *) nxt_php_root.data,
528                                nxt_php_root.len, track_vars_array TSRMLS_CC);
529 #else
530     php_register_variable_safe((char *) "SCRIPT_NAME",
531                                (char *) r->header.path.data,
532                                ctx->script_name_len, track_vars_array TSRMLS_CC);
533 
534     php_register_variable_safe((char *) "SCRIPT_FILENAME",
535                                (char *) ctx->script.data, ctx->script.len,
536                                track_vars_array TSRMLS_CC);
537 
538     php_register_variable_safe((char *) "DOCUMENT_ROOT", (char *) root,
539                                sizeof(root) - 1, track_vars_array TSRMLS_CC);
540 #endif
541 
542     php_register_variable_safe((char *) "REQUEST_METHOD",
543                                (char *) r->header.method.data,
544                                r->header.method.len, track_vars_array TSRMLS_CC);
545 
546     php_register_variable_safe((char *) "REQUEST_URI",
547                                (char *) r->header.path.data,
548                                r->header.path.len, track_vars_array TSRMLS_CC);
549 
550     if (ctx->query.data != NULL) {
551         php_register_variable_safe((char *) "QUERY_STRING",
552                                    (char *) ctx->query.data,
553                                    ctx->query.len, track_vars_array TSRMLS_CC);
554     }
555 
556     if (ctx->content_type != NULL) {
557         php_register_variable_safe((char *) "CONTENT_TYPE",
558                                    (char *) ctx->content_type->data,
559                                    ctx->content_type->len, track_vars_array TSRMLS_CC);
560     }
561 
562     if (ctx->content_length != NULL) {
563         php_register_variable_safe((char *) "CONTENT_LENGTH",
564                                    (char *) ctx->content_length->data,
565                                    ctx->content_length->len, track_vars_array TSRMLS_CC);
566     }
567 
568     var = nxt_mem_nalloc(r->mem_pool, sizeof(prefix) + ctx->max_name + 1);
569 
570     if (nxt_slow_path(var == NULL)) {
571         return;
572     }
573 
574     nxt_memcpy(var, prefix, sizeof(prefix));
575 
576     for (i = 0; i < r->header.fields_num; i++) {
577         fld = &r->header.fields[i];
578         p = var + sizeof(prefix);
579 
580         for (n = 0; n < fld->name.len; n++, p++) {
581 
582             ch = fld->name.data[n];
583 
584             if (ch >= 'a' && ch <= 'z') {
585                 *p = ch & ~0x20;
586                 continue;
587             }
588 
589             if (ch == '-') {
590                 *p = '_';
591                 continue;
592             }
593 
594             *p = ch;
595         }
596 
597         *p = '\0';
598 
599         php_register_variable_safe((char *) var, (char *) fld->value.data,
600                                    fld->value.len, track_vars_array TSRMLS_CC);
601     }
602 
603     return;
604 }
605 
606 
607 static void
608 nxt_php_log_message(char *message TSRMLS_DC)
609 {
610     return;
611 }
612