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