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