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