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