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