123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818 |
- /*
- +----------------------------------------------------------------------+
- | 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: Marcus Boerger <helly@php.net> |
- | Johannes Schlueter <johannes@php.net> |
- +----------------------------------------------------------------------+
- */
- #ifdef HAVE_CONFIG_H
- #include "config.h"
- #endif
- #include "php.h"
- #ifndef HAVE_RL_COMPLETION_MATCHES
- #define rl_completion_matches completion_matches
- #endif
- #include "php_globals.h"
- #include "php_variables.h"
- #include "zend_hash.h"
- #include "zend_modules.h"
- #include "SAPI.h"
- #include <locale.h>
- #include "zend.h"
- #include "zend_extensions.h"
- #include "php_ini.h"
- #include "php_globals.h"
- #include "php_main.h"
- #include "fopen_wrappers.h"
- #include "ext/standard/php_standard.h"
- #include "zend_smart_str.h"
- #ifdef __riscos__
- #include <unixlib/local.h>
- #endif
- #if HAVE_LIBEDIT
- #include <editline/readline.h>
- #else
- #include <readline/readline.h>
- #include <readline/history.h>
- #endif
- #include "zend_compile.h"
- #include "zend_execute.h"
- #include "zend_highlight.h"
- #include "zend_exceptions.h"
- #include "sapi/cli/cli.h"
- #include "readline_cli.h"
- #if defined(COMPILE_DL_READLINE) && !defined(PHP_WIN32)
- #include <dlfcn.h>
- #endif
- #ifndef RTLD_DEFAULT
- #define RTLD_DEFAULT NULL
- #endif
- #define DEFAULT_PROMPT "\\b \\> "
- ZEND_DECLARE_MODULE_GLOBALS(cli_readline)
- static char php_last_char = '\0';
- static FILE *pager_pipe = NULL;
- static size_t readline_shell_write(const char *str, size_t str_length) /* {{{ */
- {
- if (CLIR_G(prompt_str)) {
- smart_str_appendl(CLIR_G(prompt_str), str, str_length);
- return str_length;
- }
- if (CLIR_G(pager) && *CLIR_G(pager) && !pager_pipe) {
- pager_pipe = VCWD_POPEN(CLIR_G(pager), "w");
- }
- if (pager_pipe) {
- return fwrite(str, 1, MIN(str_length, 16384), pager_pipe);
- }
- return -1;
- }
- /* }}} */
- static size_t readline_shell_ub_write(const char *str, size_t str_length) /* {{{ */
- {
- /* We just store the last char here and then pass back to the
- caller (sapi_cli_single_write in sapi/cli) which will actually
- write due to -1 return code */
- php_last_char = str[str_length-1];
- return (size_t) -1;
- }
- /* }}} */
- static void cli_readline_init_globals(zend_cli_readline_globals *rg)
- {
- rg->pager = NULL;
- rg->prompt = NULL;
- rg->prompt_str = NULL;
- }
- PHP_INI_BEGIN()
- STD_PHP_INI_ENTRY("cli.pager", "", PHP_INI_ALL, OnUpdateString, pager, zend_cli_readline_globals, cli_readline_globals)
- STD_PHP_INI_ENTRY("cli.prompt", DEFAULT_PROMPT, PHP_INI_ALL, OnUpdateString, prompt, zend_cli_readline_globals, cli_readline_globals)
- PHP_INI_END()
- typedef enum {
- body,
- sstring,
- dstring,
- sstring_esc,
- dstring_esc,
- comment_line,
- comment_block,
- heredoc_start,
- heredoc,
- outside,
- } php_code_type;
- static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
- {
- smart_str retval = {0};
- char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
- bool unicode_warned = false;
- do {
- if (*prompt_spec == '\\') {
- switch (prompt_spec[1]) {
- case '\\':
- smart_str_appendc(&retval, '\\');
- prompt_spec++;
- break;
- case 'n':
- smart_str_appendc(&retval, '\n');
- prompt_spec++;
- break;
- case 't':
- smart_str_appendc(&retval, '\t');
- prompt_spec++;
- break;
- case 'e':
- smart_str_appendc(&retval, '\033');
- prompt_spec++;
- break;
- case 'v':
- smart_str_appends(&retval, PHP_VERSION);
- prompt_spec++;
- break;
- case 'b':
- smart_str_appends(&retval, block);
- prompt_spec++;
- break;
- case '>':
- smart_str_appendc(&retval, prompt);
- prompt_spec++;
- break;
- case '`':
- smart_str_appendc(&retval, '`');
- prompt_spec++;
- break;
- default:
- smart_str_appendc(&retval, '\\');
- break;
- }
- } else if (*prompt_spec == '`') {
- char *prompt_end = strstr(prompt_spec + 1, "`");
- char *code;
- if (prompt_end) {
- code = estrndup(prompt_spec + 1, prompt_end - prompt_spec - 1);
- CLIR_G(prompt_str) = &retval;
- zend_try {
- zend_eval_stringl(code, prompt_end - prompt_spec - 1, NULL, "php prompt code");
- } zend_end_try();
- CLIR_G(prompt_str) = NULL;
- efree(code);
- prompt_spec = prompt_end;
- }
- } else {
- if (!(*prompt_spec & 0x80)) {
- smart_str_appendc(&retval, *prompt_spec);
- } else {
- if (!unicode_warned) {
- zend_error(E_WARNING,
- "prompt contains unsupported unicode characters");
- unicode_warned = true;
- }
- smart_str_appendc(&retval, '?');
- }
- }
- } while (++prompt_spec && *prompt_spec);
- smart_str_0(&retval);
- return retval.s;
- }
- /* }}} */
- static int cli_is_valid_code(char *code, size_t len, zend_string **prompt) /* {{{ */
- {
- int valid_end = 1, last_valid_end;
- int brackets_count = 0;
- int brace_count = 0;
- size_t i;
- php_code_type code_type = body;
- char *heredoc_tag = NULL;
- size_t heredoc_len;
- for (i = 0; i < len; ++i) {
- switch(code_type) {
- default:
- switch(code[i]) {
- case '{':
- brackets_count++;
- valid_end = 0;
- break;
- case '}':
- if (brackets_count > 0) {
- brackets_count--;
- }
- valid_end = brackets_count ? 0 : 1;
- break;
- case '(':
- brace_count++;
- valid_end = 0;
- break;
- case ')':
- if (brace_count > 0) {
- brace_count--;
- }
- valid_end = 0;
- break;
- case ';':
- valid_end = brace_count == 0 && brackets_count == 0;
- break;
- case ' ':
- case '\r':
- case '\n':
- case '\t':
- break;
- case '\'':
- code_type = sstring;
- break;
- case '"':
- code_type = dstring;
- break;
- case '#':
- if (code[i+1] == '[') {
- valid_end = 0;
- break;
- }
- code_type = comment_line;
- break;
- case '/':
- if (code[i+1] == '/') {
- i++;
- code_type = comment_line;
- break;
- }
- if (code[i+1] == '*') {
- last_valid_end = valid_end;
- valid_end = 0;
- code_type = comment_block;
- i++;
- break;
- }
- valid_end = 0;
- break;
- case '?':
- if (code[i+1] == '>') {
- i++;
- code_type = outside;
- break;
- }
- valid_end = 0;
- break;
- case '<':
- valid_end = 0;
- if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
- i += 2;
- code_type = heredoc_start;
- heredoc_tag = NULL;
- heredoc_len = 0;
- }
- break;
- default:
- valid_end = 0;
- break;
- }
- break;
- case sstring:
- if (code[i] == '\\') {
- code_type = sstring_esc;
- } else {
- if (code[i] == '\'') {
- code_type = body;
- }
- }
- break;
- case sstring_esc:
- code_type = sstring;
- break;
- case dstring:
- if (code[i] == '\\') {
- code_type = dstring_esc;
- } else {
- if (code[i] == '"') {
- code_type = body;
- }
- }
- break;
- case dstring_esc:
- code_type = dstring;
- break;
- case comment_line:
- if (code[i] == '\n') {
- code_type = body;
- }
- break;
- case comment_block:
- if (code[i-1] == '*' && code[i] == '/') {
- code_type = body;
- valid_end = last_valid_end;
- }
- break;
- case heredoc_start:
- switch(code[i]) {
- case ' ':
- case '\t':
- case '\'':
- break;
- case '\r':
- case '\n':
- if (heredoc_tag) {
- code_type = heredoc;
- } else {
- /* Malformed heredoc without label */
- code_type = body;
- }
- break;
- default:
- if (!heredoc_tag) {
- heredoc_tag = code+i;
- }
- heredoc_len++;
- break;
- }
- break;
- case heredoc:
- ZEND_ASSERT(heredoc_tag);
- if (!strncmp(code + i - heredoc_len + 1, heredoc_tag, heredoc_len)) {
- unsigned char c = code[i + 1];
- char *p = code + i - heredoc_len;
- if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c >= 0x80) break;
- while (*p == ' ' || *p == '\t') p--;
- if (*p != '\n') break;
- code_type = body;
- }
- break;
- case outside:
- if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
- || (i > 3 && !strncmp(code+i-4, "<?php", 5))
- ) {
- code_type = body;
- }
- break;
- }
- }
- switch (code_type) {
- default:
- if (brace_count) {
- *prompt = cli_get_prompt("php", '(');
- } else if (brackets_count) {
- *prompt = cli_get_prompt("php", '{');
- } else {
- *prompt = cli_get_prompt("php", '>');
- }
- break;
- case sstring:
- case sstring_esc:
- *prompt = cli_get_prompt("php", '\'');
- break;
- case dstring:
- case dstring_esc:
- *prompt = cli_get_prompt("php", '"');
- break;
- case comment_block:
- *prompt = cli_get_prompt("/* ", '>');
- break;
- case heredoc:
- *prompt = cli_get_prompt("<<<", '>');
- break;
- case outside:
- *prompt = cli_get_prompt(" ", '>');
- break;
- }
- if (!valid_end || brackets_count) {
- return 0;
- } else {
- return 1;
- }
- }
- /* }}} */
- static char *cli_completion_generator_ht(const char *text, size_t textlen, int *state, HashTable *ht, void **pData) /* {{{ */
- {
- zend_string *name;
- zend_ulong number;
- if (!(*state % 2)) {
- zend_hash_internal_pointer_reset(ht);
- (*state)++;
- }
- while(zend_hash_has_more_elements(ht) == SUCCESS) {
- zend_hash_get_current_key(ht, &name, &number);
- if (!textlen || !strncmp(ZSTR_VAL(name), text, textlen)) {
- if (pData) {
- *pData = zend_hash_get_current_data_ptr(ht);
- }
- zend_hash_move_forward(ht);
- return ZSTR_VAL(name);
- }
- if (zend_hash_move_forward(ht) == FAILURE) {
- break;
- }
- }
- (*state)++;
- return NULL;
- } /* }}} */
- static char *cli_completion_generator_var(const char *text, size_t textlen, int *state) /* {{{ */
- {
- char *retval, *tmp;
- zend_array *symbol_table = &EG(symbol_table);
- tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, symbol_table, NULL);
- if (retval) {
- retval = malloc(strlen(tmp) + 2);
- retval[0] = '$';
- strcpy(&retval[1], tmp);
- rl_completion_append_character = '\0';
- }
- return retval;
- } /* }}} */
- static char *cli_completion_generator_ini(const char *text, size_t textlen, int *state) /* {{{ */
- {
- char *retval, *tmp;
- tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL);
- if (retval) {
- retval = malloc(strlen(tmp) + 2);
- retval[0] = '#';
- strcpy(&retval[1], tmp);
- rl_completion_append_character = '=';
- }
- return retval;
- } /* }}} */
- static char *cli_completion_generator_func(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
- {
- zend_function *func;
- char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func);
- if (retval) {
- rl_completion_append_character = '(';
- retval = strdup(ZSTR_VAL(func->common.function_name));
- }
- return retval;
- } /* }}} */
- static char *cli_completion_generator_class(const char *text, size_t textlen, int *state) /* {{{ */
- {
- zend_class_entry *ce;
- char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&ce);
- if (retval) {
- rl_completion_append_character = '\0';
- retval = strdup(ZSTR_VAL(ce->name));
- }
- return retval;
- } /* }}} */
- static char *cli_completion_generator_define(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
- {
- zend_class_entry **pce;
- char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce);
- if (retval) {
- rl_completion_append_character = '\0';
- retval = strdup(retval);
- }
- return retval;
- } /* }}} */
- static int cli_completion_state;
- static char *cli_completion_generator(const char *text, int index) /* {{{ */
- {
- /*
- TODO:
- - constants
- - maybe array keys
- - language constructs and other things outside a hashtable (echo, try, function, class, ...)
- - object/class members
- - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
- */
- char *retval = NULL;
- size_t textlen = strlen(text);
- if (!index) {
- cli_completion_state = 0;
- }
- if (text[0] == '$') {
- retval = cli_completion_generator_var(text, textlen, &cli_completion_state);
- } else if (text[0] == '#' && text[1] != '[') {
- retval = cli_completion_generator_ini(text, textlen, &cli_completion_state);
- } else {
- char *lc_text, *class_name_end;
- zend_string *class_name = NULL;
- zend_class_entry *ce = NULL;
- class_name_end = strstr(text, "::");
- if (class_name_end) {
- size_t class_name_len = class_name_end - text;
- class_name = zend_string_alloc(class_name_len, 0);
- zend_str_tolower_copy(ZSTR_VAL(class_name), text, class_name_len);
- if ((ce = zend_lookup_class(class_name)) == NULL) {
- zend_string_release_ex(class_name, 0);
- return NULL;
- }
- lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
- textlen -= (class_name_len + 2);
- } else {
- lc_text = zend_str_tolower_dup(text, textlen);
- }
- switch (cli_completion_state) {
- case 0:
- case 1:
- retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, ce ? &ce->function_table : EG(function_table));
- if (retval) {
- break;
- }
- ZEND_FALLTHROUGH;
- case 2:
- case 3:
- retval = cli_completion_generator_define(text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
- if (retval || ce) {
- break;
- }
- ZEND_FALLTHROUGH;
- case 4:
- case 5:
- retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state);
- break;
- default:
- break;
- }
- efree(lc_text);
- if (class_name) {
- zend_string_release_ex(class_name, 0);
- }
- if (ce && retval) {
- size_t len = ZSTR_LEN(ce->name) + 2 + strlen(retval) + 1;
- char *tmp = malloc(len);
- snprintf(tmp, len, "%s::%s", ZSTR_VAL(ce->name), retval);
- free(retval);
- retval = tmp;
- }
- }
- return retval;
- } /* }}} */
- static char **cli_code_completion(const char *text, int start, int end) /* {{{ */
- {
- return rl_completion_matches(text, cli_completion_generator);
- }
- /* }}} */
- static int readline_shell_run(void) /* {{{ */
- {
- char *line;
- size_t size = 4096, pos = 0, len;
- char *code = emalloc(size);
- zend_string *prompt = cli_get_prompt("php", '>');
- char *history_file;
- int history_lines_to_write = 0;
- if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
- zend_file_handle prepend_file;
- zend_stream_init_filename(&prepend_file, PG(auto_prepend_file));
- zend_execute_scripts(ZEND_REQUIRE, NULL, 1, &prepend_file);
- zend_destroy_file_handle(&prepend_file);
- }
- #ifndef PHP_WIN32
- history_file = tilde_expand("~/.php_history");
- #else
- spprintf(&history_file, MAX_PATH, "%s/.php_history", getenv("USERPROFILE"));
- #endif
- /* Install the default completion function for 'php -a'.
- *
- * But if readline_completion_function() was called by PHP code prior to the shell starting
- * (e.g. with 'php -d auto_prepend_file=prepend.php -a'),
- * then use that instead of PHP's default. */
- if (rl_attempted_completion_function != php_readline_completion_cb) {
- rl_attempted_completion_function = cli_code_completion;
- }
- #ifndef PHP_WIN32
- rl_special_prefixes = "$";
- #endif
- read_history(history_file);
- EG(exit_status) = 0;
- while ((line = readline(ZSTR_VAL(prompt))) != NULL) {
- if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
- free(line);
- break;
- }
- if (!pos && !*line) {
- free(line);
- continue;
- }
- len = strlen(line);
- if (line[0] == '#' && line[1] != '[') {
- char *param = strstr(&line[1], "=");
- if (param) {
- zend_string *cmd;
- param++;
- cmd = zend_string_init(&line[1], param - &line[1] - 1, 0);
- zend_alter_ini_entry_chars_ex(cmd, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0);
- zend_string_release_ex(cmd, 0);
- add_history(line);
- zend_string_release_ex(prompt, 0);
- /* TODO: This might be wrong! */
- prompt = cli_get_prompt("php", '>');
- continue;
- }
- }
- if (pos + len + 2 > size) {
- size = pos + len + 2;
- code = erealloc(code, size);
- }
- memcpy(&code[pos], line, len);
- pos += len;
- code[pos] = '\n';
- code[++pos] = '\0';
- if (*line) {
- add_history(line);
- history_lines_to_write += 1;
- }
- free(line);
- zend_string_release_ex(prompt, 0);
- if (!cli_is_valid_code(code, pos, &prompt)) {
- continue;
- }
- if (history_lines_to_write) {
- #if HAVE_LIBEDIT
- write_history(history_file);
- #else
- append_history(history_lines_to_write, history_file);
- #endif
- history_lines_to_write = 0;
- }
- zend_try {
- zend_eval_stringl(code, pos, NULL, "php shell code");
- } zend_end_try();
- pos = 0;
- if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
- php_write("\n", 1);
- }
- if (EG(exception)) {
- zend_exception_error(EG(exception), E_WARNING);
- }
- if (pager_pipe) {
- fclose(pager_pipe);
- pager_pipe = NULL;
- }
- php_last_char = '\0';
- }
- #ifdef PHP_WIN32
- efree(history_file);
- #else
- free(history_file);
- #endif
- efree(code);
- zend_string_release_ex(prompt, 0);
- return EG(exit_status);
- }
- /* }}} */
- #ifdef PHP_WIN32
- typedef cli_shell_callbacks_t *(__cdecl *get_cli_shell_callbacks)(void);
- #define GET_SHELL_CB(cb) \
- do { \
- get_cli_shell_callbacks get_callbacks; \
- HMODULE hMod = GetModuleHandle("php.exe"); \
- (cb) = NULL; \
- if (strlen(sapi_module.name) >= 3 && 0 == strncmp("cli", sapi_module.name, 3)) { \
- get_callbacks = (get_cli_shell_callbacks)GetProcAddress(hMod, "php_cli_get_shell_callbacks"); \
- if (get_callbacks) { \
- (cb) = get_callbacks(); \
- } \
- } \
- } while(0)
- #else
- /*
- #ifdef COMPILE_DL_READLINE
- This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
- extensions. If that is being changed dlsym() should only be used when building
- this extension sharedto offer compatibility.
- */
- #define GET_SHELL_CB(cb) \
- do { \
- (cb) = NULL; \
- cli_shell_callbacks_t *(*get_callbacks)(void); \
- get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
- if (get_callbacks) { \
- (cb) = get_callbacks(); \
- } \
- } while(0)
- /*#else
- #define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
- #endif*/
- #endif
- PHP_MINIT_FUNCTION(cli_readline)
- {
- cli_shell_callbacks_t *cb;
- ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
- REGISTER_INI_ENTRIES();
- #if HAVE_LIBEDIT
- REGISTER_STRING_CONSTANT("READLINE_LIB", "libedit", CONST_CS|CONST_PERSISTENT);
- #else
- REGISTER_STRING_CONSTANT("READLINE_LIB", "readline", CONST_CS|CONST_PERSISTENT);
- #endif
- GET_SHELL_CB(cb);
- if (cb) {
- cb->cli_shell_write = readline_shell_write;
- cb->cli_shell_ub_write = readline_shell_ub_write;
- cb->cli_shell_run = readline_shell_run;
- }
- return SUCCESS;
- }
- PHP_MSHUTDOWN_FUNCTION(cli_readline)
- {
- cli_shell_callbacks_t *cb;
- UNREGISTER_INI_ENTRIES();
- GET_SHELL_CB(cb);
- if (cb) {
- cb->cli_shell_write = NULL;
- cb->cli_shell_ub_write = NULL;
- cb->cli_shell_run = NULL;
- }
- return SUCCESS;
- }
- PHP_MINFO_FUNCTION(cli_readline)
- {
- php_info_print_table_start();
- php_info_print_table_header(2, "Readline Support", "enabled");
- #ifdef PHP_WIN32
- php_info_print_table_row(2, "Readline library", "WinEditLine");
- #else
- php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
- #endif
- php_info_print_table_end();
- DISPLAY_INI_ENTRIES();
- }
|