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