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