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 "ext/standard/php_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_indent.h"
66 #include "zend_exceptions.h"
67
68 #include "sapi/cli/cli.h"
69 #include "readline_cli.h"
70
71 #ifdef COMPILE_DL_READLINE
72 #include <dlfcn.h>
73 #endif
74
75 #ifndef RTLD_DEFAULT
76 #define RTLD_DEFAULT NULL
77 #endif
78
79 #define DEFAULT_PROMPT "\\b \\> "
80
81 ZEND_DECLARE_MODULE_GLOBALS(cli_readline);
82
83 static char php_last_char = '\0';
84 static FILE *pager_pipe = NULL;
85
86 static size_t readline_shell_write(const char *str, uint str_length TSRMLS_DC)
87 {
88 if (CLIR_G(prompt_str)) {
89 smart_str_appendl(CLIR_G(prompt_str), str, str_length);
90 return str_length;
91 }
92
93 if (CLIR_G(pager) && *CLIR_G(pager) && !pager_pipe) {
94 pager_pipe = VCWD_POPEN(CLIR_G(pager), "w");
95 }
96 if (pager_pipe) {
97 return fwrite(str, 1, MIN(str_length, 16384), pager_pipe);
98 }
99
100 return -1;
101 }
102
103
104 static int readline_shell_ub_write(const char *str, uint str_length TSRMLS_DC)
105 {
106
107
108
109 php_last_char = str[str_length-1];
110 return -1;
111 }
112
113
114 static void cli_readline_init_globals(zend_cli_readline_globals *rg TSRMLS_DC)
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
141 static char *cli_get_prompt(char *block, char prompt TSRMLS_DC)
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" TSRMLS_CC);
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.c;
208 }
209
210
211 static int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC)
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 (!CG(asp_tags)) {
279 valid_end = 0;
280 break;
281 }
282
283 case '?':
284 if (code[i+1] == '>') {
285 i++;
286 code_type = outside;
287 break;
288 }
289 valid_end = 0;
290 break;
291 case '<':
292 valid_end = 0;
293 if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
294 i += 2;
295 code_type = heredoc_start;
296 heredoc_len = 0;
297 }
298 break;
299 default:
300 valid_end = 0;
301 break;
302 }
303 break;
304 case sstring:
305 if (code[i] == '\\') {
306 code_type = sstring_esc;
307 } else {
308 if (code[i] == '\'') {
309 code_type = body;
310 }
311 }
312 break;
313 case sstring_esc:
314 code_type = sstring;
315 break;
316 case dstring:
317 if (code[i] == '\\') {
318 code_type = dstring_esc;
319 } else {
320 if (code[i] == '"') {
321 code_type = body;
322 }
323 }
324 break;
325 case dstring_esc:
326 code_type = dstring;
327 break;
328 case comment_line:
329 if (code[i] == '\n') {
330 code_type = body;
331 }
332 break;
333 case comment_block:
334 if (code[i-1] == '*' && code[i] == '/') {
335 code_type = body;
336 valid_end = last_valid_end;
337 }
338 break;
339 case heredoc_start:
340 switch(code[i]) {
341 case ' ':
342 case '\t':
343 case '\'':
344 break;
345 case '\r':
346 case '\n':
347 code_type = heredoc;
348 break;
349 default:
350 if (!heredoc_len) {
351 heredoc_tag = code+i;
352 }
353 heredoc_len++;
354 break;
355 }
356 break;
357 case heredoc:
358 if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {
359 code_type = body;
360 } else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {
361 code_type = body;
362 valid_end = 1;
363 }
364 break;
365 case outside:
366 if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
367 || (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))
368 || (i > 3 && !strncmp(code+i-4, "<?php", 5))
369 ) {
370 code_type = body;
371 }
372 break;
373 }
374 }
375
376 switch (code_type) {
377 default:
378 if (brace_count) {
379 *prompt = cli_get_prompt("php", '(' TSRMLS_CC);
380 } else if (brackets_count) {
381 *prompt = cli_get_prompt("php", '{' TSRMLS_CC);
382 } else {
383 *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
384 }
385 break;
386 case sstring:
387 case sstring_esc:
388 *prompt = cli_get_prompt("php", '\'' TSRMLS_CC);
389 break;
390 case dstring:
391 case dstring_esc:
392 *prompt = cli_get_prompt("php", '"' TSRMLS_CC);
393 break;
394 case comment_block:
395 *prompt = cli_get_prompt("/* ", '>' TSRMLS_CC);
396 break;
397 case heredoc:
398 *prompt = cli_get_prompt("<<<", '>' TSRMLS_CC);
399 break;
400 case outside:
401 *prompt = cli_get_prompt(" ", '>' TSRMLS_CC);
402 break;
403 }
404
405 if (!valid_end || brackets_count) {
406 return 0;
407 } else {
408 return 1;
409 }
410 }
411
412
413 static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData TSRMLS_DC)
414 {
415 char *name;
416 ulong number;
417
418 if (!(*state % 2)) {
419 zend_hash_internal_pointer_reset(ht);
420 (*state)++;
421 }
422 while(zend_hash_has_more_elements(ht) == SUCCESS) {
423 zend_hash_get_current_key(ht, &name, &number, 0);
424 if (!textlen || !strncmp(name, text, textlen)) {
425 if (pData) {
426 zend_hash_get_current_data(ht, pData);
427 }
428 zend_hash_move_forward(ht);
429 return name;
430 }
431 if (zend_hash_move_forward(ht) == FAILURE) {
432 break;
433 }
434 }
435 (*state)++;
436 return NULL;
437 }
438
439 static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC)
440 {
441 char *retval, *tmp;
442
443 tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC);
444 if (retval) {
445 retval = malloc(strlen(tmp) + 2);
446 retval[0] = '$';
447 strcpy(&retval[1], tmp);
448 rl_completion_append_character = '\0';
449 }
450 return retval;
451 }
452
453 static char *cli_completion_generator_ini(const char *text, int textlen, int *state TSRMLS_DC)
454 {
455 char *retval, *tmp;
456
457 tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL TSRMLS_CC);
458 if (retval) {
459 retval = malloc(strlen(tmp) + 2);
460 retval[0] = '#';
461 strcpy(&retval[1], tmp);
462 rl_completion_append_character = '=';
463 }
464 return retval;
465 }
466
467 static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC)
468 {
469 zend_function *func;
470 char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);
471 if (retval) {
472 rl_completion_append_character = '(';
473 retval = strdup(func->common.function_name);
474 }
475
476 return retval;
477 }
478
479 static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC)
480 {
481 zend_class_entry **pce;
482 char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC);
483 if (retval) {
484 rl_completion_append_character = '\0';
485 retval = strdup((*pce)->name);
486 }
487
488 return retval;
489 }
490
491 static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC)
492 {
493 zend_class_entry **pce;
494 char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC);
495 if (retval) {
496 rl_completion_append_character = '\0';
497 retval = strdup(retval);
498 }
499
500 return retval;
501 }
502
503 static int cli_completion_state;
504
505 static char *cli_completion_generator(const char *text, int index)
506 {
507
508
509
510
511
512
513
514
515
516 char *retval = NULL;
517 int textlen = strlen(text);
518 TSRMLS_FETCH();
519
520 if (!index) {
521 cli_completion_state = 0;
522 }
523 if (text[0] == '$') {
524 retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC);
525 } else if (text[0] == '#') {
526 retval = cli_completion_generator_ini(text, textlen, &cli_completion_state TSRMLS_CC);
527 } else {
528 char *lc_text, *class_name, *class_name_end;
529 int class_name_len;
530 zend_class_entry **pce = NULL;
531
532 class_name_end = strstr(text, "::");
533 if (class_name_end) {
534 class_name_len = class_name_end - text;
535 class_name = zend_str_tolower_dup(text, class_name_len);
536 class_name[class_name_len] = '\0';
537 if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) {
538 efree(class_name);
539 return NULL;
540 }
541 lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
542 textlen -= (class_name_len + 2);
543 } else {
544 lc_text = zend_str_tolower_dup(text, textlen);
545 }
546
547 switch (cli_completion_state) {
548 case 0:
549 case 1:
550 retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC);
551 if (retval) {
552 break;
553 }
554 case 2:
555 case 3:
556 retval = cli_completion_generator_define(text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC);
557 if (retval || pce) {
558 break;
559 }
560 case 4:
561 case 5:
562 retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC);
563 break;
564 default:
565 break;
566 }
567 efree(lc_text);
568 if (class_name_end) {
569 efree(class_name);
570 }
571 if (pce && retval) {
572 int len = class_name_len + 2 + strlen(retval) + 1;
573 char *tmp = malloc(len);
574
575 snprintf(tmp, len, "%s::%s", (*pce)->name, retval);
576 free(retval);
577 retval = tmp;
578 }
579 }
580
581 return retval;
582 }
583
584 static char **cli_code_completion(const char *text, int start, int end)
585 {
586 return rl_completion_matches(text, cli_completion_generator);
587 }
588
589
590 static int readline_shell_run(TSRMLS_D)
591 {
592 char *line;
593 size_t size = 4096, pos = 0, len;
594 char *code = emalloc(size);
595 char *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
596 char *history_file;
597 int history_lines_to_write = 0;
598
599 if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
600 zend_file_handle *prepend_file_p;
601 zend_file_handle prepend_file = {0};
602
603 prepend_file.filename = PG(auto_prepend_file);
604 prepend_file.opened_path = NULL;
605 prepend_file.free_filename = 0;
606 prepend_file.type = ZEND_HANDLE_FILENAME;
607 prepend_file_p = &prepend_file;
608
609 zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, NULL, 1, prepend_file_p);
610 }
611
612 history_file = tilde_expand("~/.php_history");
613 rl_attempted_completion_function = cli_code_completion;
614 rl_special_prefixes = "$";
615 read_history(history_file);
616
617 EG(exit_status) = 0;
618 while ((line = readline(prompt)) != NULL) {
619 if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
620 free(line);
621 break;
622 }
623
624 if (!pos && !*line) {
625 free(line);
626 continue;
627 }
628
629 len = strlen(line);
630
631 if (line[0] == '#') {
632 char *param = strstr(&line[1], "=");
633 if (param) {
634 char *cmd;
635 uint cmd_len;
636 param++;
637 cmd_len = param - &line[1] - 1;
638 cmd = estrndup(&line[1], cmd_len);
639
640 zend_alter_ini_entry_ex(cmd, cmd_len + 1, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC);
641 efree(cmd);
642 add_history(line);
643
644 efree(prompt);
645
646 prompt = cli_get_prompt("php", '>' TSRMLS_CC);
647 continue;
648 }
649 }
650
651 if (pos + len + 2 > size) {
652 size = pos + len + 2;
653 code = erealloc(code, size);
654 }
655 memcpy(&code[pos], line, len);
656 pos += len;
657 code[pos] = '\n';
658 code[++pos] = '\0';
659
660 if (*line) {
661 add_history(line);
662 history_lines_to_write += 1;
663 }
664
665 free(line);
666 efree(prompt);
667
668 if (!cli_is_valid_code(code, pos, &prompt TSRMLS_CC)) {
669 continue;
670 }
671
672 if (history_lines_to_write) {
673 #if HAVE_LIBEDIT
674 write_history(history_file);
675 #else
676 append_history(history_lines_to_write, history_file);
677 #endif
678 history_lines_to_write = 0;
679 }
680
681 zend_try {
682 zend_eval_stringl(code, pos, NULL, "php shell code" TSRMLS_CC);
683 } zend_end_try();
684
685 pos = 0;
686
687 if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
688 php_write("\n", 1 TSRMLS_CC);
689 }
690
691 if (EG(exception)) {
692 zend_exception_error(EG(exception), E_WARNING TSRMLS_CC);
693 }
694
695 if (pager_pipe) {
696 fclose(pager_pipe);
697 pager_pipe = NULL;
698 }
699
700 php_last_char = '\0';
701 }
702 free(history_file);
703 efree(code);
704 efree(prompt);
705 return EG(exit_status);
706 }
707
708
709
710
711
712
713
714
715 #define GET_SHELL_CB(cb) \
716 do { \
717 (cb) = NULL; \
718 cli_shell_callbacks_t *(*get_callbacks)(); \
719 get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
720 if (get_callbacks) { \
721 (cb) = get_callbacks(); \
722 } \
723 } while(0)
724
725
726
727
728 PHP_MINIT_FUNCTION(cli_readline)
729 {
730 cli_shell_callbacks_t *cb;
731
732 ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
733 REGISTER_INI_ENTRIES();
734
735 #if HAVE_LIBEDIT
736 REGISTER_STRING_CONSTANT("READLINE_LIB", "libedit", CONST_CS|CONST_PERSISTENT);
737 #else
738 REGISTER_STRING_CONSTANT("READLINE_LIB", "readline", CONST_CS|CONST_PERSISTENT);
739 #endif
740
741 GET_SHELL_CB(cb);
742 if (cb) {
743 cb->cli_shell_write = readline_shell_write;
744 cb->cli_shell_ub_write = readline_shell_ub_write;
745 cb->cli_shell_run = readline_shell_run;
746 }
747
748 return SUCCESS;
749 }
750
751 PHP_MSHUTDOWN_FUNCTION(cli_readline)
752 {
753 cli_shell_callbacks_t *cb;
754
755 UNREGISTER_INI_ENTRIES();
756
757 GET_SHELL_CB(cb);
758 if (cb) {
759 cb->cli_shell_write = NULL;
760 cb->cli_shell_ub_write = NULL;
761 cb->cli_shell_run = NULL;
762 }
763
764 return SUCCESS;
765 }
766
767 PHP_MINFO_FUNCTION(cli_readline)
768 {
769 php_info_print_table_start();
770 php_info_print_table_header(2, "Readline Support", "enabled");
771 php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
772 php_info_print_table_end();
773
774 DISPLAY_INI_ENTRIES();
775 }
776
777
778
779
780
781
782
783
784