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