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