xref: /PHP-8.1/ext/readline/readline.c (revision aff36587)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Thies C. Arntzen <thies@thieso.net>                          |
14    +----------------------------------------------------------------------+
15 */
16 
17 /* {{{ includes & prototypes */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #include "php_readline.h"
25 #include "readline_cli.h"
26 #include "readline_arginfo.h"
27 
28 #if HAVE_LIBREADLINE || HAVE_LIBEDIT
29 
30 #ifndef HAVE_RL_COMPLETION_MATCHES
31 #define rl_completion_matches completion_matches
32 #endif
33 
34 #ifdef HAVE_LIBEDIT
35 #include <editline/readline.h>
36 #else
37 #include <readline/readline.h>
38 #include <readline/history.h>
39 #endif
40 
41 #if HAVE_RL_CALLBACK_READ_CHAR
42 
43 static zval _prepped_callback;
44 
45 #endif
46 
47 static zval _readline_completion;
48 static zval _readline_array;
49 
50 PHP_MINIT_FUNCTION(readline);
51 PHP_MSHUTDOWN_FUNCTION(readline);
52 PHP_RSHUTDOWN_FUNCTION(readline);
53 PHP_MINFO_FUNCTION(readline);
54 
55 /* }}} */
56 
57 /* {{{ module stuff */
58 zend_module_entry readline_module_entry = {
59 	STANDARD_MODULE_HEADER,
60 	"readline",
61 	ext_functions,
62 	PHP_MINIT(readline),
63 	PHP_MSHUTDOWN(readline),
64 	NULL,
65 	PHP_RSHUTDOWN(readline),
66 	PHP_MINFO(readline),
67 	PHP_READLINE_VERSION,
68 	STANDARD_MODULE_PROPERTIES
69 };
70 
71 #ifdef COMPILE_DL_READLINE
72 ZEND_GET_MODULE(readline)
73 #endif
74 
PHP_MINIT_FUNCTION(readline)75 PHP_MINIT_FUNCTION(readline)
76 {
77 #if HAVE_LIBREADLINE
78 		/* libedit don't need this call which set the tty in cooked mode */
79 	using_history();
80 #endif
81 	ZVAL_UNDEF(&_readline_completion);
82 #if HAVE_RL_CALLBACK_READ_CHAR
83 	ZVAL_UNDEF(&_prepped_callback);
84 #endif
85 	return PHP_MINIT(cli_readline)(INIT_FUNC_ARGS_PASSTHRU);
86 }
87 
PHP_MSHUTDOWN_FUNCTION(readline)88 PHP_MSHUTDOWN_FUNCTION(readline)
89 {
90 	return PHP_MSHUTDOWN(cli_readline)(SHUTDOWN_FUNC_ARGS_PASSTHRU);
91 }
92 
PHP_RSHUTDOWN_FUNCTION(readline)93 PHP_RSHUTDOWN_FUNCTION(readline)
94 {
95 	zval_ptr_dtor(&_readline_completion);
96 	ZVAL_UNDEF(&_readline_completion);
97 #if HAVE_RL_CALLBACK_READ_CHAR
98 	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
99 		rl_callback_handler_remove();
100 		zval_ptr_dtor(&_prepped_callback);
101 		ZVAL_UNDEF(&_prepped_callback);
102 	}
103 #endif
104 
105 	return SUCCESS;
106 }
107 
PHP_MINFO_FUNCTION(readline)108 PHP_MINFO_FUNCTION(readline)
109 {
110 	PHP_MINFO(cli_readline)(ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU);
111 }
112 
113 /* }}} */
114 
115 /* {{{ Reads a line */
PHP_FUNCTION(readline)116 PHP_FUNCTION(readline)
117 {
118 	char *prompt = NULL;
119 	size_t prompt_len;
120 	char *result;
121 
122 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &prompt, &prompt_len)) {
123 		RETURN_THROWS();
124 	}
125 
126 	result = readline(prompt);
127 
128 	if (! result) {
129 		RETURN_FALSE;
130 	} else {
131 		RETVAL_STRING(result);
132 		free(result);
133 	}
134 }
135 
136 /* }}} */
137 
138 #define SAFE_STRING(s) ((s)?(char*)(s):"")
139 
140 /* {{{ Gets/sets various internal readline variables. */
PHP_FUNCTION(readline_info)141 PHP_FUNCTION(readline_info)
142 {
143 	zend_string *what = NULL;
144 	zval *value = NULL;
145 	size_t oldval;
146 	char *oldstr;
147 
148 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S!z!", &what, &value) == FAILURE) {
149 		RETURN_THROWS();
150 	}
151 
152 	if (!what) {
153 		array_init(return_value);
154 		add_assoc_string(return_value,"line_buffer",SAFE_STRING(rl_line_buffer));
155 		add_assoc_long(return_value,"point",rl_point);
156 #ifndef PHP_WIN32
157 		add_assoc_long(return_value,"end",rl_end);
158 #endif
159 #ifdef HAVE_LIBREADLINE
160 		add_assoc_long(return_value,"mark",rl_mark);
161 		add_assoc_long(return_value,"done",rl_done);
162 		add_assoc_long(return_value,"pending_input",rl_pending_input);
163 		add_assoc_string(return_value,"prompt",SAFE_STRING(rl_prompt));
164 		add_assoc_string(return_value,"terminal_name",(char *)SAFE_STRING(rl_terminal_name));
165 		add_assoc_str(return_value, "completion_append_character",
166 			rl_completion_append_character == 0
167 				? ZSTR_EMPTY_ALLOC()
168 				: ZSTR_CHAR(rl_completion_append_character));
169 		add_assoc_bool(return_value,"completion_suppress_append",rl_completion_suppress_append);
170 #endif
171 #if HAVE_ERASE_EMPTY_LINE
172 		add_assoc_long(return_value,"erase_empty_line",rl_erase_empty_line);
173 #endif
174 #ifndef PHP_WIN32
175 		add_assoc_string(return_value,"library_version",(char *)SAFE_STRING(rl_library_version));
176 #endif
177 		add_assoc_string(return_value,"readline_name",(char *)SAFE_STRING(rl_readline_name));
178 		add_assoc_long(return_value,"attempted_completion_over",rl_attempted_completion_over);
179 	} else {
180 		if (zend_string_equals_literal_ci(what,"line_buffer")) {
181 			oldstr = rl_line_buffer;
182 			if (value) {
183 				/* XXX if (rl_line_buffer) free(rl_line_buffer); */
184 				if (!try_convert_to_string(value)) {
185 					RETURN_THROWS();
186 				}
187 				rl_line_buffer = strdup(Z_STRVAL_P(value));
188 			}
189 			RETVAL_STRING(SAFE_STRING(oldstr));
190 		} else if (zend_string_equals_literal_ci(what, "point")) {
191 			RETVAL_LONG(rl_point);
192 #ifndef PHP_WIN32
193 		} else if (zend_string_equals_literal_ci(what, "end")) {
194 			RETVAL_LONG(rl_end);
195 #endif
196 #ifdef HAVE_LIBREADLINE
197 		} else if (zend_string_equals_literal_ci(what, "mark")) {
198 			RETVAL_LONG(rl_mark);
199 		} else if (zend_string_equals_literal_ci(what, "done")) {
200 			oldval = rl_done;
201 			if (value) {
202 				rl_done = zval_get_long(value);
203 			}
204 			RETVAL_LONG(oldval);
205 		} else if (zend_string_equals_literal_ci(what, "pending_input")) {
206 			oldval = rl_pending_input;
207 			if (value) {
208 				if (!try_convert_to_string(value)) {
209 					RETURN_THROWS();
210 				}
211 				rl_pending_input = Z_STRVAL_P(value)[0];
212 			}
213 			RETVAL_LONG(oldval);
214 		} else if (zend_string_equals_literal_ci(what, "prompt")) {
215 			RETVAL_STRING(SAFE_STRING(rl_prompt));
216 		} else if (zend_string_equals_literal_ci(what, "terminal_name")) {
217 			RETVAL_STRING((char *)SAFE_STRING(rl_terminal_name));
218 		} else if (zend_string_equals_literal_ci(what, "completion_suppress_append")) {
219 			oldval = rl_completion_suppress_append;
220 			if (value) {
221 				rl_completion_suppress_append = zend_is_true(value);
222 			}
223 			RETVAL_BOOL(oldval);
224 		} else if (zend_string_equals_literal_ci(what, "completion_append_character")) {
225 			oldval = rl_completion_append_character;
226 			if (value) {
227 				if (!try_convert_to_string(value)) {
228 					RETURN_THROWS();
229 				}
230 				rl_completion_append_character = (int)Z_STRVAL_P(value)[0];
231 			}
232 			RETVAL_INTERNED_STR(
233 				oldval == 0 ? ZSTR_EMPTY_ALLOC() : ZSTR_CHAR(oldval));
234 #endif
235 #if HAVE_ERASE_EMPTY_LINE
236 		} else if (zend_string_equals_literal_ci(what, "erase_empty_line")) {
237 			oldval = rl_erase_empty_line;
238 			if (value) {
239 				rl_erase_empty_line = zval_get_long(value);
240 			}
241 			RETVAL_LONG(oldval);
242 #endif
243 #ifndef PHP_WIN32
244 		} else if (zend_string_equals_literal_ci(what,"library_version")) {
245 			RETVAL_STRING((char *)SAFE_STRING(rl_library_version));
246 #endif
247 		} else if (zend_string_equals_literal_ci(what, "readline_name")) {
248 			oldstr = (char*)rl_readline_name;
249 			if (value) {
250 				/* XXX if (rl_readline_name) free(rl_readline_name); */
251 				if (!try_convert_to_string(value)) {
252 					RETURN_THROWS();
253 				}
254 				rl_readline_name = strdup(Z_STRVAL_P(value));
255 			}
256 			RETVAL_STRING(SAFE_STRING(oldstr));
257 		} else if (zend_string_equals_literal_ci(what, "attempted_completion_over")) {
258 			oldval = rl_attempted_completion_over;
259 			if (value) {
260 				rl_attempted_completion_over = zval_get_long(value);
261 			}
262 			RETVAL_LONG(oldval);
263 		}
264 	}
265 }
266 
267 /* }}} */
268 /* {{{ Adds a line to the history */
PHP_FUNCTION(readline_add_history)269 PHP_FUNCTION(readline_add_history)
270 {
271 	char *arg;
272 	size_t arg_len;
273 
274 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
275 		RETURN_THROWS();
276 	}
277 
278 	add_history(arg);
279 
280 	RETURN_TRUE;
281 }
282 
283 /* }}} */
284 /* {{{ Clears the history */
PHP_FUNCTION(readline_clear_history)285 PHP_FUNCTION(readline_clear_history)
286 {
287 	if (zend_parse_parameters_none() == FAILURE) {
288 		RETURN_THROWS();
289 	}
290 
291 #if HAVE_LIBEDIT
292 	/* clear_history is the only function where rl_initialize
293 	   is not call to ensure correct allocation */
294 	using_history();
295 #endif
296 	clear_history();
297 
298 	RETURN_TRUE;
299 }
300 
301 /* }}} */
302 
303 #ifdef HAVE_HISTORY_LIST
304 /* {{{ Lists the history */
PHP_FUNCTION(readline_list_history)305 PHP_FUNCTION(readline_list_history)
306 {
307 	HIST_ENTRY **history;
308 
309 	if (zend_parse_parameters_none() == FAILURE) {
310 		RETURN_THROWS();
311 	}
312 
313 	array_init(return_value);
314 
315 #if defined(HAVE_LIBEDIT) && defined(PHP_WIN32) /* Winedit on Windows */
316 	history = history_list();
317 
318 	if (history) {
319 		int i, n = history_length();
320 		for (i = 0; i < n; i++) {
321 				add_next_index_string(return_value, history[i]->line);
322 		}
323 	}
324 
325 #elif defined(HAVE_LIBEDIT) /* libedit */
326 	{
327 		HISTORY_STATE *hs;
328 		int i;
329 
330 		using_history();
331 		hs = history_get_history_state();
332 		if (hs && hs->length) {
333 			history = history_list();
334 			if (history) {
335 				for (i = 0; i < hs->length; i++) {
336 					add_next_index_string(return_value, history[i]->line);
337 				}
338 			}
339 		}
340 		free(hs);
341 	}
342 
343 #else /* readline */
344 	history = history_list();
345 
346 	if (history) {
347 		int i;
348 		for (i = 0; history[i]; i++) {
349 			add_next_index_string(return_value, history[i]->line);
350 		}
351 	}
352 #endif
353 }
354 /* }}} */
355 #endif
356 
357 /* {{{ Reads the history */
PHP_FUNCTION(readline_read_history)358 PHP_FUNCTION(readline_read_history)
359 {
360 	char *arg = NULL;
361 	size_t arg_len;
362 
363 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p!", &arg, &arg_len) == FAILURE) {
364 		RETURN_THROWS();
365 	}
366 
367 	if (arg && php_check_open_basedir(arg)) {
368 		RETURN_FALSE;
369 	}
370 
371 	/* XXX from & to NYI */
372 	if (read_history(arg)) {
373 		/* If filename is NULL, then read from `~/.history' */
374 		RETURN_FALSE;
375 	} else {
376 		RETURN_TRUE;
377 	}
378 }
379 
380 /* }}} */
381 /* {{{ Writes the history */
PHP_FUNCTION(readline_write_history)382 PHP_FUNCTION(readline_write_history)
383 {
384 	char *arg = NULL;
385 	size_t arg_len;
386 
387 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p!", &arg, &arg_len) == FAILURE) {
388 		RETURN_THROWS();
389 	}
390 
391 	if (arg && php_check_open_basedir(arg)) {
392 		RETURN_FALSE;
393 	}
394 
395 	if (write_history(arg)) {
396 		RETURN_FALSE;
397 	} else {
398 		RETURN_TRUE;
399 	}
400 }
401 
402 /* }}} */
403 /* {{{ Readline completion function? */
404 
_readline_command_generator(const char * text,int state)405 static char *_readline_command_generator(const char *text, int state)
406 {
407 	HashTable  *myht = Z_ARRVAL(_readline_array);
408 	zval *entry;
409 
410 	if (!state) {
411 		zend_hash_internal_pointer_reset(myht);
412 	}
413 
414 	while ((entry = zend_hash_get_current_data(myht)) != NULL) {
415 		zend_hash_move_forward(myht);
416 
417 		convert_to_string(entry);
418 		if (strncmp (Z_STRVAL_P(entry), text, strlen(text)) == 0) {
419 			return (strdup(Z_STRVAL_P(entry)));
420 		}
421 	}
422 
423 	return NULL;
424 }
425 
_readline_string_zval(zval * ret,const char * str)426 static void _readline_string_zval(zval *ret, const char *str)
427 {
428 	if (str) {
429 		ZVAL_STRING(ret, (char*)str);
430 	} else {
431 		ZVAL_NULL(ret);
432 	}
433 }
434 
_readline_long_zval(zval * ret,long l)435 static void _readline_long_zval(zval *ret, long l)
436 {
437 	ZVAL_LONG(ret, l);
438 }
439 
php_readline_completion_cb(const char * text,int start,int end)440 char **php_readline_completion_cb(const char *text, int start, int end)
441 {
442 	zval params[3];
443 	char **matches = NULL;
444 
445 	_readline_string_zval(&params[0], text);
446 	_readline_long_zval(&params[1], start);
447 	_readline_long_zval(&params[2], end);
448 
449 	if (call_user_function(NULL, NULL, &_readline_completion, &_readline_array, 3, params) == SUCCESS) {
450 		if (Z_TYPE(_readline_array) == IS_ARRAY) {
451 			SEPARATE_ARRAY(&_readline_array);
452 			if (zend_hash_num_elements(Z_ARRVAL(_readline_array))) {
453 				matches = rl_completion_matches(text,_readline_command_generator);
454 			} else {
455 				/* libedit will read matches[2] */
456 				matches = calloc(sizeof(char *), 3);
457 				if (!matches) {
458 					return NULL;
459 				}
460 				matches[0] = strdup("");
461 			}
462 		}
463 	}
464 
465 	zval_ptr_dtor(&params[0]);
466 	zval_ptr_dtor(&_readline_array);
467 
468 	return matches;
469 }
470 
PHP_FUNCTION(readline_completion_function)471 PHP_FUNCTION(readline_completion_function)
472 {
473 	zend_fcall_info fci;
474 	zend_fcall_info_cache fcc;
475 
476 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "f", &fci, &fcc)) {
477 		RETURN_THROWS();
478 	}
479 
480 	zval_ptr_dtor(&_readline_completion);
481 	ZVAL_COPY(&_readline_completion, &fci.function_name);
482 
483 	/* NOTE: The rl_attempted_completion_function variable (and others) are part of the readline library, not php */
484 	rl_attempted_completion_function = php_readline_completion_cb;
485 	if (rl_attempted_completion_function == NULL) {
486 		RETURN_FALSE;
487 	}
488 	RETURN_TRUE;
489 }
490 
491 /* }}} */
492 
493 #if HAVE_RL_CALLBACK_READ_CHAR
494 
php_rl_callback_handler(char * the_line)495 static void php_rl_callback_handler(char *the_line)
496 {
497 	zval params[1];
498 	zval dummy;
499 
500 	ZVAL_NULL(&dummy);
501 
502 	_readline_string_zval(&params[0], the_line);
503 
504 	call_user_function(NULL, NULL, &_prepped_callback, &dummy, 1, params);
505 
506 	zval_ptr_dtor(&params[0]);
507 	zval_ptr_dtor(&dummy);
508 }
509 
510 /* {{{ Initializes the readline callback interface and terminal, prints the prompt and returns immediately */
PHP_FUNCTION(readline_callback_handler_install)511 PHP_FUNCTION(readline_callback_handler_install)
512 {
513 	char *prompt;
514 	zend_fcall_info fci;
515 	zend_fcall_info_cache fcc;
516 	size_t prompt_len;
517 
518 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "sf", &prompt, &prompt_len, &fci, &fcc)) {
519 		RETURN_THROWS();
520 	}
521 
522 	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
523 		rl_callback_handler_remove();
524 		zval_ptr_dtor(&_prepped_callback);
525 	}
526 
527 	ZVAL_COPY(&_prepped_callback, &fci.function_name);
528 
529 	rl_callback_handler_install(prompt, php_rl_callback_handler);
530 
531 	RETURN_TRUE;
532 }
533 /* }}} */
534 
535 /* {{{ Informs the readline callback interface that a character is ready for input */
PHP_FUNCTION(readline_callback_read_char)536 PHP_FUNCTION(readline_callback_read_char)
537 {
538 	if (zend_parse_parameters_none() == FAILURE) {
539 		RETURN_THROWS();
540 	}
541 
542 	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
543 		rl_callback_read_char();
544 	}
545 }
546 /* }}} */
547 
548 /* {{{ Removes a previously installed callback handler and restores terminal settings */
PHP_FUNCTION(readline_callback_handler_remove)549 PHP_FUNCTION(readline_callback_handler_remove)
550 {
551 	if (zend_parse_parameters_none() == FAILURE) {
552 		RETURN_THROWS();
553 	}
554 
555 	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
556 		rl_callback_handler_remove();
557 		zval_ptr_dtor(&_prepped_callback);
558 		ZVAL_UNDEF(&_prepped_callback);
559 		RETURN_TRUE;
560 	}
561 	RETURN_FALSE;
562 }
563 /* }}} */
564 
565 /* {{{ Ask readline to redraw the display */
PHP_FUNCTION(readline_redisplay)566 PHP_FUNCTION(readline_redisplay)
567 {
568 	if (zend_parse_parameters_none() == FAILURE) {
569 		RETURN_THROWS();
570 	}
571 
572 #if HAVE_LIBEDIT
573 	/* seems libedit doesn't take care of rl_initialize in rl_redisplay
574 	 * see bug #72538 */
575 	using_history();
576 #endif
577 	rl_redisplay();
578 }
579 /* }}} */
580 
581 #endif
582 
583 #if HAVE_RL_ON_NEW_LINE
584 /* {{{ Inform readline that the cursor has moved to a new line */
PHP_FUNCTION(readline_on_new_line)585 PHP_FUNCTION(readline_on_new_line)
586 {
587 	if (zend_parse_parameters_none() == FAILURE) {
588 		RETURN_THROWS();
589 	}
590 
591 	rl_on_new_line();
592 }
593 /* }}} */
594 
595 #endif
596 
597 
598 #endif /* HAVE_LIBREADLINE */
599