2010-07-11 13 views
18

Descargo de responsabilidad; Estoy plenamente consciente de las dificultades y los "males" de eval, incluyendo pero no limitado a: problemas de rendimiento, seguridad, etc. portabilidadEvaluación PHP y errores de captura (tanto como sea posible)

El problema

de leer el manual de PHP en eval .. .

eval() devuelve NULL a menos de retorno es llamada en el código evaluado, en el que caso el valor pasado para volver es devueltos. Si hay un error de análisis en , el código evaluado, eval() devuelve FALSE y la ejecución del siguiente código continúa normalmente. No es posible detectar un error de análisis en eval() usando set_error_handler().

En resumen, no hay captura de error, excepto devolver falso, que es muy útil, pero estoy seguro de que podría hacerlo mucho mejor.

La razón

Una parte de la funcionalidad del sitio que estoy trabajando se basa en la ejecución de las expresiones. Me gustaría no pasar por el camino de sandbox o módulos de ejecución, así que he terminado de usar eval. Antes de gritar "¿y si el cliente se volviera malo?" saber que el cliente es bastante confiable; él no querría romper su propio sitio, y cualquiera que tenga acceso a esta funcionalidad posee prácticamente el servidor, independientemente de eval.

El cliente conoce expresiones como las de Excel, y no es un problema explicar las pequeñas diferencias, sin embargo, tener algún tipo de advertencia es prácticamente una funcionalidad estándar.

Esto es lo que tengo hasta ahora:

define('CR',chr(13)); 
define('LF',chr(10)); 

function test($cond=''){ 
    $cond=trim($cond); 
    if($cond=='')return 'Success (condition was empty).'; $result=false; 
    $cond='$result = '.str_replace(array(CR,LF),' ',$cond).';'; 
    try { 
     $success=eval($cond); 
     if($success===false)return 'Error: could not run expression.'; 
     return 'Success (condition return '.($result?'true':'false').').'; 
    }catch(Exception $e){ 
     return 'Error: exception '.get_class($e).', '.$e->getMessage().'.'; 
    } 
} 

Notas

  • La función devuelve una cadena de mensaje en cualquier caso
  • La expresión de código debe ser un pedazo de una sola línea de PHP, sin etiquetas PHP y sin un punto y coma final
  • Las nuevas líneas se convierten en espacios
  • Se añade una variable para contener el resultado (expresión debe devolver verdadero o falso, y con el fin de no entrar en conflicto con el regreso de eval, se utiliza una variable temp.)

Por lo tanto, lo que podría agregar a una mayor ayudar al usuario? ¿Hay otras funciones de análisis que puedan identificar mejor los posibles errores/problemas?

Chris.

+0

si se pudiera proporcionar más información sobre lo que "expresiones" que va a utilizar, tal vez podamos ayudar Más. Podría pensar en algunas cosas buenas token_get_all para validar la entrada del usuario;) – NikiC

+0

¿Código PHP normal? Planeo permitir el acceso completo a PHP, con la posible excepción de definir funciones y clases, lo cual no es necesario. – Christian

Respuesta

11

He encontrado una buena alternativa/respuesta a mi pregunta.

En primer lugar, permítanme comenzar diciendo que la sugerencia de nikic funciona cuando configuro error_reporting (E_ALL); los avisos se muestran en salida de PHP, y gracias a OB, se pueden capturar.

A continuación, he encontrado este código muy útil:

/** 
* Check the syntax of some PHP code. 
* @param string $code PHP code to check. 
* @return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned. 
*/ 
function php_syntax_error($code){ 
    if(!defined("CR")) 
     define("CR","\r"); 
    if(!defined("LF")) 
     define("LF","\n") ; 
    if(!defined("CRLF")) 
     define("CRLF","\r\n") ; 
    $braces=0; 
    $inString=0; 
    foreach (token_get_all('<?php ' . $code) as $token) { 
     if (is_array($token)) { 
      switch ($token[0]) { 
       case T_CURLY_OPEN: 
       case T_DOLLAR_OPEN_CURLY_BRACES: 
       case T_START_HEREDOC: ++$inString; break; 
       case T_END_HEREDOC: --$inString; break; 
      } 
     } else if ($inString & 1) { 
      switch ($token) { 
       case '`': case '\'': 
       case '"': --$inString; break; 
      } 
     } else { 
      switch ($token) { 
       case '`': case '\'': 
       case '"': ++$inString; break; 
       case '{': ++$braces; break; 
       case '}': 
        if ($inString) { 
         --$inString; 
        } else { 
         --$braces; 
         if ($braces < 0) break 2; 
        } 
        break; 
      } 
     } 
    } 
    $inString = @ini_set('log_errors', false); 
    $token = @ini_set('display_errors', true); 
    ob_start(); 
    $code = substr($code, strlen('<?php ')); 
    $braces || $code = "if(0){{$code}\n}"; 
    if (eval($code) === false) { 
     if ($braces) { 
      $braces = PHP_INT_MAX; 
     } else { 
      false !== strpos($code,CR) && $code = strtr(str_replace(CRLF,LF,$code),CR,LF); 
      $braces = substr_count($code,LF); 
     } 
     $code = ob_get_clean(); 
     $code = strip_tags($code); 
     if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) { 
      $code[2] = (int) $code[2]; 
      $code = $code[2] <= $braces 
       ? array($code[1], $code[2]) 
       : array('unexpected $end' . substr($code[1], 14), $braces); 
     } else $code = array('syntax error', 0); 
    } else { 
     ob_end_clean(); 
     $code = false; 
    } 
    @ini_set('display_errors', $token); 
    @ini_set('log_errors', $inString); 
    return $code; 
} 

parece que fácilmente hace exactamente lo que necesito (yay)!

+0

Guau, esto se ve bien. Y de nuevo aprendí algo nuevo: 'break' ¡puede tener un argumento! Más adelante echaré un vistazo a este código para comprender realmente lo que hace. Solo una nota: si hay errores de análisis pesado en el código, 'token_get_all' puede generar errores por sí mismo, p. si está usando '\\' en la fuente (en PHP <5.3). – NikiC

+1

Es muy agradable. Lamentablemente, todavía no proporciona, no puede, protección contra llamadas a funciones incorrectas, p. errores ortográficos simples en los nombres de las funciones. PHP realmente debería pensar en detectar errores fatales en eval(). –

12

No creo que eval te arroje una excepción, arrojará errores normales.Para la captura de éstos lugar una

ob_start(); 

antes de la eval y como después de la eval:

if ('' !== $error = ob_get_clean()) { 
    // output the error somehow to the client 
} 

no creo que haya otra función como eval con una mejor gestión de errores. Hay php_check_syntax pero solo valida el archivo.

+0

Sí, los definí, lo siento por no mencionar. Corregido el código anterior. En cuanto a las excepciones, estoy bastante seguro de que tampoco lo hacen. Sin embargo, este código podría fallar en el futuro después de quizás agregarle excepciones, por lo que no perjudica su manejo de todos modos. – Christian

+0

No he podido encontrar 'check_syntax'. – hakre

+0

@hakre thx fijo – NikiC

2

También puede intentar algo como esto:

$filePath = '/tmp/tmp_eval'.mt_rand(); 
file_put_contents($filePath, $evalCode); 
register_shutdown_function('unlink', $filePath); 
require($filePath); 

Así que cualquier error en $ evalCode estará a cargo de manejador de errores.

4

Como prueba de errores de análisis dentro de eval():

$result = @eval($evalcode . "; return true;"); 

Si $result == false, $evalcode tiene un error de análisis y no ejecuta el 'verdadero retorno' parte. Obviamente no hay que $evalcoderetorno en sí algo, pero con este truco se puede probar para analizar errores en expresiones con eficacia ...

+0

¡Una idea muy inteligente! No lo he probado todavía, pero lo haré. – itoctopus

+0

muy buena solución – Hien

+1

¡No puede detectar los errores de análisis en el código 'eval'!Simplemente detendría la ejecución de tu script. – DUzun

Cuestiones relacionadas