text: /*
     +----------------------------------------------------------------------+
     | PHP Version 7                                                        |
     +----------------------------------------------------------------------+
     | 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:           |
     | http://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 <thies@thieso.net>                          |
     +----------------------------------------------------------------------+
  */
  
  /* {{{ includes & prototypes */
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  
  #include "php.h"
  #include "php_readline.h"
  #include "readline_cli.h"
  #include "readline_arginfo.h"
  
  #if HAVE_LIBREADLINE || HAVE_LIBEDIT
  
  #ifndef HAVE_RL_COMPLETION_MATCHES
  #define rl_completion_matches completion_matches
  #endif
  
  #ifdef HAVE_LIBEDIT
  #include <editline/readline.h>
  #else
  #include <readline/readline.h>
  #include <readline/history.h>
  #endif
  
  PHP_FUNCTION(readline);
  PHP_FUNCTION(readline_add_history);
  PHP_FUNCTION(readline_info);
  PHP_FUNCTION(readline_clear_history);
  #ifdef HAVE_HISTORY_LIST
  PHP_FUNCTION(readline_list_history);
  #endif
  PHP_FUNCTION(readline_read_history);
  PHP_FUNCTION(readline_write_history);
  PHP_FUNCTION(readline_completion_function);
  
  #if HAVE_RL_CALLBACK_READ_CHAR
  PHP_FUNCTION(readline_callback_handler_install);
  PHP_FUNCTION(readline_callback_read_char);
  PHP_FUNCTION(readline_callback_handler_remove);
  PHP_FUNCTION(readline_redisplay);
  PHP_FUNCTION(readline_on_new_line);
  
  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 */
  static const zend_function_entry php_readline_functions[] = {
  	PHP_FE(readline,	   		        arginfo_readline)
  	PHP_FE(readline_info,  	            arginfo_readline_info)
  	PHP_FE(readline_add_history, 		arginfo_readline_add_history)
  	PHP_FE(readline_clear_history, 		arginfo_readline_clear_history)
  #ifdef HAVE_HISTORY_LIST
  	PHP_FE(readline_list_history, 		arginfo_readline_list_history)
  #endif
  	PHP_FE(readline_read_history, 		arginfo_readline_read_history)
  	PHP_FE(readline_write_history, 		arginfo_readline_write_history)
  	PHP_FE(readline_completion_function,arginfo_readline_completion_function)
  #if HAVE_RL_CALLBACK_READ_CHAR
  	PHP_FE(readline_callback_handler_install, arginfo_readline_callback_handler_install)
  	PHP_FE(readline_callback_read_char,			arginfo_readline_callback_read_char)
  	PHP_FE(readline_callback_handler_remove,	arginfo_readline_callback_handler_remove)
  	PHP_FE(readline_redisplay, arginfo_readline_redisplay)
  #endif
  #if HAVE_RL_ON_NEW_LINE
  	PHP_FE(readline_on_new_line, arginfo_readline_on_new_line)
  #endif
  	PHP_FE_END
  };
  
  zend_module_entry readline_module_entry = {
  	STANDARD_MODULE_HEADER,
  	"readline",
  	php_readline_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)
  {
  #if HAVE_LIBREADLINE
  		/* libedit don't need this call which set the tty in cooked mode */
  	using_history();
  #endif
  	ZVAL_UNDEF(&_readline_completion);
  #if HAVE_RL_CALLBACK_READ_CHAR
  	ZVAL_UNDEF(&_prepped_callback);
  #endif
     	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);
  #if 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);
  }
  
  /* }}} */
  
  /* {{{ proto string readline([string prompt])
     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_FALSE;
  	}
  
  	result = readline(prompt);
  
  	if (! result) {
  		RETURN_FALSE;
  	} else {
  		RETVAL_STRING(result);
  		free(result);
  	}
  }
  
  /* }}} */
  
  #define SAFE_STRING(s) ((s)?(char*)(s):"")
  
  /* {{{ proto mixed readline_info([string varname [, string newvalue]])
     Gets/sets various internal readline variables. */
  PHP_FUNCTION(readline_info)
  {
  	char *what = NULL;
  	zval *value = NULL;
  	size_t what_len, oldval;
  	char *oldstr;
  
  	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sz", &what, &what_len, &value) == FAILURE) {
  		return;
  	}
  
  	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
  #if 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 (!strcasecmp(what,"line_buffer")) {
  			oldstr = rl_line_buffer;
  			if (value) {
  				/* XXX if (rl_line_buffer) free(rl_line_buffer); */
  				if (!try_convert_to_string(value)) {
  					return;
  				}
  				rl_line_buffer = strdup(Z_STRVAL_P(value));
  			}
  			RETVAL_STRING(SAFE_STRING(oldstr));
  		} else if (!strcasecmp(what, "point")) {
  			RETVAL_LONG(rl_point);
  #ifndef PHP_WIN32
  		} else if (!strcasecmp(what, "end")) {
  			RETVAL_LONG(rl_end);
  #endif
  #ifdef HAVE_LIBREADLINE
  		} else if (!strcasecmp(what, "mark")) {
  			RETVAL_LONG(rl_mark);
  		} else if (!strcasecmp(what, "done")) {
  			oldval = rl_done;
  			if (value) {
  				convert_to_long_ex(value);
  				rl_done = Z_LVAL_P(value);
  			}
  			RETVAL_LONG(oldval);
  		} else if (!strcasecmp(what, "pending_input")) {
  			oldval = rl_pending_input;
  			if (value) {
  				if (!try_convert_to_string(value)) {
  					return;
  				}
  				rl_pending_input = Z_STRVAL_P(value)[0];
  			}
  			RETVAL_LONG(oldval);
  		} else if (!strcasecmp(what, "prompt")) {
  			RETVAL_STRING(SAFE_STRING(rl_prompt));
  		} else if (!strcasecmp(what, "terminal_name")) {
  			RETVAL_STRING((char *)SAFE_STRING(rl_terminal_name));
  		} else if (!strcasecmp(what, "completion_suppress_append")) {
  			oldval = rl_completion_suppress_append;
  			if (value) {
  				rl_completion_suppress_append = zend_is_true(value);
  			}
  			RETVAL_BOOL(oldval);
  		} else if (!strcasecmp(what, "completion_append_character")) {
  			oldval = rl_completion_append_character;
  			if (value) {
  				if (!try_convert_to_string(value)) {
  					return;
  				}
  				rl_completion_append_character = (int)Z_STRVAL_P(value)[0];
  			}
  			RETVAL_INTERNED_STR(
  				oldval == 0 ? ZSTR_EMPTY_ALLOC() : ZSTR_CHAR(oldval));
  #endif
  #if HAVE_ERASE_EMPTY_LINE
  		} else if (!strcasecmp(what, "erase_empty_line")) {
  			oldval = rl_erase_empty_line;
  			if (value) {
  				convert_to_long_ex(value);
  				rl_erase_empty_line = Z_LVAL_P(value);
  			}
  			RETVAL_LONG(oldval);
  #endif
  #ifndef PHP_WIN32
  		} else if (!strcasecmp(what,"library_version")) {
  			RETVAL_STRING((char *)SAFE_STRING(rl_library_version));
  #endif
  		} else if (!strcasecmp(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;
  				}
  				rl_readline_name = strdup(Z_STRVAL_P(value));
  			}
  			RETVAL_STRING(SAFE_STRING(oldstr));
  		} else if (!strcasecmp(what, "attempted_completion_over")) {
  			oldval = rl_attempted_completion_over;
  			if (value) {
  				convert_to_long_ex(value);
  				rl_attempted_completion_over = Z_LVAL_P(value);
  			}
  			RETVAL_LONG(oldval);
  		}
  	}
  }
  
  /* }}} */
  /* {{{ proto bool readline_add_history(string prompt)
     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;
  	}
  
  	add_history(arg);
  
  	RETURN_TRUE;
  }
  
  /* }}} */
  /* {{{ proto bool readline_clear_history(void)
     Clears the history */
  PHP_FUNCTION(readline_clear_history)
  {
  	if (zend_parse_parameters_none() == FAILURE) {
  		return;
  	}
  
  #if 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
  /* {{{ proto array readline_list_history(void)
     Lists the history */
  PHP_FUNCTION(readline_list_history)
  {
  	HIST_ENTRY **history;
  
  	if (zend_parse_parameters_none() == FAILURE) {
  		return;
  	}
  
  	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
  
  /* {{{ proto bool readline_read_history([string filename])
     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;
  	}
  
  	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;
  	}
  }
  
  /* }}} */
  /* {{{ proto bool readline_write_history([string filename])
     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;
  	}
  
  	if (arg && php_check_open_basedir(arg)) {
  		RETURN_FALSE;
  	}
  
  	if (write_history(arg)) {
  		RETURN_FALSE;
  	} else {
  		RETURN_TRUE;
  	}
  }
  
  /* }}} */
  /* {{{ proto bool readline_completion_function(string funcname)
     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_ex(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);
  	}
  }
  
  static void _readline_long_zval(zval *ret, long l)
  {
  	ZVAL_LONG(ret, l);
  }
  
  static char **_readline_completion_cb(const char *text, int start, int end)
  {
  	zval params[3];
  	char **matches = NULL;
  
  	_readline_string_zval(&params[0], text);
  	_readline_long_zval(&params[1], start);
  	_readline_long_zval(&params[2], end);
  
  	if (call_user_function(NULL, NULL, &_readline_completion, &_readline_array, 3, params) == SUCCESS) {
  		if (Z_TYPE(_readline_array) == IS_ARRAY) {
  			if (zend_hash_num_elements(Z_ARRVAL(_readline_array))) {
  				matches = rl_completion_matches(text,_readline_command_generator);
  			} else {
  				matches = malloc(sizeof(char *) * 2);
  				if (!matches) {
  					return NULL;
  				}
  				matches[0] = strdup("");
  				matches[1] = NULL;
  			}
  		}
  	}
  
  	zval_ptr_dtor(&params[0]);
  	zval_ptr_dtor(&_readline_array);
  
  	return matches;
  }
  
  PHP_FUNCTION(readline_completion_function)
  {
  	zval *arg;
  
  	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "z", &arg)) {
  		RETURN_FALSE;
  	}
  
  	if (!zend_is_callable(arg, 0, NULL)) {
  		zend_string *name = zend_get_callable_name(arg);
  		php_error_docref(NULL, E_WARNING, "%s is not callable", ZSTR_VAL(name));
  		zend_string_release_ex(name, 0);
  		RETURN_FALSE;
  	}
  
  	zval_ptr_dtor(&_readline_completion);
  	ZVAL_COPY(&_readline_completion, arg);
  
  	rl_attempted_completion_function = _readline_completion_cb;
  	if (rl_attempted_completion_function == NULL) {
  		RETURN_FALSE;
  	}
  	RETURN_TRUE;
  }
  
  /* }}} */
  
  #if 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(&params[0], the_line);
  
  	call_user_function(NULL, NULL, &_prepped_callback, &dummy, 1, params);
  
  	zval_ptr_dtor(&params[0]);
  	zval_ptr_dtor(&dummy);
  }
  
  /* {{{ proto void readline_callback_handler_install(string prompt, mixed callback)
     Initializes the readline callback interface and terminal, prints the prompt and returns immediately */
  PHP_FUNCTION(readline_callback_handler_install)
  {
  	zval *callback;
  	char *prompt;
  	size_t prompt_len;
  
  	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &prompt, &prompt_len, &callback)) {
  		return;
  	}
  
  	if (!zend_is_callable(callback, 0, NULL)) {
  		zend_string *name = zend_get_callable_name(callback);
  		php_error_docref(NULL, E_WARNING, "%s is not callable", ZSTR_VAL(name));
  		zend_string_release_ex(name, 0);
  		RETURN_FALSE;
  	}
  
  	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
  		rl_callback_handler_remove();
  		zval_ptr_dtor(&_prepped_callback);
  	}
  
  	ZVAL_COPY(&_prepped_callback, callback);
  
  	rl_callback_handler_install(prompt, php_rl_callback_handler);
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto void readline_callback_read_char()
     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;
  	}
  
  	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
  		rl_callback_read_char();
  	}
  }
  /* }}} */
  
  /* {{{ proto bool readline_callback_handler_remove()
     Removes a previously installed callback handler and restores terminal settings */
  PHP_FUNCTION(readline_callback_handler_remove)
  {
  	if (zend_parse_parameters_none() == FAILURE) {
  		return;
  	}
  
  	if (Z_TYPE(_prepped_callback) != IS_UNDEF) {
  		rl_callback_handler_remove();
  		zval_ptr_dtor(&_prepped_callback);
  		ZVAL_UNDEF(&_prepped_callback);
  		RETURN_TRUE;
  	}
  	RETURN_FALSE;
  }
  /* }}} */
  
  /* {{{ proto void readline_redisplay(void)
     Ask readline to redraw the display */
  PHP_FUNCTION(readline_redisplay)
  {
  	if (zend_parse_parameters_none() == FAILURE) {
  		return;
  	}
  
  #if HAVE_LIBEDIT
  	/* seems libedit doesn't take care of rl_initialize in rl_redisplay
  	 * see bug #72538 */
  	using_history();
  #endif
  	rl_redisplay();
  }
  /* }}} */
  
  #endif
  
  #if HAVE_RL_ON_NEW_LINE
  /* {{{ proto void readline_on_new_line(void)
     Inform readline that the cursor has moved to a new line */
  PHP_FUNCTION(readline_on_new_line)
  {
  	if (zend_parse_parameters_none() == FAILURE) {
  		return;
  	}
  
  	rl_on_new_line();
  }
  /* }}} */
  
  #endif
  
  
  #endif /* HAVE_LIBREADLINE */
  