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