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