1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 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 = NULL;
63
64#endif
65
66static zval *_readline_completion = NULL;
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_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        return PHP_MINIT(cli_readline)(INIT_FUNC_ARGS_PASSTHRU);
178}
179
180PHP_MSHUTDOWN_FUNCTION(readline)
181{
182    return PHP_MSHUTDOWN(cli_readline)(SHUTDOWN_FUNC_ARGS_PASSTHRU);
183}
184
185PHP_RSHUTDOWN_FUNCTION(readline)
186{
187    if (_readline_completion) {
188        zval_dtor(_readline_completion);
189        FREE_ZVAL(_readline_completion);
190    }
191#if HAVE_RL_CALLBACK_READ_CHAR
192    if (_prepped_callback) {
193        rl_callback_handler_remove();
194        zval_ptr_dtor(&_prepped_callback);
195        _prepped_callback = 0;
196    }
197#endif
198
199    return SUCCESS;
200}
201
202PHP_MINFO_FUNCTION(readline)
203{
204    PHP_MINFO(cli_readline)(ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU);
205}
206
207/* }}} */
208
209/* {{{ proto string readline([string prompt])
210   Reads a line */
211PHP_FUNCTION(readline)
212{
213    char *prompt = NULL;
214    int prompt_len;
215    char *result;
216
217    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &prompt, &prompt_len)) {
218        RETURN_FALSE;
219    }
220
221    result = readline(prompt);
222
223    if (! result) {
224        RETURN_FALSE;
225    } else {
226        RETVAL_STRING(result,1);
227        free(result);
228    }
229}
230
231/* }}} */
232
233#define SAFE_STRING(s) ((s)?(char*)(s):"")
234
235/* {{{ proto mixed readline_info([string varname [, string newvalue]])
236   Gets/sets various internal readline variables. */
237PHP_FUNCTION(readline_info)
238{
239    char *what = NULL;
240    zval **value = NULL;
241    int what_len, oldval;
242    char *oldstr;
243
244    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sZ", &what, &what_len, &value) == FAILURE) {
245        return;
246    }
247
248    if (!what) {
249        array_init(return_value);
250        add_assoc_string(return_value,"line_buffer",SAFE_STRING(rl_line_buffer),1);
251        add_assoc_long(return_value,"point",rl_point);
252        add_assoc_long(return_value,"end",rl_end);
253#ifdef HAVE_LIBREADLINE
254        add_assoc_long(return_value,"mark",rl_mark);
255        add_assoc_long(return_value,"done",rl_done);
256        add_assoc_long(return_value,"pending_input",rl_pending_input);
257        add_assoc_string(return_value,"prompt",SAFE_STRING(rl_prompt),1);
258        add_assoc_string(return_value,"terminal_name",(char *)SAFE_STRING(rl_terminal_name),1);
259#endif
260#if HAVE_ERASE_EMPTY_LINE
261        add_assoc_long(return_value,"erase_empty_line",rl_erase_empty_line);
262#endif
263        add_assoc_string(return_value,"library_version",(char *)SAFE_STRING(rl_library_version),1);
264        add_assoc_string(return_value,"readline_name",(char *)SAFE_STRING(rl_readline_name),1);
265        add_assoc_long(return_value,"attempted_completion_over",rl_attempted_completion_over);
266    } else {
267        if (!strcasecmp(what,"line_buffer")) {
268            oldstr = rl_line_buffer;
269            if (value) {
270                /* XXX if (rl_line_buffer) free(rl_line_buffer); */
271                convert_to_string_ex(value);
272                rl_line_buffer = strdup(Z_STRVAL_PP(value));
273            }
274            RETVAL_STRING(SAFE_STRING(oldstr),1);
275        } else if (!strcasecmp(what, "point")) {
276            RETVAL_LONG(rl_point);
277        } else if (!strcasecmp(what, "end")) {
278            RETVAL_LONG(rl_end);
279#ifdef HAVE_LIBREADLINE
280        } else if (!strcasecmp(what, "mark")) {
281            RETVAL_LONG(rl_mark);
282        } else if (!strcasecmp(what, "done")) {
283            oldval = rl_done;
284            if (value) {
285                convert_to_long_ex(value);
286                rl_done = Z_LVAL_PP(value);
287            }
288            RETVAL_LONG(oldval);
289        } else if (!strcasecmp(what, "pending_input")) {
290            oldval = rl_pending_input;
291            if (value) {
292                convert_to_string_ex(value);
293                rl_pending_input = Z_STRVAL_PP(value)[0];
294            }
295            RETVAL_LONG(oldval);
296        } else if (!strcasecmp(what, "prompt")) {
297            RETVAL_STRING(SAFE_STRING(rl_prompt),1);
298        } else if (!strcasecmp(what, "terminal_name")) {
299            RETVAL_STRING((char *)SAFE_STRING(rl_terminal_name),1);
300#endif
301#if HAVE_ERASE_EMPTY_LINE
302        } else if (!strcasecmp(what, "erase_empty_line")) {
303            oldval = rl_erase_empty_line;
304            if (value) {
305                convert_to_long_ex(value);
306                rl_erase_empty_line = Z_LVAL_PP(value);
307            }
308            RETVAL_LONG(oldval);
309#endif
310        } else if (!strcasecmp(what,"library_version")) {
311            RETVAL_STRING((char *)SAFE_STRING(rl_library_version),1);
312        } else if (!strcasecmp(what, "readline_name")) {
313            oldstr = (char*)rl_readline_name;
314            if (value) {
315                /* XXX if (rl_readline_name) free(rl_readline_name); */
316                convert_to_string_ex(value);
317                rl_readline_name = strdup(Z_STRVAL_PP(value));;
318            }
319            RETVAL_STRING(SAFE_STRING(oldstr),1);
320        } else if (!strcasecmp(what, "attempted_completion_over")) {
321            oldval = rl_attempted_completion_over;
322            if (value) {
323                convert_to_long_ex(value);
324                rl_attempted_completion_over = Z_LVAL_PP(value);
325            }
326            RETVAL_LONG(oldval);
327        }
328    }
329}
330
331/* }}} */
332/* {{{ proto bool readline_add_history(string prompt)
333   Adds a line to the history */
334PHP_FUNCTION(readline_add_history)
335{
336    char *arg;
337    int arg_len;
338
339    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
340        return;
341    }
342
343    add_history(arg);
344
345    RETURN_TRUE;
346}
347
348/* }}} */
349/* {{{ proto bool readline_clear_history(void)
350   Clears the history */
351PHP_FUNCTION(readline_clear_history)
352{
353    if (zend_parse_parameters_none() == FAILURE) {
354        return;
355    }
356
357#if HAVE_LIBEDIT
358    /* clear_history is the only function where rl_initialize
359       is not call to ensure correct allocation */
360    using_history();
361#endif
362    clear_history();
363
364    RETURN_TRUE;
365}
366
367/* }}} */
368/* {{{ proto array readline_list_history(void)
369   Lists the history */
370#ifndef HAVE_LIBEDIT
371PHP_FUNCTION(readline_list_history)
372{
373    HIST_ENTRY **history;
374
375    if (zend_parse_parameters_none() == FAILURE) {
376        return;
377    }
378
379    history = history_list();
380
381    array_init(return_value);
382
383    if (history) {
384        int i;
385        for (i = 0; history[i]; i++) {
386            add_next_index_string(return_value,history[i]->line,1);
387        }
388    }
389}
390#endif
391/* }}} */
392/* {{{ proto bool readline_read_history([string filename])
393   Reads the history */
394PHP_FUNCTION(readline_read_history)
395{
396    char *arg = NULL;
397    int arg_len;
398
399    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|p", &arg, &arg_len) == FAILURE) {
400        return;
401    }
402
403    if (php_check_open_basedir(arg TSRMLS_CC)) {
404        RETURN_FALSE;
405    }
406
407    /* XXX from & to NYI */
408    if (read_history(arg)) {
409        RETURN_FALSE;
410    } else {
411        RETURN_TRUE;
412    }
413}
414
415/* }}} */
416/* {{{ proto bool readline_write_history([string filename])
417   Writes the history */
418PHP_FUNCTION(readline_write_history)
419{
420    char *arg = NULL;
421    int arg_len;
422
423    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|p", &arg, &arg_len) == FAILURE) {
424        return;
425    }
426
427    if (php_check_open_basedir(arg TSRMLS_CC)) {
428        RETURN_FALSE;
429    }
430
431    if (write_history(arg)) {
432        RETURN_FALSE;
433    } else {
434        RETURN_TRUE;
435    }
436}
437
438/* }}} */
439/* {{{ proto bool readline_completion_function(string funcname)
440   Readline completion function? */
441
442static char *_readline_command_generator(const char *text, int state)
443{
444    HashTable  *myht = Z_ARRVAL(_readline_array);
445    zval **entry;
446
447    if (!state) {
448        zend_hash_internal_pointer_reset(myht);
449    }
450
451    while (zend_hash_get_current_data(myht, (void **)&entry) == SUCCESS) {
452        zend_hash_move_forward(myht);
453
454        convert_to_string_ex(entry);
455        if (strncmp (Z_STRVAL_PP(entry), text, strlen(text)) == 0) {
456            return (strdup(Z_STRVAL_PP(entry)));
457        }
458    }
459
460    return NULL;
461}
462
463static zval *_readline_string_zval(const char *str)
464{
465    zval *ret;
466    int len;
467
468    MAKE_STD_ZVAL(ret);
469
470    if (str) {
471        len = strlen(str);
472        ZVAL_STRINGL(ret, (char*)str, len, 1);
473    } else {
474        ZVAL_NULL(ret);
475    }
476
477    return ret;
478}
479
480static zval *_readline_long_zval(long l)
481{
482    zval *ret;
483    MAKE_STD_ZVAL(ret);
484
485    Z_TYPE_P(ret) = IS_LONG;
486    Z_LVAL_P(ret) = l;
487    return ret;
488}
489
490static char **_readline_completion_cb(const char *text, int start, int end)
491{
492    zval *params[3];
493    int i;
494    char **matches = NULL;
495    TSRMLS_FETCH();
496
497    params[0]=_readline_string_zval(text);
498    params[1]=_readline_long_zval(start);
499    params[2]=_readline_long_zval(end);
500
501    if (call_user_function(CG(function_table), NULL, _readline_completion, &_readline_array, 3, params TSRMLS_CC) == SUCCESS) {
502        if (Z_TYPE(_readline_array) == IS_ARRAY) {
503            if (zend_hash_num_elements(Z_ARRVAL(_readline_array))) {
504                matches = rl_completion_matches(text,_readline_command_generator);
505            } else {
506                matches = malloc(sizeof(char *) * 2);
507                if (!matches) {
508                    return NULL;
509                }
510                matches[0] = strdup("");
511                matches[1] = '\0';
512            }
513        }
514    }
515
516    for (i = 0; i < 3; i++) {
517        zval_ptr_dtor(&params[i]);
518    }
519    zval_dtor(&_readline_array);
520
521    return matches;
522}
523
524PHP_FUNCTION(readline_completion_function)
525{
526    zval *arg = NULL;
527    char *name = NULL;
528
529    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg)) {
530        RETURN_FALSE;
531    }
532
533    if (!zend_is_callable(arg, 0, &name TSRMLS_CC)) {
534        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not callable", name);
535        efree(name);
536        RETURN_FALSE;
537    }
538    efree(name);
539
540    if (_readline_completion) {
541        zval_dtor(_readline_completion);
542        FREE_ZVAL(_readline_completion);
543    }
544
545    MAKE_STD_ZVAL(_readline_completion);
546    *_readline_completion = *arg;
547    zval_copy_ctor(_readline_completion);
548
549    rl_attempted_completion_function = _readline_completion_cb;
550    if (rl_attempted_completion_function == NULL) {
551        efree(name);
552        RETURN_FALSE;
553    }
554    RETURN_TRUE;
555}
556
557/* }}} */
558
559#if HAVE_RL_CALLBACK_READ_CHAR
560
561static void php_rl_callback_handler(char *the_line)
562{
563    zval *params[1];
564    zval dummy;
565    TSRMLS_FETCH();
566
567    ZVAL_NULL(&dummy);
568
569    params[0] = _readline_string_zval(the_line);
570
571    call_user_function(CG(function_table), NULL, _prepped_callback, &dummy, 1, params TSRMLS_CC);
572
573    zval_ptr_dtor(&params[0]);
574    zval_dtor(&dummy);
575}
576
577/* {{{ proto void readline_callback_handler_install(string prompt, mixed callback)
578   Initializes the readline callback interface and terminal, prints the prompt and returns immediately */
579PHP_FUNCTION(readline_callback_handler_install)
580{
581    zval *callback;
582    char *name = NULL;
583    char *prompt;
584    int prompt_len;
585
586    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &prompt, &prompt_len, &callback)) {
587        return;
588    }
589
590    if (!zend_is_callable(callback, 0, &name TSRMLS_CC)) {
591        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not callable", name);
592        efree(name);
593        RETURN_FALSE;
594    }
595    efree(name);
596
597    if (_prepped_callback) {
598        rl_callback_handler_remove();
599        zval_dtor(_prepped_callback);
600        FREE_ZVAL(_prepped_callback);
601    }
602
603    ALLOC_ZVAL(_prepped_callback);
604    MAKE_COPY_ZVAL(&callback, _prepped_callback);
605
606    rl_callback_handler_install(prompt, php_rl_callback_handler);
607
608    RETURN_TRUE;
609}
610/* }}} */
611
612/* {{{ proto void readline_callback_read_char()
613   Informs the readline callback interface that a character is ready for input */
614PHP_FUNCTION(readline_callback_read_char)
615{
616    if (_prepped_callback) {
617        rl_callback_read_char();
618    }
619}
620/* }}} */
621
622/* {{{ proto bool readline_callback_handler_remove()
623   Removes a previously installed callback handler and restores terminal settings */
624PHP_FUNCTION(readline_callback_handler_remove)
625{
626    if (_prepped_callback) {
627        rl_callback_handler_remove();
628        zval_dtor(_prepped_callback);
629        FREE_ZVAL(_prepped_callback);
630        _prepped_callback = 0;
631        RETURN_TRUE;
632    }
633    RETURN_FALSE;
634}
635/* }}} */
636
637/* {{{ proto void readline_redisplay(void)
638   Ask readline to redraw the display */
639PHP_FUNCTION(readline_redisplay)
640{
641    rl_redisplay();
642}
643/* }}} */
644
645#endif
646
647#if HAVE_RL_ON_NEW_LINE
648/* {{{ proto void readline_on_new_line(void)
649   Inform readline that the cursor has moved to a new line */
650PHP_FUNCTION(readline_on_new_line)
651{
652    rl_on_new_line();
653}
654/* }}} */
655
656#endif
657
658
659#endif /* HAVE_LIBREADLINE */
660
661/*
662 * Local variables:
663 * tab-width: 4
664 * c-basic-offset: 4
665 * End:
666 */
667