php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
php_cli_server.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Moriyoshi Koizumi <moriyoshi@php.net> |
14 | Xinchen Hui <laruence@php.net> |
15 +----------------------------------------------------------------------+
16*/
17
18#include <stdio.h>
19#include <stdlib.h>
20#include <fcntl.h>
21#include <assert.h>
22#include <signal.h>
23
24#ifdef PHP_WIN32
25# include <process.h>
26# include <io.h>
27# include "win32/console.h"
28# include "win32/time.h"
29# include "win32/signal.h"
30# include "win32/php_registry.h"
31# include <sys/timeb.h>
32#else
33# include <php_config.h>
34#endif
35
36#ifdef __riscos__
37#include <unixlib/local.h>
38#endif
39
40#ifdef HAVE_SYS_TIME_H
41#include <sys/time.h>
42#endif
43#ifdef HAVE_UNISTD_H
44#include <unistd.h>
45#endif
46
47#include <signal.h>
48#include <locale.h>
49
50#ifdef HAVE_DLFCN_H
51#include <dlfcn.h>
52#endif
53
54#ifdef HAVE_PRCTL
55# include <sys/prctl.h>
56#endif
57
58#ifdef HAVE_PROCCTL
59# include <sys/procctl.h>
60#endif
61
62#include "SAPI.h"
63#include "php.h"
64#include "php_ini.h"
65#include "php_main.h"
66#include "php_globals.h"
67#include "php_variables.h"
68#include "zend_hash.h"
69#include "zend_modules.h"
70#include "fopen_wrappers.h"
71#include "http_status_codes.h"
72
73#include "zend_compile.h"
74#include "zend_execute.h"
75#include "zend_highlight.h"
76#include "zend_exceptions.h"
77
78#include "php_getopt.h"
79
80#ifndef PHP_WIN32
81# define php_select(m, r, w, e, t) select(m, r, w, e, t)
82# define SOCK_EINVAL EINVAL
83# define SOCK_EAGAIN EAGAIN
84# define SOCK_EINTR EINTR
85# define SOCK_EADDRINUSE EADDRINUSE
86#else
87# include "win32/select.h"
88# define SOCK_EINVAL WSAEINVAL
89# define SOCK_EAGAIN WSAEWOULDBLOCK
90# define SOCK_EINTR WSAEINTR
91# define SOCK_EADDRINUSE WSAEADDRINUSE
92#endif
93
94#include "ext/standard/file.h" /* for php_set_sock_blocking() :-( */
95#include "zend_smart_str.h"
96#include "ext/standard/html.h"
97#include "ext/standard/url.h" /* for php_raw_url_decode() */
98#include "ext/date/php_date.h" /* for php_format_date() */
99#include "php_network.h"
100
101#include "php_http_parser.h"
102#include "php_cli_server.h"
104#include "mime_type_map.h"
105
108
109#define OUTPUT_NOT_CHECKED -1
110#define OUTPUT_IS_TTY 1
111#define OUTPUT_NOT_TTY 0
112
113#ifdef HAVE_FORK
114# include <sys/wait.h>
115static pid_t php_cli_server_master;
116static pid_t *php_cli_server_workers;
117static zend_long php_cli_server_workers_max;
118#endif
119
120static zend_string* cli_concat_persistent_zstr_with_char(zend_string *old_str, const char *at, size_t length);
121
129
150
162
167
171
189
205
210
211static const php_cli_server_http_response_status_code_pair template_map[] = {
212 { 400, "<h1>%s</h1><p>Your browser sent a request that this server could not understand.</p>" },
213 { 404, "<h1>%s</h1><p>The requested resource <code class=\"url\">%s</code> was not found on this server.</p>" },
214 { 405, "<h1>%s</h1><p>Requested method not allowed.</p>" },
215 { 500, "<h1>%s</h1><p>The server is temporarily unavailable.</p>" },
216 { 501, "<h1>%s</h1><p>Request method not supported.</p>" }
217};
218
219#define PHP_CLI_SERVER_LOG_PROCESS 1
220#define PHP_CLI_SERVER_LOG_ERROR 2
221#define PHP_CLI_SERVER_LOG_MESSAGE 3
222
223static int php_cli_server_log_level = 3;
224
225#if defined(HAVE_UNISTD_H) || defined(PHP_WIN32)
226static int php_cli_output_is_tty = OUTPUT_NOT_CHECKED;
227#endif
228
229static const char php_cli_server_request_error_unexpected_eof[] = "Unexpected EOF";
230
231static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len);
232static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len);
233static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk);
234static void php_cli_server_logf(int type, const char *format, ...);
235static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message);
236
238
239/* {{{ static char php_cli_server_css[]
240 * copied from ext/standard/info.c
241 */
242static const char php_cli_server_css[] = "<style>\n" \
243 "body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }\n" \
244 "h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }\n" \
245 "h1, p { padding-left: 10px; }\n" \
246 "code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}\n" \
247 "</style>\n";
248/* }}} */
249
250#ifdef PHP_WIN32
251static bool php_cli_server_get_system_time(char *buf) {
252 struct _timeb system_time;
253 errno_t err;
254
255 if (buf == NULL) {
256 return false;
257 }
258
259 _ftime(&system_time);
260 err = ctime_s(buf, 52, &(system_time.time) );
261 if (err) {
262 return false;
263 }
264 return true;
265}
266#else
267static bool php_cli_server_get_system_time(char *buf) {
268 struct timeval tv;
269 struct tm tm;
270
272
273 if (!php_localtime_r(&tv.tv_sec, &tm)) {
274 return false;
275 }
276 return php_asctime_r(&tm, buf) != NULL;
277}
278#endif
279
280/* Destructor for php_cli_server_request->headers, this frees header value */
281static void cli_header_value_dtor(zval *zv) /* {{{ */
282{
283 zend_string_release_ex(Z_STR_P(zv), /* persistent */ true);
284} /* }}} */
285
286static char *get_last_error(void) /* {{{ */
287{
288 return pestrdup(strerror(errno), 1);
289} /* }}} */
290
291static int status_comp(const void *a, const void *b) /* {{{ */
292{
295
296 if (pa->code < pb->code) {
297 return -1;
298 } else if (pa->code > pb->code) {
299 return 1;
300 }
301
302 return 0;
303} /* }}} */
304
305static const char *get_status_string(int code) /* {{{ */
306{
307 http_response_status_code_pair needle = {code, NULL},
308 *result = NULL;
309
310 result = bsearch(&needle, http_status_map, http_status_map_len, sizeof(needle), status_comp);
311
312 if (result) {
313 return result->str;
314 }
315
316 /* Returning NULL would require complicating append_http_status_line() to
317 * not segfault in that case, so let's just return a placeholder, since RFC
318 * 2616 requires a reason phrase. This is basically what a lot of other Web
319 * servers do in this case anyway. */
320 return "Unknown Status Code";
321} /* }}} */
322
323static const char *get_template_string(int code) /* {{{ */
324{
325 size_t e = (sizeof(template_map) / sizeof(php_cli_server_http_response_status_code_pair));
326 size_t s = 0;
327
328 while (e != s) {
329 size_t c = MIN((e + s + 1) / 2, e - 1);
330 int d = template_map[c].code;
331 if (d > code) {
332 e = c;
333 } else if (d < code) {
334 s = c;
335 } else {
336 return template_map[c].str;
337 }
338 }
339 return NULL;
340} /* }}} */
341
342static void append_http_status_line(smart_str *buffer, int protocol_version, int response_code, bool persistent) /* {{{ */
343{
344 if (!response_code) {
345 response_code = 200;
346 }
347 smart_str_appendl_ex(buffer, "HTTP", 4, persistent);
348 smart_str_appendc_ex(buffer, '/', persistent);
349 smart_str_append_long_ex(buffer, protocol_version / 100, persistent);
350 smart_str_appendc_ex(buffer, '.', persistent);
351 smart_str_append_long_ex(buffer, protocol_version % 100, persistent);
352 smart_str_appendc_ex(buffer, ' ', persistent);
353 smart_str_append_long_ex(buffer, response_code, persistent);
354 smart_str_appendc_ex(buffer, ' ', persistent);
355 smart_str_appends_ex(buffer, get_status_string(response_code), persistent);
356 smart_str_appendl_ex(buffer, "\r\n", 2, persistent);
357} /* }}} */
358
359static void append_essential_headers(smart_str* buffer, php_cli_server_client *client, bool persistent, sapi_headers_struct *sapi_headers) /* {{{ */
360{
361 zval *val;
362 struct timeval tv = {0};
363 bool append_date_header = true;
364
365 if (sapi_headers != NULL) {
368 while (h) {
369 if (h->header_len > strlen("Date:")) {
370 if (strncasecmp(h->header, "Date:", strlen("Date:")) == 0) {
371 append_date_header = false;
372 break;
373 }
374 }
376 }
377 }
378
379 if (NULL != (val = zend_hash_find(&client->request.headers, ZSTR_KNOWN(ZEND_STR_HOST)))) {
380 smart_str_appends_ex(buffer, "Host: ", persistent);
381 smart_str_append_ex(buffer, Z_STR_P(val), persistent);
382 smart_str_appends_ex(buffer, "\r\n", persistent);
383 }
384
385 if (append_date_header && !gettimeofday(&tv, NULL)) {
386 zend_string *dt = php_format_date("D, d M Y H:i:s", sizeof("D, d M Y H:i:s") - 1, tv.tv_sec, 0);
387 smart_str_appends_ex(buffer, "Date: ", persistent);
388 smart_str_append_ex(buffer, dt, persistent);
389 smart_str_appends_ex(buffer, " GMT\r\n", persistent);
391 }
392
393 smart_str_appendl_ex(buffer, "Connection: close\r\n", sizeof("Connection: close\r\n") - 1, persistent);
394} /* }}} */
395
396static const char *get_mime_type(const php_cli_server *server, const char *ext, size_t ext_len) /* {{{ */
397{
398 char *ret;
399 ALLOCA_FLAG(use_heap)
400 char *ext_lower = do_alloca(ext_len + 1, use_heap);
401 zend_str_tolower_copy(ext_lower, ext, ext_len);
402 ret = zend_hash_str_find_ptr(&server->extension_mime_types, ext_lower, ext_len);
403 free_alloca(ext_lower, use_heap);
404 return (const char*)ret;
405} /* }}} */
406
408{
410
414
415 client = SG(server_context);
416
417 /* Need to duplicate the header HashTable */
418 RETURN_ARR(zend_array_dup(&client->request.headers_original_case));
419}
420/* }}} */
421
422static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{{ */
423{
424 char *s, *p;
426 ALLOCA_FLAG(use_heap)
427
428 if (h->header_len > 0) {
429 p = strchr(h->header, ':');
430 len = p - h->header;
431 if (p && (len > 0)) {
432 while (len > 0 && (h->header[len-1] == ' ' || h->header[len-1] == '\t')) {
433 len--;
434 }
435 if (len) {
436 s = do_alloca(len + 1, use_heap);
437 memcpy(s, h->header, len);
438 s[len] = 0;
439 do {
440 p++;
441 } while (*p == ' ' || *p == '\t');
442 add_assoc_stringl_ex(return_value, s, (uint32_t)len, p, h->header_len - (p - h->header));
443 free_alloca(s, use_heap);
444 }
445 }
446 }
447}
448/* }}} */
449
451{
454 }
455
457 zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t)add_response_header, return_value);
458}
459/* }}} */
460
461/* {{{ cli_server module */
462
463static void cli_server_init_globals(zend_cli_server_globals *cg)
464{
465 cg->color = 0;
466}
467
469 STD_PHP_INI_BOOLEAN("cli_server.color", "0", PHP_INI_ALL, OnUpdateBool, color, zend_cli_server_globals, cli_server_globals)
471
472static PHP_MINIT_FUNCTION(cli_server)
473{
474 ZEND_INIT_MODULE_GLOBALS(cli_server, cli_server_init_globals, NULL);
476 return SUCCESS;
477}
478
479static PHP_MSHUTDOWN_FUNCTION(cli_server)
480{
482 return SUCCESS;
483}
484
485static PHP_MINFO_FUNCTION(cli_server)
486{
488}
489
490static zend_module_entry cli_server_module_entry = {
492 "cli_server",
493 NULL,
494 PHP_MINIT(cli_server),
495 PHP_MSHUTDOWN(cli_server),
496 NULL,
497 NULL,
498 PHP_MINFO(cli_server),
501};
502/* }}} */
503
512
513static int sapi_cli_server_startup(sapi_module_struct *sapi_module) /* {{{ */
514{
515 return php_module_startup(sapi_module, &cli_server_module_entry);
516} /* }}} */
517
518static size_t sapi_cli_server_ub_write(const char *str, size_t str_length) /* {{{ */
519{
520 php_cli_server_client *client = SG(server_context);
521 if (!client) {
522 return 0;
523 }
524 return php_cli_server_client_send_through(client, str, str_length);
525} /* }}} */
526
527static void sapi_cli_server_flush(void *server_context) /* {{{ */
528{
529 php_cli_server_client *client = server_context;
530
531 if (!client) {
532 return;
533 }
534
535 if (!ZEND_VALID_SOCKET(client->sock)) {
537 return;
538 }
539
540 if (!SG(headers_sent)) {
542 SG(headers_sent) = 1;
543 }
544} /* }}} */
545
546static int sapi_cli_server_discard_headers(sapi_headers_struct *sapi_headers) /* {{{ */{
548}
549/* }}} */
550
551static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{{ */
552{
553 php_cli_server_client *client = SG(server_context);
554 smart_str buffer = { 0 };
557
558 if (client == NULL || SG(request_info).no_headers) {
560 }
561
562 if (SG(sapi_headers).http_status_line) {
563 smart_str_appends(&buffer, SG(sapi_headers).http_status_line);
564 smart_str_appendl(&buffer, "\r\n", 2);
565 } else {
566 append_http_status_line(&buffer, client->request.protocol_version, SG(sapi_headers).http_response_code, 0);
567 }
568
569 append_essential_headers(&buffer, client, 0, sapi_headers);
570
572 while (h) {
573 if (h->header_len) {
574 smart_str_appendl(&buffer, h->header, h->header_len);
575 smart_str_appendl(&buffer, "\r\n", 2);
576 }
578 }
579 smart_str_appendl(&buffer, "\r\n", 2);
580
581 php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
582
583 smart_str_free(&buffer);
585}
586/* }}} */
587
588static char *sapi_cli_server_read_cookies(void) /* {{{ */
589{
590 php_cli_server_client *client = SG(server_context);
591 zval *val;
592 if (NULL == (val = zend_hash_str_find(&client->request.headers, "cookie", sizeof("cookie")-1))) {
593 return NULL;
594 }
595 return Z_STRVAL_P(val);
596} /* }}} */
597
598static size_t sapi_cli_server_read_post(char *buf, size_t count_bytes) /* {{{ */
599{
600 php_cli_server_client *client = SG(server_context);
601 if (client->request.content) {
602 size_t content_len = client->request.content_len;
603 size_t nbytes_copied = MIN(client->post_read_offset + count_bytes, content_len) - client->post_read_offset;
604 memmove(buf, client->request.content + client->post_read_offset, nbytes_copied);
605 client->post_read_offset += nbytes_copied;
606 return nbytes_copied;
607 }
608 return 0;
609} /* }}} */
610
611static void sapi_cli_server_register_variable(zval *track_vars_array, const char *key, const char *val) /* {{{ */
612{
613 char *new_val = (char *)val;
614 size_t new_val_len;
615
616 if (NULL == val) {
617 return;
618 }
619
620 if (sapi_module.input_filter(PARSE_SERVER, (char*)key, &new_val, strlen(val), &new_val_len)) {
621 php_register_variable_safe((char *)key, new_val, new_val_len, track_vars_array);
622 }
623} /* }}} */
624
625static void sapi_cli_server_register_known_var_char(zval *track_vars_array,
626 const char *var_name, size_t var_name_len, const char *value, size_t value_len)
627{
628 zval new_entry;
629
630 if (!value) {
631 return;
632 }
633
634 ZVAL_STRINGL_FAST(&new_entry, value, value_len);
635
636 php_register_known_variable(var_name, var_name_len, &new_entry, track_vars_array);
637}
638
639static void sapi_cli_server_register_known_var_str(zval *track_vars_array,
640 const char *var_name, size_t var_name_len, /* const */ zend_string *value)
641{
642 zval new_entry;
643
644 if (!value) {
645 return;
646 }
647
648 ZVAL_STR_COPY(&new_entry, value);
649
650 php_register_known_variable(var_name, var_name_len, &new_entry, track_vars_array);
651}
652
653/* The entry zval will always contain a zend_string* */
654static int sapi_cli_server_register_entry_cb(zval *entry, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ {
655 zval *track_vars_array = va_arg(args, zval *);
656
657 ZEND_ASSERT(Z_TYPE_P(entry) == IS_STRING);
658
659 if (hash_key->key) {
660 char *real_key, *key;
661 uint32_t i;
662 key = estrndup(ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key));
663 for(i=0; i<ZSTR_LEN(hash_key->key); i++) {
664 if (key[i] == '-') {
665 key[i] = '_';
666 } else {
667 key[i] = toupper(key[i]);
668 }
669 }
670 spprintf(&real_key, 0, "%s_%s", "HTTP", key);
671 if (strcmp(key, "CONTENT_TYPE") == 0 || strcmp(key, "CONTENT_LENGTH") == 0) {
672 /* Is it possible to use sapi_cli_server_register_known_var_char() and not go through the SAPI filter? */
673 sapi_cli_server_register_variable(track_vars_array, key, Z_STRVAL_P(entry));
674 }
675 sapi_cli_server_register_variable(track_vars_array, real_key, Z_STRVAL_P(entry));
676 efree(key);
677 efree(real_key);
678 }
679
681}
682/* }}} */
683
684static void sapi_cli_server_register_variables(zval *track_vars_array) /* {{{ */
685{
686 php_cli_server_client *client = SG(server_context);
687
688 sapi_cli_server_register_known_var_char(track_vars_array,
689 "DOCUMENT_ROOT", strlen("DOCUMENT_ROOT"), client->server->document_root, client->server->document_root_len);
690 {
691 char *tmp;
692 if ((tmp = strrchr(ZSTR_VAL(client->addr_str), ':'))) {
693 char addr[64], port[8];
694 const char *addr_start = ZSTR_VAL(client->addr_str), *addr_end = tmp;
695 if (addr_start[0] == '[') addr_start++;
696 if (addr_end[-1] == ']') addr_end--;
697
698 strncpy(port, tmp + 1, 8);
699 port[7] = '\0';
700 size_t addr_len = addr_end - addr_start;
701 strncpy(addr, addr_start, addr_len);
702 addr[addr_len] = '\0';
703 ZEND_ASSERT(addr_len == strlen(addr));
704 sapi_cli_server_register_known_var_char(track_vars_array,
705 "REMOTE_ADDR", strlen("REMOTE_ADDR"), addr, addr_len);
706 sapi_cli_server_register_known_var_char(track_vars_array,
707 "REMOTE_PORT", strlen("REMOTE_PORT"), port, strlen(port));
708 } else {
709 sapi_cli_server_register_known_var_str(track_vars_array,
710 "REMOTE_ADDR", strlen("REMOTE_ADDR"), client->addr_str);
711 }
712 }
713 {
714 zend_string *tmp = strpprintf(0, "PHP/%s (Development Server)", PHP_VERSION);
715 sapi_cli_server_register_known_var_str(track_vars_array, "SERVER_SOFTWARE", strlen("SERVER_SOFTWARE"), tmp);
716 zend_string_release_ex(tmp, /* persistent */ false);
717 }
718 {
719 zend_string *tmp = strpprintf(0, "HTTP/%d.%d", client->request.protocol_version / 100, client->request.protocol_version % 100);
720 sapi_cli_server_register_known_var_str(track_vars_array, "SERVER_PROTOCOL", strlen("SERVER_PROTOCOL"), tmp);
721 zend_string_release_ex(tmp, /* persistent */ false);
722 }
723 sapi_cli_server_register_known_var_char(track_vars_array,
724 "SERVER_NAME", strlen("SERVER_NAME"), client->server->host, strlen(client->server->host));
725 {
726 zend_string *tmp = strpprintf(0, "%i", client->server->port);
727 sapi_cli_server_register_known_var_str(track_vars_array, "SERVER_PORT", strlen("SERVER_PORT"), tmp);
728 zend_string_release_ex(tmp, /* persistent */ false);
729 }
730
731 sapi_cli_server_register_known_var_str(track_vars_array,
732 "REQUEST_URI", strlen("REQUEST_URI"), client->request.request_uri);
733 sapi_cli_server_register_known_var_char(track_vars_array,
734 "REQUEST_METHOD", strlen("REQUEST_METHOD"),
735 SG(request_info).request_method, strlen(SG(request_info).request_method));
736 sapi_cli_server_register_known_var_char(track_vars_array,
737 "SCRIPT_NAME", strlen("SCRIPT_NAME"), client->request.vpath, client->request.vpath_len);
738 if (SG(request_info).path_translated) {
739 sapi_cli_server_register_known_var_char(track_vars_array,
740 "SCRIPT_FILENAME", strlen("SCRIPT_FILENAME"),
741 SG(request_info).path_translated, strlen(SG(request_info).path_translated));
742 } else if (client->server->router) {
743 sapi_cli_server_register_known_var_char(track_vars_array,
744 "SCRIPT_FILENAME", strlen("SCRIPT_FILENAME"), client->server->router, client->server->router_len);
745 }
746 if (client->request.path_info) {
747 sapi_cli_server_register_known_var_char(track_vars_array,
748 "PATH_INFO", strlen("PATH_INFO"), client->request.path_info, client->request.path_info_len);
749 }
750 if (client->request.path_info_len) {
751 zend_string *tmp = strpprintf(0, "%s%s", client->request.vpath, client->request.path_info);
752 sapi_cli_server_register_known_var_str(track_vars_array, "PHP_SELF", strlen("PHP_SELF"), tmp);
753 zend_string_release_ex(tmp, /* persistent */ false);
754 } else {
755 sapi_cli_server_register_known_var_char(track_vars_array,
756 "PHP_SELF", strlen("PHP_SELF"), client->request.vpath, client->request.vpath_len);
757 }
758 if (client->request.query_string) {
759 /* Use sapi_cli_server_register_variable() to pass query string through SAPI input filter,
760 * and check keys are proper PHP var names */
761 sapi_cli_server_register_variable(track_vars_array, "QUERY_STRING", client->request.query_string);
762 }
763 /* Use sapi_cli_server_register_variable() to pass header values through SAPI input filter,
764 * and check keys are proper PHP var names */
765 zend_hash_apply_with_arguments(&client->request.headers, (apply_func_args_t)sapi_cli_server_register_entry_cb, 1, track_vars_array);
766} /* }}} */
767
768static void sapi_cli_server_log_write(int type, const char *msg) /* {{{ */
769{
770 char buf[52];
771
772 if (php_cli_server_log_level < type) {
773 return;
774 }
775
776 if (!php_cli_server_get_system_time(buf)) {
777 memmove(buf, "unknown time, can't be fetched", sizeof("unknown time, can't be fetched"));
778 } else {
779 size_t l = strlen(buf);
780 if (l > 0) {
781 buf[l - 1] = '\0';
782 } else {
783 memmove(buf, "unknown", sizeof("unknown"));
784 }
785 }
786#ifdef HAVE_FORK
787 if (php_cli_server_workers_max > 1) {
788 fprintf(stderr, "[%ld] [%s] %s\n", (long) getpid(), buf, msg);
789 } else {
790 fprintf(stderr, "[%s] %s\n", buf, msg);
791 }
792#else
793 fprintf(stderr, "[%s] %s\n", buf, msg);
794#endif
795} /* }}} */
796
797static void sapi_cli_server_log_message(const char *msg, int syslog_type_int) /* {{{ */
798{
799 sapi_cli_server_log_write(PHP_CLI_SERVER_LOG_MESSAGE, msg);
800} /* }}} */
801
802/* {{{ sapi_module_struct cli_server_sapi_module */
804 "cli-server", /* name */
805 "Built-in HTTP server", /* pretty name */
806
807 sapi_cli_server_startup, /* startup */
808 php_module_shutdown_wrapper, /* shutdown */
809
810 NULL, /* activate */
811 NULL, /* deactivate */
812
813 sapi_cli_server_ub_write, /* unbuffered write */
814 sapi_cli_server_flush, /* flush */
815 NULL, /* get uid */
816 NULL, /* getenv */
817
818 php_error, /* error handler */
819
820 NULL, /* header handler */
821 sapi_cli_server_send_headers, /* send headers handler */
822 NULL, /* send header handler */
823
824 sapi_cli_server_read_post, /* read POST data */
825 sapi_cli_server_read_cookies, /* read Cookies */
826
827 sapi_cli_server_register_variables, /* register server variables */
828 sapi_cli_server_log_message, /* Log message */
829 NULL, /* Get request time */
830 NULL, /* Child terminate */
831
833}; /* }}} */
834
835static void php_cli_server_poller_ctor(php_cli_server_poller *poller) /* {{{ */
836{
837 FD_ZERO(&poller->rfds);
838 FD_ZERO(&poller->wfds);
839 poller->max_fd = -1;
840} /* }}} */
841
842static void php_cli_server_poller_add(php_cli_server_poller *poller, int mode, php_socket_t fd) /* {{{ */
843{
844 if (mode & POLLIN) {
845 PHP_SAFE_FD_SET(fd, &poller->rfds);
846 }
847 if (mode & POLLOUT) {
848 PHP_SAFE_FD_SET(fd, &poller->wfds);
849 }
850 if (fd > poller->max_fd) {
851 poller->max_fd = fd;
852 }
853} /* }}} */
854
855static void php_cli_server_poller_remove(php_cli_server_poller *poller, int mode, php_socket_t fd) /* {{{ */
856{
857 if (mode & POLLIN) {
858 PHP_SAFE_FD_CLR(fd, &poller->rfds);
859 }
860 if (mode & POLLOUT) {
861 PHP_SAFE_FD_CLR(fd, &poller->wfds);
862 }
863#ifndef PHP_WIN32
864 if (fd == poller->max_fd) {
865 while (fd > 0) {
866 fd--;
867 if (PHP_SAFE_FD_ISSET(fd, &poller->rfds) || PHP_SAFE_FD_ISSET(fd, &poller->wfds)) {
868 break;
869 }
870 }
871 poller->max_fd = fd;
872 }
873#endif
874} /* }}} */
875
876static int php_cli_server_poller_poll(php_cli_server_poller *poller, struct timeval *tv) /* {{{ */
877{
878 memmove(&poller->active.rfds, &poller->rfds, sizeof(poller->rfds));
879 memmove(&poller->active.wfds, &poller->wfds, sizeof(poller->wfds));
880 return php_select(poller->max_fd + 1, &poller->active.rfds, &poller->active.wfds, NULL, tv);
881} /* }}} */
882
883static zend_result php_cli_server_poller_iter_on_active(php_cli_server_poller *poller, void *opaque, zend_result(*callback)(void *, php_socket_t fd, int events)) /* {{{ */
884{
886#ifdef PHP_WIN32
887 struct socket_entry {
888 SOCKET fd;
889 int events;
890 } entries[FD_SETSIZE * 2];
891 size_t i;
892 struct socket_entry *n = entries, *m;
893
894 for (i = 0; i < poller->active.rfds.fd_count; i++) {
895 n->events = POLLIN;
896 n->fd = poller->active.rfds.fd_array[i];
897 n++;
898 }
899
900 m = n;
901 for (i = 0; i < poller->active.wfds.fd_count; i++) {
902 struct socket_entry *e;
903 SOCKET fd = poller->active.wfds.fd_array[i];
904 for (e = entries; e < m; e++) {
905 if (e->fd == fd) {
906 e->events |= POLLOUT;
907 }
908 }
909 if (e == m) {
910 assert(n < entries + FD_SETSIZE * 2);
911 n->events = POLLOUT;
912 n->fd = fd;
913 n++;
914 }
915 }
916
917 {
918 struct socket_entry *e = entries;
919 for (; e < n; e++) {
920 if (SUCCESS != callback(opaque, e->fd, e->events)) {
921 retval = FAILURE;
922 }
923 }
924 }
925
926#else
928 const php_socket_t max_fd = poller->max_fd;
929
930 for (fd=0 ; fd<=max_fd ; fd++) {
931 if (PHP_SAFE_FD_ISSET(fd, &poller->active.rfds)) {
932 if (SUCCESS != callback(opaque, fd, POLLIN)) {
933 retval = FAILURE;
934 }
935 }
936 if (PHP_SAFE_FD_ISSET(fd, &poller->active.wfds)) {
937 if (SUCCESS != callback(opaque, fd, POLLOUT)) {
938 retval = FAILURE;
939 }
940 }
941 }
942#endif
943 return retval;
944} /* }}} */
945
946static size_t php_cli_server_chunk_size(const php_cli_server_chunk *chunk) /* {{{ */
947{
948 switch (chunk->type) {
949 case PHP_CLI_SERVER_CHUNK_HEAP:
950 return chunk->data.heap.len;
951 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
952 return chunk->data.immortal.len;
953 }
954 return 0;
955} /* }}} */
956
957static void php_cli_server_chunk_dtor(php_cli_server_chunk *chunk) /* {{{ */
958{
959 switch (chunk->type) {
960 case PHP_CLI_SERVER_CHUNK_HEAP:
961 if (chunk->data.heap.block != chunk) {
962 pefree(chunk->data.heap.block, 1);
963 }
964 break;
965 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
966 break;
967 }
968} /* }}} */
969
970static void php_cli_server_buffer_dtor(php_cli_server_buffer *buffer) /* {{{ */
971{
972 php_cli_server_chunk *chunk, *next;
973 for (chunk = buffer->first; chunk; chunk = next) {
974 next = chunk->next;
975 php_cli_server_chunk_dtor(chunk);
976 pefree(chunk, 1);
977 }
978} /* }}} */
979
980static void php_cli_server_buffer_ctor(php_cli_server_buffer *buffer) /* {{{ */
981{
982 buffer->first = NULL;
983 buffer->last = NULL;
984} /* }}} */
985
986static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
987{
989 for (last = chunk; last->next; last = last->next);
990 if (!buffer->last) {
991 buffer->first = chunk;
992 } else {
993 buffer->last->next = chunk;
994 }
995 buffer->last = last;
996} /* }}} */
997
998static void php_cli_server_buffer_prepend(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
999{
1001 for (last = chunk; last->next; last = last->next);
1002 last->next = buffer->first;
1003 if (!buffer->last) {
1004 buffer->last = last;
1005 }
1006 buffer->first = chunk;
1007} /* }}} */
1008
1009static size_t php_cli_server_buffer_size(const php_cli_server_buffer *buffer) /* {{{ */
1010{
1011 php_cli_server_chunk *chunk;
1012 size_t retval = 0;
1013 for (chunk = buffer->first; chunk; chunk = chunk->next) {
1014 retval += php_cli_server_chunk_size(chunk);
1015 }
1016 return retval;
1017} /* }}} */
1018
1019static php_cli_server_chunk *php_cli_server_chunk_immortal_new(const char *buf, size_t len) /* {{{ */
1020{
1022
1023 chunk->type = PHP_CLI_SERVER_CHUNK_IMMORTAL;
1024 chunk->next = NULL;
1025 chunk->data.immortal.p = buf;
1026 chunk->data.immortal.len = len;
1027 return chunk;
1028} /* }}} */
1029
1030static php_cli_server_chunk *php_cli_server_chunk_heap_new(void *block, char *buf, size_t len) /* {{{ */
1031{
1033
1034 chunk->type = PHP_CLI_SERVER_CHUNK_HEAP;
1035 chunk->next = NULL;
1036 chunk->data.heap.block = block;
1037 chunk->data.heap.p = buf;
1038 chunk->data.heap.len = len;
1039 return chunk;
1040} /* }}} */
1041
1042static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len) /* {{{ */
1043{
1045
1046 chunk->type = PHP_CLI_SERVER_CHUNK_HEAP;
1047 chunk->next = NULL;
1048 chunk->data.heap.block = chunk;
1049 chunk->data.heap.p = (char *)(chunk + 1);
1050 chunk->data.heap.len = len;
1051 return chunk;
1052} /* }}} */
1053
1054static void php_cli_server_content_sender_dtor(php_cli_server_content_sender *sender) /* {{{ */
1055{
1056 php_cli_server_buffer_dtor(&sender->buffer);
1057} /* }}} */
1058
1059static void php_cli_server_content_sender_ctor(php_cli_server_content_sender *sender) /* {{{ */
1060{
1061 php_cli_server_buffer_ctor(&sender->buffer);
1062} /* }}} */
1063
1064static int php_cli_server_content_sender_send(php_cli_server_content_sender *sender, php_socket_t fd, size_t *nbytes_sent_total) /* {{{ */
1065{
1066 php_cli_server_chunk *chunk, *next;
1067 size_t _nbytes_sent_total = 0;
1068
1069 for (chunk = sender->buffer.first; chunk; chunk = next) {
1070#ifdef PHP_WIN32
1071 int nbytes_sent;
1072#else
1073 ssize_t nbytes_sent;
1074#endif
1075 next = chunk->next;
1076
1077 switch (chunk->type) {
1078 case PHP_CLI_SERVER_CHUNK_HEAP:
1079#ifdef PHP_WIN32
1080 nbytes_sent = send(fd, chunk->data.heap.p, (int)chunk->data.heap.len, 0);
1081#else
1082 nbytes_sent = send(fd, chunk->data.heap.p, chunk->data.heap.len, 0);
1083#endif
1084 if (nbytes_sent < 0) {
1085 *nbytes_sent_total = _nbytes_sent_total;
1086 return php_socket_errno();
1087#ifdef PHP_WIN32
1088 } else if (nbytes_sent == chunk->data.heap.len) {
1089#else
1090 } else if (nbytes_sent == (ssize_t)chunk->data.heap.len) {
1091#endif
1092 php_cli_server_chunk_dtor(chunk);
1093 pefree(chunk, 1);
1094 sender->buffer.first = next;
1095 if (!next) {
1096 sender->buffer.last = NULL;
1097 }
1098 } else {
1099 chunk->data.heap.p += nbytes_sent;
1100 chunk->data.heap.len -= nbytes_sent;
1101 }
1102 _nbytes_sent_total += nbytes_sent;
1103 break;
1104
1105 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
1106#ifdef PHP_WIN32
1107 nbytes_sent = send(fd, chunk->data.immortal.p, (int)chunk->data.immortal.len, 0);
1108#else
1109 nbytes_sent = send(fd, chunk->data.immortal.p, chunk->data.immortal.len, 0);
1110#endif
1111 if (nbytes_sent < 0) {
1112 *nbytes_sent_total = _nbytes_sent_total;
1113 return php_socket_errno();
1114#ifdef PHP_WIN32
1115 } else if (nbytes_sent == chunk->data.immortal.len) {
1116#else
1117 } else if (nbytes_sent == (ssize_t)chunk->data.immortal.len) {
1118#endif
1119 php_cli_server_chunk_dtor(chunk);
1120 pefree(chunk, 1);
1121 sender->buffer.first = next;
1122 if (!next) {
1123 sender->buffer.last = NULL;
1124 }
1125 } else {
1126 chunk->data.immortal.p += nbytes_sent;
1127 chunk->data.immortal.len -= nbytes_sent;
1128 }
1129 _nbytes_sent_total += nbytes_sent;
1130 break;
1131 }
1132 }
1133 *nbytes_sent_total = _nbytes_sent_total;
1134 return 0;
1135} /* }}} */
1136
1137static bool php_cli_server_content_sender_pull(php_cli_server_content_sender *sender, int fd, size_t *nbytes_read) /* {{{ */
1138{
1139#ifdef PHP_WIN32
1140 int _nbytes_read;
1141#else
1142 ssize_t _nbytes_read;
1143#endif
1144 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(131072);
1145
1146#ifdef PHP_WIN32
1147 _nbytes_read = read(fd, chunk->data.heap.p, (unsigned int)chunk->data.heap.len);
1148#else
1149 _nbytes_read = read(fd, chunk->data.heap.p, chunk->data.heap.len);
1150#endif
1151 if (_nbytes_read < 0) {
1152 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1153 char *errstr = get_last_error();
1154 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s", errstr);
1155 pefree(errstr, 1);
1156 }
1157 php_cli_server_chunk_dtor(chunk);
1158 pefree(chunk, 1);
1159 return false;
1160 }
1161 chunk->data.heap.len = _nbytes_read;
1162 php_cli_server_buffer_append(&sender->buffer, chunk);
1163 *nbytes_read = _nbytes_read;
1164 return true;
1165} /* }}} */
1166
1167#ifdef HAVE_UNISTD_H
1168static int php_cli_is_output_tty(void) /* {{{ */
1169{
1170 if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) {
1171 php_cli_output_is_tty = isatty(STDOUT_FILENO);
1172 }
1173 return php_cli_output_is_tty;
1174} /* }}} */
1175#elif defined(PHP_WIN32)
1176static int php_cli_is_output_tty() /* {{{ */
1177{
1178 if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) {
1180 }
1181 return php_cli_output_is_tty;
1182} /* }}} */
1183#endif
1184
1185static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message) /* {{{ */
1186{
1187 int color = 0, effective_status = status;
1188 char *basic_buf, *message_buf = "", *error_buf = "";
1189 bool append_error_message = 0;
1190
1191 if (PG(last_error_message)) {
1192 if (PG(last_error_type) & E_FATAL_ERRORS) {
1193 if (status == 200) {
1194 /* the status code isn't changed by a fatal error, so fake it */
1195 effective_status = 500;
1196 }
1197
1198 append_error_message = 1;
1199 }
1200 }
1201
1202#if defined(HAVE_UNISTD_H) || defined(PHP_WIN32)
1203 if (CLI_SERVER_G(color) && php_cli_is_output_tty() == OUTPUT_IS_TTY) {
1204 if (effective_status >= 500) {
1205 /* server error: red */
1206 color = 1;
1207 } else if (effective_status >= 400) {
1208 /* client error: yellow */
1209 color = 3;
1210 } else if (effective_status >= 200) {
1211 /* success: green */
1212 color = 2;
1213 }
1214 }
1215#endif
1216
1217 /* basic */
1218 spprintf(&basic_buf, 0, "%s [%d]: %s %s", ZSTR_VAL(client->addr_str), status,
1219 php_http_method_str(client->request.request_method), ZSTR_VAL(client->request.request_uri));
1220 if (!basic_buf) {
1221 return;
1222 }
1223
1224 /* message */
1225 if (message) {
1226 spprintf(&message_buf, 0, " - %s", message);
1227 if (!message_buf) {
1228 efree(basic_buf);
1229 return;
1230 }
1231 }
1232
1233 /* error */
1234 if (append_error_message) {
1235 spprintf(&error_buf, 0, " - %s in %s on line %d",
1236 ZSTR_VAL(PG(last_error_message)), ZSTR_VAL(PG(last_error_file)), PG(last_error_lineno));
1237 if (!error_buf) {
1238 efree(basic_buf);
1239 if (message) {
1240 efree(message_buf);
1241 }
1242 return;
1243 }
1244 }
1245
1246 if (color) {
1247 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "\x1b[3%dm%s%s%s\x1b[0m", color, basic_buf, message_buf, error_buf);
1248 } else {
1249 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s%s%s", basic_buf, message_buf, error_buf);
1250 }
1251
1252 efree(basic_buf);
1253 if (message) {
1254 efree(message_buf);
1255 }
1256 if (append_error_message) {
1257 efree(error_buf);
1258 }
1259} /* }}} */
1260
1261static void php_cli_server_logf(int type, const char *format, ...) /* {{{ */
1262{
1263 char *buf = NULL;
1264 va_list ap;
1265
1266 if (php_cli_server_log_level < type) {
1267 return;
1268 }
1269
1270 va_start(ap, format);
1271 vspprintf(&buf, 0, format, ap);
1272 va_end(ap);
1273
1274 if (!buf) {
1275 return;
1276 }
1277
1278 sapi_cli_server_log_write(type, buf);
1279
1280 efree(buf);
1281} /* }}} */
1282
1283static php_socket_t php_network_listen_socket(const char *host, int *port, int socktype, int *af, socklen_t *socklen, zend_string **errstr) /* {{{ */
1284{
1286 int err = 0;
1287 struct sockaddr *sa = NULL, **p, **sal;
1288
1289 int num_addrs = php_network_getaddresses(host, socktype, &sal, errstr);
1290 if (num_addrs == 0) {
1291 return -1;
1292 }
1293 for (p = sal; *p; p++) {
1294 if (sa) {
1295 pefree(sa, 1);
1296 sa = NULL;
1297 }
1298
1299 retval = socket((*p)->sa_family, socktype, 0);
1300 if (retval == SOCK_ERR) {
1301 continue;
1302 }
1303
1304 switch ((*p)->sa_family) {
1305#if defined(HAVE_GETADDRINFO) && defined(HAVE_IPV6)
1306 case AF_INET6:
1307 sa = pemalloc(sizeof(struct sockaddr_in6), 1);
1308 *(struct sockaddr_in6 *)sa = *(struct sockaddr_in6 *)*p;
1309 ((struct sockaddr_in6 *)sa)->sin6_port = htons(*port);
1310 *socklen = sizeof(struct sockaddr_in6);
1311 break;
1312#endif
1313 case AF_INET:
1314 sa = pemalloc(sizeof(struct sockaddr_in), 1);
1315 *(struct sockaddr_in *)sa = *(struct sockaddr_in *)*p;
1316 ((struct sockaddr_in *)sa)->sin_port = htons(*port);
1317 *socklen = sizeof(struct sockaddr_in);
1318 break;
1319 default:
1320 /* Unknown family */
1321 *socklen = 0;
1323 continue;
1324 }
1325
1326#ifdef SO_REUSEADDR
1327 {
1328 int val = 1;
1329 setsockopt(retval, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val));
1330 }
1331#endif
1332
1333 if (bind(retval, sa, *socklen) == SOCK_CONN_ERR) {
1335 if (err == SOCK_EINVAL || err == SOCK_EADDRINUSE) {
1336 goto out;
1337 }
1339 retval = SOCK_ERR;
1340 continue;
1341 }
1342 err = 0;
1343
1344 *af = sa->sa_family;
1345 if (*port == 0) {
1346 if (getsockname(retval, sa, socklen)) {
1348 goto out;
1349 }
1350 switch (sa->sa_family) {
1351#if defined(HAVE_GETADDRINFO) && defined(HAVE_IPV6)
1352 case AF_INET6:
1353 *port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
1354 break;
1355#endif
1356 case AF_INET:
1357 *port = ntohs(((struct sockaddr_in *)sa)->sin_port);
1358 break;
1359 }
1360 }
1361
1362 break;
1363 }
1364
1365 if (retval == SOCK_ERR) {
1366 goto out;
1367 }
1368
1369 if (listen(retval, SOMAXCONN)) {
1371 goto out;
1372 }
1373
1374out:
1375 if (sa) {
1376 pefree(sa, 1);
1377 }
1378 if (sal) {
1380 }
1381 if (err) {
1384 }
1385 if (errstr) {
1386 *errstr = php_socket_error_str(err);
1387 }
1388 return SOCK_ERR;
1389 }
1390 return retval;
1391} /* }}} */
1392
1393static void php_cli_server_request_ctor(php_cli_server_request *req) /* {{{ */
1394{
1395 req->protocol_version = 0;
1396 req->request_uri = NULL;
1397 req->vpath = NULL;
1398 req->vpath_len = 0;
1399 req->path_translated = NULL;
1400 req->path_translated_len = 0;
1401 req->path_info = NULL;
1402 req->path_info_len = 0;
1403 req->query_string = NULL;
1404 req->query_string_len = 0;
1405 zend_hash_init(&req->headers, 0, NULL, cli_header_value_dtor, 1);
1406 /* No destructor is registered as the value pointed by is the same as for &req->headers */
1410 req->content = NULL;
1411 req->content_len = 0;
1412 req->ext = NULL;
1413 req->ext_len = 0;
1414} /* }}} */
1415
1416static void php_cli_server_request_dtor(php_cli_server_request *req) /* {{{ */
1417{
1418 if (req->request_uri) {
1419 zend_string_release_ex(req->request_uri, /* persistent */ true);
1420 }
1421 if (req->vpath) {
1422 pefree(req->vpath, 1);
1423 }
1424 if (req->path_translated) {
1425 pefree(req->path_translated, 1);
1426 }
1427 if (req->path_info) {
1428 pefree(req->path_info, 1);
1429 }
1430 if (req->query_string) {
1431 pefree(req->query_string, 1);
1432 }
1435 if (req->content) {
1436 pefree(req->content, 1);
1437 }
1438} /* }}} */
1439
1440static void php_cli_server_request_translate_vpath(const php_cli_server *server, php_cli_server_request *request, const char *document_root, size_t document_root_len) /* {{{ */
1441{
1442 zend_stat_t sb = {0};
1443 static const char *index_files[] = { "index.php", "index.html", NULL };
1444 char *buf = safe_pemalloc(1, request->vpath_len, 1 + document_root_len + 1 + sizeof("index.html"), 1);
1445 char *p = buf, *prev_path = NULL, *q, *vpath;
1446 size_t prev_path_len = 0;
1447
1448 memmove(p, document_root, document_root_len);
1449 p += document_root_len;
1450 vpath = p;
1451 if (request->vpath_len != 0) {
1452 if (request->vpath[0] != '/') {
1453 *p++ = DEFAULT_SLASH;
1454 }
1455 memmove(p, request->vpath, request->vpath_len);
1456#ifdef PHP_WIN32
1457 q = p + request->vpath_len;
1458 do {
1459 if (*q == '/') {
1460 *q = '\\';
1461 }
1462 } while (q-- > p);
1463#endif
1464 p += request->vpath_len;
1465 }
1466 *p = '\0';
1467 q = p;
1468 while (q > buf) {
1469 if (!php_sys_stat(buf, &sb)) {
1470 if (sb.st_mode & S_IFDIR) {
1471 const char **file = index_files;
1472 if (q[-1] != DEFAULT_SLASH) {
1473 *q++ = DEFAULT_SLASH;
1474 }
1475 while (*file) {
1476 size_t l = strlen(*file);
1477 memmove(q, *file, l + 1);
1478 if (!php_sys_stat(buf, &sb) && (sb.st_mode & S_IFREG)) {
1479 q += l;
1480 break;
1481 }
1482 file++;
1483 }
1484 if (!*file) {
1485 if (prev_path) {
1486 pefree(prev_path, 1);
1487 }
1488 pefree(buf, 1);
1489 return;
1490 }
1491 }
1492 break; /* regular file */
1493 }
1494 if (prev_path) {
1495 pefree(prev_path, 1);
1496 *q = DEFAULT_SLASH;
1497 }
1498 while (q > buf && *(--q) != DEFAULT_SLASH);
1499 prev_path_len = p - q;
1500 prev_path = pestrndup(q, prev_path_len, 1);
1501 *q = '\0';
1502 }
1503 if (prev_path) {
1504 request->path_info_len = prev_path_len;
1505#ifdef PHP_WIN32
1506 while (prev_path_len--) {
1507 if (prev_path[prev_path_len] == '\\') {
1508 prev_path[prev_path_len] = '/';
1509 }
1510 }
1511#endif
1512 request->path_info = prev_path;
1513 pefree(request->vpath, 1);
1514 request->vpath = pestrndup(vpath, q - vpath, 1);
1515 request->vpath_len = q - vpath;
1516 request->path_translated = buf;
1517 request->path_translated_len = q - buf;
1518 } else {
1519 pefree(request->vpath, 1);
1520 request->vpath = pestrndup(vpath, q - vpath, 1);
1521 request->vpath_len = q - vpath;
1522 request->path_translated = buf;
1523 request->path_translated_len = q - buf;
1524 }
1525#ifdef PHP_WIN32
1526 {
1527 uint32_t i = 0;
1528 for (;i<request->vpath_len;i++) {
1529 if (request->vpath[i] == '\\') {
1530 request->vpath[i] = '/';
1531 }
1532 }
1533 }
1534#endif
1535 request->sb = sb;
1536} /* }}} */
1537
1538static void normalize_vpath(char **retval, size_t *retval_len, const char *vpath, size_t vpath_len, int persistent) /* {{{ */
1539{
1540 char *decoded_vpath = NULL;
1541 char *decoded_vpath_end;
1542 char *p;
1543
1544 *retval = NULL;
1545 *retval_len = 0;
1546
1547 decoded_vpath = pestrndup(vpath, vpath_len, persistent);
1548 if (!decoded_vpath) {
1549 return;
1550 }
1551
1552 decoded_vpath_end = decoded_vpath + php_raw_url_decode(decoded_vpath, (int)vpath_len);
1553
1554#ifdef PHP_WIN32
1555 {
1556 char *p = decoded_vpath;
1557
1558 do {
1559 if (*p == '\\') {
1560 *p = '/';
1561 }
1562 } while (*p++);
1563 }
1564#endif
1565
1566 p = decoded_vpath;
1567
1568 if (p < decoded_vpath_end && *p == '/') {
1569 char *n = p;
1570 while (n < decoded_vpath_end && *n == '/') n++;
1571 memmove(++p, n, decoded_vpath_end - n);
1572 decoded_vpath_end -= n - p;
1573 }
1574
1575 while (p < decoded_vpath_end) {
1576 char *n = p;
1577 while (n < decoded_vpath_end && *n != '/') n++;
1578 if (n - p == 2 && p[0] == '.' && p[1] == '.') {
1579 if (p > decoded_vpath) {
1580 --p;
1581 for (;;) {
1582 if (p == decoded_vpath) {
1583 if (*p == '/') {
1584 p++;
1585 }
1586 break;
1587 }
1588 if (*(--p) == '/') {
1589 p++;
1590 break;
1591 }
1592 }
1593 }
1594 while (n < decoded_vpath_end && *n == '/') n++;
1595 memmove(p, n, decoded_vpath_end - n);
1596 decoded_vpath_end -= n - p;
1597 } else if (n - p == 1 && p[0] == '.') {
1598 while (n < decoded_vpath_end && *n == '/') n++;
1599 memmove(p, n, decoded_vpath_end - n);
1600 decoded_vpath_end -= n - p;
1601 } else {
1602 if (n < decoded_vpath_end) {
1603 char *nn = n;
1604 while (nn < decoded_vpath_end && *nn == '/') nn++;
1605 p = n + 1;
1606 memmove(p, nn, decoded_vpath_end - nn);
1607 decoded_vpath_end -= nn - p;
1608 } else {
1609 p = n;
1610 }
1611 }
1612 }
1613
1614 *decoded_vpath_end = '\0';
1615 *retval = decoded_vpath;
1616 *retval_len = decoded_vpath_end - decoded_vpath;
1617} /* }}} */
1618
1619// TODO Update these functions and php_http_parser.h
1620/* {{{ php_cli_server_client_read_request */
1621static int php_cli_server_client_read_request_on_message_begin(php_http_parser *parser)
1622{
1623 return 0;
1624}
1625
1626static int php_cli_server_client_read_request_on_path(php_http_parser *parser, const char *at, size_t length)
1627{
1629 {
1630 char *vpath;
1631 size_t vpath_len;
1632 if (UNEXPECTED(client->request.vpath != NULL)) {
1633 return 1;
1634 }
1635 normalize_vpath(&vpath, &vpath_len, at, length, 1);
1636 client->request.vpath = vpath;
1637 client->request.vpath_len = vpath_len;
1638 }
1639 return 0;
1640}
1641
1642static int php_cli_server_client_read_request_on_query_string(php_http_parser *parser, const char *at, size_t length)
1643{
1645 if (EXPECTED(client->request.query_string == NULL)) {
1646 client->request.query_string = pestrndup(at, length, 1);
1647 client->request.query_string_len = length;
1648 } else {
1649 ZEND_ASSERT(length <= PHP_HTTP_MAX_HEADER_SIZE && PHP_HTTP_MAX_HEADER_SIZE - length >= client->request.query_string_len);
1650 client->request.query_string = perealloc(client->request.query_string, client->request.query_string_len + length + 1, 1);
1651 memcpy(client->request.query_string + client->request.query_string_len, at, length);
1652 client->request.query_string_len += length;
1653 client->request.query_string[client->request.query_string_len] = '\0';
1654 }
1655 return 0;
1656}
1657
1658static int php_cli_server_client_read_request_on_url(php_http_parser *parser, const char *at, size_t length)
1659{
1661 if (EXPECTED(client->request.request_uri == NULL)) {
1662 client->request.request_method = parser->method;
1663 client->request.request_uri = zend_string_init(at, length, /* persistent */ true);
1664 GC_MAKE_PERSISTENT_LOCAL(client->request.request_uri);
1665 } else {
1666 ZEND_ASSERT(client->request.request_method == parser->method);
1667 ZEND_ASSERT(length <= PHP_HTTP_MAX_HEADER_SIZE && PHP_HTTP_MAX_HEADER_SIZE - length >= client->request.query_string_len);
1668 /* Extend URI, append content to it */
1669 client->request.request_uri = cli_concat_persistent_zstr_with_char(client->request.request_uri, at, length);
1670 }
1671 return 0;
1672}
1673
1674static int php_cli_server_client_read_request_on_fragment(php_http_parser *parser, const char *at, size_t length)
1675{
1676 return 0;
1677}
1678
1679static void php_cli_server_client_save_header(php_cli_server_client *client)
1680{
1681 /* Wrap header value in a zval to add is to the HashTable which acts as an array */
1682 zval tmp;
1683 /* strip off the colon */
1684 zend_string *lc_header_name = zend_string_tolower_ex(client->current_header_name, /* persistent */ true);
1685 GC_MAKE_PERSISTENT_LOCAL(lc_header_name);
1686
1687 zval *entry = zend_hash_find(&client->request.headers, lc_header_name);
1688 bool with_comma = !zend_string_equals_literal(lc_header_name, "set-cookie");
1689
1694 if (!with_comma || entry == NULL) {
1695 ZVAL_STR(&tmp, client->current_header_value);
1696 } else {
1697 zend_string *curval = Z_STR_P(entry);
1698 zend_string *newval = zend_string_safe_alloc(1, ZSTR_LEN(curval), ZSTR_LEN(client->current_header_value) + 2, /* persistent */true);
1699
1700 memcpy(ZSTR_VAL(newval), ZSTR_VAL(curval), ZSTR_LEN(curval));
1701 memcpy(ZSTR_VAL(newval) + ZSTR_LEN(curval), ", ", 2);
1702 memcpy(ZSTR_VAL(newval) + ZSTR_LEN(curval) + 2, ZSTR_VAL(client->current_header_value), ZSTR_LEN(client->current_header_value) + 1);
1703
1704 ZVAL_STR(&tmp, newval);
1705 }
1706
1707 /* Add/Update the wrapped zend_string to the HashTable */
1708 zend_hash_update(&client->request.headers, lc_header_name, &tmp);
1709 zend_hash_update(&client->request.headers_original_case, client->current_header_name, &tmp);
1710
1711 zend_string_release_ex(lc_header_name, /* persistent */ true);
1712 zend_string_release_ex(client->current_header_name, /* persistent */ true);
1713
1714 client->current_header_name = NULL;
1715 client->current_header_value = NULL;
1716}
1717
1718static zend_string* cli_concat_persistent_zstr_with_char(zend_string *old_str, const char *at, size_t length)
1719{
1720 /* Assert that there is only one reference to the string, as then zend_string_extends()
1721 * will reallocate it such that we do not need to release the old value. */
1722 ZEND_ASSERT(GC_REFCOUNT(old_str) == 1);
1723 /* Previous element was part of header value, append content to it */
1724 size_t old_length = ZSTR_LEN(old_str);
1725 zend_string *str = zend_string_extend(old_str, old_length + length, /* persistent */ true);
1726 memcpy(ZSTR_VAL(str) + old_length, at, length);
1727 // Null terminate
1728 ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0';
1729 return str;
1730}
1731
1732static int php_cli_server_client_read_request_on_header_field(php_http_parser *parser, const char *at, size_t length)
1733{
1735 switch (client->last_header_element) {
1736 case HEADER_VALUE:
1737 /* Save previous header before creating new one */
1738 php_cli_server_client_save_header(client);
1740 case HEADER_NONE:
1741 /* Create new header field */
1742 client->current_header_name = zend_string_init(at, length, /* persistent */ true);
1743 GC_MAKE_PERSISTENT_LOCAL(client->current_header_name);
1744 break;
1745 case HEADER_FIELD: {
1746 /* Append header name to the previous value of it */
1747 client->current_header_name = cli_concat_persistent_zstr_with_char(client->current_header_name, at, length);
1748 break;
1749 }
1750 }
1751
1752 client->last_header_element = HEADER_FIELD;
1753 return 0;
1754}
1755
1756static int php_cli_server_client_read_request_on_header_value(php_http_parser *parser, const char *at, size_t length)
1757{
1759 switch (client->last_header_element) {
1760 case HEADER_FIELD:
1761 /* Previous element was the header field, create the header value */
1762 client->current_header_value = zend_string_init(at, length, /* persistent */ true);
1763 GC_MAKE_PERSISTENT_LOCAL(client->current_header_value);
1764 break;
1765 case HEADER_VALUE: {
1766 /* Append header value to the previous value of it */
1767 client->current_header_value = cli_concat_persistent_zstr_with_char(client->current_header_value, at, length);
1768 break;
1769 }
1770 case HEADER_NONE:
1771 /* Cannot happen as a header field must have been found before */
1772 assert(0);
1773 break;
1774 }
1775 client->last_header_element = HEADER_VALUE;
1776 return 0;
1777}
1778
1779static int php_cli_server_client_read_request_on_headers_complete(php_http_parser *parser)
1780{
1782 switch (client->last_header_element) {
1783 case HEADER_NONE:
1784 break;
1785 case HEADER_FIELD:
1786 /* Previous element was the header field, set it's value to an empty string */
1787 client->current_header_value = ZSTR_EMPTY_ALLOC();
1789 case HEADER_VALUE:
1790 /* Save last header value */
1791 php_cli_server_client_save_header(client);
1792 break;
1793 }
1794 client->last_header_element = HEADER_NONE;
1795 return 0;
1796}
1797
1798static int php_cli_server_client_read_request_on_body(php_http_parser *parser, const char *at, size_t length)
1799{
1801 if (!client->request.content) {
1802 client->request.content = pemalloc(parser->content_length, 1);
1803 client->request.content_len = 0;
1804 }
1805 client->request.content = perealloc(client->request.content, client->request.content_len + length, 1);
1806 memmove(client->request.content + client->request.content_len, at, length);
1807 client->request.content_len += length;
1808 return 0;
1809}
1810
1811static int php_cli_server_client_read_request_on_message_complete(php_http_parser *parser)
1812{
1814 client->request.protocol_version = parser->http_major * 100 + parser->http_minor;
1815 php_cli_server_request_translate_vpath(client->server, &client->request, client->server->document_root, client->server->document_root_len);
1816 if (client->request.vpath) {
1817 const char *vpath = client->request.vpath;
1818 const char *end = vpath + client->request.vpath_len;
1819 const char *p = end;
1820 client->request.ext = end;
1821 client->request.ext_len = 0;
1822 while (p > vpath) {
1823 --p;
1824 if (*p == '.') {
1825 ++p;
1826 client->request.ext = p;
1827 client->request.ext_len = end - p;
1828 break;
1829 }
1830 }
1831 }
1832 client->request_read = true;
1833 return 0;
1834}
1835
1836/* Returns:
1837 -1 when an error occurs
1838 0 when the request has not been read (try again?)
1839 1 when the request has been read
1840*/
1841static int php_cli_server_client_read_request(php_cli_server_client *client, char **errstr)
1842{
1843 char buf[16384];
1844 static const php_http_parser_settings settings = {
1845 php_cli_server_client_read_request_on_message_begin,
1846 php_cli_server_client_read_request_on_path,
1847 php_cli_server_client_read_request_on_query_string,
1848 php_cli_server_client_read_request_on_url,
1849 php_cli_server_client_read_request_on_fragment,
1850 php_cli_server_client_read_request_on_header_field,
1851 php_cli_server_client_read_request_on_header_value,
1852 php_cli_server_client_read_request_on_headers_complete,
1853 php_cli_server_client_read_request_on_body,
1854 php_cli_server_client_read_request_on_message_complete
1855 };
1856 size_t nbytes_consumed;
1857 int nbytes_read;
1858 if (client->request_read) {
1859 return 1;
1860 }
1861 nbytes_read = recv(client->sock, buf, sizeof(buf) - 1, 0);
1862 if (nbytes_read < 0) {
1863 int err = php_socket_errno();
1864 if (err == SOCK_EAGAIN) {
1865 return 0;
1866 }
1867
1868 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1869 *errstr = php_socket_strerror(err, NULL, 0);
1870 }
1871
1872 return -1;
1873 } else if (nbytes_read == 0) {
1874 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1875 *errstr = estrdup(php_cli_server_request_error_unexpected_eof);
1876 }
1877
1878 return -1;
1879 }
1880 client->parser.data = client;
1881 nbytes_consumed = php_http_parser_execute(&client->parser, &settings, buf, nbytes_read);
1882 if (nbytes_consumed != (size_t)nbytes_read) {
1883 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1884 if ((buf[0] & 0x80) /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
1885 *errstr = estrdup("Unsupported SSL request");
1886 } else {
1887 *errstr = estrdup("Malformed HTTP request");
1888 }
1889 }
1890
1891 return -1;
1892 }
1893
1894 return client->request_read ? 1: 0;
1895}
1896/* }}} */
1897
1898static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len) /* {{{ */
1899{
1900 struct timeval tv = { 10, 0 };
1901#ifdef PHP_WIN32
1902 int nbytes_left = (int)str_len;
1903#else
1904 ssize_t nbytes_left = (ssize_t)str_len;
1905#endif
1906 do {
1907#ifdef PHP_WIN32
1908 int nbytes_sent;
1909#else
1910 ssize_t nbytes_sent;
1911#endif
1912
1913 nbytes_sent = send(client->sock, str + str_len - nbytes_left, nbytes_left, 0);
1914 if (nbytes_sent < 0) {
1915 int err = php_socket_errno();
1916 if (err == SOCK_EAGAIN) {
1917 int nfds = php_pollfd_for(client->sock, POLLOUT, &tv);
1918 if (nfds > 0) {
1919 continue;
1920 } else {
1921 /* error or timeout */
1923 return nbytes_left;
1924 }
1925 } else {
1927 return nbytes_left;
1928 }
1929 }
1930 nbytes_left -= nbytes_sent;
1931 } while (nbytes_left > 0);
1932
1933 return str_len;
1934} /* }}} */
1935
1936static void php_cli_server_client_populate_request_info(const php_cli_server_client *client, sapi_request_info *request_info) /* {{{ */
1937{
1938 zval *val;
1939
1940 request_info->request_method = php_http_method_str(client->request.request_method);
1941 request_info->proto_num = client->request.protocol_version;
1942 request_info->request_uri = ZSTR_VAL(client->request.request_uri);
1943 request_info->path_translated = client->request.path_translated;
1944 request_info->query_string = client->request.query_string;
1945 request_info->content_length = client->request.content_len;
1946 request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL;
1947 if (NULL != (val = zend_hash_str_find(&client->request.headers, "content-type", sizeof("content-type")-1))) {
1948 request_info->content_type = Z_STRVAL_P(val);
1949 } else {
1950 request_info->content_type = NULL;
1951 }
1952} /* }}} */
1953
1954// TODO Remove?
1955static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
1956{
1957} /* }}} */
1958
1959static void php_cli_server_client_ctor(php_cli_server_client *client, php_cli_server *server, php_socket_t client_sock, struct sockaddr *addr, socklen_t addr_len) /* {{{ */
1960{
1961 client->server = server;
1962 client->sock = client_sock;
1963 client->addr = addr;
1964 client->addr_len = addr_len;
1965
1966 // TODO To prevent the reallocation need to retrieve a persistent string
1967 // Create a new php_network_populate_name_from_sockaddr_ex() API with a persistent flag?
1968 zend_string *tmp_addr = NULL;
1969 php_network_populate_name_from_sockaddr(addr, addr_len, &tmp_addr, NULL, 0);
1970 client->addr_str = zend_string_dup(tmp_addr, /* persistent */ true);
1972 zend_string_release_ex(tmp_addr, /* persistent */ false);
1973
1975 client->request_read = false;
1976
1977 client->last_header_element = HEADER_NONE;
1978 client->current_header_name = NULL;
1979 client->current_header_value = NULL;
1980
1981 client->post_read_offset = 0;
1982
1983 php_cli_server_request_ctor(&client->request);
1984
1985 client->content_sender_initialized = false;
1986 client->file_fd = -1;
1987} /* }}} */
1988
1989static void php_cli_server_client_dtor(php_cli_server_client *client) /* {{{ */
1990{
1991 php_cli_server_request_dtor(&client->request);
1992 if (client->file_fd >= 0) {
1993 close(client->file_fd);
1994 client->file_fd = -1;
1995 }
1996 pefree(client->addr, 1);
1997 zend_string_release_ex(client->addr_str, /* persistent */ true);
1998
1999 if (client->content_sender_initialized) {
2000 /* Headers must be set if we reached the content initialisation */
2001 assert(client->current_header_name == NULL);
2002 assert(client->current_header_value == NULL);
2003 php_cli_server_content_sender_dtor(&client->content_sender);
2004 }
2005} /* }}} */
2006
2007static void php_cli_server_close_connection(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2008{
2009 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s Closing", ZSTR_VAL(client->addr_str));
2010
2011 zend_hash_index_del(&server->clients, client->sock);
2012} /* }}} */
2013
2014static zend_result php_cli_server_send_error_page(php_cli_server *server, php_cli_server_client *client, int status) /* {{{ */
2015{
2016 zend_string *escaped_request_uri = NULL;
2017 const char *status_string = get_status_string(status);
2018 const char *content_template = get_template_string(status);
2019 char *errstr = get_last_error();
2020 assert(status_string && content_template);
2021
2022 php_cli_server_content_sender_ctor(&client->content_sender);
2023 client->content_sender_initialized = true;
2024
2025 if (client->request.request_method != PHP_HTTP_HEAD) {
2026 escaped_request_uri = php_escape_html_entities_ex((const unsigned char *) ZSTR_VAL(client->request.request_uri), ZSTR_LEN(client->request.request_uri), 0, ENT_QUOTES, NULL, /* double_encode */ 0, /* quiet */ 0);
2027
2028 {
2029 static const char prologue_template[] = "<!doctype html><html><head><title>%d %s</title>";
2030 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(prologue_template) + 3 + strlen(status_string) + 1);
2031 if (!chunk) {
2032 goto fail;
2033 }
2034 snprintf(chunk->data.heap.p, chunk->data.heap.len, prologue_template, status, status_string);
2035 chunk->data.heap.len = strlen(chunk->data.heap.p);
2036 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2037 }
2038 {
2039 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(php_cli_server_css, sizeof(php_cli_server_css) - 1);
2040 if (!chunk) {
2041 goto fail;
2042 }
2043 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2044 }
2045 {
2046 static const char template[] = "</head><body>";
2047 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(template, sizeof(template) - 1);
2048 if (!chunk) {
2049 goto fail;
2050 }
2051 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2052 }
2053 {
2054 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(content_template) + ZSTR_LEN(escaped_request_uri) + 3 + strlen(status_string) + 1);
2055 if (!chunk) {
2056 goto fail;
2057 }
2058 snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, ZSTR_VAL(escaped_request_uri));
2059 chunk->data.heap.len = strlen(chunk->data.heap.p);
2060 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2061 }
2062 {
2063 static const char epilogue_template[] = "</body></html>";
2064 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(epilogue_template, sizeof(epilogue_template) - 1);
2065 if (!chunk) {
2066 goto fail;
2067 }
2068 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2069 }
2070 }
2071
2072 {
2073 php_cli_server_chunk *chunk;
2074 smart_str buffer = { 0 };
2075 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2076 if (!buffer.s) {
2077 /* out of memory */
2078 goto fail;
2079 }
2080 append_essential_headers(&buffer, client, 1, NULL);
2081 smart_str_appends_ex(&buffer, SAPI_PHP_VERSION_HEADER "\r\n", 1);
2082 smart_str_appends_ex(&buffer, "Content-Type: text/html; charset=UTF-8\r\n", 1);
2083 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2084 smart_str_append_unsigned_ex(&buffer, php_cli_server_buffer_size(&client->content_sender.buffer), 1);
2085 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2086 if (status == 405) {
2087 smart_str_appends_ex(&buffer, "Allow: ", 1);
2088 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_GET), 1);
2089 smart_str_appends_ex(&buffer, ", ", 1);
2090 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_HEAD), 1);
2091 smart_str_appends_ex(&buffer, ", ", 1);
2092 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_POST), 1);
2093 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2094 }
2095 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2096
2097 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2098 if (!chunk) {
2099 smart_str_free_ex(&buffer, 1);
2100 goto fail;
2101 }
2102 php_cli_server_buffer_prepend(&client->content_sender.buffer, chunk);
2103 }
2104
2105 php_cli_server_log_response(client, status, errstr ? errstr : "?");
2106 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2107 if (errstr) {
2108 pefree(errstr, 1);
2109 }
2110 if (escaped_request_uri) {
2111 zend_string_free(escaped_request_uri);
2112 }
2113 return SUCCESS;
2114
2115fail:
2116 if (errstr) {
2117 pefree(errstr, 1);
2118 }
2119 if (escaped_request_uri) {
2120 zend_string_free(escaped_request_uri);
2121 }
2122 return FAILURE;
2123} /* }}} */
2124
2125static zend_result php_cli_server_dispatch_script(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2126{
2127 if (strlen(client->request.path_translated) != client->request.path_translated_len) {
2128 /* can't handle paths that contain nul bytes */
2129 return php_cli_server_send_error_page(server, client, 400);
2130 }
2131
2132 zend_file_handle zfd;
2133 zend_stream_init_filename(&zfd, SG(request_info).path_translated);
2134 zfd.primary_script = 1;
2135 zend_try {
2136 php_execute_script(&zfd);
2137 } zend_end_try();
2139
2140 php_cli_server_log_response(client, SG(sapi_headers).http_response_code, NULL);
2141 return SUCCESS;
2142} /* }}} */
2143
2144static zend_result php_cli_server_begin_send_static(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2145{
2146 int fd;
2147 int status = 200;
2148
2149 if (client->request.request_method == PHP_HTTP_DELETE
2150 || client->request.request_method == PHP_HTTP_PUT
2151 || client->request.request_method == PHP_HTTP_PATCH) {
2152 return php_cli_server_send_error_page(server, client, 405);
2153 }
2154
2155 if (client->request.path_translated && strlen(client->request.path_translated) != client->request.path_translated_len) {
2156 /* can't handle paths that contain nul bytes */
2157 return php_cli_server_send_error_page(server, client, 400);
2158 }
2159
2160#ifdef PHP_WIN32
2161 /* The win32 namespace will cut off trailing dots and spaces. Since the
2162 VCWD functionality isn't used here, a sophisticated functionality
2163 would have to be reimplemented to know ahead there are no files
2164 with invalid names there. The simplest is just to forbid invalid
2165 filenames, which is done here. */
2166 if (client->request.path_translated &&
2167 ('.' == client->request.path_translated[client->request.path_translated_len-1] ||
2168 ' ' == client->request.path_translated[client->request.path_translated_len-1])) {
2169 return php_cli_server_send_error_page(server, client, 500);
2170 }
2171
2172 fd = client->request.path_translated ? php_win32_ioutil_open(client->request.path_translated, O_RDONLY): -1;
2173#else
2174 fd = client->request.path_translated ? open(client->request.path_translated, O_RDONLY): -1;
2175#endif
2176 if (fd < 0) {
2177 return php_cli_server_send_error_page(server, client, 404);
2178 }
2179
2180 php_cli_server_content_sender_ctor(&client->content_sender);
2181 client->content_sender_initialized = true;
2182 if (client->request.request_method != PHP_HTTP_HEAD) {
2183 client->file_fd = fd;
2184 }
2185
2186 {
2187 php_cli_server_chunk *chunk;
2188 smart_str buffer = { 0 };
2189 const char *mime_type = get_mime_type(server, client->request.ext, client->request.ext_len);
2190
2191 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2192 if (!buffer.s) {
2193 /* out of memory */
2194 php_cli_server_log_response(client, 500, NULL);
2195 return FAILURE;
2196 }
2197 append_essential_headers(&buffer, client, 1, NULL);
2198 if (mime_type) {
2199 smart_str_appendl_ex(&buffer, "Content-Type: ", sizeof("Content-Type: ") - 1, 1);
2200 smart_str_appends_ex(&buffer, mime_type, 1);
2201 if (strncmp(mime_type, "text/", 5) == 0) {
2202 smart_str_appends_ex(&buffer, "; charset=UTF-8", 1);
2203 }
2204 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2205 }
2206 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2207 smart_str_append_unsigned_ex(&buffer, client->request.sb.st_size, 1);
2208 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2209 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2210 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2211 if (!chunk) {
2212 smart_str_free_ex(&buffer, 1);
2213 php_cli_server_log_response(client, 500, NULL);
2214 return FAILURE;
2215 }
2216 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2217 }
2218 php_cli_server_log_response(client, 200, NULL);
2219 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2220 return SUCCESS;
2221}
2222/* }}} */
2223
2224static zend_result php_cli_server_request_startup(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2225 zval *auth;
2226 php_cli_server_client_populate_request_info(client, &SG(request_info));
2227 if (NULL != (auth = zend_hash_str_find(&client->request.headers, "authorization", sizeof("authorization")-1))) {
2229 }
2230 SG(sapi_headers).http_response_code = 200;
2231 if (FAILURE == php_request_startup()) {
2232 return FAILURE;
2233 }
2234 PG(during_request_startup) = 0;
2235
2236 return SUCCESS;
2237}
2238/* }}} */
2239
2240static void php_cli_server_request_shutdown(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2242 php_cli_server_close_connection(server, client);
2243 destroy_request_info(&SG(request_info));
2244 SG(server_context) = NULL;
2245 SG(rfc1867_uploaded_files) = NULL;
2246 SG(request_parse_body_context).throw_exceptions = false;
2247 memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
2248}
2249/* }}} */
2250
2251static bool php_cli_server_dispatch_router(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2252{
2253 bool decline = false;
2254 zend_file_handle zfd;
2255 char *old_cwd;
2256
2257 ALLOCA_FLAG(use_heap)
2258 old_cwd = do_alloca(MAXPATHLEN, use_heap);
2259 old_cwd[0] = '\0';
2261
2262 zend_stream_init_filename(&zfd, server->router);
2263 zfd.primary_script = 1;
2264
2265 zend_try {
2266 zval retval;
2268 int sg_options_back = SG(options);
2269 /* Don't chdir to the router script because the file path may be relative. */
2271 CG(skip_shebang) = true;
2272 bool result = php_execute_script_ex(&zfd, &retval);
2273 SG(options) = sg_options_back;
2274 if (result) {
2275 if (Z_TYPE(retval) != IS_UNDEF) {
2276 decline = Z_TYPE(retval) == IS_FALSE;
2278 }
2279 }
2280 } zend_end_try();
2281
2283
2284 if (old_cwd[0] != '\0') {
2285 php_ignore_value(VCWD_CHDIR(old_cwd));
2286 }
2287
2288 free_alloca(old_cwd, use_heap);
2289
2290 return decline;
2291}
2292/* }}} */
2293
2294static zend_result php_cli_server_dispatch(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2295{
2296 int is_static_file = 0;
2297 const char *ext = client->request.ext;
2298
2299 SG(server_context) = client;
2300 if (client->request.ext_len != 3
2301 || (ext[0] != 'p' && ext[0] != 'P') || (ext[1] != 'h' && ext[1] != 'H') || (ext[2] != 'p' && ext[2] != 'P')
2302 || !client->request.path_translated) {
2303 is_static_file = 1;
2304 }
2305
2306 if (server->router || !is_static_file) {
2307 if (FAILURE == php_cli_server_request_startup(server, client)) {
2308 php_cli_server_request_shutdown(server, client);
2309 return FAILURE;
2310 }
2311 }
2312
2313 if (server->router) {
2314 if (!php_cli_server_dispatch_router(server, client)) {
2315 php_cli_server_request_shutdown(server, client);
2316 return SUCCESS;
2317 }
2318 }
2319
2320 if (!is_static_file) {
2321 // TODO What?
2322 if (SUCCESS == php_cli_server_dispatch_script(server, client)
2323 || FAILURE == php_cli_server_send_error_page(server, client, 500)) {
2324 if (SG(sapi_headers).http_response_code == 304) {
2325 SG(sapi_headers).send_default_content_type = 0;
2326 }
2327 php_cli_server_request_shutdown(server, client);
2328 return SUCCESS;
2329 }
2330 } else {
2331 if (server->router) {
2332 static int (*send_header_func)(sapi_headers_struct *);
2333 send_header_func = sapi_module.send_headers;
2334 /* do not generate default content type header */
2335 SG(sapi_headers).send_default_content_type = 0;
2336 /* we don't want headers to be sent */
2337 sapi_module.send_headers = sapi_cli_server_discard_headers;
2339 sapi_module.send_headers = send_header_func;
2340 SG(sapi_headers).send_default_content_type = 1;
2341 SG(rfc1867_uploaded_files) = NULL;
2342 SG(request_parse_body_context).throw_exceptions = false;
2343 memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
2344 }
2345 if (FAILURE == php_cli_server_begin_send_static(server, client)) {
2346 php_cli_server_close_connection(server, client);
2347 }
2348 SG(server_context) = NULL;
2349 return SUCCESS;
2350 }
2351
2352 SG(server_context) = NULL;
2353 destroy_request_info(&SG(request_info));
2354 return SUCCESS;
2355}
2356/* }}} */
2357
2358static void php_cli_server_mime_type_ctor(php_cli_server *server, const php_cli_server_ext_mime_type_pair *mime_type_map) /* {{{ */
2359{
2361
2362 zend_hash_init(&server->extension_mime_types, 0, NULL, NULL, 1);
2363 GC_MAKE_PERSISTENT_LOCAL(&server->extension_mime_types);
2364
2365 for (pair = mime_type_map; pair->ext; pair++) {
2366 size_t ext_len = strlen(pair->ext);
2367 zend_hash_str_add_ptr(&server->extension_mime_types, pair->ext, ext_len, (void*)pair->mime_type);
2368 }
2369} /* }}} */
2370
2371static void php_cli_server_dtor(php_cli_server *server) /* {{{ */
2372{
2373 zend_hash_destroy(&server->clients);
2374 zend_hash_destroy(&server->extension_mime_types);
2375 if (ZEND_VALID_SOCKET(server->server_sock)) {
2376 closesocket(server->server_sock);
2377 }
2378 if (server->host) {
2379 pefree(server->host, 1);
2380 }
2381 if (server->document_root) {
2382 pefree(server->document_root, 1);
2383 }
2384 if (server->router) {
2385 pefree(server->router, 1);
2386 }
2387#ifdef HAVE_FORK
2388 if (php_cli_server_workers_max > 1 &&
2389 php_cli_server_workers &&
2390 getpid() == php_cli_server_master) {
2391 zend_long php_cli_server_worker;
2392
2393 for (php_cli_server_worker = 0;
2394 php_cli_server_worker < php_cli_server_workers_max;
2395 php_cli_server_worker++) {
2396 int php_cli_server_worker_status;
2397
2398 do {
2399 if (waitpid(php_cli_server_workers[php_cli_server_worker],
2400 &php_cli_server_worker_status,
2401 0) == FAILURE) {
2402 /* an extremely bad thing happened */
2403 break;
2404 }
2405
2406 } while (!WIFEXITED(php_cli_server_worker_status) &&
2407 !WIFSIGNALED(php_cli_server_worker_status));
2408 }
2409
2410 pefree(php_cli_server_workers, 1);
2411 }
2412#endif
2413} /* }}} */
2414
2415static void php_cli_server_client_dtor_wrapper(zval *zv) /* {{{ */
2416{
2418
2419 shutdown(p->sock, SHUT_RDWR);
2420 closesocket(p->sock);
2421 php_cli_server_poller_remove(&p->server->poller, POLLIN | POLLOUT, p->sock);
2422 php_cli_server_client_dtor(p);
2423 pefree(p, 1);
2424} /* }}} */
2425
2432static char *php_cli_server_parse_addr(const char *addr, int *pport) {
2433 const char *p, *end;
2434 long port;
2435
2436 if (addr[0] == '[') {
2437 /* Encapsulated [hostOrIP]:port */
2438 const char *start = addr + 1;
2439 end = strchr(start, ']');
2440 if (!end) {
2441 /* No ending ] delimiter to match [ */
2442 return NULL;
2443 }
2444
2445 p = end + 1;
2446 if (*p != ':') {
2447 /* Invalid char following address/missing port */
2448 return NULL;
2449 }
2450
2451 port = strtol(p + 1, (char**)&p, 10);
2452 if (p && *p) {
2453 /* Non-numeric in port */
2454 return NULL;
2455 }
2456 if (port < 0 || port > 65535) {
2457 /* Invalid port */
2458 return NULL;
2459 }
2460
2461 /* Full [hostOrIP]:port provided */
2462 *pport = (int)port;
2463 return pestrndup(start, end - start, 1);
2464 }
2465
2466 end = strchr(addr, ':');
2467 if (!end) {
2468 /* Missing port */
2469 return NULL;
2470 }
2471
2472 port = strtol(end + 1, (char**)&p, 10);
2473 if (p && *p) {
2474 /* Non-numeric port */
2475 return NULL;
2476 }
2477 if (port < 0 || port > 65535) {
2478 /* Invalid port */
2479 return NULL;
2480 }
2481 *pport = (int)port;
2482 return pestrndup(addr, end - addr, 1);
2483}
2484
2485#if defined(HAVE_PRCTL) || defined(HAVE_PROCCTL)
2486static void php_cli_server_worker_install_pdeathsig(void)
2487{
2488 // Ignore failure to register PDEATHSIG, it's not available on all platforms anyway
2489#if defined(HAVE_PRCTL)
2490 prctl(PR_SET_PDEATHSIG, SIGTERM);
2491#elif defined(HAVE_PROCCTL)
2492 int signal = SIGTERM;
2493 procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signal);
2494#endif
2495
2496 // Check if parent has exited just after the fork
2497 if (getppid() != php_cli_server_master) {
2498 exit(1);
2499 }
2500}
2501#endif
2502
2503static void php_cli_server_startup_workers(void) {
2504 char *workers = getenv("PHP_CLI_SERVER_WORKERS");
2505 if (!workers) {
2506 return;
2507 }
2508
2509#ifdef HAVE_FORK
2510 php_cli_server_workers_max = ZEND_ATOL(workers);
2511 if (php_cli_server_workers_max > 1) {
2512 zend_long php_cli_server_worker;
2513
2514 php_cli_server_workers = pecalloc(
2515 php_cli_server_workers_max, sizeof(pid_t), 1);
2516
2517 php_cli_server_master = getpid();
2518
2519 for (php_cli_server_worker = 0;
2520 php_cli_server_worker < php_cli_server_workers_max;
2521 php_cli_server_worker++) {
2522 pid_t pid = fork();
2523
2524 if (pid < 0) {
2525 /* no more forks allowed, work with what we have ... */
2526 php_cli_server_workers_max =
2527 php_cli_server_worker + 1;
2528 return;
2529 } else if (pid == 0) {
2530#if defined(HAVE_PRCTL) || defined(HAVE_PROCCTL)
2531 php_cli_server_worker_install_pdeathsig();
2532#endif
2533 return;
2534 } else {
2535 php_cli_server_workers[php_cli_server_worker] = pid;
2536 }
2537 }
2538 } else {
2539 fprintf(stderr, "number of workers must be larger than 1\n");
2540 }
2541#else
2542 fprintf(stderr, "forking is not supported on this platform\n");
2543#endif
2544}
2545
2546static zend_result php_cli_server_ctor(php_cli_server *server, const char *addr, const char *document_root, const char *router) /* {{{ */
2547{
2549 char *host = NULL;
2550 zend_string *errstr = NULL;
2551 char *_document_root = NULL;
2552 char *_router = NULL;
2553 int port = 3000;
2554 php_socket_t server_sock = SOCK_ERR;
2555
2556 host = php_cli_server_parse_addr(addr, &port);
2557 if (!host) {
2558 fprintf(stderr, "Invalid address: %s\n", addr);
2559 retval = FAILURE;
2560 goto out;
2561 }
2562
2563 server_sock = php_network_listen_socket(host, &port, SOCK_STREAM, &server->address_family, &server->socklen, &errstr);
2564 if (server_sock == SOCK_ERR) {
2565 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to listen on %s:%d (reason: %s)", host, port, errstr ? ZSTR_VAL(errstr) : "?");
2566 if (errstr) {
2567 zend_string_release_ex(errstr, 0);
2568 }
2569 retval = FAILURE;
2570 goto out;
2571 }
2572 // server_sock needs to be non-blocking when using multiple processes. Without it, the first process would
2573 // successfully accept the connection but the others would block, causing client sockets of the same select
2574 // call not to be handled.
2575 if (SUCCESS != php_set_sock_blocking(server_sock, 0)) {
2576 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to make server socket non-blocking");
2577 retval = FAILURE;
2578 goto out;
2579 }
2580 server->server_sock = server_sock;
2581
2582 php_cli_server_startup_workers();
2583
2584 php_cli_server_poller_ctor(&server->poller);
2585
2586 php_cli_server_poller_add(&server->poller, POLLIN, server_sock);
2587
2588 server->host = host;
2589 server->port = port;
2590
2591 zend_hash_init(&server->clients, 0, NULL, php_cli_server_client_dtor_wrapper, 1);
2592
2593 {
2594 size_t document_root_len = strlen(document_root);
2595 _document_root = pestrndup(document_root, document_root_len, 1);
2596 server->document_root = _document_root;
2597 server->document_root_len = document_root_len;
2598 }
2599
2600 if (router) {
2601 size_t router_len = strlen(router);
2602 _router = pestrndup(router, router_len, 1);
2603 server->router = _router;
2604 server->router_len = router_len;
2605 } else {
2606 server->router = NULL;
2607 server->router_len = 0;
2608 }
2609
2610 php_cli_server_mime_type_ctor(server, mime_type_map);
2611
2612 server->is_running = 1;
2613out:
2614 if (retval == FAILURE) {
2615 if (host) {
2616 pefree(host, 1);
2617 }
2618 if (_document_root) {
2619 pefree(_document_root, 1);
2620 }
2621 if (_router) {
2622 pefree(_router, 1);
2623 }
2624 if (server_sock > -1) {
2625 closesocket(server_sock);
2626 }
2627 }
2628 return retval;
2629} /* }}} */
2630
2631static zend_result php_cli_server_recv_event_read_request(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2632{
2633 char *errstr = NULL;
2634
2635 switch (php_cli_server_client_read_request(client, &errstr)) {
2636 case -1:
2637 if (errstr) {
2638 if (strcmp(errstr, php_cli_server_request_error_unexpected_eof) == 0 && client->parser.state == s_start_req) {
2639 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE,
2640 "%s Closed without sending a request; it was probably just an unused speculative preconnection", ZSTR_VAL(client->addr_str));
2641 } else {
2642 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s Invalid request (%s)", ZSTR_VAL(client->addr_str), errstr);
2643 }
2644 efree(errstr);
2645 }
2646 php_cli_server_close_connection(server, client);
2647 return FAILURE;
2648 case 1:
2649 if (client->request.request_method == PHP_HTTP_NOT_IMPLEMENTED) {
2650 return php_cli_server_send_error_page(server, client, 501);
2651 }
2652 php_cli_server_poller_remove(&server->poller, POLLIN, client->sock);
2653 return php_cli_server_dispatch(server, client);
2654 case 0:
2655 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2656 return SUCCESS;
2658 }
2659 /* Under ASAN the compiler somehow doesn't realise that the switch block always returns */
2660 return FAILURE;
2661} /* }}} */
2662
2663static zend_result php_cli_server_send_event(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2664{
2665 if (client->content_sender_initialized) {
2666 if (client->file_fd >= 0 && !client->content_sender.buffer.first) {
2667 size_t nbytes_read;
2668 if (!php_cli_server_content_sender_pull(&client->content_sender, client->file_fd, &nbytes_read)) {
2669 php_cli_server_close_connection(server, client);
2670 return FAILURE;
2671 }
2672 if (nbytes_read == 0) {
2673 close(client->file_fd);
2674 client->file_fd = -1;
2675 }
2676 }
2677
2678 size_t nbytes_sent;
2679 int err = php_cli_server_content_sender_send(&client->content_sender, client->sock, &nbytes_sent);
2680 if (err && err != SOCK_EAGAIN) {
2681 php_cli_server_close_connection(server, client);
2682 return FAILURE;
2683 }
2684
2685 if (!client->content_sender.buffer.first && client->file_fd < 0) {
2686 php_cli_server_close_connection(server, client);
2687 }
2688 }
2689 return SUCCESS;
2690}
2691/* }}} */
2692
2698
2699static zend_result php_cli_server_do_event_for_each_fd_callback(void *_params, php_socket_t fd, int event) /* {{{ */
2700{
2702 php_cli_server *server = params->server;
2703 if (server->server_sock == fd) {
2705 php_socket_t client_sock;
2706 socklen_t socklen = server->socklen;
2707 struct sockaddr *sa = pemalloc(server->socklen, 1);
2708 client_sock = accept(server->server_sock, sa, &socklen);
2709 if (!ZEND_VALID_SOCKET(client_sock)) {
2710 int err = php_socket_errno();
2711 if (err != SOCK_EAGAIN && php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
2712 char *errstr = php_socket_strerror(php_socket_errno(), NULL, 0);
2713 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR,
2714 "Failed to accept a client (reason: %s)", errstr);
2715 efree(errstr);
2716 }
2717 pefree(sa, 1);
2718 return FAILURE;
2719 }
2720 if (SUCCESS != php_set_sock_blocking(client_sock, 0)) {
2721 pefree(sa, 1);
2722 closesocket(client_sock);
2723 return FAILURE;
2724 }
2726
2727 php_cli_server_client_ctor(client, server, client_sock, sa, socklen);
2728
2729 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s Accepted", ZSTR_VAL(client->addr_str));
2730
2731 zend_hash_index_update_ptr(&server->clients, client_sock, client);
2732
2733 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2734 } else {
2736 if (NULL != (client = zend_hash_index_find_ptr(&server->clients, fd))) {
2737 if (event & POLLIN) {
2738 params->rhandler(server, client);
2739 }
2740 if (event & POLLOUT) {
2741 params->whandler(server, client);
2742 }
2743 }
2744 }
2745 return SUCCESS;
2746} /* }}} */
2747
2748static void php_cli_server_do_event_for_each_fd(php_cli_server *server,
2750 zend_result(*whandler)(php_cli_server*, php_cli_server_client*)) /* {{{ */
2751{
2753 server,
2754 rhandler,
2755 whandler
2756 };
2757
2758 if (SUCCESS != php_cli_server_poller_iter_on_active(&server->poller, &params, php_cli_server_do_event_for_each_fd_callback)) {
2759 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to poll event");
2760 }
2761} /* }}} */
2762
2763static zend_result php_cli_server_do_event_loop(php_cli_server *server) /* {{{ */
2764{
2766 while (server->is_running) {
2767 struct timeval tv = { 1, 0 };
2768 int n = php_cli_server_poller_poll(&server->poller, &tv);
2769 if (n > 0) {
2770 php_cli_server_do_event_for_each_fd(server,
2771 php_cli_server_recv_event_read_request,
2772 php_cli_server_send_event);
2773 } else if (n == 0) {
2774 /* do nothing */
2775 } else {
2776 int err = php_socket_errno();
2777 if (err != SOCK_EINTR) {
2778 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
2779 char *errstr = php_socket_strerror(err, NULL, 0);
2780 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s", errstr);
2781 efree(errstr);
2782 }
2783 retval = FAILURE;
2784 goto out;
2785 }
2786 }
2787 }
2788out:
2789 return retval;
2790} /* }}} */
2791
2792static php_cli_server server;
2793
2794static void php_cli_server_sigint_handler(int sig) /* {{{ */
2795{
2796 server.is_running = 0;
2797}
2798/* }}} */
2799
2800/* Returns status code */
2801int do_cli_server(int argc, char **argv) /* {{{ */
2802{
2803 char *php_optarg = NULL;
2804 int php_optind = 1;
2805 int c, r;
2806 const char *server_bind_address = NULL;
2807 extern const opt_struct OPTIONS[];
2808 const char *document_root = NULL;
2809#ifdef PHP_WIN32
2810 char document_root_tmp[MAXPATHLEN];
2811 size_t k;
2812#endif
2813 const char *router = NULL;
2814 char document_root_buf[MAXPATHLEN];
2815
2816 while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2))!=-1) {
2817 switch (c) {
2818 case 'S':
2819 server_bind_address = php_optarg;
2820 break;
2821 case 't':
2822#ifndef PHP_WIN32
2823 document_root = php_optarg;
2824#else
2825 k = strlen(php_optarg);
2826 if (k + 1 > MAXPATHLEN) {
2827 fprintf(stderr, "Document root path is too long.\n");
2828 return 1;
2829 }
2830 memmove(document_root_tmp, php_optarg, k + 1);
2831 /* Clean out any trailing garbage that might have been passed
2832 from a batch script. */
2833 do {
2834 document_root_tmp[k] = '\0';
2835 k--;
2836 } while ('"' == document_root_tmp[k] || ' ' == document_root_tmp[k]);
2837 document_root = document_root_tmp;
2838#endif
2839 break;
2840 case 'q':
2841 if (php_cli_server_log_level > 1) {
2842 php_cli_server_log_level--;
2843 }
2844 break;
2845 }
2846 }
2847
2848 if (document_root) {
2849 zend_stat_t sb = {0};
2850
2851 if (php_sys_stat(document_root, &sb)) {
2852 fprintf(stderr, "Directory %s does not exist.\n", document_root);
2853 return 1;
2854 }
2855 if (!S_ISDIR(sb.st_mode)) {
2856 fprintf(stderr, "%s is not a directory.\n", document_root);
2857 return 1;
2858 }
2859 if (VCWD_REALPATH(document_root, document_root_buf)) {
2860 document_root = document_root_buf;
2861 }
2862 } else {
2863 char *ret = NULL;
2864
2865#ifdef HAVE_GETCWD
2866 ret = VCWD_GETCWD(document_root_buf, MAXPATHLEN);
2867#elif defined(HAVE_GETWD)
2868 ret = VCWD_GETWD(document_root_buf);
2869#endif
2870 document_root = ret ? document_root_buf: ".";
2871 }
2872
2873 if (argc > php_optind) {
2874 router = argv[php_optind];
2875 }
2876
2877 if (FAILURE == php_cli_server_ctor(&server, server_bind_address, document_root, router)) {
2878 return 1;
2879 }
2880 sapi_module.phpinfo_as_text = 0;
2881
2882 {
2883 r = 0;
2884 bool ipv6 = strchr(server.host, ':');
2885 php_cli_server_logf(
2887 "PHP %s Development Server (http://%s%s%s:%d) started",
2888 PHP_VERSION, ipv6 ? "[" : "", server.host,
2889 ipv6 ? "]" : "", server.port);
2890 }
2891
2892#if defined(SIGINT)
2893 signal(SIGINT, php_cli_server_sigint_handler);
2894#endif
2895
2896#if defined(SIGPIPE)
2897 signal(SIGPIPE, SIG_IGN);
2898#endif
2899
2901
2902 if (SUCCESS != php_cli_server_do_event_loop(&server)) {
2903 r = 1;
2904 }
2905 php_cli_server_dtor(&server);
2906 return r;
2907} /* }}} */
SAPI_API sapi_module_struct sapi_module
Definition SAPI.c:65
SAPI_API int sapi_send_headers(void)
Definition SAPI.c:843
#define SAPI_OPTION_NO_CHDIR
Definition SAPI.h:27
#define SG(v)
Definition SAPI.h:160
#define SAPI_HEADER_SENT_SUCCESSFULLY
Definition SAPI.h:303
#define STANDARD_SAPI_MODULE_PROPERTIES
Definition SAPI.h:324
struct _sapi_module_struct sapi_module_struct
Definition SAPI.h:60
#define SAPI_PHP_VERSION_HEADER
Definition SAPI.h:309
size_t len
Definition apprentice.c:174
file_private const char ext[]
zval callback
Definition assert.c:25
fprintf($stream, string $format, mixed ... $values)
getenv(?string $name=null, bool $local_only=false)
http_response_code(int $response_code=0)
file(string $filename, int $flags=0, $context=null)
strrchr(string $haystack, string $needle, bool $before_needle=false)
gettimeofday(bool $as_float=false)
assert(mixed $assertion, Throwable|string|null $description=null)
strchr(string $haystack, string $needle, bool $before_needle=false)
headers_sent(&$filename=null, &$line=null)
char s[4]
Definition cdf.c:77
#define STDOUT_FILENO
Definition cgi_main.c:291
zend_long ptrdiff_t
PHP_WINUTIL_API BOOL php_win32_console_fileno_has_vt100(zend_long fileno)
Definition console.c:36
PHP_WINUTIL_API BOOL php_win32_console_fileno_is_console(zend_long fileno)
Definition console.c:22
DNS_STATUS status
Definition dns_win32.c:49
unsigned int socklen_t
Definition fastcgi.c:87
zend_ffi_type * type
Definition ffi.c:3812
zval * zv
Definition ffi.c:3975
zend_long n
Definition ffi.c:4979
memcpy(ptr1, ptr2, size)
char * err
Definition ffi.c:3029
memset(ptr, 0, type->size)
zval * val
Definition ffi.c:4262
buf start
Definition ffi.c:4687
ffi persistent
Definition ffi.c:3633
zend_ffi_ctype_name_buf buf
Definition ffi.c:4685
char * mode
#define NULL
Definition gdcache.h:45
PHPAPI int php_getopt(int argc, char *const *argv, const opt_struct opts[], char **optarg, int *optind, int show_err, int arg_start)
Definition getopt.c:55
#define SUCCESS
Definition hash_sha3.c:261
PHPAPI zend_string * php_escape_html_entities_ex(const unsigned char *old, size_t oldlen, int all, int flags, const char *hint_charset, bool double_encode, bool quiet)
Definition html.c:1099
struct _http_response_status_code_pair http_response_status_code_pair
#define arginfo_apache_request_headers
void php_request_shutdown(void *dummy)
Definition main.c:1885
PHPAPI bool php_execute_script_ex(zend_file_handle *primary_file, zval *retval)
Definition main.c:2502
zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_module)
Definition main.c:2103
PHPAPI void php_handle_aborted_connection(void)
Definition main.c:2656
PHPAPI bool php_execute_script(zend_file_handle *primary_file)
Definition main.c:2613
zend_result php_request_startup(void)
Definition main.c:1801
PHPAPI int php_handle_auth_data(const char *auth)
Definition main.c:2669
int php_module_shutdown_wrapper(sapi_module_struct *sapi_globals)
Definition main.c:2416
#define next(ls)
Definition minilua.c:2661
PHPAPI zend_result php_set_sock_blocking(php_socket_t socketd, bool block)
Definition network.c:1145
PHPAPI char * php_socket_strerror(long err, char *buf, size_t bufsize)
Definition network.c:1045
PHPAPI void php_network_freeaddresses(struct sockaddr **sal)
Definition network.c:133
PHPAPI void php_network_populate_name_from_sockaddr(struct sockaddr *sa, socklen_t sl, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen)
Definition network.c:643
#define SOCK_CONN_ERR
Definition network.c:86
#define SOCK_ERR
Definition network.c:85
PHPAPI zend_string * php_socket_error_str(long err)
Definition network.c:1079
PHPAPI int php_network_getaddresses(const char *host, int socktype, struct sockaddr ***sal, zend_string **error_string)
Definition network.c:148
const SIGINT
const SIGTERM
const SIG_IGN
const P_PID
const SIGPIPE
#define memmove(a, b, c)
#define PHP_FUNCTION
Definition php.h:364
#define PHP_MSHUTDOWN_FUNCTION
Definition php.h:401
#define PHP_FE_END
Definition php.h:377
#define PHP_MINFO
Definition php.h:396
#define PHP_FALIAS
Definition php.h:371
#define PHP_MINIT_FUNCTION
Definition php.h:400
#define PHP_MSHUTDOWN
Definition php.h:393
#define PHP_FE
Definition php.h:369
#define PHP_MINFO_FUNCTION
Definition php.h:404
#define php_ignore_value(x)
Definition php.h:275
#define PHP_MINIT
Definition php.h:392
#define php_error
Definition php.h:310
cli_set_process_title(string $title)
sapi_module_struct cli_server_sapi_module
#define PHP_CLI_SERVER_LOG_ERROR
int do_cli_server(int argc, char **argv)
#define OUTPUT_NOT_CHECKED
#define php_select(m, r, w, e, t)
#define OUTPUT_IS_TTY
#define SOCK_EINVAL
#define PHP_CLI_SERVER_LOG_PROCESS
#define SOCK_EAGAIN
const zend_function_entry server_additional_functions[]
#define SOCK_EADDRINUSE
#define PHP_CLI_SERVER_LOG_MESSAGE
#define SOCK_EINTR
short color
#define CLI_SERVER_G(v)
PHPAPI zend_string * php_format_date(const char *format, size_t format_len, time_t ts, bool localtime)
Definition php_date.c:875
unsigned const char * end
Definition php_ffi.h:51
unsigned const char * pos
Definition php_ffi.h:52
apache_request_headers()
apache_response_headers()
getallheaders()
#define arginfo_apache_response_headers
#define arginfo_getallheaders
struct _opt_struct opt_struct
#define PG(v)
Definition php_globals.h:31
const char * php_http_method_str(enum php_http_method m)
void php_http_parser_init(php_http_parser *parser, enum php_http_parser_type t)
size_t php_http_parser_execute(php_http_parser *parser, const php_http_parser_settings *settings, const char *data, size_t len)
php_http_method
@ PHP_HTTP_PUT
@ PHP_HTTP_POST
@ PHP_HTTP_GET
@ PHP_HTTP_NOT_IMPLEMENTED
@ PHP_HTTP_DELETE
@ PHP_HTTP_HEAD
@ PHP_HTTP_PATCH
@ PHP_HTTP_REQUEST
@ s_start_req
#define PHP_INI_ALL
Definition php_ini.h:45
#define PHP_INI_BEGIN
Definition php_ini.h:52
#define STD_PHP_INI_BOOLEAN
Definition php_ini.h:66
#define PHP_INI_END
Definition php_ini.h:53
PHP_JSON_API size_t int options
Definition php_json.h:102
#define PHP_SAFE_FD_ISSET(fd, set)
#define PHP_SAFE_FD_SET(fd, set)
#define PHP_SAFE_FD_CLR(fd, set)
#define shutdown(s, n)
Definition php_network.h:30
#define POLLIN
#define SHUT_RDWR
Definition php_network.h:88
#define POLLOUT
#define php_socket_errno()
Definition php_network.h:60
int php_socket_t
#define closesocket
Definition php_network.h:24
php_output_handler * active
Definition php_output.h:140
PHPAPI struct tm * php_localtime_r(const time_t *const timep, struct tm *p_tm)
Definition reentrancy.c:110
PHPAPI char * php_asctime_r(const struct tm *tm, char *buf)
Definition reentrancy.c:152
unsigned char key[REFLECTION_KEY_LEN]
PHPAPI void php_register_variable_safe(const char *var, const char *strval, size_t str_len, zval *track_vars_array)
PHPAPI void php_register_known_variable(const char *var_name, size_t var_name_len, zval *value, zval *track_vars_array)
#define PARSE_SERVER
#define PHP_VERSION
Definition php_version.h:7
char * msg
Definition phpdbg.h:289
int fd
Definition phpdbg.h:282
zend_constant * data
bool fail
Definition session.c:1065
struct timeval tv
Definition session.c:1280
zend_string * var_name
Definition session.c:966
p
Definition session.c:1105
const SOCK_STREAM
const SOL_SOCKET
const AF_INET
const SOMAXCONN
const AF_INET6
const SO_REUSEADDR
#define vspprintf
Definition spprintf.h:31
#define strpprintf
Definition spprintf.h:30
#define spprintf
Definition spprintf.h:29
#define ENT_QUOTES
Definition html.h:37
zend_string * key
Definition zend_hash.h:96
Definition file.h:177
php_cli_server_chunk * last
php_cli_server_chunk * first
struct php_cli_server_chunk::@233151213056232267003236072065271010051202340307::@230064163132226040346367371042105363243245040131 immortal
struct php_cli_server_chunk::@233151213056232267003236072065271010051202340307::@360034026114335335340342177115137143132356151352 heap
struct php_cli_server_chunk * next
php_cli_server_content_sender content_sender
zend_string * current_header_value
struct php_cli_server * server
zend_string * current_header_name
php_cli_server_request request
enum php_cli_server_client::@326210001211157365011322155322033100011060360171 last_header_element
struct sockaddr * addr
php_http_parser parser
php_cli_server_buffer buffer
zend_result(* whandler)(php_cli_server *, php_cli_server_client *)
zend_result(* rhandler)(php_cli_server *, php_cli_server_client *)
struct php_cli_server_poller::@202135166053323335022124200361260302351356236101 active
enum php_http_method request_method
HashTable extension_mime_types
php_cli_server_poller poller
php_socket_t server_sock
unsigned short http_minor
unsigned char method
unsigned short http_major
char * header
Definition SAPI.h:45
size_t header_len
Definition SAPI.h:46
zend_llist headers
Definition SAPI.h:51
zend_long content_length
Definition SAPI.h:76
char * query_string
Definition SAPI.h:74
char * path_translated
Definition SAPI.h:78
char * auth_digest
Definition SAPI.h:97
char * auth_user
Definition SAPI.h:95
const char * request_method
Definition SAPI.h:73
char * request_uri
Definition SAPI.h:79
const char * content_type
Definition SAPI.h:84
char * auth_password
Definition SAPI.h:96
$obj a
Definition test.php:84
PHPAPI size_t php_raw_url_decode(char *str, size_t len)
Definition url.c:644
#define close(a)
#define errno
#define zend_try
Definition zend.h:270
#define zend_end_try()
Definition zend.h:280
ZEND_API void add_assoc_stringl_ex(zval *arg, const char *key, size_t key_len, const char *str, size_t length)
Definition zend_API.c:1991
struct _zend_function_entry zend_function_entry
#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)
Definition zend_API.h:272
#define ZEND_DECLARE_MODULE_GLOBALS(module_name)
Definition zend_API.h:268
#define RETURN_ARR(r)
Definition zend_API.h:1050
#define zend_parse_parameters_none()
Definition zend_API.h:353
#define RETURN_THROWS()
Definition zend_API.h:1060
#define ZVAL_STRINGL_FAST(z, s, l)
Definition zend_API.h:983
#define array_init(arg)
Definition zend_API.h:537
#define estrndup(s, length)
Definition zend_alloc.h:165
#define pestrdup(s, persistent)
Definition zend_alloc.h:206
#define perealloc(ptr, size, persistent)
Definition zend_alloc.h:201
#define pestrndup(s, length, persistent)
Definition zend_alloc.h:207
#define efree(ptr)
Definition zend_alloc.h:155
#define estrdup(s)
Definition zend_alloc.h:164
#define pefree(ptr, persistent)
Definition zend_alloc.h:191
#define pemalloc(size, persistent)
Definition zend_alloc.h:189
#define safe_pemalloc(nmemb, size, offset, persistent)
Definition zend_alloc.h:190
#define pecalloc(nmemb, size, persistent)
Definition zend_alloc.h:200
struct _zval_struct zval
strlen(string $string)
strncmp(string $string1, string $string2, int $length)
exit(string|int $status=0)
strcmp(string $string1, string $string2)
uint32_t num_args
zend_string_release_ex(func->internal_function.function_name, 0)
zval * args
ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle)
#define strncasecmp(s1, s2, n)
#define snprintf
#define E_FATAL_ERRORS
Definition zend_errors.h:47
#define CG(v)
ZEND_API void ZEND_FASTCALL zend_hash_destroy(HashTable *ht)
Definition zend_hash.c:1727
ZEND_API void zend_hash_apply_with_arguments(HashTable *ht, apply_func_args_t apply_func, int num_args,...)
Definition zend_hash.c:2137
ZEND_API zval *ZEND_FASTCALL zend_hash_str_find(const HashTable *ht, const char *str, size_t len)
Definition zend_hash.c:2689
ZEND_API zend_result ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h)
Definition zend_hash.c:1692
ZEND_API HashTable *ZEND_FASTCALL zend_array_dup(HashTable *source)
Definition zend_hash.c:2438
ZEND_API zval *ZEND_FASTCALL zend_hash_update(HashTable *ht, zend_string *key, zval *pData)
Definition zend_hash.c:997
ZEND_API zval *ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key)
Definition zend_hash.c:2668
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent)
Definition zend_hash.h:108
struct _zend_hash_key zend_hash_key
int(* apply_func_args_t)(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key)
Definition zend_hash.h:152
#define ZEND_HASH_APPLY_KEEP
Definition zend_hash.h:146
#define UNREGISTER_INI_ENTRIES()
Definition zend_ini.h:204
#define REGISTER_INI_ENTRIES()
Definition zend_ini.h:203
#define DISPLAY_INI_ENTRIES()
Definition zend_ini.h:205
struct _zend_file_handle zend_file_handle
ZEND_API void * zend_llist_get_next_ex(zend_llist *l, zend_llist_position *pos)
Definition zend_llist.c:286
ZEND_API void zend_llist_apply_with_argument(zend_llist *l, llist_apply_with_arg_func_t func, void *arg)
Definition zend_llist.c:231
ZEND_API void * zend_llist_get_first_ex(zend_llist *l, zend_llist_position *pos)
Definition zend_llist.c:260
zend_llist_element * zend_llist_position
Definition zend_llist.h:47
void(* llist_apply_with_arg_func_t)(void *data, void *arg)
Definition zend_llist.h:34
int32_t zend_long
Definition zend_long.h:42
#define ZEND_ATOL(s)
Definition zend_long.h:101
struct _zend_string zend_string
#define STANDARD_MODULE_HEADER
struct _zend_module_entry zend_module_entry
#define STANDARD_MODULE_PROPERTIES
ZEND_API char *ZEND_FASTCALL zend_str_tolower_copy(char *dest, const char *source, size_t length)
ZEND_API zend_string *ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent)
int last
#define ALLOCA_FLAG(name)
#define MIN(a, b)
#define ZEND_FALLTHROUGH
#define EXPECTED(condition)
#define do_alloca(p, use_heap)
#define ZEND_ASSERT(c)
#define ZEND_VALID_SOCKET(sock)
#define free_alloca(p, use_heap)
#define EMPTY_SWITCH_DEFAULT_CASE()
#define UNEXPECTED(condition)
#define zend_signal_init()
ZEND_DLIMPORT int isatty(int fd)
ZEND_API void zend_stream_init_filename(zend_file_handle *handle, const char *filename)
Definition zend_stream.c:70
struct stat zend_stat_t
Definition zend_stream.h:94
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define ZSTR_KNOWN(idx)
#define zend_string_equals_literal(str, literal)
#define ZSTR_EMPTY_ALLOC()
#define ZSTR_LEN(zstr)
Definition zend_string.h:69
#define Z_TYPE_P(zval_p)
Definition zend_types.h:660
#define ZVAL_STR(z, s)
#define ZVAL_UNDEF(z)
#define IS_FALSE
Definition zend_types.h:602
#define Z_STRVAL_P(zval_p)
Definition zend_types.h:975
#define IS_UNDEF
Definition zend_types.h:600
#define IS_STRING
Definition zend_types.h:606
#define ZVAL_STR_COPY(z, s)
struct _zend_array HashTable
Definition zend_types.h:386
#define Z_STR_P(zval_p)
Definition zend_types.h:972
#define Z_PTR_P(zval_p)
#define GC_MAKE_PERSISTENT_LOCAL(p)
@ FAILURE
Definition zend_types.h:61
ZEND_RESULT_CODE zend_result
Definition zend_types.h:64
#define GC_REFCOUNT(p)
Definition zend_types.h:707
#define Z_TYPE(zval)
Definition zend_types.h:659
ZEND_API void zval_ptr_dtor(zval *zval_ptr)
#define DEFAULT_SLASH
#define VCWD_GETWD(buf)
#define S_ISDIR(mode)
#define VCWD_CHDIR(path)
#define php_sys_stat
#define VCWD_GETCWD(buff, size)
#define MAXPATHLEN
#define VCWD_REALPATH(path, real_path)
zval retval
zval * return_value
bool result
zval * ret
value
out($f, $s)