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