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