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: Thies C. Arntzen <thies@thieso.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* $Id$ */
20
21 /* {{{ includes & prototypes */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "php.h"
28 #include "php_readline.h"
29 #include "readline_cli.h"
30
31 #if HAVE_LIBREADLINE || HAVE_LIBEDIT
32
33 #ifndef HAVE_RL_COMPLETION_MATCHES
34 #define rl_completion_matches completion_matches
35 #endif
36
37 #ifdef HAVE_LIBEDIT
38 #include <editline/readline.h>
39 #else
40 #include <readline/readline.h>
41 #include <readline/history.h>
42 #endif
43
44 PHP_FUNCTION(readline);
45 PHP_FUNCTION(readline_add_history);
46 PHP_FUNCTION(readline_info);
47 PHP_FUNCTION(readline_clear_history);
48 #ifndef HAVE_LIBEDIT
49 PHP_FUNCTION(readline_list_history);
50 #endif
51 PHP_FUNCTION(readline_read_history);
52 PHP_FUNCTION(readline_write_history);
53 PHP_FUNCTION(readline_completion_function);
54
55 #if HAVE_RL_CALLBACK_READ_CHAR
56 PHP_FUNCTION(readline_callback_handler_install);
57 PHP_FUNCTION(readline_callback_read_char);
58 PHP_FUNCTION(readline_callback_handler_remove);
59 PHP_FUNCTION(readline_redisplay);
60 PHP_FUNCTION(readline_on_new_line);
61
62 static zval _prepped_callback;
63
64 #endif
65
66 static zval _readline_completion;
67 static zval _readline_array;
68
69 PHP_MINIT_FUNCTION(readline);
70 PHP_MSHUTDOWN_FUNCTION(readline);
71 PHP_RSHUTDOWN_FUNCTION(readline);
72 PHP_MINFO_FUNCTION(readline);
73
74 /* }}} */
75
76 /* {{{ arginfo */
77 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline, 0, 0, 0)
78 ZEND_ARG_INFO(0, prompt)
79 ZEND_END_ARG_INFO()
80
81 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_info, 0, 0, 0)
82 ZEND_ARG_INFO(0, varname)
83 ZEND_ARG_INFO(0, newvalue)
84 ZEND_END_ARG_INFO()
85
86 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_add_history, 0, 0, 1)
87 ZEND_ARG_INFO(0, prompt)
88 ZEND_END_ARG_INFO()
89
90 ZEND_BEGIN_ARG_INFO(arginfo_readline_clear_history, 0)
91 ZEND_END_ARG_INFO()
92
93 #ifndef HAVE_LIBEDIT
94 ZEND_BEGIN_ARG_INFO(arginfo_readline_list_history, 0)
95 ZEND_END_ARG_INFO()
96 #endif
97
98 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_read_history, 0, 0, 0)
99 ZEND_ARG_INFO(0, filename)
100 ZEND_END_ARG_INFO()
101
102 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_write_history, 0, 0, 0)
103 ZEND_ARG_INFO(0, filename)
104 ZEND_END_ARG_INFO()
105
106 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_completion_function, 0, 0, 1)
107 ZEND_ARG_INFO(0, funcname)
108 ZEND_END_ARG_INFO()
109
110 #if HAVE_RL_CALLBACK_READ_CHAR
111 ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_callback_handler_install, 0, 0, 2)
112 ZEND_ARG_INFO(0, prompt)
113 ZEND_ARG_INFO(0, callback)
114 ZEND_END_ARG_INFO()
115
116 ZEND_BEGIN_ARG_INFO(arginfo_readline_callback_read_char, 0)
117 ZEND_END_ARG_INFO()
118
119 ZEND_BEGIN_ARG_INFO(arginfo_readline_callback_handler_remove, 0)
120 ZEND_END_ARG_INFO()
121
122 ZEND_BEGIN_ARG_INFO(arginfo_readline_redisplay, 0)
123 ZEND_END_ARG_INFO()
124
125 ZEND_BEGIN_ARG_INFO(arginfo_readline_on_new_line, 0)
126 ZEND_END_ARG_INFO()
127 #endif
128 /* }}} */
129
130 /* {{{ module stuff */
131 static const zend_function_entry php_readline_functions[] = {
132 PHP_FE(readline, arginfo_readline)
133 PHP_FE(readline_info, arginfo_readline_info)
134 PHP_FE(readline_add_history, arginfo_readline_add_history)
135 PHP_FE(readline_clear_history, arginfo_readline_clear_history)
136 #ifndef HAVE_LIBEDIT
137 PHP_FE(readline_list_history, arginfo_readline_list_history)
138 #endif
139 PHP_FE(readline_read_history, arginfo_readline_read_history)
140 PHP_FE(readline_write_history, arginfo_readline_write_history)
141 PHP_FE(readline_completion_function,arginfo_readline_completion_function)
142 #if HAVE_RL_CALLBACK_READ_CHAR
143 PHP_FE(readline_callback_handler_install, arginfo_readline_callback_handler_install)
144 PHP_FE(readline_callback_read_char, arginfo_readline_callback_read_char)
145 PHP_FE(readline_callback_handler_remove, arginfo_readline_callback_handler_remove)
146 PHP_FE(readline_redisplay, arginfo_readline_redisplay)
147 #endif
148 #if HAVE_RL_ON_NEW_LINE
149 PHP_FE(readline_on_new_line, arginfo_readline_on_new_line)
150 #endif
151 PHP_FE_END
152 };
153
154 zend_module_entry readline_module_entry = {
155 STANDARD_MODULE_HEADER,
156 "readline",
157 php_readline_functions,
158 PHP_MINIT(readline),
159 PHP_MSHUTDOWN(readline),
160 NULL,
161 PHP_RSHUTDOWN(readline),
162 PHP_MINFO(readline),
163 PHP_READLINE_VERSION,
164 STANDARD_MODULE_PROPERTIES
165 };
166
167 #ifdef COMPILE_DL_READLINE
168 ZEND_GET_MODULE(readline)
169 #endif
170
PHP_MINIT_FUNCTION(readline)171 PHP_MINIT_FUNCTION(readline)
172 {
173 #if HAVE_LIBREADLINE
174 /* libedit don't need this call which set the tty in cooked mode */
175 using_history();
176 #endif
177 ZVAL_UNDEF(&_readline_completion);
178 #if HAVE_RL_CALLBACK_READ_CHAR
179 ZVAL_UNDEF(&_prepped_callback);
180 #endif
181 return PHP_MINIT(cli_readline)(INIT_FUNC_ARGS_PASSTHRU);
182 }
183
PHP_MSHUTDOWN_FUNCTION(readline)184 PHP_MSHUTDOWN_FUNCTION(readline)
185 {
186 return PHP_MSHUTDOWN(cli_readline)(SHUTDOWN_FUNC_ARGS_PASSTHRU);
187 }
188
PHP_RSHUTDOWN_FUNCTION(readline)189 PHP_RSHUTDOWN_FUNCTION(readline)
190 {
191 zval_ptr_dtor(&_readline_completion);
192 ZVAL_UNDEF(&_readline_completion);
193 #if HAVE_RL_CALLBACK_READ_CHAR
194 if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
195 rl_callback_handler_remove();
196 zval_ptr_dtor(&_prepped_callback);
197 ZVAL_UNDEF(&_prepped_callback);
198 }
199 #endif
200
201 return SUCCESS;
202 }
203
PHP_MINFO_FUNCTION(readline)204 PHP_MINFO_FUNCTION(readline)
205 {
206 PHP_MINFO(cli_readline)(ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU);
207 }
208
209 /* }}} */
210
211 /* {{{ proto string readline([string prompt])
212 Reads a line */
PHP_FUNCTION(readline)213 PHP_FUNCTION(readline)
214 {
215 char *prompt = NULL;
216 size_t prompt_len;
217 char *result;
218
219 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &prompt, &prompt_len)) {
220 RETURN_FALSE;
221 }
222
223 result = readline(prompt);
224
225 if (! result) {
226 RETURN_FALSE;
227 } else {
228 RETVAL_STRING(result);
229 free(result);
230 }
231 }
232
233 /* }}} */
234
235 #define SAFE_STRING(s) ((s)?(char*)(s):"")
236
237 /* {{{ proto mixed readline_info([string varname [, string newvalue]])
238 Gets/sets various internal readline variables. */
PHP_FUNCTION(readline_info)239 PHP_FUNCTION(readline_info)
240 {
241 char *what = NULL;
242 zval *value = NULL;
243 size_t what_len, oldval;
244 char *oldstr;
245
246 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sz", &what, &what_len, &value) == FAILURE) {
247 return;
248 }
249
250 if (!what) {
251 array_init(return_value);
252 add_assoc_string(return_value,"line_buffer",SAFE_STRING(rl_line_buffer));
253 add_assoc_long(return_value,"point",rl_point);
254 add_assoc_long(return_value,"end",rl_end);
255 #ifdef HAVE_LIBREADLINE
256 add_assoc_long(return_value,"mark",rl_mark);
257 add_assoc_long(return_value,"done",rl_done);
258 add_assoc_long(return_value,"pending_input",rl_pending_input);
259 add_assoc_string(return_value,"prompt",SAFE_STRING(rl_prompt));
260 add_assoc_string(return_value,"terminal_name",(char *)SAFE_STRING(rl_terminal_name));
261 #endif
262 #if HAVE_ERASE_EMPTY_LINE
263 add_assoc_long(return_value,"erase_empty_line",rl_erase_empty_line);
264 #endif
265 add_assoc_string(return_value,"library_version",(char *)SAFE_STRING(rl_library_version));
266 add_assoc_string(return_value,"readline_name",(char *)SAFE_STRING(rl_readline_name));
267 add_assoc_long(return_value,"attempted_completion_over",rl_attempted_completion_over);
268 } else {
269 if (!strcasecmp(what,"line_buffer")) {
270 oldstr = rl_line_buffer;
271 if (value) {
272 /* XXX if (rl_line_buffer) free(rl_line_buffer); */
273 convert_to_string_ex(value);
274 rl_line_buffer = strdup(Z_STRVAL_P(value));
275 }
276 RETVAL_STRING(SAFE_STRING(oldstr));
277 } else if (!strcasecmp(what, "point")) {
278 RETVAL_LONG(rl_point);
279 } else if (!strcasecmp(what, "end")) {
280 RETVAL_LONG(rl_end);
281 #ifdef HAVE_LIBREADLINE
282 } else if (!strcasecmp(what, "mark")) {
283 RETVAL_LONG(rl_mark);
284 } else if (!strcasecmp(what, "done")) {
285 oldval = rl_done;
286 if (value) {
287 convert_to_long_ex(value);
288 rl_done = Z_LVAL_P(value);
289 }
290 RETVAL_LONG(oldval);
291 } else if (!strcasecmp(what, "pending_input")) {
292 oldval = rl_pending_input;
293 if (value) {
294 convert_to_string_ex(value);
295 rl_pending_input = Z_STRVAL_P(value)[0];
296 }
297 RETVAL_LONG(oldval);
298 } else if (!strcasecmp(what, "prompt")) {
299 RETVAL_STRING(SAFE_STRING(rl_prompt));
300 } else if (!strcasecmp(what, "terminal_name")) {
301 RETVAL_STRING((char *)SAFE_STRING(rl_terminal_name));
302 #endif
303 #if HAVE_ERASE_EMPTY_LINE
304 } else if (!strcasecmp(what, "erase_empty_line")) {
305 oldval = rl_erase_empty_line;
306 if (value) {
307 convert_to_long_ex(value);
308 rl_erase_empty_line = Z_LVAL_P(value);
309 }
310 RETVAL_LONG(oldval);
311 #endif
312 } else if (!strcasecmp(what,"library_version")) {
313 RETVAL_STRING((char *)SAFE_STRING(rl_library_version));
314 } else if (!strcasecmp(what, "readline_name")) {
315 oldstr = (char*)rl_readline_name;
316 if (value) {
317 /* XXX if (rl_readline_name) free(rl_readline_name); */
318 convert_to_string_ex(value);
319 rl_readline_name = strdup(Z_STRVAL_P(value));;
320 }
321 RETVAL_STRING(SAFE_STRING(oldstr));
322 } else if (!strcasecmp(what, "attempted_completion_over")) {
323 oldval = rl_attempted_completion_over;
324 if (value) {
325 convert_to_long_ex(value);
326 rl_attempted_completion_over = Z_LVAL_P(value);
327 }
328 RETVAL_LONG(oldval);
329 }
330 }
331 }
332
333 /* }}} */
334 /* {{{ proto bool readline_add_history(string prompt)
335 Adds a line to the history */
PHP_FUNCTION(readline_add_history)336 PHP_FUNCTION(readline_add_history)
337 {
338 char *arg;
339 size_t arg_len;
340
341 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
342 return;
343 }
344
345 add_history(arg);
346
347 RETURN_TRUE;
348 }
349
350 /* }}} */
351 /* {{{ proto bool readline_clear_history(void)
352 Clears the history */
PHP_FUNCTION(readline_clear_history)353 PHP_FUNCTION(readline_clear_history)
354 {
355 if (zend_parse_parameters_none() == FAILURE) {
356 return;
357 }
358
359 #if HAVE_LIBEDIT
360 /* clear_history is the only function where rl_initialize
361 is not call to ensure correct allocation */
362 using_history();
363 #endif
364 clear_history();
365
366 RETURN_TRUE;
367 }
368
369 /* }}} */
370 /* {{{ proto array readline_list_history(void)
371 Lists the history */
372 #ifndef HAVE_LIBEDIT
PHP_FUNCTION(readline_list_history)373 PHP_FUNCTION(readline_list_history)
374 {
375 HIST_ENTRY **history;
376
377 if (zend_parse_parameters_none() == FAILURE) {
378 return;
379 }
380
381 history = history_list();
382
383 array_init(return_value);
384
385 if (history) {
386 int i;
387 for (i = 0; history[i]; i++) {
388 add_next_index_string(return_value,history[i]->line);
389 }
390 }
391 }
392 #endif
393 /* }}} */
394 /* {{{ proto bool readline_read_history([string filename])
395 Reads the history */
PHP_FUNCTION(readline_read_history)396 PHP_FUNCTION(readline_read_history)
397 {
398 char *arg = NULL;
399 size_t arg_len;
400
401 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p", &arg, &arg_len) == FAILURE) {
402 return;
403 }
404
405 if (arg && php_check_open_basedir(arg)) {
406 RETURN_FALSE;
407 }
408
409 /* XXX from & to NYI */
410 if (read_history(arg)) {
411 /* If filename is NULL, then read from `~/.history' */
412 RETURN_FALSE;
413 } else {
414 RETURN_TRUE;
415 }
416 }
417
418 /* }}} */
419 /* {{{ proto bool readline_write_history([string filename])
420 Writes the history */
PHP_FUNCTION(readline_write_history)421 PHP_FUNCTION(readline_write_history)
422 {
423 char *arg = NULL;
424 size_t arg_len;
425
426 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p", &arg, &arg_len) == FAILURE) {
427 return;
428 }
429
430 if (arg && php_check_open_basedir(arg)) {
431 RETURN_FALSE;
432 }
433
434 if (write_history(arg)) {
435 RETURN_FALSE;
436 } else {
437 RETURN_TRUE;
438 }
439 }
440
441 /* }}} */
442 /* {{{ proto bool readline_completion_function(string funcname)
443 Readline completion function? */
444
_readline_command_generator(const char * text,int state)445 static char *_readline_command_generator(const char *text, int state)
446 {
447 HashTable *myht = Z_ARRVAL(_readline_array);
448 zval *entry;
449
450 if (!state) {
451 zend_hash_internal_pointer_reset(myht);
452 }
453
454 while ((entry = zend_hash_get_current_data(myht)) != NULL) {
455 zend_hash_move_forward(myht);
456
457 convert_to_string_ex(entry);
458 if (strncmp (Z_STRVAL_P(entry), text, strlen(text)) == 0) {
459 return (strdup(Z_STRVAL_P(entry)));
460 }
461 }
462
463 return NULL;
464 }
465
_readline_string_zval(zval * ret,const char * str)466 static void _readline_string_zval(zval *ret, const char *str)
467 {
468 if (str) {
469 ZVAL_STRING(ret, (char*)str);
470 } else {
471 ZVAL_NULL(ret);
472 }
473 }
474
_readline_long_zval(zval * ret,long l)475 static void _readline_long_zval(zval *ret, long l)
476 {
477 ZVAL_LONG(ret, l);
478 }
479
_readline_completion_cb(const char * text,int start,int end)480 static char **_readline_completion_cb(const char *text, int start, int end)
481 {
482 zval params[3];
483 int i;
484 char **matches = NULL;
485
486 _readline_string_zval(¶ms[0], text);
487 _readline_long_zval(¶ms[1], start);
488 _readline_long_zval(¶ms[2], end);
489
490 if (call_user_function(CG(function_table), NULL, &_readline_completion, &_readline_array, 3, params) == SUCCESS) {
491 if (Z_TYPE(_readline_array) == IS_ARRAY) {
492 if (zend_hash_num_elements(Z_ARRVAL(_readline_array))) {
493 matches = rl_completion_matches(text,_readline_command_generator);
494 } else {
495 matches = malloc(sizeof(char *) * 2);
496 if (!matches) {
497 return NULL;
498 }
499 matches[0] = strdup("");
500 matches[1] = '\0';
501 }
502 }
503 }
504
505 for (i = 0; i < 3; i++) {
506 zval_ptr_dtor(¶ms[i]);
507 }
508 zval_ptr_dtor(&_readline_array);
509
510 return matches;
511 }
512
PHP_FUNCTION(readline_completion_function)513 PHP_FUNCTION(readline_completion_function)
514 {
515 zval *arg = NULL;
516 zend_string *name = NULL;
517
518 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "z", &arg)) {
519 RETURN_FALSE;
520 }
521
522 if (!zend_is_callable(arg, 0, &name)) {
523 php_error_docref(NULL, E_WARNING, "%s is not callable", ZSTR_VAL(name));
524 zend_string_release(name);
525 RETURN_FALSE;
526 }
527 zend_string_release(name);
528
529 zval_ptr_dtor(&_readline_completion);
530 ZVAL_COPY(&_readline_completion, arg);
531
532 rl_attempted_completion_function = _readline_completion_cb;
533 if (rl_attempted_completion_function == NULL) {
534 RETURN_FALSE;
535 }
536 RETURN_TRUE;
537 }
538
539 /* }}} */
540
541 #if HAVE_RL_CALLBACK_READ_CHAR
542
php_rl_callback_handler(char * the_line)543 static void php_rl_callback_handler(char *the_line)
544 {
545 zval params[1];
546 zval dummy;
547
548 ZVAL_NULL(&dummy);
549
550 _readline_string_zval(¶ms[0], the_line);
551
552 call_user_function(CG(function_table), NULL, &_prepped_callback, &dummy, 1, params);
553
554 zval_ptr_dtor(¶ms[0]);
555 zval_ptr_dtor(&dummy);
556 }
557
558 /* {{{ proto void readline_callback_handler_install(string prompt, mixed callback)
559 Initializes the readline callback interface and terminal, prints the prompt and returns immediately */
PHP_FUNCTION(readline_callback_handler_install)560 PHP_FUNCTION(readline_callback_handler_install)
561 {
562 zval *callback;
563 zend_string *name = NULL;
564 char *prompt;
565 size_t prompt_len;
566
567 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &prompt, &prompt_len, &callback)) {
568 return;
569 }
570
571 if (!zend_is_callable(callback, 0, &name)) {
572 php_error_docref(NULL, E_WARNING, "%s is not callable", ZSTR_VAL(name));
573 zend_string_release(name);
574 RETURN_FALSE;
575 }
576 zend_string_release(name);
577
578 if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
579 rl_callback_handler_remove();
580 zval_ptr_dtor(&_prepped_callback);
581 }
582
583 ZVAL_COPY(&_prepped_callback, callback);
584
585 rl_callback_handler_install(prompt, php_rl_callback_handler);
586
587 RETURN_TRUE;
588 }
589 /* }}} */
590
591 /* {{{ proto void readline_callback_read_char()
592 Informs the readline callback interface that a character is ready for input */
PHP_FUNCTION(readline_callback_read_char)593 PHP_FUNCTION(readline_callback_read_char)
594 {
595 if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
596 rl_callback_read_char();
597 }
598 }
599 /* }}} */
600
601 /* {{{ proto bool readline_callback_handler_remove()
602 Removes a previously installed callback handler and restores terminal settings */
PHP_FUNCTION(readline_callback_handler_remove)603 PHP_FUNCTION(readline_callback_handler_remove)
604 {
605 if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
606 rl_callback_handler_remove();
607 zval_ptr_dtor(&_prepped_callback);
608 ZVAL_UNDEF(&_prepped_callback);
609 RETURN_TRUE;
610 }
611 RETURN_FALSE;
612 }
613 /* }}} */
614
615 /* {{{ proto void readline_redisplay(void)
616 Ask readline to redraw the display */
PHP_FUNCTION(readline_redisplay)617 PHP_FUNCTION(readline_redisplay)
618 {
619 #if HAVE_LIBEDIT
620 /* seems libedit doesn't take care of rl_initialize in rl_redisplay
621 * see bug #72538 */
622 using_history();
623 #endif
624 rl_redisplay();
625 }
626 /* }}} */
627
628 #endif
629
630 #if HAVE_RL_ON_NEW_LINE
631 /* {{{ proto void readline_on_new_line(void)
632 Inform readline that the cursor has moved to a new line */
PHP_FUNCTION(readline_on_new_line)633 PHP_FUNCTION(readline_on_new_line)
634 {
635 rl_on_new_line();
636 }
637 /* }}} */
638
639 #endif
640
641
642 #endif /* HAVE_LIBREADLINE */
643
644 /*
645 * Local variables:
646 * tab-width: 4
647 * c-basic-offset: 4
648 * End:
649 */
650