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