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