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