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