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