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