簡體   English   中英

需要一個任意的 PHP 文件而不會將變量泄漏到作用域中

[英]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() );

編輯:

這在技術上甚至不是一個完美的解決方案,因為它在filescope_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

?>

簡而言之,這個解決方案:

  • 隱藏所有全局變量和超全局變量。 變量本身($_GET、$_POST 等)仍然可以修改,但它們將恢復到以前的狀態。
  • 不影響變量。 (幾乎)一切都可以使用,包括$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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM