From 695ae92c9765018dbb3738f6733065ad27c3d4c2 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Wed, 26 Feb 2003 22:11:12 +0000 Subject: [PATCH] 1) Make the output of system() binary safe 2) Solved a memory leak when the return_value variable passed by reference is not an integer in system()/exec()/passthru(). 3) Solved a bug in exec(), which would make it append to the 2nd parameter (passed by reference) if the parameter is an array instead of overwriting it. 4) Changed the code to use the streams code, resulting in a smaller code base. 5) Various cleanups resulting in reduction of overall code base inside the file by ~ 1/3. 6) Speed improvements of ~2.5 times compared to previous performance (based on attached PHP script). --- ext/standard/exec.c | 314 +++++++++++++++++--------------------------- ext/standard/exec.h | 4 +- 2 files changed, 120 insertions(+), 198 deletions(-) diff --git a/ext/standard/exec.c b/ext/standard/exec.c index 375609abc5..7d17c8e00f 100644 --- a/ext/standard/exec.c +++ b/ext/standard/exec.c @@ -12,7 +12,8 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Rasmus Lerdorf | + | Author: Rasmus Lerdorf | + | Ilia Alshanetsky | +----------------------------------------------------------------------+ */ /* $Id$ */ @@ -49,278 +50,201 @@ #include #endif -/* {{{ php_Exec +/* {{{ php_exec * If type==0, only last line of output is returned (exec) * If type==1, all lines will be printed and last lined returned (system) * If type==2, all lines will be saved to given array (exec with &$array) * If type==3, output will be printed binary, no lines will be saved or returned (passthru) * */ -int php_Exec(int type, char *cmd, pval *array, pval *return_value TSRMLS_DC) +int php_exec(int type, char *cmd, pval *array, pval *return_value TSRMLS_DC) { FILE *fp; char *buf, *tmp=NULL; - int buflen = 0; - int t, l, output=1; - int overflow_limit, lcmd, ldir; - char *b, *c, *d=NULL; - php_stream *stream = NULL; - int pclose_return = 0; + int buflen, l, pclose_return; + char *cmd_p, *b, *c, *d=NULL; + php_stream *stream; + size_t bufl = 0; #if PHP_SIGCHILD void (*sig_handler)(); #endif - buf = (char *) emalloc(EXEC_INPUT_BUF); - buflen = EXEC_INPUT_BUF; - if (PG(safe_mode)) { - lcmd = strlen(cmd); - ldir = strlen(PG(safe_mode_exec_dir)); - l = lcmd + ldir + 2; - overflow_limit = l; - c = strchr(cmd, ' '); - if (c) *c = '\0'; + if ((c = strchr(cmd, ' '))) { + *c = '\0'; + c++; + } if (strstr(cmd, "..")) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "No '..' components allowed in path"); - efree(buf); - return -1; + goto err; } - d = emalloc(l); - strcpy(d, PG(safe_mode_exec_dir)); - overflow_limit -= ldir; b = strrchr(cmd, PHP_DIR_SEPARATOR); - if (b) { - strcat(d, b); - overflow_limit -= strlen(b); - } else { - strcat(d, "/"); - strcat(d, cmd); - overflow_limit-=(strlen(cmd)+1); - } + spprintf(&d, 0, "%s%s%s%s", PG(safe_mode_exec_dir), (b ? "" : "/"), (b ? b : cmd), (c ? " " : ""), (c ? c : "")); if (c) { - *c = ' '; - strncat(d, c, overflow_limit); + *(c - 1) = ' '; } - tmp = php_escape_shell_cmd(d); + cmd_p = php_escape_shell_cmd(d); efree(d); - d = tmp; -#if PHP_SIGCHILD - sig_handler = signal (SIGCHLD, SIG_DFL); -#endif -#ifdef PHP_WIN32 - fp = VCWD_POPEN(d, "rb"); -#else - fp = VCWD_POPEN(d, "r"); -#endif - if (!fp) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to fork [%s]", d); - efree(d); - efree(buf); -#if PHP_SIGCHILD - signal (SIGCHLD, sig_handler); -#endif - return -1; - } + d = cmd_p; + } else { + cmd_p = cmd; + } - } else { /* not safe_mode */ #if PHP_SIGCHILD - sig_handler = signal (SIGCHLD, SIG_DFL); + sig_handler = signal (SIGCHLD, SIG_DFL); #endif + #ifdef PHP_WIN32 - fp = VCWD_POPEN(cmd, "rb"); + fp = VCWD_POPEN(cmd_p, "rb"); #else - fp = VCWD_POPEN(cmd, "r"); + fp = VCWD_POPEN(cmd_p, "r"); #endif - if (!fp) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to fork [%s]", cmd); - efree(buf); -#if PHP_SIGCHILD - signal (SIGCHLD, sig_handler); -#endif - return -1; - } + if (!fp) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to fork [%s]", cmd); + goto err; } - buf[0] = '\0'; - if (type==2) { - if (Z_TYPE_P(array) != IS_ARRAY) { - pval_destructor(array); - array_init(array); - } - } - - /* we register the resource so that case of an aborted connection the - * fd gets pclosed - */ stream = php_stream_fopen_from_pipe(fp, "rb"); - if (type != 3) { - l=0; - while ( !feof(fp) || l != 0 ) { - l = 0; - /* Read a line or fill the buffer, whichever comes first */ - do { - if ( buflen <= (l+1) ) { - buf = erealloc(buf, buflen + EXEC_INPUT_BUF); - buflen += EXEC_INPUT_BUF; - } + buf = (char *) emalloc(EXEC_INPUT_BUF); + buflen = EXEC_INPUT_BUF; - if ( fgets(&(buf[l]), buflen - l, fp) == NULL ) { - /* eof */ - break; + if (type != 3) { + b = buf; + + while (php_stream_get_line(stream, b, EXEC_INPUT_BUF, &bufl)) { + /* no new line found, let's read some more */ + if (b[bufl - 1] != '\n' && !php_stream_eof(stream)) { + if (buflen < (bufl + EXEC_INPUT_BUF)) { + bufl += b - buf; + buflen = bufl + EXEC_INPUT_BUF; + buf = erealloc(buf, buflen); + b = buf + bufl; + } else { + b += bufl; } - l += strlen(&(buf[l])); - } while ( (l > 0) && (buf[l-1] != '\n') ); - - if ( feof(fp) && (l == 0) ) { - break; + continue; + } else if (b != buf) { + bufl += buflen - EXEC_INPUT_BUF; } - if (type == 1) { - if (output) PUTS(buf); + PHPWRITE(buf, bufl); sapi_flush(TSRMLS_C); - } - else if (type == 2) { + } else if (type == 2) { /* strip trailing whitespaces */ - l = strlen(buf); - t = l; - while (l-- && isspace((int)buf[l])); - if (l < t) { - buf[l + 1] = '\0'; + l = bufl; + while (l-- && isspace(buf[l])); + if (l != (bufl - 1)) { + bufl = l + 1; + buf[bufl] = '\0'; } - add_next_index_string(array, buf, 1); + add_next_index_stringl(array, buf, bufl, 1); } + b = buf; } + if (bufl) { + /* strip trailing whitespaces if we have not done so already */ + if (type != 2) { + l = bufl; + while (l-- && isspace(buf[l])); + if (l != (bufl - 1)) { + bufl = l + 1; + buf[bufl] = '\0'; + } + } - /* strip trailing spaces */ - l = strlen(buf); - t = l; - while (l && isspace((int)buf[l - 1])) { - l--; - } - if (l < t) buf[l] = '\0'; - - /* Return last line from the shell command */ - if (PG(magic_quotes_runtime)) { - int len; - - tmp = php_addslashes(buf, 0, &len, 0 TSRMLS_CC); - RETVAL_STRINGL(tmp, len, 0); - } else { - RETVAL_STRINGL(buf, l, 1); + /* Return last line from the shell command */ + if (PG(magic_quotes_runtime)) { + int len; + + tmp = php_addslashes(buf, bufl, &len, 0 TSRMLS_CC); + RETVAL_STRINGL(tmp, len, 0); + } else { + RETVAL_STRINGL(buf, bufl, 1); + } + } else { /* should return NULL, but for BC we return "" */ + RETVAL_EMPTY_STRING(); } } else { - size_t b; - - while((b = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) { - if (output) { - PHPWRITE(buf, b); - } + while((bufl = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) { + PHPWRITE(buf, bufl); } } pclose_return = php_stream_close(stream); + efree(buf); +done: #if PHP_SIGCHILD signal (SIGCHLD, sig_handler); #endif if (d) { efree(d); } - efree(buf); return pclose_return; +err: + pclose_return = -1; + goto done; } /* }}} */ -/* {{{ proto string exec(string command [, array output [, int return_value]]) - Execute an external program */ -PHP_FUNCTION(exec) +static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode) { - pval **arg1, **arg2, **arg3; - int arg_count = ZEND_NUM_ARGS(); + char *cmd; + int cmd_len; + zval *ret_code=NULL, *ret_array=NULL; int ret; - if (arg_count < 1 || arg_count > 3 || zend_get_parameters_ex(arg_count, &arg1, &arg2, &arg3) == FAILURE) { - WRONG_PARAM_COUNT; + if (mode) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z", &cmd, &cmd_len, &ret_code) == FAILURE) { + RETURN_FALSE; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|zz", &cmd, &cmd_len, &ret_array, &ret_code) == FAILURE) { + RETURN_FALSE; + } } - - if (!Z_STRLEN_PP(arg1)) { - PHP_EMPTY_EXEC_PARAM; + if (!cmd_len) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot execute a blank command"); + RETURN_FALSE; } - - switch (arg_count) { - case 1: - ret = php_Exec(0, Z_STRVAL_PP(arg1), NULL, return_value TSRMLS_CC); - break; - case 2: - ret = php_Exec(2, Z_STRVAL_PP(arg1), *arg2, return_value TSRMLS_CC); - break; - case 3: - ret = php_Exec(2, Z_STRVAL_PP(arg1), *arg2, return_value TSRMLS_CC); - Z_TYPE_PP(arg3) = IS_LONG; - Z_LVAL_PP(arg3)=ret; - break; + + if (!ret_array) { + ret = php_exec(mode, cmd, NULL, return_value TSRMLS_CC); + } else { + zval_dtor(ret_array); + array_init(ret_array); + ret = php_exec(2, cmd, ret_array, return_value TSRMLS_CC); + } + if (ret_code) { + zval_dtor(ret_code); + ZVAL_LONG(ret_code, ret); } } +/* {{{ proto string exec(string command [, array &output [, int &return_value]]) + Execute an external program */ +PHP_FUNCTION(exec) +{ + php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} + /* }}} */ -/* {{{ proto int system(string command [, int return_value]) +/* {{{ proto int system(string command [, int &return_value]) Execute an external program and display output */ PHP_FUNCTION(system) { - pval **arg1, **arg2; - int arg_count = ZEND_NUM_ARGS(); - int ret; - - if (arg_count < 1 || arg_count > 2 || zend_get_parameters_ex(arg_count, &arg1, &arg2) == FAILURE) { - WRONG_PARAM_COUNT; - } - - if (!Z_STRLEN_PP(arg1)) { - PHP_EMPTY_EXEC_PARAM; - } - - switch (arg_count) { - case 1: - ret = php_Exec(1, Z_STRVAL_PP(arg1), NULL, return_value TSRMLS_CC); - break; - case 2: - ret = php_Exec(1, Z_STRVAL_PP(arg1), NULL, return_value TSRMLS_CC); - Z_TYPE_PP(arg2) = IS_LONG; - Z_LVAL_PP(arg2)=ret; - break; - } + php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ proto void passthru(string command [, int return_value]) +/* {{{ proto void passthru(string command [, int &return_value]) Execute an external program and display raw output */ PHP_FUNCTION(passthru) { - pval **arg1, **arg2; - int arg_count = ZEND_NUM_ARGS(); - int ret; - - if (arg_count < 1 || arg_count > 2 || zend_get_parameters_ex(arg_count, &arg1, &arg2) == FAILURE) { - WRONG_PARAM_COUNT; - } - - if (!Z_STRLEN_PP(arg1)) { - PHP_EMPTY_EXEC_PARAM; - } - - switch (arg_count) { - case 1: - ret = php_Exec(3, Z_STRVAL_PP(arg1), NULL, return_value TSRMLS_CC); - break; - case 2: - ret = php_Exec(3, Z_STRVAL_PP(arg1), NULL, return_value TSRMLS_CC); - Z_TYPE_PP(arg2) = IS_LONG; - Z_LVAL_PP(arg2)=ret; - break; - } + php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); } /* }}} */ diff --git a/ext/standard/exec.h b/ext/standard/exec.h index a0d4ab46ae..6d5fb66f70 100644 --- a/ext/standard/exec.h +++ b/ext/standard/exec.h @@ -36,8 +36,6 @@ PHP_MINIT_FUNCTION(proc_open); char *php_escape_shell_cmd(char *); char *php_escape_shell_arg(char *); -int php_Exec(int type, char *cmd, pval *array, pval *return_value TSRMLS_DC); - -#define PHP_EMPTY_EXEC_PARAM { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot execute a blank command"); RETURN_FALSE; } +int php_exec(int type, char *cmd, pval *array, pval *return_value TSRMLS_DC); #endif /* EXEC_H */ -- 2.50.1