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"
103 #include "php_cli_server_arginfo.h"
104 #include "mime_type_map.h"
105
106 #include "php_cli_process_title.h"
107 #include "php_cli_process_title_arginfo.h"
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>
115 static pid_t php_cli_server_master;
116 static pid_t *php_cli_server_workers;
117 static zend_long php_cli_server_workers_max;
118 #endif
119
120 static zend_string* cli_concat_persistent_zstr_with_char(zend_string *old_str, const char *at, size_t length);
121
122 typedef struct php_cli_server_poller {
123 fd_set rfds, wfds;
124 struct {
125 fd_set rfds, wfds;
126 } active;
127 php_socket_t max_fd;
128 } php_cli_server_poller;
129
130 typedef struct php_cli_server_request {
131 enum php_http_method request_method;
132 int protocol_version;
133 zend_string *request_uri;
134 char *vpath;
135 size_t vpath_len;
136 char *path_translated;
137 size_t path_translated_len;
138 char *path_info;
139 size_t path_info_len;
140 char *query_string;
141 size_t query_string_len;
142 HashTable headers;
143 HashTable headers_original_case;
144 char *content;
145 size_t content_len;
146 const char *ext;
147 size_t ext_len;
148 zend_stat_t sb;
149 } php_cli_server_request;
150
151 typedef struct php_cli_server_chunk {
152 struct php_cli_server_chunk *next;
153 enum php_cli_server_chunk_type {
154 PHP_CLI_SERVER_CHUNK_HEAP,
155 PHP_CLI_SERVER_CHUNK_IMMORTAL
156 } type;
157 union {
158 struct { void *block; char *p; size_t len; } heap;
159 struct { const char *p; size_t len; } immortal;
160 } data;
161 } php_cli_server_chunk;
162
163 typedef struct php_cli_server_buffer {
164 php_cli_server_chunk *first;
165 php_cli_server_chunk *last;
166 } php_cli_server_buffer;
167
168 typedef struct php_cli_server_content_sender {
169 php_cli_server_buffer buffer;
170 } php_cli_server_content_sender;
171
172 typedef struct php_cli_server_client {
173 struct php_cli_server *server;
174 php_socket_t sock;
175 struct sockaddr *addr;
176 socklen_t addr_len;
177 zend_string *addr_str;
178 php_http_parser parser;
179 bool request_read;
180 zend_string *current_header_name;
181 zend_string *current_header_value;
182 enum { HEADER_NONE=0, HEADER_FIELD, HEADER_VALUE } last_header_element;
183 size_t post_read_offset;
184 php_cli_server_request request;
185 bool content_sender_initialized;
186 php_cli_server_content_sender content_sender;
187 int file_fd;
188 } php_cli_server_client;
189
190 typedef struct php_cli_server {
191 php_socket_t server_sock;
192 php_cli_server_poller poller;
193 int is_running;
194 char *host;
195 int port;
196 int address_family;
197 char *document_root;
198 size_t document_root_len;
199 char *router;
200 size_t router_len;
201 socklen_t socklen;
202 HashTable clients;
203 HashTable extension_mime_types;
204 } php_cli_server;
205
206 typedef struct php_cli_server_http_response_status_code_pair {
207 int code;
208 const char *str;
209 } php_cli_server_http_response_status_code_pair;
210
211 static 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
223 static int php_cli_server_log_level = 3;
224
225 #if defined(HAVE_UNISTD_H) || defined(PHP_WIN32)
226 static int php_cli_output_is_tty = OUTPUT_NOT_CHECKED;
227 #endif
228
229 static const char php_cli_server_request_error_unexpected_eof[] = "Unexpected EOF";
230
231 static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len);
232 static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len);
233 static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk);
234 static void php_cli_server_logf(int type, const char *format, ...);
235 static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message);
236
237 ZEND_DECLARE_MODULE_GLOBALS(cli_server)
238
239 /* {{{ static char php_cli_server_css[]
240 * copied from ext/standard/info.c
241 */
242 static 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
php_cli_server_get_system_time(char * buf)251 static 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
php_cli_server_get_system_time(char * buf)267 static bool php_cli_server_get_system_time(char *buf) {
268 struct timeval tv;
269 struct tm tm;
270
271 gettimeofday(&tv, NULL);
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 */
cli_header_value_dtor(zval * zv)281 static void cli_header_value_dtor(zval *zv) /* {{{ */
282 {
283 zend_string_release_ex(Z_STR_P(zv), /* persistent */ true);
284 } /* }}} */
285
get_last_error(void)286 static char *get_last_error(void) /* {{{ */
287 {
288 return pestrdup(strerror(errno), 1);
289 } /* }}} */
290
status_comp(const void * a,const void * b)291 static int status_comp(const void *a, const void *b) /* {{{ */
292 {
293 const http_response_status_code_pair *pa = (const http_response_status_code_pair *) a;
294 const http_response_status_code_pair *pb = (const http_response_status_code_pair *) b;
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
get_status_string(int code)305 static 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
get_template_string(int code)323 static 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
append_http_status_line(smart_str * buffer,int protocol_version,int response_code,bool persistent)342 static 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
append_essential_headers(smart_str * buffer,php_cli_server_client * client,bool persistent,sapi_headers_struct * sapi_headers)359 static 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) {
366 zend_llist_position pos;
367 sapi_header_struct *h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
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 }
375 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
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);
390 zend_string_release_ex(dt, 0);
391 }
392
393 smart_str_appendl_ex(buffer, "Connection: close\r\n", sizeof("Connection: close\r\n") - 1, persistent);
394 } /* }}} */
395
get_mime_type(const php_cli_server * server,const char * ext,size_t ext_len)396 static 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
PHP_FUNCTION(apache_request_headers)407 PHP_FUNCTION(apache_request_headers) /* {{{ */
408 {
409 php_cli_server_client *client;
410
411 if (zend_parse_parameters_none() == FAILURE) {
412 RETURN_THROWS();
413 }
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
add_response_header(sapi_header_struct * h,zval * return_value)422 static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{{ */
423 {
424 char *s, *p;
425 ptrdiff_t len;
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
PHP_FUNCTION(apache_response_headers)450 PHP_FUNCTION(apache_response_headers) /* {{{ */
451 {
452 if (zend_parse_parameters_none() == FAILURE) {
453 RETURN_THROWS();
454 }
455
456 array_init(return_value);
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
cli_server_init_globals(zend_cli_server_globals * cg)463 static void cli_server_init_globals(zend_cli_server_globals *cg)
464 {
465 cg->color = 0;
466 }
467
468 PHP_INI_BEGIN()
469 STD_PHP_INI_BOOLEAN("cli_server.color", "0", PHP_INI_ALL, OnUpdateBool, color, zend_cli_server_globals, cli_server_globals)
PHP_INI_END()470 PHP_INI_END()
471
472 static PHP_MINIT_FUNCTION(cli_server)
473 {
474 ZEND_INIT_MODULE_GLOBALS(cli_server, cli_server_init_globals, NULL);
475 REGISTER_INI_ENTRIES();
476 return SUCCESS;
477 }
478
PHP_MSHUTDOWN_FUNCTION(cli_server)479 static PHP_MSHUTDOWN_FUNCTION(cli_server)
480 {
481 UNREGISTER_INI_ENTRIES();
482 return SUCCESS;
483 }
484
PHP_MINFO_FUNCTION(cli_server)485 static PHP_MINFO_FUNCTION(cli_server)
486 {
487 DISPLAY_INI_ENTRIES();
488 }
489
490 static zend_module_entry cli_server_module_entry = {
491 STANDARD_MODULE_HEADER,
492 "cli_server",
493 NULL,
494 PHP_MINIT(cli_server),
495 PHP_MSHUTDOWN(cli_server),
496 NULL,
497 NULL,
498 PHP_MINFO(cli_server),
499 PHP_VERSION,
500 STANDARD_MODULE_PROPERTIES
501 };
502 /* }}} */
503
504 const zend_function_entry server_additional_functions[] = {
505 PHP_FE(cli_set_process_title, arginfo_cli_set_process_title)
506 PHP_FE(cli_get_process_title, arginfo_cli_get_process_title)
507 PHP_FE(apache_request_headers, arginfo_apache_request_headers)
508 PHP_FE(apache_response_headers, arginfo_apache_response_headers)
509 PHP_FALIAS(getallheaders, apache_request_headers, arginfo_getallheaders)
510 PHP_FE_END
511 };
512
sapi_cli_server_startup(sapi_module_struct * sapi_module_ptr)513 static int sapi_cli_server_startup(sapi_module_struct *sapi_module_ptr) /* {{{ */
514 {
515 return php_module_startup(sapi_module_ptr, &cli_server_module_entry);
516 } /* }}} */
517
sapi_cli_server_ub_write(const char * str,size_t str_length)518 static 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
sapi_cli_server_flush(void * server_context)527 static 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)) {
536 php_handle_aborted_connection();
537 return;
538 }
539
540 if (!SG(headers_sent)) {
541 sapi_send_headers();
542 SG(headers_sent) = 1;
543 }
544 } /* }}} */
545
sapi_cli_server_discard_headers(sapi_headers_struct * sapi_headers)546 static int sapi_cli_server_discard_headers(sapi_headers_struct *sapi_headers) /* {{{ */{
547 return SAPI_HEADER_SENT_SUCCESSFULLY;
548 }
549 /* }}} */
550
sapi_cli_server_send_headers(sapi_headers_struct * sapi_headers)551 static 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 };
555 sapi_header_struct *h;
556 zend_llist_position pos;
557
558 if (client == NULL || SG(request_info).no_headers) {
559 return SAPI_HEADER_SENT_SUCCESSFULLY;
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
571 h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
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 }
577 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
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);
584 return SAPI_HEADER_SENT_SUCCESSFULLY;
585 }
586 /* }}} */
587
sapi_cli_server_read_cookies(void)588 static 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
sapi_cli_server_read_post(char * buf,size_t count_bytes)598 static 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
sapi_cli_server_register_variable(zval * track_vars_array,const char * key,const char * val)611 static 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
sapi_cli_server_register_known_var_char(zval * track_vars_array,const char * var_name,size_t var_name_len,const char * value,size_t value_len)625 static 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
sapi_cli_server_register_known_var_str(zval * track_vars_array,const char * var_name,size_t var_name_len,zend_string * value)639 static 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* */
sapi_cli_server_register_entry_cb(zval * entry,int num_args,va_list args,zend_hash_key * hash_key)654 static 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
680 return ZEND_HASH_APPLY_KEEP;
681 }
682 /* }}} */
683
sapi_cli_server_register_variables(zval * track_vars_array)684 static 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
sapi_cli_server_log_write(int type,const char * msg)768 static 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
sapi_cli_server_log_message(const char * msg,int syslog_type_int)797 static 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 */
803 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
832 STANDARD_SAPI_MODULE_PROPERTIES
833 }; /* }}} */
834
php_cli_server_poller_ctor(php_cli_server_poller * poller)835 static 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
php_cli_server_poller_add(php_cli_server_poller * poller,int mode,php_socket_t fd)842 static 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
php_cli_server_poller_remove(php_cli_server_poller * poller,int mode,php_socket_t fd)855 static 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
php_cli_server_poller_poll(php_cli_server_poller * poller,struct timeval * tv)876 static 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
php_cli_server_poller_iter_on_active(php_cli_server_poller * poller,void * opaque,zend_result (* callback)(void *,php_socket_t fd,int events))883 static 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 {
885 zend_result retval = SUCCESS;
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
927 php_socket_t fd;
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
php_cli_server_chunk_size(const php_cli_server_chunk * chunk)946 static 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
php_cli_server_chunk_dtor(php_cli_server_chunk * chunk)957 static 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
php_cli_server_buffer_dtor(php_cli_server_buffer * buffer)970 static 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
php_cli_server_buffer_ctor(php_cli_server_buffer * buffer)980 static void php_cli_server_buffer_ctor(php_cli_server_buffer *buffer) /* {{{ */
981 {
982 buffer->first = NULL;
983 buffer->last = NULL;
984 } /* }}} */
985
php_cli_server_buffer_append(php_cli_server_buffer * buffer,php_cli_server_chunk * chunk)986 static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
987 {
988 php_cli_server_chunk *last;
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
php_cli_server_buffer_prepend(php_cli_server_buffer * buffer,php_cli_server_chunk * chunk)998 static void php_cli_server_buffer_prepend(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
999 {
1000 php_cli_server_chunk *last;
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
php_cli_server_buffer_size(const php_cli_server_buffer * buffer)1009 static 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
php_cli_server_chunk_immortal_new(const char * buf,size_t len)1019 static php_cli_server_chunk *php_cli_server_chunk_immortal_new(const char *buf, size_t len) /* {{{ */
1020 {
1021 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1);
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
php_cli_server_chunk_heap_new(void * block,char * buf,size_t len)1030 static php_cli_server_chunk *php_cli_server_chunk_heap_new(void *block, char *buf, size_t len) /* {{{ */
1031 {
1032 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1);
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
php_cli_server_chunk_heap_new_self_contained(size_t len)1042 static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len) /* {{{ */
1043 {
1044 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk) + len, 1);
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
php_cli_server_content_sender_dtor(php_cli_server_content_sender * sender)1054 static void php_cli_server_content_sender_dtor(php_cli_server_content_sender *sender) /* {{{ */
1055 {
1056 php_cli_server_buffer_dtor(&sender->buffer);
1057 } /* }}} */
1058
php_cli_server_content_sender_ctor(php_cli_server_content_sender * sender)1059 static void php_cli_server_content_sender_ctor(php_cli_server_content_sender *sender) /* {{{ */
1060 {
1061 php_cli_server_buffer_ctor(&sender->buffer);
1062 } /* }}} */
1063
php_cli_server_content_sender_send(php_cli_server_content_sender * sender,php_socket_t fd,size_t * nbytes_sent_total)1064 static 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
php_cli_server_content_sender_pull(php_cli_server_content_sender * sender,int fd,size_t * nbytes_read)1137 static 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
php_cli_is_output_tty(void)1168 static 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)
php_cli_is_output_tty()1176 static int php_cli_is_output_tty() /* {{{ */
1177 {
1178 if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) {
1179 php_cli_output_is_tty = php_win32_console_fileno_is_console(STDOUT_FILENO) && php_win32_console_fileno_has_vt100(STDOUT_FILENO);
1180 }
1181 return php_cli_output_is_tty;
1182 } /* }}} */
1183 #endif
1184
php_cli_server_log_response(php_cli_server_client * client,int status,const char * message)1185 static 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
php_cli_server_logf(int type,const char * format,...)1261 static 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
php_network_listen_socket(const char * host,int * port,int socktype,int * af,socklen_t * socklen,zend_string ** errstr)1283 static php_socket_t php_network_listen_socket(const char *host, int *port, int socktype, int *af, socklen_t *socklen, zend_string **errstr) /* {{{ */
1284 {
1285 php_socket_t retval = SOCK_ERR;
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;
1322 closesocket(retval);
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) {
1334 err = php_socket_errno();
1335 if (err == SOCK_EINVAL || err == SOCK_EADDRINUSE) {
1336 goto out;
1337 }
1338 closesocket(retval);
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)) {
1347 err = php_socket_errno();
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)) {
1370 err = php_socket_errno();
1371 goto out;
1372 }
1373
1374 out:
1375 if (sa) {
1376 pefree(sa, 1);
1377 }
1378 if (sal) {
1379 php_network_freeaddresses(sal);
1380 }
1381 if (err) {
1382 if (ZEND_VALID_SOCKET(retval)) {
1383 closesocket(retval);
1384 }
1385 if (errstr) {
1386 *errstr = php_socket_error_str(err);
1387 }
1388 return SOCK_ERR;
1389 }
1390 return retval;
1391 } /* }}} */
1392
php_cli_server_request_ctor(php_cli_server_request * req)1393 static 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 */
1407 GC_MAKE_PERSISTENT_LOCAL(&req->headers);
1408 zend_hash_init(&req->headers_original_case, 0, NULL, NULL, 1);
1409 GC_MAKE_PERSISTENT_LOCAL(&req->headers_original_case);
1410 req->content = NULL;
1411 req->content_len = 0;
1412 req->ext = NULL;
1413 req->ext_len = 0;
1414 } /* }}} */
1415
php_cli_server_request_dtor(php_cli_server_request * req)1416 static 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 }
1433 zend_hash_destroy(&req->headers);
1434 zend_hash_destroy(&req->headers_original_case);
1435 if (req->content) {
1436 pefree(req->content, 1);
1437 }
1438 } /* }}} */
1439
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)1440 static 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
normalize_vpath(char ** retval,size_t * retval_len,const char * vpath,size_t vpath_len,int persistent)1538 static 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 */
php_cli_server_client_read_request_on_message_begin(php_http_parser * parser)1621 static int php_cli_server_client_read_request_on_message_begin(php_http_parser *parser)
1622 {
1623 return 0;
1624 }
1625
php_cli_server_client_read_request_on_path(php_http_parser * parser,const char * at,size_t length)1626 static int php_cli_server_client_read_request_on_path(php_http_parser *parser, const char *at, size_t length)
1627 {
1628 php_cli_server_client *client = parser->data;
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
php_cli_server_client_read_request_on_query_string(php_http_parser * parser,const char * at,size_t length)1642 static int php_cli_server_client_read_request_on_query_string(php_http_parser *parser, const char *at, size_t length)
1643 {
1644 php_cli_server_client *client = parser->data;
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
php_cli_server_client_read_request_on_url(php_http_parser * parser,const char * at,size_t length)1658 static int php_cli_server_client_read_request_on_url(php_http_parser *parser, const char *at, size_t length)
1659 {
1660 php_cli_server_client *client = parser->data;
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
php_cli_server_client_read_request_on_fragment(php_http_parser * parser,const char * at,size_t length)1674 static 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
php_cli_server_client_save_header(php_cli_server_client * client)1679 static 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
1690 /**
1691 * `Set-Cookie` HTTP header being the exception, they can have 1 or more values separated
1692 * by a comma while still possibly be set separately by the client.
1693 **/
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
cli_concat_persistent_zstr_with_char(zend_string * old_str,const char * at,size_t length)1718 static 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
php_cli_server_client_read_request_on_header_field(php_http_parser * parser,const char * at,size_t length)1732 static int php_cli_server_client_read_request_on_header_field(php_http_parser *parser, const char *at, size_t length)
1733 {
1734 php_cli_server_client *client = parser->data;
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);
1739 ZEND_FALLTHROUGH;
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
php_cli_server_client_read_request_on_header_value(php_http_parser * parser,const char * at,size_t length)1756 static int php_cli_server_client_read_request_on_header_value(php_http_parser *parser, const char *at, size_t length)
1757 {
1758 php_cli_server_client *client = parser->data;
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
php_cli_server_client_read_request_on_headers_complete(php_http_parser * parser)1779 static int php_cli_server_client_read_request_on_headers_complete(php_http_parser *parser)
1780 {
1781 php_cli_server_client *client = parser->data;
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();
1788 ZEND_FALLTHROUGH;
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
php_cli_server_client_read_request_on_body(php_http_parser * parser,const char * at,size_t length)1798 static int php_cli_server_client_read_request_on_body(php_http_parser *parser, const char *at, size_t length)
1799 {
1800 php_cli_server_client *client = parser->data;
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
php_cli_server_client_read_request_on_message_complete(php_http_parser * parser)1811 static int php_cli_server_client_read_request_on_message_complete(php_http_parser *parser)
1812 {
1813 php_cli_server_client *client = parser->data;
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 */
php_cli_server_client_read_request(php_cli_server_client * client,char ** errstr)1841 static 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
php_cli_server_client_send_through(php_cli_server_client * client,const char * str,size_t str_len)1898 static 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 */
1922 php_handle_aborted_connection();
1923 return nbytes_left;
1924 }
1925 } else {
1926 php_handle_aborted_connection();
1927 return nbytes_left;
1928 }
1929 }
1930 nbytes_left -= nbytes_sent;
1931 } while (nbytes_left > 0);
1932
1933 return str_len;
1934 } /* }}} */
1935
php_cli_server_client_populate_request_info(const php_cli_server_client * client,sapi_request_info * request_info)1936 static 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 }
1950 } /* }}} */
1951
1952 // TODO Remove?
destroy_request_info(sapi_request_info * request_info)1953 static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
1954 {
1955 } /* }}} */
1956
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)1957 static 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) /* {{{ */
1958 {
1959 client->server = server;
1960 client->sock = client_sock;
1961 client->addr = addr;
1962 client->addr_len = addr_len;
1963
1964 // TODO To prevent the reallocation need to retrieve a persistent string
1965 // Create a new php_network_populate_name_from_sockaddr_ex() API with a persistent flag?
1966 zend_string *tmp_addr = NULL;
1967 php_network_populate_name_from_sockaddr(addr, addr_len, &tmp_addr, NULL, 0);
1968 client->addr_str = zend_string_dup(tmp_addr, /* persistent */ true);
1969 GC_MAKE_PERSISTENT_LOCAL(client->addr_str);
1970 zend_string_release_ex(tmp_addr, /* persistent */ false);
1971
1972 php_http_parser_init(&client->parser, PHP_HTTP_REQUEST);
1973 client->request_read = false;
1974
1975 client->last_header_element = HEADER_NONE;
1976 client->current_header_name = NULL;
1977 client->current_header_value = NULL;
1978
1979 client->post_read_offset = 0;
1980
1981 php_cli_server_request_ctor(&client->request);
1982
1983 client->content_sender_initialized = false;
1984 client->file_fd = -1;
1985 } /* }}} */
1986
php_cli_server_client_dtor(php_cli_server_client * client)1987 static void php_cli_server_client_dtor(php_cli_server_client *client) /* {{{ */
1988 {
1989 php_cli_server_request_dtor(&client->request);
1990 if (client->file_fd >= 0) {
1991 close(client->file_fd);
1992 client->file_fd = -1;
1993 }
1994 pefree(client->addr, 1);
1995 zend_string_release_ex(client->addr_str, /* persistent */ true);
1996
1997 if (client->content_sender_initialized) {
1998 /* Headers must be set if we reached the content initialisation */
1999 assert(client->current_header_name == NULL);
2000 assert(client->current_header_value == NULL);
2001 php_cli_server_content_sender_dtor(&client->content_sender);
2002 }
2003 } /* }}} */
2004
php_cli_server_close_connection(php_cli_server * server,php_cli_server_client * client)2005 static void php_cli_server_close_connection(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2006 {
2007 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s Closing", ZSTR_VAL(client->addr_str));
2008
2009 zend_hash_index_del(&server->clients, client->sock);
2010 } /* }}} */
2011
php_cli_server_send_error_page(php_cli_server * server,php_cli_server_client * client,int status)2012 static zend_result php_cli_server_send_error_page(php_cli_server *server, php_cli_server_client *client, int status) /* {{{ */
2013 {
2014 zend_string *escaped_request_uri = NULL;
2015 const char *status_string = get_status_string(status);
2016 const char *content_template = get_template_string(status);
2017 char *errstr = get_last_error();
2018 assert(status_string && content_template);
2019
2020 php_cli_server_content_sender_ctor(&client->content_sender);
2021 client->content_sender_initialized = true;
2022
2023 if (client->request.request_method != PHP_HTTP_HEAD) {
2024 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);
2025
2026 {
2027 static const char prologue_template[] = "<!doctype html><html><head><title>%d %s</title>";
2028 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(prologue_template) + 3 + strlen(status_string) + 1);
2029 if (!chunk) {
2030 goto fail;
2031 }
2032 snprintf(chunk->data.heap.p, chunk->data.heap.len, prologue_template, status, status_string);
2033 chunk->data.heap.len = strlen(chunk->data.heap.p);
2034 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2035 }
2036 {
2037 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(php_cli_server_css, sizeof(php_cli_server_css) - 1);
2038 if (!chunk) {
2039 goto fail;
2040 }
2041 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2042 }
2043 {
2044 static const char template[] = "</head><body>";
2045 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(template, sizeof(template) - 1);
2046 if (!chunk) {
2047 goto fail;
2048 }
2049 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2050 }
2051 {
2052 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);
2053 if (!chunk) {
2054 goto fail;
2055 }
2056 snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, ZSTR_VAL(escaped_request_uri));
2057 chunk->data.heap.len = strlen(chunk->data.heap.p);
2058 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2059 }
2060 {
2061 static const char epilogue_template[] = "</body></html>";
2062 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(epilogue_template, sizeof(epilogue_template) - 1);
2063 if (!chunk) {
2064 goto fail;
2065 }
2066 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2067 }
2068 }
2069
2070 {
2071 php_cli_server_chunk *chunk;
2072 smart_str buffer = { 0 };
2073 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2074 if (!buffer.s) {
2075 /* out of memory */
2076 goto fail;
2077 }
2078 append_essential_headers(&buffer, client, 1, NULL);
2079 smart_str_appends_ex(&buffer, SAPI_PHP_VERSION_HEADER "\r\n", 1);
2080 smart_str_appends_ex(&buffer, "Content-Type: text/html; charset=UTF-8\r\n", 1);
2081 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2082 smart_str_append_unsigned_ex(&buffer, php_cli_server_buffer_size(&client->content_sender.buffer), 1);
2083 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2084 if (status == 405) {
2085 smart_str_appends_ex(&buffer, "Allow: ", 1);
2086 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_GET), 1);
2087 smart_str_appends_ex(&buffer, ", ", 1);
2088 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_HEAD), 1);
2089 smart_str_appends_ex(&buffer, ", ", 1);
2090 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_POST), 1);
2091 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2092 }
2093 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2094
2095 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2096 if (!chunk) {
2097 smart_str_free_ex(&buffer, 1);
2098 goto fail;
2099 }
2100 php_cli_server_buffer_prepend(&client->content_sender.buffer, chunk);
2101 }
2102
2103 php_cli_server_log_response(client, status, errstr ? errstr : "?");
2104 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2105 if (errstr) {
2106 pefree(errstr, 1);
2107 }
2108 if (escaped_request_uri) {
2109 zend_string_free(escaped_request_uri);
2110 }
2111 return SUCCESS;
2112
2113 fail:
2114 if (errstr) {
2115 pefree(errstr, 1);
2116 }
2117 if (escaped_request_uri) {
2118 zend_string_free(escaped_request_uri);
2119 }
2120 return FAILURE;
2121 } /* }}} */
2122
php_cli_server_dispatch_script(php_cli_server * server,php_cli_server_client * client)2123 static zend_result php_cli_server_dispatch_script(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2124 {
2125 if (strlen(client->request.path_translated) != client->request.path_translated_len) {
2126 /* can't handle paths that contain nul bytes */
2127 return php_cli_server_send_error_page(server, client, 400);
2128 }
2129
2130 zend_file_handle zfd;
2131 zend_stream_init_filename(&zfd, SG(request_info).path_translated);
2132 zfd.primary_script = 1;
2133 zend_try {
2134 php_execute_script(&zfd);
2135 } zend_end_try();
2136 zend_destroy_file_handle(&zfd);
2137
2138 php_cli_server_log_response(client, SG(sapi_headers).http_response_code, NULL);
2139 return SUCCESS;
2140 } /* }}} */
2141
php_cli_server_begin_send_static(php_cli_server * server,php_cli_server_client * client)2142 static zend_result php_cli_server_begin_send_static(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2143 {
2144 int fd;
2145 int status = 200;
2146
2147 if (client->request.request_method == PHP_HTTP_DELETE
2148 || client->request.request_method == PHP_HTTP_PUT
2149 || client->request.request_method == PHP_HTTP_PATCH) {
2150 return php_cli_server_send_error_page(server, client, 405);
2151 }
2152
2153 if (client->request.path_translated && strlen(client->request.path_translated) != client->request.path_translated_len) {
2154 /* can't handle paths that contain nul bytes */
2155 return php_cli_server_send_error_page(server, client, 400);
2156 }
2157
2158 #ifdef PHP_WIN32
2159 /* The win32 namespace will cut off trailing dots and spaces. Since the
2160 VCWD functionality isn't used here, a sophisticated functionality
2161 would have to be reimplemented to know ahead there are no files
2162 with invalid names there. The simplest is just to forbid invalid
2163 filenames, which is done here. */
2164 if (client->request.path_translated &&
2165 ('.' == client->request.path_translated[client->request.path_translated_len-1] ||
2166 ' ' == client->request.path_translated[client->request.path_translated_len-1])) {
2167 return php_cli_server_send_error_page(server, client, 500);
2168 }
2169
2170 fd = client->request.path_translated ? php_win32_ioutil_open(client->request.path_translated, O_RDONLY): -1;
2171 #else
2172 fd = client->request.path_translated ? open(client->request.path_translated, O_RDONLY): -1;
2173 #endif
2174 if (fd < 0) {
2175 return php_cli_server_send_error_page(server, client, 404);
2176 }
2177
2178 php_cli_server_content_sender_ctor(&client->content_sender);
2179 client->content_sender_initialized = true;
2180 if (client->request.request_method != PHP_HTTP_HEAD) {
2181 client->file_fd = fd;
2182 }
2183
2184 {
2185 php_cli_server_chunk *chunk;
2186 smart_str buffer = { 0 };
2187 const char *mime_type = get_mime_type(server, client->request.ext, client->request.ext_len);
2188
2189 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2190 if (!buffer.s) {
2191 /* out of memory */
2192 php_cli_server_log_response(client, 500, NULL);
2193 return FAILURE;
2194 }
2195 append_essential_headers(&buffer, client, 1, NULL);
2196 if (mime_type) {
2197 smart_str_appendl_ex(&buffer, "Content-Type: ", sizeof("Content-Type: ") - 1, 1);
2198 smart_str_appends_ex(&buffer, mime_type, 1);
2199 if (strncmp(mime_type, "text/", 5) == 0) {
2200 smart_str_appends_ex(&buffer, "; charset=UTF-8", 1);
2201 }
2202 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2203 }
2204 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2205 smart_str_append_unsigned_ex(&buffer, client->request.sb.st_size, 1);
2206 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2207 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2208 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2209 if (!chunk) {
2210 smart_str_free_ex(&buffer, 1);
2211 php_cli_server_log_response(client, 500, NULL);
2212 return FAILURE;
2213 }
2214 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2215 }
2216 php_cli_server_log_response(client, 200, NULL);
2217 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2218 return SUCCESS;
2219 }
2220 /* }}} */
2221
php_cli_server_request_startup(php_cli_server * server,php_cli_server_client * client)2222 static zend_result php_cli_server_request_startup(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2223 zval *auth;
2224 php_cli_server_client_populate_request_info(client, &SG(request_info));
2225 if (NULL != (auth = zend_hash_str_find(&client->request.headers, "authorization", sizeof("authorization")-1))) {
2226 php_handle_auth_data(Z_STRVAL_P(auth));
2227 }
2228 SG(sapi_headers).http_response_code = 200;
2229 if (FAILURE == php_request_startup()) {
2230 return FAILURE;
2231 }
2232 PG(during_request_startup) = 0;
2233
2234 return SUCCESS;
2235 }
2236 /* }}} */
2237
php_cli_server_request_shutdown(php_cli_server * server,php_cli_server_client * client)2238 static void php_cli_server_request_shutdown(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2239 php_request_shutdown(0);
2240 php_cli_server_close_connection(server, client);
2241 destroy_request_info(&SG(request_info));
2242 SG(server_context) = NULL;
2243 SG(rfc1867_uploaded_files) = NULL;
2244 SG(request_parse_body_context).throw_exceptions = false;
2245 memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
2246 }
2247 /* }}} */
2248
php_cli_server_dispatch_router(php_cli_server * server,php_cli_server_client * client)2249 static bool php_cli_server_dispatch_router(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2250 {
2251 bool decline = false;
2252 zend_file_handle zfd;
2253 char *old_cwd;
2254
2255 ALLOCA_FLAG(use_heap)
2256 old_cwd = do_alloca(MAXPATHLEN, use_heap);
2257 old_cwd[0] = '\0';
2258 php_ignore_value(VCWD_GETCWD(old_cwd, MAXPATHLEN - 1));
2259
2260 zend_stream_init_filename(&zfd, server->router);
2261 zfd.primary_script = 1;
2262
2263 zend_try {
2264 zval retval;
2265 ZVAL_UNDEF(&retval);
2266 int sg_options_back = SG(options);
2267 /* Don't chdir to the router script because the file path may be relative. */
2268 SG(options) |= SAPI_OPTION_NO_CHDIR;
2269 CG(skip_shebang) = true;
2270 bool result = php_execute_script_ex(&zfd, &retval);
2271 SG(options) = sg_options_back;
2272 if (result) {
2273 if (Z_TYPE(retval) != IS_UNDEF) {
2274 decline = Z_TYPE(retval) == IS_FALSE;
2275 zval_ptr_dtor(&retval);
2276 }
2277 }
2278 } zend_end_try();
2279
2280 zend_destroy_file_handle(&zfd);
2281
2282 if (old_cwd[0] != '\0') {
2283 php_ignore_value(VCWD_CHDIR(old_cwd));
2284 }
2285
2286 free_alloca(old_cwd, use_heap);
2287
2288 return decline;
2289 }
2290 /* }}} */
2291
php_cli_server_dispatch(php_cli_server * server,php_cli_server_client * client)2292 static zend_result php_cli_server_dispatch(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2293 {
2294 int is_static_file = 0;
2295 const char *ext = client->request.ext;
2296
2297 SG(server_context) = client;
2298 if (client->request.ext_len != 3
2299 || (ext[0] != 'p' && ext[0] != 'P') || (ext[1] != 'h' && ext[1] != 'H') || (ext[2] != 'p' && ext[2] != 'P')
2300 || !client->request.path_translated) {
2301 is_static_file = 1;
2302 }
2303
2304 if (server->router || !is_static_file) {
2305 if (FAILURE == php_cli_server_request_startup(server, client)) {
2306 php_cli_server_request_shutdown(server, client);
2307 return FAILURE;
2308 }
2309 }
2310
2311 if (server->router) {
2312 if (!php_cli_server_dispatch_router(server, client)) {
2313 php_cli_server_request_shutdown(server, client);
2314 return SUCCESS;
2315 }
2316 }
2317
2318 if (!is_static_file) {
2319 // TODO What?
2320 if (SUCCESS == php_cli_server_dispatch_script(server, client)
2321 || FAILURE == php_cli_server_send_error_page(server, client, 500)) {
2322 if (SG(sapi_headers).http_response_code == 304) {
2323 SG(sapi_headers).send_default_content_type = 0;
2324 }
2325 php_cli_server_request_shutdown(server, client);
2326 return SUCCESS;
2327 }
2328 } else {
2329 if (server->router) {
2330 static int (*send_header_func)(sapi_headers_struct *);
2331 send_header_func = sapi_module.send_headers;
2332 /* do not generate default content type header */
2333 SG(sapi_headers).send_default_content_type = 0;
2334 /* we don't want headers to be sent */
2335 sapi_module.send_headers = sapi_cli_server_discard_headers;
2336 php_request_shutdown(0);
2337 sapi_module.send_headers = send_header_func;
2338 SG(sapi_headers).send_default_content_type = 1;
2339 SG(rfc1867_uploaded_files) = NULL;
2340 SG(request_parse_body_context).throw_exceptions = false;
2341 memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
2342 }
2343 if (FAILURE == php_cli_server_begin_send_static(server, client)) {
2344 php_cli_server_close_connection(server, client);
2345 }
2346 SG(server_context) = NULL;
2347 return SUCCESS;
2348 }
2349
2350 SG(server_context) = NULL;
2351 destroy_request_info(&SG(request_info));
2352 return SUCCESS;
2353 }
2354 /* }}} */
2355
php_cli_server_mime_type_ctor(php_cli_server * server,const php_cli_server_ext_mime_type_pair * mime_type_map_ptr)2356 static void php_cli_server_mime_type_ctor(php_cli_server *server, const php_cli_server_ext_mime_type_pair *mime_type_map_ptr) /* {{{ */
2357 {
2358 const php_cli_server_ext_mime_type_pair *pair;
2359
2360 zend_hash_init(&server->extension_mime_types, 0, NULL, NULL, 1);
2361 GC_MAKE_PERSISTENT_LOCAL(&server->extension_mime_types);
2362
2363 for (pair = mime_type_map_ptr; pair->ext; pair++) {
2364 size_t ext_len = strlen(pair->ext);
2365 zend_hash_str_add_ptr(&server->extension_mime_types, pair->ext, ext_len, (void*)pair->mime_type);
2366 }
2367 } /* }}} */
2368
php_cli_server_dtor(php_cli_server * server)2369 static void php_cli_server_dtor(php_cli_server *server) /* {{{ */
2370 {
2371 zend_hash_destroy(&server->clients);
2372 zend_hash_destroy(&server->extension_mime_types);
2373 if (ZEND_VALID_SOCKET(server->server_sock)) {
2374 closesocket(server->server_sock);
2375 }
2376 if (server->host) {
2377 pefree(server->host, 1);
2378 }
2379 if (server->document_root) {
2380 pefree(server->document_root, 1);
2381 }
2382 if (server->router) {
2383 pefree(server->router, 1);
2384 }
2385 #ifdef HAVE_FORK
2386 if (php_cli_server_workers_max > 1 &&
2387 php_cli_server_workers &&
2388 getpid() == php_cli_server_master) {
2389 zend_long php_cli_server_worker;
2390
2391 for (php_cli_server_worker = 0;
2392 php_cli_server_worker < php_cli_server_workers_max;
2393 php_cli_server_worker++) {
2394 int php_cli_server_worker_status;
2395
2396 do {
2397 if (waitpid(php_cli_server_workers[php_cli_server_worker],
2398 &php_cli_server_worker_status,
2399 0) == (pid_t) -1) {
2400 /* an extremely bad thing happened */
2401 break;
2402 }
2403
2404 } while (!WIFEXITED(php_cli_server_worker_status) &&
2405 !WIFSIGNALED(php_cli_server_worker_status));
2406 }
2407
2408 pefree(php_cli_server_workers, 1);
2409 }
2410 #endif
2411 } /* }}} */
2412
php_cli_server_client_dtor_wrapper(zval * zv)2413 static void php_cli_server_client_dtor_wrapper(zval *zv) /* {{{ */
2414 {
2415 php_cli_server_client *p = Z_PTR_P(zv);
2416
2417 shutdown(p->sock, SHUT_RDWR);
2418 closesocket(p->sock);
2419 php_cli_server_poller_remove(&p->server->poller, POLLIN | POLLOUT, p->sock);
2420 php_cli_server_client_dtor(p);
2421 pefree(p, 1);
2422 } /* }}} */
2423
2424 /**
2425 * Parse the host and port portions of an address specifier in
2426 * one of the following forms:
2427 * - hostOrIP:port
2428 * - [hostOrIP]:port
2429 */
php_cli_server_parse_addr(const char * addr,int * pport)2430 static char *php_cli_server_parse_addr(const char *addr, int *pport) {
2431 const char *p, *end;
2432 long port;
2433
2434 if (addr[0] == '[') {
2435 /* Encapsulated [hostOrIP]:port */
2436 const char *start = addr + 1;
2437 end = strchr(start, ']');
2438 if (!end) {
2439 /* No ending ] delimiter to match [ */
2440 return NULL;
2441 }
2442
2443 p = end + 1;
2444 if (*p != ':') {
2445 /* Invalid char following address/missing port */
2446 return NULL;
2447 }
2448
2449 port = strtol(p + 1, (char**)&p, 10);
2450 if (p && *p) {
2451 /* Non-numeric in port */
2452 return NULL;
2453 }
2454 if (port < 0 || port > 65535) {
2455 /* Invalid port */
2456 return NULL;
2457 }
2458
2459 /* Full [hostOrIP]:port provided */
2460 *pport = (int)port;
2461 return pestrndup(start, end - start, 1);
2462 }
2463
2464 end = strchr(addr, ':');
2465 if (!end) {
2466 /* Missing port */
2467 return NULL;
2468 }
2469
2470 port = strtol(end + 1, (char**)&p, 10);
2471 if (p && *p) {
2472 /* Non-numeric port */
2473 return NULL;
2474 }
2475 if (port < 0 || port > 65535) {
2476 /* Invalid port */
2477 return NULL;
2478 }
2479 *pport = (int)port;
2480 return pestrndup(addr, end - addr, 1);
2481 }
2482
2483 #if defined(HAVE_PRCTL) || defined(HAVE_PROCCTL)
php_cli_server_worker_install_pdeathsig(void)2484 static void php_cli_server_worker_install_pdeathsig(void)
2485 {
2486 // Ignore failure to register PDEATHSIG, it's not available on all platforms anyway
2487 #if defined(HAVE_PRCTL)
2488 prctl(PR_SET_PDEATHSIG, SIGTERM);
2489 #elif defined(HAVE_PROCCTL)
2490 int signal = SIGTERM;
2491 procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signal);
2492 #endif
2493
2494 // Check if parent has exited just after the fork
2495 if (getppid() != php_cli_server_master) {
2496 exit(1);
2497 }
2498 }
2499 #endif
2500
php_cli_server_startup_workers(void)2501 static void php_cli_server_startup_workers(void) {
2502 char *workers = getenv("PHP_CLI_SERVER_WORKERS");
2503 if (!workers) {
2504 return;
2505 }
2506
2507 #ifdef HAVE_FORK
2508 php_cli_server_workers_max = ZEND_ATOL(workers);
2509 if (php_cli_server_workers_max > 1) {
2510 zend_long php_cli_server_worker;
2511
2512 php_cli_server_workers = pecalloc(
2513 php_cli_server_workers_max, sizeof(pid_t), 1);
2514
2515 php_cli_server_master = getpid();
2516
2517 for (php_cli_server_worker = 0;
2518 php_cli_server_worker < php_cli_server_workers_max;
2519 php_cli_server_worker++) {
2520 pid_t pid = fork();
2521
2522 if (pid < 0) {
2523 /* no more forks allowed, work with what we have ... */
2524 php_cli_server_workers_max =
2525 php_cli_server_worker + 1;
2526 return;
2527 } else if (pid == 0) {
2528 #if defined(HAVE_PRCTL) || defined(HAVE_PROCCTL)
2529 php_cli_server_worker_install_pdeathsig();
2530 #endif
2531 return;
2532 } else {
2533 php_cli_server_workers[php_cli_server_worker] = pid;
2534 }
2535 }
2536 } else {
2537 fprintf(stderr, "number of workers must be larger than 1\n");
2538 }
2539 #else
2540 fprintf(stderr, "forking is not supported on this platform\n");
2541 #endif
2542 }
2543
php_cli_server_ctor(php_cli_server * server,const char * addr,const char * document_root,const char * router)2544 static zend_result php_cli_server_ctor(php_cli_server *server, const char *addr, const char *document_root, const char *router) /* {{{ */
2545 {
2546 zend_result retval = SUCCESS;
2547 char *host = NULL;
2548 zend_string *errstr = NULL;
2549 char *_document_root = NULL;
2550 char *_router = NULL;
2551 int port = 3000;
2552 php_socket_t server_sock = SOCK_ERR;
2553
2554 host = php_cli_server_parse_addr(addr, &port);
2555 if (!host) {
2556 fprintf(stderr, "Invalid address: %s\n", addr);
2557 retval = FAILURE;
2558 goto out;
2559 }
2560
2561 server_sock = php_network_listen_socket(host, &port, SOCK_STREAM, &server->address_family, &server->socklen, &errstr);
2562 if (server_sock == SOCK_ERR) {
2563 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to listen on %s:%d (reason: %s)", host, port, errstr ? ZSTR_VAL(errstr) : "?");
2564 if (errstr) {
2565 zend_string_release_ex(errstr, 0);
2566 }
2567 retval = FAILURE;
2568 goto out;
2569 }
2570 // server_sock needs to be non-blocking when using multiple processes. Without it, the first process would
2571 // successfully accept the connection but the others would block, causing client sockets of the same select
2572 // call not to be handled.
2573 if (SUCCESS != php_set_sock_blocking(server_sock, 0)) {
2574 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to make server socket non-blocking");
2575 retval = FAILURE;
2576 goto out;
2577 }
2578 server->server_sock = server_sock;
2579
2580 php_cli_server_startup_workers();
2581
2582 php_cli_server_poller_ctor(&server->poller);
2583
2584 php_cli_server_poller_add(&server->poller, POLLIN, server_sock);
2585
2586 server->host = host;
2587 server->port = port;
2588
2589 zend_hash_init(&server->clients, 0, NULL, php_cli_server_client_dtor_wrapper, 1);
2590
2591 {
2592 size_t document_root_len = strlen(document_root);
2593 _document_root = pestrndup(document_root, document_root_len, 1);
2594 server->document_root = _document_root;
2595 server->document_root_len = document_root_len;
2596 }
2597
2598 if (router) {
2599 size_t router_len = strlen(router);
2600 _router = pestrndup(router, router_len, 1);
2601 server->router = _router;
2602 server->router_len = router_len;
2603 } else {
2604 server->router = NULL;
2605 server->router_len = 0;
2606 }
2607
2608 php_cli_server_mime_type_ctor(server, mime_type_map);
2609
2610 server->is_running = 1;
2611 out:
2612 if (retval == FAILURE) {
2613 if (host) {
2614 pefree(host, 1);
2615 }
2616 if (_document_root) {
2617 pefree(_document_root, 1);
2618 }
2619 if (_router) {
2620 pefree(_router, 1);
2621 }
2622 if (server_sock > -1) {
2623 closesocket(server_sock);
2624 }
2625 }
2626 return retval;
2627 } /* }}} */
2628
php_cli_server_recv_event_read_request(php_cli_server * server,php_cli_server_client * client)2629 static zend_result php_cli_server_recv_event_read_request(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2630 {
2631 char *errstr = NULL;
2632
2633 switch (php_cli_server_client_read_request(client, &errstr)) {
2634 case -1:
2635 if (errstr) {
2636 if (strcmp(errstr, php_cli_server_request_error_unexpected_eof) == 0 && client->parser.state == s_start_req) {
2637 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE,
2638 "%s Closed without sending a request; it was probably just an unused speculative preconnection", ZSTR_VAL(client->addr_str));
2639 } else {
2640 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s Invalid request (%s)", ZSTR_VAL(client->addr_str), errstr);
2641 }
2642 efree(errstr);
2643 }
2644 php_cli_server_close_connection(server, client);
2645 return FAILURE;
2646 case 1:
2647 if (client->request.request_method == PHP_HTTP_NOT_IMPLEMENTED) {
2648 return php_cli_server_send_error_page(server, client, 501);
2649 }
2650 php_cli_server_poller_remove(&server->poller, POLLIN, client->sock);
2651 return php_cli_server_dispatch(server, client);
2652 case 0:
2653 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2654 return SUCCESS;
2655 EMPTY_SWITCH_DEFAULT_CASE();
2656 }
2657 /* Under ASAN the compiler somehow doesn't realise that the switch block always returns */
2658 return FAILURE;
2659 } /* }}} */
2660
php_cli_server_send_event(php_cli_server * server,php_cli_server_client * client)2661 static zend_result php_cli_server_send_event(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2662 {
2663 if (client->content_sender_initialized) {
2664 if (client->file_fd >= 0 && !client->content_sender.buffer.first) {
2665 size_t nbytes_read;
2666 if (!php_cli_server_content_sender_pull(&client->content_sender, client->file_fd, &nbytes_read)) {
2667 php_cli_server_close_connection(server, client);
2668 return FAILURE;
2669 }
2670 if (nbytes_read == 0) {
2671 close(client->file_fd);
2672 client->file_fd = -1;
2673 }
2674 }
2675
2676 size_t nbytes_sent;
2677 int err = php_cli_server_content_sender_send(&client->content_sender, client->sock, &nbytes_sent);
2678 if (err && err != SOCK_EAGAIN) {
2679 php_cli_server_close_connection(server, client);
2680 return FAILURE;
2681 }
2682
2683 if (!client->content_sender.buffer.first && client->file_fd < 0) {
2684 php_cli_server_close_connection(server, client);
2685 }
2686 }
2687 return SUCCESS;
2688 }
2689 /* }}} */
2690
2691 typedef struct php_cli_server_do_event_for_each_fd_callback_params {
2692 php_cli_server *server;
2693 zend_result(*rhandler)(php_cli_server*, php_cli_server_client*);
2694 zend_result(*whandler)(php_cli_server*, php_cli_server_client*);
2695 } php_cli_server_do_event_for_each_fd_callback_params;
2696
php_cli_server_do_event_for_each_fd_callback(void * _params,php_socket_t fd,int event)2697 static zend_result php_cli_server_do_event_for_each_fd_callback(void *_params, php_socket_t fd, int event) /* {{{ */
2698 {
2699 php_cli_server_do_event_for_each_fd_callback_params *params = _params;
2700 php_cli_server *server = params->server;
2701 if (server->server_sock == fd) {
2702 php_cli_server_client *client = NULL;
2703 php_socket_t client_sock;
2704 socklen_t socklen = server->socklen;
2705 struct sockaddr *sa = pemalloc(server->socklen, 1);
2706 client_sock = accept(server->server_sock, sa, &socklen);
2707 if (!ZEND_VALID_SOCKET(client_sock)) {
2708 int err = php_socket_errno();
2709 if (err != SOCK_EAGAIN && php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
2710 char *errstr = php_socket_strerror(php_socket_errno(), NULL, 0);
2711 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR,
2712 "Failed to accept a client (reason: %s)", errstr);
2713 efree(errstr);
2714 }
2715 pefree(sa, 1);
2716 return FAILURE;
2717 }
2718 if (SUCCESS != php_set_sock_blocking(client_sock, 0)) {
2719 pefree(sa, 1);
2720 closesocket(client_sock);
2721 return FAILURE;
2722 }
2723 client = pemalloc(sizeof(php_cli_server_client), 1);
2724
2725 php_cli_server_client_ctor(client, server, client_sock, sa, socklen);
2726
2727 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s Accepted", ZSTR_VAL(client->addr_str));
2728
2729 zend_hash_index_update_ptr(&server->clients, client_sock, client);
2730
2731 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2732 } else {
2733 php_cli_server_client *client;
2734 if (NULL != (client = zend_hash_index_find_ptr(&server->clients, fd))) {
2735 if (event & POLLIN) {
2736 params->rhandler(server, client);
2737 }
2738 if (event & POLLOUT) {
2739 params->whandler(server, client);
2740 }
2741 }
2742 }
2743 return SUCCESS;
2744 } /* }}} */
2745
php_cli_server_do_event_for_each_fd(php_cli_server * server,zend_result (* rhandler)(php_cli_server *,php_cli_server_client *),zend_result (* whandler)(php_cli_server *,php_cli_server_client *))2746 static void php_cli_server_do_event_for_each_fd(php_cli_server *server,
2747 zend_result(*rhandler)(php_cli_server*, php_cli_server_client*),
2748 zend_result(*whandler)(php_cli_server*, php_cli_server_client*)) /* {{{ */
2749 {
2750 php_cli_server_do_event_for_each_fd_callback_params params = {
2751 server,
2752 rhandler,
2753 whandler
2754 };
2755
2756 if (SUCCESS != php_cli_server_poller_iter_on_active(&server->poller, ¶ms, php_cli_server_do_event_for_each_fd_callback)) {
2757 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to poll event");
2758 }
2759 } /* }}} */
2760
php_cli_server_do_event_loop(php_cli_server * server)2761 static zend_result php_cli_server_do_event_loop(php_cli_server *server) /* {{{ */
2762 {
2763 zend_result retval = SUCCESS;
2764 while (server->is_running) {
2765 struct timeval tv = { 1, 0 };
2766 int n = php_cli_server_poller_poll(&server->poller, &tv);
2767 if (n > 0) {
2768 php_cli_server_do_event_for_each_fd(server,
2769 php_cli_server_recv_event_read_request,
2770 php_cli_server_send_event);
2771 } else if (n == 0) {
2772 /* do nothing */
2773 } else {
2774 int err = php_socket_errno();
2775 if (err != SOCK_EINTR) {
2776 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
2777 char *errstr = php_socket_strerror(err, NULL, 0);
2778 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s", errstr);
2779 efree(errstr);
2780 }
2781 retval = FAILURE;
2782 goto out;
2783 }
2784 }
2785 }
2786 out:
2787 return retval;
2788 } /* }}} */
2789
2790 static php_cli_server server;
2791
php_cli_server_sigint_handler(int sig)2792 static void php_cli_server_sigint_handler(int sig) /* {{{ */
2793 {
2794 server.is_running = 0;
2795 }
2796 /* }}} */
2797
2798 /* Returns status code */
do_cli_server(int argc,char ** argv)2799 int do_cli_server(int argc, char **argv) /* {{{ */
2800 {
2801 char *php_optarg = NULL;
2802 int php_optind = 1;
2803 int c, r;
2804 const char *server_bind_address = NULL;
2805 extern const opt_struct OPTIONS[];
2806 const char *document_root = NULL;
2807 #ifdef PHP_WIN32
2808 char document_root_tmp[MAXPATHLEN];
2809 size_t k;
2810 #endif
2811 const char *router = NULL;
2812 char document_root_buf[MAXPATHLEN];
2813
2814 while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2))!=-1) {
2815 switch (c) {
2816 case 'S':
2817 server_bind_address = php_optarg;
2818 break;
2819 case 't':
2820 #ifndef PHP_WIN32
2821 document_root = php_optarg;
2822 #else
2823 k = strlen(php_optarg);
2824 if (k + 1 > MAXPATHLEN) {
2825 fprintf(stderr, "Document root path is too long.\n");
2826 return 1;
2827 }
2828 memmove(document_root_tmp, php_optarg, k + 1);
2829 /* Clean out any trailing garbage that might have been passed
2830 from a batch script. */
2831 do {
2832 document_root_tmp[k] = '\0';
2833 k--;
2834 } while ('"' == document_root_tmp[k] || ' ' == document_root_tmp[k]);
2835 document_root = document_root_tmp;
2836 #endif
2837 break;
2838 case 'q':
2839 if (php_cli_server_log_level > 1) {
2840 php_cli_server_log_level--;
2841 }
2842 break;
2843 }
2844 }
2845
2846 if (document_root) {
2847 zend_stat_t sb = {0};
2848
2849 if (php_sys_stat(document_root, &sb)) {
2850 fprintf(stderr, "Directory %s does not exist.\n", document_root);
2851 return 1;
2852 }
2853 if (!S_ISDIR(sb.st_mode)) {
2854 fprintf(stderr, "%s is not a directory.\n", document_root);
2855 return 1;
2856 }
2857 if (VCWD_REALPATH(document_root, document_root_buf)) {
2858 document_root = document_root_buf;
2859 }
2860 } else {
2861 char *ret = NULL;
2862
2863 #ifdef HAVE_GETCWD
2864 ret = VCWD_GETCWD(document_root_buf, MAXPATHLEN);
2865 #elif defined(HAVE_GETWD)
2866 ret = VCWD_GETWD(document_root_buf);
2867 #endif
2868 document_root = ret ? document_root_buf: ".";
2869 }
2870
2871 if (argc > php_optind) {
2872 router = argv[php_optind];
2873 }
2874
2875 if (FAILURE == php_cli_server_ctor(&server, server_bind_address, document_root, router)) {
2876 return 1;
2877 }
2878 sapi_module.phpinfo_as_text = 0;
2879
2880 {
2881 r = 0;
2882 bool ipv6 = strchr(server.host, ':');
2883 php_cli_server_logf(
2884 PHP_CLI_SERVER_LOG_PROCESS,
2885 "PHP %s Development Server (http://%s%s%s:%d) started",
2886 PHP_VERSION, ipv6 ? "[" : "", server.host,
2887 ipv6 ? "]" : "", server.port);
2888 }
2889
2890 #if defined(SIGINT)
2891 signal(SIGINT, php_cli_server_sigint_handler);
2892 #endif
2893
2894 #if defined(SIGPIPE)
2895 signal(SIGPIPE, SIG_IGN);
2896 #endif
2897
2898 zend_signal_init();
2899
2900 if (SUCCESS != php_cli_server_do_event_loop(&server)) {
2901 r = 1;
2902 }
2903 php_cli_server_dtor(&server);
2904 return r;
2905 } /* }}} */
2906