tabla - include_once php
Requerir un archivo PHP arbitrario sin filtrar variables en el alcance (4)
Después de algunas investigaciones, esto es lo que se me ocurrió. La única solución (limpia) es usar funciones miembro y variables de instancia / clase.
Necesitas:
- Haz referencia a todo usando
$this
y no argumentos de función. - Desarmar todos los globales, superglobales y restaurarlos después.
- Utilice una posible condición de carrera de algún tipo. es decir: en mi ejemplo a continuación,
render()
establecerá las variables de instancia que_render()
usará después. En un sistema de múltiples subprocesos, esto crea una condición de carrera: el subproceso A puede llamar a render () al mismo tiempo que el subproceso B y los datos serán inexactos para uno de ellos. Afortunadamente, por ahora, PHP no es multihilo. - Use un archivo temporal para incluir, que contiene un cierre, para evitar el uso de
eval
.
La plantilla de plantilla que se me ocurrió:
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();
}
}
Y aquí está el caso de prueba:
// 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
Y el archivo de plantilla:
<?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
?>
En resumen, esta solución:
- Oculta a todos los globales y superglobales. Las variables en sí mismas ($ _GET, $ _POST, etc.) todavía pueden modificarse, pero volverán a ser lo que eran anteriormente.
- No sombrea las variables. (Casi) todo puede ser usado, incluyendo
$this
. (Excepto por$GLOBALS
, ver más abajo). - No trae nada al alcance que no haya sido aprobado.
- No pierde ningún dato ni desencadena destructores, porque el refcount nunca llega a cero para ninguna variable.
- No utiliza
eval
ni nada de eso.
Aquí está el resultado que tengo para lo anterior:
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
Si vuelves $GLOBALS
después del hecho, debería ser como antes de la llamada.
El único problema posible es que alguien todavía puede ejecutar algo como:
unset($GLOBALS);
... y estas jodido. Y no hay manera de evitar eso.
¿Es posible en PHP require
un archivo arbitrario sin perder ninguna variable del alcance actual en el espacio de nombres de la variable del archivo requerido o contaminar el alcance de la variable global?
Tengo ganas de crear plantillas ligeras con archivos PHP y me preguntaba por motivos de pureza si era posible cargar un archivo de plantilla sin ninguna variable en su ámbito, sino las previstas.
He configurado una prueba que me gustaría que pasara una solución. Debe ser posible exigir RequiredFile.php
y hacer que devuelva Success, no leaking variables.
.
RequiredFile.php:
<?php
print array() === get_defined_vars()
? "Success, no leaking variables."
: "Failed, leaked variables: ".implode(", ",array_keys(get_defined_vars()));
?>
Lo más cercano que obtuve fue el uso de un cierre, pero aún así devuelve 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() );
¿Algunas ideas?
He podido encontrar una solución utilizando eval
para incluir la variable como una constante, evitando así que se filtre.
Si bien el uso de eval
definitivamente no es una solución perfecta, crea un alcance "perfectamente limpio" para el archivo requerido, algo que PHP no parece poder hacer de forma nativa.
$scope = function( $file, array $scope_array ) {
extract( $scope_array ); unset( $scope_array );
eval( "unset( /$file ); require( ''".str_replace( "''", "//'", $file )."'' );" );
};
$scope( "test.php", array() );
EDITAR:
Esto, técnicamente, ni siquiera es una solución perfecta, ya que crea una "sombra" sobre el file
y scope_array
variables scope_array
, evitando que se pasen al ámbito de forma natural.
EDIT2:
Podría resistirme a intentar escribir una solución sin sombras. El código ejecutado no debe tener acceso a $this
, variables globales o locales de los ámbitos anteriores, a menos que se transfiera directamente.
$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() );
Mira esto:
$scope = function() {
// It''s very simple :)
extract( func_get_arg(1) );
require func_get_arg(0);
};
$scope( "RequiredFile.php", array() );
Si necesita un motor de plantillas muy simple, su enfoque con una función es lo suficientemente bueno. Dígame, ¿cuáles son las desventajas reales de exponer esa variable $_file
?
Si necesitas hacer un trabajo real, toma Twig y deja de preocuparte. Cualquier motor de plantillas adecuado compila tus plantillas en PHP puro de todos modos, para que no pierdas velocidad. También obtiene ventajas significativas: una sintaxis más simple, htmlspecialchars
forzada y otros.
Siempre puedes ocultar tu $_file
en una superglobal:
$_SERVER[''MY_COMPLEX_NAME''] = $_file;
unset($_file);
include($_SERVER[''MY_COMPLEX_NAME'']);
unset($_SERVER[''MY_COMPLEX_NAME'']);