[英]Require an arbitrary PHP file without leaking variables into scope
在 PHP 中是否有可能在不將任何變量從當前范圍泄漏到所需文件的變量命名空間或污染全局變量范圍的情況下require
任意文件?
我想用 PHP 文件做輕量級模板,並且想知道是否有可能加載一個模板文件,它的范圍內沒有任何變量但預期的變量。
我已經設置了一個我希望解決方案通過的測試。 它應該能夠要求RequiredFile.php
並讓它返回Success, no leaking variables.
.
必填文件.php:
<?php
print array() === get_defined_vars()
? "Success, no leaking variables."
: "Failed, leaked variables: ".implode(", ",array_keys(get_defined_vars()));
?>
我得到的最接近的是使用閉包,但它仍然返回Failed, leaked variables: _file
。
$scope = function( $_file, array $scope_variables ) {
extract( $scope_variables ); unset( $scope_variables );
//No way to prevent $_file from leaking since it's used in the require call
require( $_file );
};
$scope( "RequiredFile.php", array() );
有任何想法嗎?
看這個:
$scope = function() {
// It's very simple :)
extract(func_get_arg(1));
require func_get_arg(0);
};
$scope("RequiredFile.php", []);
我已經能夠想出一個使用eval
將變量內聯為常量的解決方案,從而防止它泄漏。
雖然使用eval
絕對不是一個完美的解決方案,但它確實為所需文件創建了一個“完美干凈”的范圍,這是 PHP 本身似乎無法做到的。
$scope = function( $file, array $scope_array ) {
extract( $scope_array ); unset( $scope_array );
eval( "unset( \$file ); require( '".str_replace( "'", "\\'", $file )."' );" );
};
$scope( "test.php", array() );
編輯:
這在技術上甚至不是一個完美的解決方案,因為它在file
和scope_array
變量上創建了一個“陰影”,阻止它們自然地傳遞到作用域中。
編輯2:
我無法抗拒嘗試編寫無陰影解決方案。 除非直接傳入,否則執行的代碼不應訪問先前作用域中的$this
、全局或局部變量。
$scope = function( $file, array $scope_array ) {
$clear_globals = function( Closure $closure ) {
$old_globals = $GLOBALS;
$GLOBALS = array();
$closure();
$GLOBALS = $old_globals;
};
$clear_globals( function() use ( $file, $scope_array ) {
//remove the only variable that will leak from the scope
$eval_code = "unset( \$eval_code );";
//we must sort the var name array so that assignments happens in order
//that forces $var = $_var before $_var = $__var;
$scope_key_array = array_keys( $scope_array );
rsort( $scope_key_array );
//build variable scope reassignment
foreach( $scope_key_array as $var_name ) {
$var_name = str_replace( "'", "\\'", $var_name );
$eval_code .= "\${'$var_name'} = \${'_{$var_name}'};";
$eval_code .= "unset( \${'_{$var_name}'} );";
}
unset( $var_name );
//extract scope into _* variable namespace
extract( $scope_array, EXTR_PREFIX_ALL, "" ); unset( $scope_array );
//add file require with inlined filename
$eval_code .= "require( '".str_replace( "'", "\\'", $file )."' );";
unset( $file );
eval( $eval_code );
} );
};
$scope( "test.php", array() );
經過一番研究,這就是我想出的。 唯一(干凈)的解決方案是使用成員函數和實例/類變量。
你需要:
$this
而不是函數參數引用所有內容。render()
將設置_render()
之后將使用的實例變量。 在多線程系統中,這會產生競爭條件:線程 A 可能會與線程 B 同時調用 render(),並且其中一個的數據將不准確。 幸運的是,就目前而言,PHP 不是多線程的。eval
。我想出的模板類:
class template {
// Store the template data
protected $_data = array();
// Store the template filename
protected $_file, $_tmpfile;
// Store the backed up $GLOBALS and superglobals
protected $_backup;
// Render a template $file with some $data
public function render($file, $data) {
$this->_file = $file;
$this->_data = $data;
$this->_render();
}
// Restore the unset superglobals
protected function _restore() {
// Unset all variables to make sure the template don't inject anything
foreach ($GLOBALS as $var => $value) {
// Unset $GLOBALS and you're screwed
if ($var === 'GLOBALS') continue;
unset($GLOBALS[$var]);
}
// Restore all variables
foreach ($this->_backup as $var => $value) {
// Set back all global variables
$GLOBALS[$var] = $value;
}
}
// Backup the global variables and superglobals
protected function _backup() {
foreach ($GLOBALS as $var => $value) {
// Unset $GLOBALS and you're screwed
if ($var === 'GLOBALS') continue;
$this->_backup[$var] = $value;
unset($GLOBALS[$var]);
}
}
// Render the template
protected function _render() {
$this->_backup();
$this->_tmpfile = tempnam(sys_get_temp_dir(), __CLASS__);
$code = '<?php $render = function() {'.
'extract('.var_export($this->_data, true).');'.
'require "'.$this->_file.'";'.
'}; $render();'
file_put_contents($this->_tmpfile, $code);
include $this->_tmpfile;
$this->_restore();
}
}
這是測試用例:
// Setting some global/superglobals
$_GET['get'] = 'get is still set';
$hello = 'hello is still set';
$t = new template;
$t->render('template.php', array('foo'=>'bar', 'this'=>'hello world'));
// Checking if those globals/superglobals are still set
var_dump($_GET['get'], $hello);
// Those shouldn't be set anymore
var_dump($_SERVER['bar'], $GLOBALS['stack']); // undefined indices
和模板文件:
<?php
var_dump($GLOBALS); // prints an empty list
$_SERVER['bar'] = 'baz'; // will be unset later
$GLOBALS['stack'] = 'overflow'; // will be unset later
var_dump(get_defined_vars()); // foo, this
?>
簡而言之,這個解決方案:
$this
。 (除了$GLOBALS
,見下文)。eval
或類似的東西。這是我對上述結果的結果:
array(1) {
["GLOBALS"]=>
*RECURSION*
}
array(2) {
["this"]=>
string(11) "hello world"
["foo"]=>
string(3) "bar"
}
string(10) "get is still set"
string(12) "hello is still set"
Notice: Undefined index: bar in /var/www/temp/test.php on line 75
Call Stack:
0.0003 658056 1. {main}() /var/www/temp/test.php:0
Notice: Undefined index: stack in /var/www/temp/test.php on line 75
Call Stack:
0.0003 658056 1. {main}() /var/www/temp/test.php:0
NULL
NULL
如果您在事后轉儲$GLOBALS
,它應該就像調用之前一樣。
唯一可能的問題是有人仍然可以執行以下操作:
unset($GLOBALS);
……你完蛋了。 沒有辦法解決這個問題。
如果您需要一個非常簡單的模板引擎,那么您使用函數的方法就足夠了。 告訴我,暴露$_file
變量的真正缺點是什么?
如果您需要做真正的工作,請抓住Twig並停止擔心。 無論如何,任何合適的模板引擎都會將您的模板編譯為純 PHP,因此您不會失去速度。 您還獲得了顯着的優勢 - 更簡單的語法、強制的htmlspecialchars
等。
您總是可以將$_file
隱藏在超全局變量中: $_SERVER['MY_COMPLEX_NAME'] = $_file;
unset($_file);
include($_SERVER['MY_COMPLEX_NAME']);
unset($_SERVER['MY_COMPLEX_NAME']);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.