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