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