1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2017 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: Marcus Boerger <helly@php.net> |
16 | Johannes Schlueter <johannes@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 /* $Id$ */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "php.h"
27
28 #ifndef HAVE_RL_COMPLETION_MATCHES
29 #define rl_completion_matches completion_matches
30 #endif
31
32 #include "php_globals.h"
33 #include "php_variables.h"
34 #include "zend_hash.h"
35 #include "zend_modules.h"
36
37 #include "SAPI.h"
38
39 #if HAVE_SETLOCALE
40 #include <locale.h>
41 #endif
42 #include "zend.h"
43 #include "zend_extensions.h"
44 #include "php_ini.h"
45 #include "php_globals.h"
46 #include "php_main.h"
47 #include "fopen_wrappers.h"
48 #include "ext/standard/php_standard.h"
49 #include "zend_smart_str.h"
50
51 #ifdef __riscos__
52 #include <unixlib/local.h>
53 #endif
54
55 #if HAVE_LIBEDIT
56 #include <editline/readline.h>
57 #else
58 #include <readline/readline.h>
59 #include <readline/history.h>
60 #endif
61
62 #include "zend_compile.h"
63 #include "zend_execute.h"
64 #include "zend_highlight.h"
65 #include "zend_exceptions.h"
66
67 #include "sapi/cli/cli.h"
68 #include "readline_cli.h"
69
70 #ifdef COMPILE_DL_READLINE
71 #include <dlfcn.h>
72 #endif
73
74 #ifndef RTLD_DEFAULT
75 #define RTLD_DEFAULT NULL
76 #endif
77
78 #define DEFAULT_PROMPT "\\b \\> "
79
80 ZEND_DECLARE_MODULE_GLOBALS(cli_readline);
81
82 static char php_last_char = '\0';
83 static FILE *pager_pipe = NULL;
84
readline_shell_write(const char * str,size_t str_length)85 static size_t readline_shell_write(const char *str, size_t str_length) /* {{{ */
86 {
87 if (CLIR_G(prompt_str)) {
88 smart_str_appendl(CLIR_G(prompt_str), str, str_length);
89 return str_length;
90 }
91
92 if (CLIR_G(pager) && *CLIR_G(pager) && !pager_pipe) {
93 pager_pipe = VCWD_POPEN(CLIR_G(pager), "w");
94 }
95 if (pager_pipe) {
96 return fwrite(str, 1, MIN(str_length, 16384), pager_pipe);
97 }
98
99 return -1;
100 }
101 /* }}} */
102
readline_shell_ub_write(const char * str,size_t str_length)103 static size_t readline_shell_ub_write(const char *str, size_t str_length) /* {{{ */
104 {
105 /* We just store the last char here and then pass back to the
106 caller (sapi_cli_single_write in sapi/cli) which will actually
107 write due to -1 return code */
108 php_last_char = str[str_length-1];
109
110 return (size_t) -1;
111 }
112 /* }}} */
113
cli_readline_init_globals(zend_cli_readline_globals * rg)114 static void cli_readline_init_globals(zend_cli_readline_globals *rg)
115 {
116 rg->pager = NULL;
117 rg->prompt = NULL;
118 rg->prompt_str = NULL;
119 }
120
121 PHP_INI_BEGIN()
122 STD_PHP_INI_ENTRY("cli.pager", "", PHP_INI_ALL, OnUpdateString, pager, zend_cli_readline_globals, cli_readline_globals)
123 STD_PHP_INI_ENTRY("cli.prompt", DEFAULT_PROMPT, PHP_INI_ALL, OnUpdateString, prompt, zend_cli_readline_globals, cli_readline_globals)
124 PHP_INI_END()
125
126
127
128 typedef enum {
129 body,
130 sstring,
131 dstring,
132 sstring_esc,
133 dstring_esc,
134 comment_line,
135 comment_block,
136 heredoc_start,
137 heredoc,
138 outside,
139 } php_code_type;
140
cli_get_prompt(char * block,char prompt)141 static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
142 {
143 smart_str retval = {0};
144 char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
145
146 do {
147 if (*prompt_spec == '\\') {
148 switch (prompt_spec[1]) {
149 case '\\':
150 smart_str_appendc(&retval, '\\');
151 prompt_spec++;
152 break;
153 case 'n':
154 smart_str_appendc(&retval, '\n');
155 prompt_spec++;
156 break;
157 case 't':
158 smart_str_appendc(&retval, '\t');
159 prompt_spec++;
160 break;
161 case 'e':
162 smart_str_appendc(&retval, '\033');
163 prompt_spec++;
164 break;
165
166
167 case 'v':
168 smart_str_appends(&retval, PHP_VERSION);
169 prompt_spec++;
170 break;
171 case 'b':
172 smart_str_appends(&retval, block);
173 prompt_spec++;
174 break;
175 case '>':
176 smart_str_appendc(&retval, prompt);
177 prompt_spec++;
178 break;
179 case '`':
180 smart_str_appendc(&retval, '`');
181 prompt_spec++;
182 break;
183 default:
184 smart_str_appendc(&retval, '\\');
185 break;
186 }
187 } else if (*prompt_spec == '`') {
188 char *prompt_end = strstr(prompt_spec + 1, "`");
189 char *code;
190
191 if (prompt_end) {
192 code = estrndup(prompt_spec + 1, prompt_end - prompt_spec - 1);
193
194 CLIR_G(prompt_str) = &retval;
195 zend_try {
196 zend_eval_stringl(code, prompt_end - prompt_spec - 1, NULL, "php prompt code");
197 } zend_end_try();
198 CLIR_G(prompt_str) = NULL;
199 efree(code);
200 prompt_spec = prompt_end;
201 }
202 } else {
203 smart_str_appendc(&retval, *prompt_spec);
204 }
205 } while (++prompt_spec && *prompt_spec);
206 smart_str_0(&retval);
207 return retval.s;
208 }
209 /* }}} */
210
cli_is_valid_code(char * code,int len,zend_string ** prompt)211 static int cli_is_valid_code(char *code, int len, zend_string **prompt) /* {{{ */
212 {
213 int valid_end = 1, last_valid_end;
214 int brackets_count = 0;
215 int brace_count = 0;
216 int i;
217 php_code_type code_type = body;
218 char *heredoc_tag;
219 int heredoc_len;
220
221 for (i = 0; i < len; ++i) {
222 switch(code_type) {
223 default:
224 switch(code[i]) {
225 case '{':
226 brackets_count++;
227 valid_end = 0;
228 break;
229 case '}':
230 if (brackets_count > 0) {
231 brackets_count--;
232 }
233 valid_end = brackets_count ? 0 : 1;
234 break;
235 case '(':
236 brace_count++;
237 valid_end = 0;
238 break;
239 case ')':
240 if (brace_count > 0) {
241 brace_count--;
242 }
243 valid_end = 0;
244 break;
245 case ';':
246 valid_end = brace_count == 0 && brackets_count == 0;
247 break;
248 case ' ':
249 case '\r':
250 case '\n':
251 case '\t':
252 break;
253 case '\'':
254 code_type = sstring;
255 break;
256 case '"':
257 code_type = dstring;
258 break;
259 case '#':
260 code_type = comment_line;
261 break;
262 case '/':
263 if (code[i+1] == '/') {
264 i++;
265 code_type = comment_line;
266 break;
267 }
268 if (code[i+1] == '*') {
269 last_valid_end = valid_end;
270 valid_end = 0;
271 code_type = comment_block;
272 i++;
273 break;
274 }
275 valid_end = 0;
276 break;
277 case '?':
278 if (code[i+1] == '>') {
279 i++;
280 code_type = outside;
281 break;
282 }
283 valid_end = 0;
284 break;
285 case '<':
286 valid_end = 0;
287 if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
288 i += 2;
289 code_type = heredoc_start;
290 heredoc_len = 0;
291 }
292 break;
293 default:
294 valid_end = 0;
295 break;
296 }
297 break;
298 case sstring:
299 if (code[i] == '\\') {
300 code_type = sstring_esc;
301 } else {
302 if (code[i] == '\'') {
303 code_type = body;
304 }
305 }
306 break;
307 case sstring_esc:
308 code_type = sstring;
309 break;
310 case dstring:
311 if (code[i] == '\\') {
312 code_type = dstring_esc;
313 } else {
314 if (code[i] == '"') {
315 code_type = body;
316 }
317 }
318 break;
319 case dstring_esc:
320 code_type = dstring;
321 break;
322 case comment_line:
323 if (code[i] == '\n') {
324 code_type = body;
325 }
326 break;
327 case comment_block:
328 if (code[i-1] == '*' && code[i] == '/') {
329 code_type = body;
330 valid_end = last_valid_end;
331 }
332 break;
333 case heredoc_start:
334 switch(code[i]) {
335 case ' ':
336 case '\t':
337 case '\'':
338 break;
339 case '\r':
340 case '\n':
341 code_type = heredoc;
342 break;
343 default:
344 if (!heredoc_len) {
345 heredoc_tag = code+i;
346 }
347 heredoc_len++;
348 break;
349 }
350 break;
351 case heredoc:
352 if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {
353 code_type = body;
354 } else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {
355 code_type = body;
356 valid_end = 1;
357 }
358 break;
359 case outside:
360 if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
361 || (i > 3 && !strncmp(code+i-4, "<?php", 5))
362 ) {
363 code_type = body;
364 }
365 break;
366 }
367 }
368
369 switch (code_type) {
370 default:
371 if (brace_count) {
372 *prompt = cli_get_prompt("php", '(');
373 } else if (brackets_count) {
374 *prompt = cli_get_prompt("php", '{');
375 } else {
376 *prompt = cli_get_prompt("php", '>');
377 }
378 break;
379 case sstring:
380 case sstring_esc:
381 *prompt = cli_get_prompt("php", '\'');
382 break;
383 case dstring:
384 case dstring_esc:
385 *prompt = cli_get_prompt("php", '"');
386 break;
387 case comment_block:
388 *prompt = cli_get_prompt("/* ", '>');
389 break;
390 case heredoc:
391 *prompt = cli_get_prompt("<<<", '>');
392 break;
393 case outside:
394 *prompt = cli_get_prompt(" ", '>');
395 break;
396 }
397
398 if (!valid_end || brackets_count) {
399 return 0;
400 } else {
401 return 1;
402 }
403 }
404 /* }}} */
405
cli_completion_generator_ht(const char * text,int textlen,int * state,HashTable * ht,void ** pData)406 static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData) /* {{{ */
407 {
408 zend_string *name;
409 zend_ulong number;
410
411 if (!(*state % 2)) {
412 zend_hash_internal_pointer_reset(ht);
413 (*state)++;
414 }
415 while(zend_hash_has_more_elements(ht) == SUCCESS) {
416 zend_hash_get_current_key(ht, &name, &number);
417 if (!textlen || !strncmp(ZSTR_VAL(name), text, textlen)) {
418 if (pData) {
419 *pData = zend_hash_get_current_data_ptr(ht);
420 }
421 zend_hash_move_forward(ht);
422 return ZSTR_VAL(name);
423 }
424 if (zend_hash_move_forward(ht) == FAILURE) {
425 break;
426 }
427 }
428 (*state)++;
429 return NULL;
430 } /* }}} */
431
cli_completion_generator_var(const char * text,int textlen,int * state)432 static char *cli_completion_generator_var(const char *text, int textlen, int *state) /* {{{ */
433 {
434 char *retval, *tmp;
435 zend_array *symbol_table = &EG(symbol_table);
436
437 tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, symbol_table, NULL);
438 if (retval) {
439 retval = malloc(strlen(tmp) + 2);
440 retval[0] = '$';
441 strcpy(&retval[1], tmp);
442 rl_completion_append_character = '\0';
443 }
444 return retval;
445 } /* }}} */
446
cli_completion_generator_ini(const char * text,int textlen,int * state)447 static char *cli_completion_generator_ini(const char *text, int textlen, int *state) /* {{{ */
448 {
449 char *retval, *tmp;
450
451 tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL);
452 if (retval) {
453 retval = malloc(strlen(tmp) + 2);
454 retval[0] = '#';
455 strcpy(&retval[1], tmp);
456 rl_completion_append_character = '=';
457 }
458 return retval;
459 } /* }}} */
460
cli_completion_generator_func(const char * text,int textlen,int * state,HashTable * ht)461 static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht) /* {{{ */
462 {
463 zend_function *func;
464 char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func);
465 if (retval) {
466 rl_completion_append_character = '(';
467 retval = strdup(ZSTR_VAL(func->common.function_name));
468 }
469
470 return retval;
471 } /* }}} */
472
cli_completion_generator_class(const char * text,int textlen,int * state)473 static char *cli_completion_generator_class(const char *text, int textlen, int *state) /* {{{ */
474 {
475 zend_class_entry *ce;
476 char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&ce);
477 if (retval) {
478 rl_completion_append_character = '\0';
479 retval = strdup(ZSTR_VAL(ce->name));
480 }
481
482 return retval;
483 } /* }}} */
484
cli_completion_generator_define(const char * text,int textlen,int * state,HashTable * ht)485 static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht) /* {{{ */
486 {
487 zend_class_entry **pce;
488 char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce);
489 if (retval) {
490 rl_completion_append_character = '\0';
491 retval = strdup(retval);
492 }
493
494 return retval;
495 } /* }}} */
496
497 static int cli_completion_state;
498
cli_completion_generator(const char * text,int index)499 static char *cli_completion_generator(const char *text, int index) /* {{{ */
500 {
501 /*
502 TODO:
503 - constants
504 - maybe array keys
505 - language constructs and other things outside a hashtable (echo, try, function, class, ...)
506 - object/class members
507
508 - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
509 */
510 char *retval = NULL;
511 int textlen = strlen(text);
512
513 if (!index) {
514 cli_completion_state = 0;
515 }
516 if (text[0] == '$') {
517 retval = cli_completion_generator_var(text, textlen, &cli_completion_state);
518 } else if (text[0] == '#') {
519 retval = cli_completion_generator_ini(text, textlen, &cli_completion_state);
520 } else {
521 char *lc_text, *class_name_end;
522 int class_name_len;
523 zend_string *class_name;
524 zend_class_entry *ce = NULL;
525
526 class_name_end = strstr(text, "::");
527 if (class_name_end) {
528 class_name_len = class_name_end - text;
529 class_name = zend_string_alloc(class_name_len, 0);
530 zend_str_tolower_copy(ZSTR_VAL(class_name), text, class_name_len);
531 if ((ce = zend_lookup_class(class_name)) == NULL) {
532 zend_string_release(class_name);
533 return NULL;
534 }
535 lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
536 textlen -= (class_name_len + 2);
537 } else {
538 lc_text = zend_str_tolower_dup(text, textlen);
539 }
540
541 switch (cli_completion_state) {
542 case 0:
543 case 1:
544 retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, ce ? &ce->function_table : EG(function_table));
545 if (retval) {
546 break;
547 }
548 case 2:
549 case 3:
550 retval = cli_completion_generator_define(text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
551 if (retval || ce) {
552 break;
553 }
554 case 4:
555 case 5:
556 retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state);
557 break;
558 default:
559 break;
560 }
561 efree(lc_text);
562 if (class_name_end) {
563 zend_string_release(class_name);
564 }
565 if (ce && retval) {
566 int len = class_name_len + 2 + strlen(retval) + 1;
567 char *tmp = malloc(len);
568
569 snprintf(tmp, len, "%s::%s", ZSTR_VAL(ce->name), retval);
570 free(retval);
571 retval = tmp;
572 }
573 }
574
575 return retval;
576 } /* }}} */
577
cli_code_completion(const char * text,int start,int end)578 static char **cli_code_completion(const char *text, int start, int end) /* {{{ */
579 {
580 return rl_completion_matches(text, cli_completion_generator);
581 }
582 /* }}} */
583
readline_shell_run(void)584 static int readline_shell_run(void) /* {{{ */
585 {
586 char *line;
587 size_t size = 4096, pos = 0, len;
588 char *code = emalloc(size);
589 zend_string *prompt = cli_get_prompt("php", '>');
590 char *history_file;
591 int history_lines_to_write = 0;
592
593 if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
594 zend_file_handle *prepend_file_p;
595 zend_file_handle prepend_file = {{0}};
596
597 prepend_file.filename = PG(auto_prepend_file);
598 prepend_file.opened_path = NULL;
599 prepend_file.free_filename = 0;
600 prepend_file.type = ZEND_HANDLE_FILENAME;
601 prepend_file_p = &prepend_file;
602
603 zend_execute_scripts(ZEND_REQUIRE, NULL, 1, prepend_file_p);
604 }
605
606 history_file = tilde_expand("~/.php_history");
607 rl_attempted_completion_function = cli_code_completion;
608 rl_special_prefixes = "$";
609 read_history(history_file);
610
611 EG(exit_status) = 0;
612 while ((line = readline(ZSTR_VAL(prompt))) != NULL) {
613 if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
614 free(line);
615 break;
616 }
617
618 if (!pos && !*line) {
619 free(line);
620 continue;
621 }
622
623 len = strlen(line);
624
625 if (line[0] == '#') {
626 char *param = strstr(&line[1], "=");
627 if (param) {
628 zend_string *cmd;
629 param++;
630 cmd = zend_string_init(&line[1], param - &line[1] - 1, 0);
631
632 zend_alter_ini_entry_chars_ex(cmd, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0);
633 zend_string_release(cmd);
634 add_history(line);
635
636 zend_string_release(prompt);
637 /* TODO: This might be wrong! */
638 prompt = cli_get_prompt("php", '>');
639 continue;
640 }
641 }
642
643 if (pos + len + 2 > size) {
644 size = pos + len + 2;
645 code = erealloc(code, size);
646 }
647 memcpy(&code[pos], line, len);
648 pos += len;
649 code[pos] = '\n';
650 code[++pos] = '\0';
651
652 if (*line) {
653 add_history(line);
654 history_lines_to_write += 1;
655 }
656
657 free(line);
658 zend_string_release(prompt);
659
660 if (!cli_is_valid_code(code, pos, &prompt)) {
661 continue;
662 }
663
664 if (history_lines_to_write) {
665 #if HAVE_LIBEDIT
666 write_history(history_file);
667 #else
668 append_history(history_lines_to_write, history_file);
669 #endif
670 history_lines_to_write = 0;
671 }
672
673 zend_try {
674 zend_eval_stringl(code, pos, NULL, "php shell code");
675 } zend_end_try();
676
677 pos = 0;
678
679 if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
680 php_write("\n", 1);
681 }
682
683 if (EG(exception)) {
684 zend_exception_error(EG(exception), E_WARNING);
685 }
686
687 if (pager_pipe) {
688 fclose(pager_pipe);
689 pager_pipe = NULL;
690 }
691
692 php_last_char = '\0';
693 }
694 free(history_file);
695 efree(code);
696 zend_string_release(prompt);
697 return EG(exit_status);
698 }
699 /* }}} */
700
701 /*
702 #ifdef COMPILE_DL_READLINE
703 This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
704 extensions. If that is being changed dlsym() should only be used when building
705 this extension sharedto offer compatibility.
706 */
707 #define GET_SHELL_CB(cb) \
708 do { \
709 (cb) = NULL; \
710 cli_shell_callbacks_t *(*get_callbacks)(); \
711 get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
712 if (get_callbacks) { \
713 (cb) = get_callbacks(); \
714 } \
715 } while(0)
716 /*#else
717 #define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
718 #endif*/
719
PHP_MINIT_FUNCTION(cli_readline)720 PHP_MINIT_FUNCTION(cli_readline)
721 {
722 cli_shell_callbacks_t *cb;
723
724 ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
725 REGISTER_INI_ENTRIES();
726
727 #if HAVE_LIBEDIT
728 REGISTER_STRING_CONSTANT("READLINE_LIB", "libedit", CONST_CS|CONST_PERSISTENT);
729 #else
730 REGISTER_STRING_CONSTANT("READLINE_LIB", "readline", CONST_CS|CONST_PERSISTENT);
731 #endif
732
733 GET_SHELL_CB(cb);
734 if (cb) {
735 cb->cli_shell_write = readline_shell_write;
736 cb->cli_shell_ub_write = readline_shell_ub_write;
737 cb->cli_shell_run = readline_shell_run;
738 }
739
740 return SUCCESS;
741 }
742
PHP_MSHUTDOWN_FUNCTION(cli_readline)743 PHP_MSHUTDOWN_FUNCTION(cli_readline)
744 {
745 cli_shell_callbacks_t *cb;
746
747 UNREGISTER_INI_ENTRIES();
748
749 GET_SHELL_CB(cb);
750 if (cb) {
751 cb->cli_shell_write = NULL;
752 cb->cli_shell_ub_write = NULL;
753 cb->cli_shell_run = NULL;
754 }
755
756 return SUCCESS;
757 }
758
PHP_MINFO_FUNCTION(cli_readline)759 PHP_MINFO_FUNCTION(cli_readline)
760 {
761 php_info_print_table_start();
762 php_info_print_table_header(2, "Readline Support", "enabled");
763 php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
764 php_info_print_table_end();
765
766 DISPLAY_INI_ENTRIES();
767 }
768
769 /*
770 * Local variables:
771 * tab-width: 4
772 * c-basic-offset: 4
773 * End:
774 * vim600: sw=4 ts=4 fdm=marker
775 * vim<600: sw=4 ts=4
776 */
777