/* +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Thies C. Arntzen | +----------------------------------------------------------------------+ */ /* {{{ includes & prototypes */ #ifdef HAVE_CONFIG_H #include #endif #include "php.h" #include "php_readline.h" #include "readline_cli.h" #include "readline_arginfo.h" #if defined(HAVE_LIBREADLINE) || defined(HAVE_LIBEDIT) #ifndef HAVE_RL_COMPLETION_MATCHES #define rl_completion_matches completion_matches #endif #ifdef HAVE_LIBEDIT #include #else #include #include #endif #ifdef HAVE_RL_CALLBACK_READ_CHAR static zval _prepped_callback; #endif static zval _readline_completion; static zval _readline_array; PHP_MINIT_FUNCTION(readline); PHP_MSHUTDOWN_FUNCTION(readline); PHP_RSHUTDOWN_FUNCTION(readline); PHP_MINFO_FUNCTION(readline); /* }}} */ /* {{{ module stuff */ zend_module_entry readline_module_entry = { STANDARD_MODULE_HEADER, "readline", ext_functions, PHP_MINIT(readline), PHP_MSHUTDOWN(readline), NULL, PHP_RSHUTDOWN(readline), PHP_MINFO(readline), PHP_READLINE_VERSION, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_READLINE ZEND_GET_MODULE(readline) #endif PHP_MINIT_FUNCTION(readline) { #ifdef HAVE_LIBREADLINE /* libedit don't need this call which set the tty in cooked mode */ using_history(); #endif ZVAL_UNDEF(&_readline_completion); #ifdef HAVE_RL_CALLBACK_READ_CHAR ZVAL_UNDEF(&_prepped_callback); #endif register_readline_symbols(module_number); return PHP_MINIT(cli_readline)(INIT_FUNC_ARGS_PASSTHRU); } PHP_MSHUTDOWN_FUNCTION(readline) { return PHP_MSHUTDOWN(cli_readline)(SHUTDOWN_FUNC_ARGS_PASSTHRU); } PHP_RSHUTDOWN_FUNCTION(readline) { zval_ptr_dtor(&_readline_completion); ZVAL_UNDEF(&_readline_completion); #ifdef HAVE_RL_CALLBACK_READ_CHAR if (Z_TYPE(_prepped_callback) != IS_UNDEF) { rl_callback_handler_remove(); zval_ptr_dtor(&_prepped_callback); ZVAL_UNDEF(&_prepped_callback); } #endif return SUCCESS; } PHP_MINFO_FUNCTION(readline) { PHP_MINFO(cli_readline)(ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU); } /* }}} */ /* {{{ Reads a line */ PHP_FUNCTION(readline) { char *prompt = NULL; size_t prompt_len; char *result; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &prompt, &prompt_len)) { RETURN_THROWS(); } result = readline(prompt); if (! result) { RETURN_FALSE; } else { RETVAL_STRING(result); free(result); } } /* }}} */ #define SAFE_STRING(s) ((s)?(char*)(s):"") /* {{{ Gets/sets various internal readline variables. */ PHP_FUNCTION(readline_info) { zend_string *what = NULL; zval *value = NULL; size_t oldval; char *oldstr; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S!z!", &what, &value) == FAILURE) { RETURN_THROWS(); } if (!what) { array_init(return_value); add_assoc_string(return_value,"line_buffer",SAFE_STRING(rl_line_buffer)); add_assoc_long(return_value,"point",rl_point); #ifndef PHP_WIN32 add_assoc_long(return_value,"end",rl_end); #endif #ifdef HAVE_LIBREADLINE add_assoc_long(return_value,"mark",rl_mark); add_assoc_long(return_value,"done",rl_done); add_assoc_long(return_value,"pending_input",rl_pending_input); add_assoc_string(return_value,"prompt",SAFE_STRING(rl_prompt)); add_assoc_string(return_value,"terminal_name",(char *)SAFE_STRING(rl_terminal_name)); add_assoc_str(return_value, "completion_append_character", rl_completion_append_character == 0 ? ZSTR_EMPTY_ALLOC() : ZSTR_CHAR(rl_completion_append_character)); add_assoc_bool(return_value,"completion_suppress_append",rl_completion_suppress_append); #endif #ifdef HAVE_ERASE_EMPTY_LINE add_assoc_long(return_value,"erase_empty_line",rl_erase_empty_line); #endif #ifndef PHP_WIN32 add_assoc_string(return_value,"library_version",(char *)SAFE_STRING(rl_library_version)); #endif add_assoc_string(return_value,"readline_name",(char *)SAFE_STRING(rl_readline_name)); add_assoc_long(return_value,"attempted_completion_over",rl_attempted_completion_over); } else { if (zend_string_equals_literal_ci(what,"line_buffer")) { oldstr = strdup(rl_line_buffer ? rl_line_buffer : ""); if (value) { if (!try_convert_to_string(value)) { RETURN_THROWS(); } #if !defined(PHP_WIN32) && !defined(HAVE_LIBEDIT) if (!rl_line_buffer) { rl_line_buffer = malloc(Z_STRLEN_P(value) + 1); } else if (strlen(oldstr) < Z_STRLEN_P(value)) { rl_extend_line_buffer(Z_STRLEN_P(value) + 1); free(oldstr); oldstr = strdup(rl_line_buffer ? rl_line_buffer : ""); } memcpy(rl_line_buffer, Z_STRVAL_P(value), Z_STRLEN_P(value) + 1); #else char *tmp = strdup(Z_STRVAL_P(value)); if (tmp) { if (rl_line_buffer) { free(rl_line_buffer); } rl_line_buffer = tmp; } #endif #if !defined(PHP_WIN32) rl_end = Z_STRLEN_P(value); #endif } RETVAL_STRING(SAFE_STRING(oldstr)); free(oldstr); } else if (zend_string_equals_literal_ci(what, "point")) { RETVAL_LONG(rl_point); #ifndef PHP_WIN32 } else if (zend_string_equals_literal_ci(what, "end")) { RETVAL_LONG(rl_end); #endif #ifdef HAVE_LIBREADLINE } else if (zend_string_equals_literal_ci(what, "mark")) { RETVAL_LONG(rl_mark); } else if (zend_string_equals_literal_ci(what, "done")) { oldval = rl_done; if (value) { rl_done = zval_get_long(value); } RETVAL_LONG(oldval); } else if (zend_string_equals_literal_ci(what, "pending_input")) { oldval = rl_pending_input; if (value) { if (!try_convert_to_string(value)) { RETURN_THROWS(); } rl_pending_input = Z_STRVAL_P(value)[0]; } RETVAL_LONG(oldval); } else if (zend_string_equals_literal_ci(what, "prompt")) { RETVAL_STRING(SAFE_STRING(rl_prompt)); } else if (zend_string_equals_literal_ci(what, "terminal_name")) { RETVAL_STRING((char *)SAFE_STRING(rl_terminal_name)); } else if (zend_string_equals_literal_ci(what, "completion_suppress_append")) { oldval = rl_completion_suppress_append; if (value) { rl_completion_suppress_append = zend_is_true(value); } RETVAL_BOOL(oldval); } else if (zend_string_equals_literal_ci(what, "completion_append_character")) { oldval = rl_completion_append_character; if (value) { if (!try_convert_to_string(value)) { RETURN_THROWS(); } rl_completion_append_character = (int)Z_STRVAL_P(value)[0]; } RETVAL_INTERNED_STR( oldval == 0 ? ZSTR_EMPTY_ALLOC() : ZSTR_CHAR(oldval)); #endif #ifdef HAVE_ERASE_EMPTY_LINE } else if (zend_string_equals_literal_ci(what, "erase_empty_line")) { oldval = rl_erase_empty_line; if (value) { rl_erase_empty_line = zval_get_long(value); } RETVAL_LONG(oldval); #endif #ifndef PHP_WIN32 } else if (zend_string_equals_literal_ci(what,"library_version")) { RETVAL_STRING((char *)SAFE_STRING(rl_library_version)); #endif } else if (zend_string_equals_literal_ci(what, "readline_name")) { oldstr = (char*)rl_readline_name; if (value) { /* XXX if (rl_readline_name) free(rl_readline_name); */ if (!try_convert_to_string(value)) { RETURN_THROWS(); } rl_readline_name = strdup(Z_STRVAL_P(value)); } RETVAL_STRING(SAFE_STRING(oldstr)); } else if (zend_string_equals_literal_ci(what, "attempted_completion_over")) { oldval = rl_attempted_completion_over; if (value) { rl_attempted_completion_over = zval_get_long(value); } RETVAL_LONG(oldval); } } } /* }}} */ /* {{{ Adds a line to the history */ PHP_FUNCTION(readline_add_history) { char *arg; size_t arg_len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) { RETURN_THROWS(); } add_history(arg); RETURN_TRUE; } /* }}} */ /* {{{ Clears the history */ PHP_FUNCTION(readline_clear_history) { if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } #ifdef HAVE_LIBEDIT /* clear_history is the only function where rl_initialize is not call to ensure correct allocation */ using_history(); #endif clear_history(); RETURN_TRUE; } /* }}} */ #ifdef HAVE_HISTORY_LIST /* {{{ Lists the history */ PHP_FUNCTION(readline_list_history) { HIST_ENTRY **history; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } array_init(return_value); #if defined(HAVE_LIBEDIT) && defined(PHP_WIN32) /* Winedit on Windows */ history = history_list(); if (history) { int i, n = history_length(); for (i = 0; i < n; i++) { add_next_index_string(return_value, history[i]->line); } } #elif defined(HAVE_LIBEDIT) /* libedit */ { HISTORY_STATE *hs; int i; using_history(); hs = history_get_history_state(); if (hs && hs->length) { history = history_list(); if (history) { for (i = 0; i < hs->length; i++) { add_next_index_string(return_value, history[i]->line); } } } free(hs); } #else /* readline */ history = history_list(); if (history) { int i; for (i = 0; history[i]; i++) { add_next_index_string(return_value, history[i]->line); } } #endif } /* }}} */ #endif /* {{{ Reads the history */ PHP_FUNCTION(readline_read_history) { char *arg = NULL; size_t arg_len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p!", &arg, &arg_len) == FAILURE) { RETURN_THROWS(); } if (arg && php_check_open_basedir(arg)) { RETURN_FALSE; } /* XXX from & to NYI */ if (read_history(arg)) { /* If filename is NULL, then read from `~/.history' */ RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ Writes the history */ PHP_FUNCTION(readline_write_history) { char *arg = NULL; size_t arg_len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|p!", &arg, &arg_len) == FAILURE) { RETURN_THROWS(); } if (arg && php_check_open_basedir(arg)) { RETURN_FALSE; } if (write_history(arg)) { RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ Readline completion function? */ static char *_readline_command_generator(const char *text, int state) { HashTable *myht = Z_ARRVAL(_readline_array); zval *entry; if (!state) { zend_hash_internal_pointer_reset(myht); } while ((entry = zend_hash_get_current_data(myht)) != NULL) { zend_hash_move_forward(myht); convert_to_string(entry); if (strncmp (Z_STRVAL_P(entry), text, strlen(text)) == 0) { return (strdup(Z_STRVAL_P(entry))); } } return NULL; } static void _readline_string_zval(zval *ret, const char *str) { if (str) { ZVAL_STRING(ret, (char*)str); } else { ZVAL_NULL(ret); } } char **php_readline_completion_cb(const char *text, int start, int end) { zval params[3]; char **matches = NULL; _readline_string_zval(¶ms[0], text); ZVAL_LONG(¶ms[1], start); ZVAL_LONG(¶ms[2], end); if (call_user_function(NULL, NULL, &_readline_completion, &_readline_array, 3, params) == SUCCESS) { if (Z_TYPE(_readline_array) == IS_ARRAY) { SEPARATE_ARRAY(&_readline_array); if (zend_hash_num_elements(Z_ARRVAL(_readline_array))) { matches = rl_completion_matches(text,_readline_command_generator); } else { /* libedit will read matches[2] */ matches = calloc(3, sizeof(char *)); if (!matches) { return NULL; } matches[0] = strdup(""); } } } zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&_readline_array); return matches; } PHP_FUNCTION(readline_completion_function) { zend_fcall_info fci; zend_fcall_info_cache fcc; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "f", &fci, &fcc)) { RETURN_THROWS(); } zval_ptr_dtor(&_readline_completion); ZVAL_COPY(&_readline_completion, &fci.function_name); /* NOTE: The rl_attempted_completion_function variable (and others) are part of the readline library, not php */ rl_attempted_completion_function = php_readline_completion_cb; if (rl_attempted_completion_function == NULL) { RETURN_FALSE; } RETURN_TRUE; } /* }}} */ #ifdef HAVE_RL_CALLBACK_READ_CHAR static void php_rl_callback_handler(char *the_line) { zval params[1]; zval dummy; ZVAL_NULL(&dummy); _readline_string_zval(¶ms[0], the_line); call_user_function(NULL, NULL, &_prepped_callback, &dummy, 1, params); zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&dummy); } /* {{{ Initializes the readline callback interface and terminal, prints the prompt and returns immediately */ PHP_FUNCTION(readline_callback_handler_install) { char *prompt; zend_fcall_info fci; zend_fcall_info_cache fcc; size_t prompt_len; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "sf", &prompt, &prompt_len, &fci, &fcc)) { RETURN_THROWS(); } if (Z_TYPE(_prepped_callback) != IS_UNDEF) { rl_callback_handler_remove(); zval_ptr_dtor(&_prepped_callback); } ZVAL_COPY(&_prepped_callback, &fci.function_name); rl_callback_handler_install(prompt, php_rl_callback_handler); RETURN_TRUE; } /* }}} */ /* {{{ Informs the readline callback interface that a character is ready for input */ PHP_FUNCTION(readline_callback_read_char) { if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } if (Z_TYPE(_prepped_callback) != IS_UNDEF) { rl_callback_read_char(); } } /* }}} */ /* {{{ Removes a previously installed callback handler and restores terminal settings */ PHP_FUNCTION(readline_callback_handler_remove) { if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } if (Z_TYPE(_prepped_callback) != IS_UNDEF) { rl_callback_handler_remove(); zval_ptr_dtor(&_prepped_callback); ZVAL_UNDEF(&_prepped_callback); RETURN_TRUE; } RETURN_FALSE; } /* }}} */ /* {{{ Ask readline to redraw the display */ PHP_FUNCTION(readline_redisplay) { if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } #ifdef HAVE_LIBEDIT /* seems libedit doesn't take care of rl_initialize in rl_redisplay * see bug #72538 */ using_history(); #endif rl_redisplay(); } /* }}} */ #endif #ifdef HAVE_RL_ON_NEW_LINE /* {{{ Inform readline that the cursor has moved to a new line */ PHP_FUNCTION(readline_on_new_line) { if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } rl_on_new_line(); } /* }}} */ #endif #endif /* HAVE_LIBREADLINE */