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