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;
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_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() TSRMLS_CC, "|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() TSRMLS_CC, "|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() TSRMLS_CC, "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() TSRMLS_CC, "|p", &arg, &arg_len) == FAILURE) {
402        return;
403    }
404
405    if (php_check_open_basedir(arg TSRMLS_CC)) {
406        RETURN_FALSE;
407    }
408
409    /* XXX from & to NYI */
410    if (read_history(arg)) {
411        RETURN_FALSE;
412    } else {
413        RETURN_TRUE;
414    }
415}
416
417/* }}} */
418/* {{{ proto bool readline_write_history([string filename])
419   Writes the history */
420PHP_FUNCTION(readline_write_history)
421{
422    char *arg = NULL;
423    size_t arg_len;
424
425    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|p", &arg, &arg_len) == FAILURE) {
426        return;
427    }
428
429    if (php_check_open_basedir(arg TSRMLS_CC)) {
430        RETURN_FALSE;
431    }
432
433    if (write_history(arg)) {
434        RETURN_FALSE;
435    } else {
436        RETURN_TRUE;
437    }
438}
439
440/* }}} */
441/* {{{ proto bool readline_completion_function(string funcname)
442   Readline completion function? */
443
444static char *_readline_command_generator(const char *text, int state)
445{
446    HashTable  *myht = Z_ARRVAL(_readline_array);
447    zval *entry;
448
449    if (!state) {
450        zend_hash_internal_pointer_reset(myht);
451    }
452
453    while ((entry = zend_hash_get_current_data(myht)) != NULL) {
454        zend_hash_move_forward(myht);
455
456        convert_to_string_ex(entry);
457        if (strncmp (Z_STRVAL_P(entry), text, strlen(text)) == 0) {
458            return (strdup(Z_STRVAL_P(entry)));
459        }
460    }
461
462    return NULL;
463}
464
465static void _readline_string_zval(zval *ret, const char *str)
466{
467    if (str) {
468        ZVAL_STRING(ret, (char*)str);
469    } else {
470        ZVAL_NULL(ret);
471    }
472}
473
474static void _readline_long_zval(zval *ret, long l)
475{
476    ZVAL_LONG(ret, l);
477}
478
479static char **_readline_completion_cb(const char *text, int start, int end)
480{
481    zval params[3];
482    int i;
483    char **matches = NULL;
484    TSRMLS_FETCH();
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 TSRMLS_CC) == 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() TSRMLS_CC, "z", &arg)) {
519        RETURN_FALSE;
520    }
521
522    if (!zend_is_callable(arg, 0, &name TSRMLS_CC)) {
523        php_error_docref(NULL TSRMLS_CC, 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    TSRMLS_FETCH();
548
549    ZVAL_NULL(&dummy);
550
551    _readline_string_zval(&params[0], the_line);
552
553    call_user_function(CG(function_table), NULL, &_prepped_callback, &dummy, 1, params TSRMLS_CC);
554
555    zval_ptr_dtor(&params[0]);
556    zval_dtor(&dummy);
557}
558
559/* {{{ proto void readline_callback_handler_install(string prompt, mixed callback)
560   Initializes the readline callback interface and terminal, prints the prompt and returns immediately */
561PHP_FUNCTION(readline_callback_handler_install)
562{
563    zval *callback;
564    zend_string *name = NULL;
565    char *prompt;
566    size_t prompt_len;
567
568    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &prompt, &prompt_len, &callback)) {
569        return;
570    }
571
572    if (!zend_is_callable(callback, 0, &name TSRMLS_CC)) {
573        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not callable", name->val);
574        zend_string_release(name);
575        RETURN_FALSE;
576    }
577    zend_string_release(name);
578
579    if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
580        rl_callback_handler_remove();
581        zval_dtor(&_prepped_callback);
582    }
583
584    ZVAL_DUP(&_prepped_callback, callback);
585
586    rl_callback_handler_install(prompt, php_rl_callback_handler);
587
588    RETURN_TRUE;
589}
590/* }}} */
591
592/* {{{ proto void readline_callback_read_char()
593   Informs the readline callback interface that a character is ready for input */
594PHP_FUNCTION(readline_callback_read_char)
595{
596    if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
597        rl_callback_read_char();
598    }
599}
600/* }}} */
601
602/* {{{ proto bool readline_callback_handler_remove()
603   Removes a previously installed callback handler and restores terminal settings */
604PHP_FUNCTION(readline_callback_handler_remove)
605{
606    if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
607        rl_callback_handler_remove();
608        zval_dtor(&_prepped_callback);
609        ZVAL_UNDEF(&_prepped_callback);
610        RETURN_TRUE;
611    }
612    RETURN_FALSE;
613}
614/* }}} */
615
616/* {{{ proto void readline_redisplay(void)
617   Ask readline to redraw the display */
618PHP_FUNCTION(readline_redisplay)
619{
620    rl_redisplay();
621}
622/* }}} */
623
624#endif
625
626#if HAVE_RL_ON_NEW_LINE
627/* {{{ proto void readline_on_new_line(void)
628   Inform readline that the cursor has moved to a new line */
629PHP_FUNCTION(readline_on_new_line)
630{
631    rl_on_new_line();
632}
633/* }}} */
634
635#endif
636
637
638#endif /* HAVE_LIBREADLINE */
639
640/*
641 * Local variables:
642 * tab-width: 4
643 * c-basic-offset: 4
644 * End:
645 */
646