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