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