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