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