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