1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2015 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
44PHP_FUNCTION(readline);
45PHP_FUNCTION(readline_add_history);
46PHP_FUNCTION(readline_info);
47PHP_FUNCTION(readline_clear_history);
48#ifndef HAVE_LIBEDIT
49PHP_FUNCTION(readline_list_history);
50#endif
51PHP_FUNCTION(readline_read_history);
52PHP_FUNCTION(readline_write_history);
53PHP_FUNCTION(readline_completion_function);
54
55#if HAVE_RL_CALLBACK_READ_CHAR
56PHP_FUNCTION(readline_callback_handler_install);
57PHP_FUNCTION(readline_callback_read_char);
58PHP_FUNCTION(readline_callback_handler_remove);
59PHP_FUNCTION(readline_redisplay);
60PHP_FUNCTION(readline_on_new_line);
61
62static zval _prepped_callback;
63
64#endif
65
66static zval _readline_completion;
67static zval _readline_array;
68
69PHP_MINIT_FUNCTION(readline);
70PHP_MSHUTDOWN_FUNCTION(readline);
71PHP_RSHUTDOWN_FUNCTION(readline);
72PHP_MINFO_FUNCTION(readline);
73
74/* }}} */
75
76/* {{{ arginfo */
77ZEND_BEGIN_ARG_INFO_EX(arginfo_readline, 0, 0, 0)
78    ZEND_ARG_INFO(0, prompt)
79ZEND_END_ARG_INFO()
80
81ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_info, 0, 0, 0)
82    ZEND_ARG_INFO(0, varname)
83    ZEND_ARG_INFO(0, newvalue)
84ZEND_END_ARG_INFO()
85
86ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_add_history, 0, 0, 1)
87    ZEND_ARG_INFO(0, prompt)
88ZEND_END_ARG_INFO()
89
90ZEND_BEGIN_ARG_INFO(arginfo_readline_clear_history, 0)
91ZEND_END_ARG_INFO()
92
93#ifndef HAVE_LIBEDIT
94ZEND_BEGIN_ARG_INFO(arginfo_readline_list_history, 0)
95ZEND_END_ARG_INFO()
96#endif
97
98ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_read_history, 0, 0, 0)
99    ZEND_ARG_INFO(0, filename)
100ZEND_END_ARG_INFO()
101
102ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_write_history, 0, 0, 0)
103    ZEND_ARG_INFO(0, filename)
104ZEND_END_ARG_INFO()
105
106ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_completion_function, 0, 0, 1)
107    ZEND_ARG_INFO(0, funcname)
108ZEND_END_ARG_INFO()
109
110#if HAVE_RL_CALLBACK_READ_CHAR
111ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_callback_handler_install, 0, 0, 2)
112    ZEND_ARG_INFO(0, prompt)
113    ZEND_ARG_INFO(0, callback)
114ZEND_END_ARG_INFO()
115
116ZEND_BEGIN_ARG_INFO(arginfo_readline_callback_read_char, 0)
117ZEND_END_ARG_INFO()
118
119ZEND_BEGIN_ARG_INFO(arginfo_readline_callback_handler_remove, 0)
120ZEND_END_ARG_INFO()
121
122ZEND_BEGIN_ARG_INFO(arginfo_readline_redisplay, 0)
123ZEND_END_ARG_INFO()
124
125ZEND_BEGIN_ARG_INFO(arginfo_readline_on_new_line, 0)
126ZEND_END_ARG_INFO()
127#endif
128/* }}} */
129
130/* {{{ module stuff */
131static 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
154zend_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
168ZEND_GET_MODULE(readline)
169#endif
170
171PHP_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
184PHP_MSHUTDOWN_FUNCTION(readline)
185{
186    return PHP_MSHUTDOWN(cli_readline)(SHUTDOWN_FUNC_ARGS_PASSTHRU);
187}
188
189PHP_RSHUTDOWN_FUNCTION(readline)
190{
191    zval_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
204PHP_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 */
213PHP_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. */
239PHP_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_PP(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 */
336PHP_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 */
353PHP_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
373PHP_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 */
396PHP_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 */
421PHP_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
445static 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
466static 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
475static void _readline_long_zval(zval *ret, long l)
476{
477    ZVAL_LONG(ret, l);
478}
479
480static 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(&params[0], text);
487    _readline_long_zval(&params[1], start);
488    _readline_long_zval(&params[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(&params[i]);
507    }
508    zval_dtor(&_readline_array);
509
510    return matches;
511}
512
513PHP_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", name->val);
524        zend_string_release(name);
525        RETURN_FALSE;
526    }
527    zend_string_release(name);
528
529    zval_dtor(&_readline_completion);
530    ZVAL_DUP(&_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
543static 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(&params[0], the_line);
551
552    call_user_function(CG(function_table), NULL, &_prepped_callback, &dummy, 1, params);
553
554    zval_ptr_dtor(&params[0]);
555    zval_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 */
560PHP_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", name->val);
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_dtor(&_prepped_callback);
581    }
582
583    ZVAL_DUP(&_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 */
593PHP_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 */
603PHP_FUNCTION(readline_callback_handler_remove)
604{
605    if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
606        rl_callback_handler_remove();
607        zval_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 */
617PHP_FUNCTION(readline_redisplay)
618{
619    rl_redisplay();
620}
621/* }}} */
622
623#endif
624
625#if HAVE_RL_ON_NEW_LINE
626/* {{{ proto void readline_on_new_line(void)
627   Inform readline that the cursor has moved to a new line */
628PHP_FUNCTION(readline_on_new_line)
629{
630    rl_on_new_line();
631}
632/* }}} */
633
634#endif
635
636
637#endif /* HAVE_LIBREADLINE */
638
639/*
640 * Local variables:
641 * tab-width: 4
642 * c-basic-offset: 4
643 * End:
644 */
645