web-development-kb-es.site

Obtenga PHP para dejar de reemplazar '.' ¿Caracteres en arrays $ _GET o $ _POST?

Si paso PHP variables con . en sus nombres a través de $ _GET PHP las reemplaza automáticamente con _ caracteres. Por ejemplo:

<?php
echo "url is ".$_SERVER['REQUEST_URI']."<p>";
echo "x.y is ".$_GET['x.y'].".<p>";
echo "x_y is ".$_GET['x_y'].".<p>";

... produce lo siguiente:

url is /SpShipTool/php/testGetUrl.php?x.y=a.b
x.y is .
x_y is a.b.

... mi pregunta es la siguiente: ¿hay alguna manera en que pueda hacer que esto se detenga? Por mi vida no puedo entender lo que he hecho para merecer esto

La versión de PHP con la que estoy corriendo es 5.2.4-2ubuntu5.3.

69
Dave Carpeneto

Aquí está la explicación de PHP.net de por qué lo hace:

Puntos en nombres de variables entrantes

Normalmente, PHP no altera los nombres de las variables cuando se pasan a un script. Sin embargo, debe tenerse en cuenta que el punto (punto, parada completa) no es un carácter válido en un PHP nombre de variable. Por la razón, miralo:

<?php
$varname.ext;  /* invalid variable name */
?>

Ahora, lo que el analizador ve es una variable llamada $ varname, seguida por el operador de concatenación de cadenas, seguida de la cadena de prueba (es decir, una cadena sin comillas que no coincide con ninguna clave conocida o palabras reservadas) 'ext'. Obviamente, esto no tiene el resultado deseado.

Por esta razón, es importante tener en cuenta que PHP reemplazará automáticamente cualquier punto en los nombres de variables entrantes con guiones bajos.

Eso es de http://ca.php.net/variables.external .

Además, de acuerdo con este comentario estos otros caracteres se convierten en guiones bajos:

La lista completa de caracteres de nombre de campo que PHP convierte a _ (guión bajo) es la siguiente (no solo un punto):

  • chr (32) () (espacio)
  • chr (46) (.) (punto)
  • chr (91) ([) (corchete abierto)
  • chr (128) - chr (159) (varios)

Así que parece que estás atascado con él, así que tendrás que convertir los guiones bajos a puntos en tu script usando sugerencia de dawnerd (solo usaría str_replace aunque.)

63
Jeremy Ruten

Hace mucho tiempo que respondí a la pregunta, pero en realidad hay una mejor respuesta (o solución alternativa). PHP te permite en flujo de entrada sin formato , para que puedas hacer algo como esto:

$query_string = file_get_contents('php://input');

que le dará la matriz $ _POST en formato de cadena de consulta, los períodos como deberían ser.

Luego puede analizarlo si lo necesita (según comentario del POSTER )

<?php
// Function to fix up PHP's messing up input containing dots, etc.
// `$source` can be either 'POST' or 'GET'
function getRealInput($source) {
    $pairs = explode("&", $source == 'POST' ? file_get_contents("php://input") : $_SERVER['QUERY_STRING']);
    $vars = array();
    foreach ($pairs as $pair) {
        $nv = explode("=", $pair);
        $name = urldecode($nv[0]);
        $value = urldecode($nv[1]);
        $vars[$name] = $value;
    }
    return $vars;
}

// Wrapper functions specifically for GET and POST:
function getRealGET() { return getRealInput('GET'); }
function getRealPOST() { return getRealInput('POST'); }
?>

Muy útil para los parámetros de OpenID, que contienen ambos '.' y '_', cada uno con un cierto significado!

55
crb

Resaltando una respuesta real de Johan en un comentario de arriba, acabo de envolver toda mi publicación en una matriz de nivel superior que evita completamente el problema sin requerir un procesamiento pesado.

En la forma que hagas

<input name="data[database.username]">  
<input name="data[database.password]">  
<input name="data[something.else.really.deep]">  

en lugar de

<input name="database.username"> 
<input name="database.password"> 
<input name="something.else.really.deep">  

y en el controlador de correos, simplemente desenvuélvalo:

$posdata = $_POST['data'];

Para mí, esto fue un cambio de dos líneas, ya que mis puntos de vista estaban completamente basados ​​en la plantilla.

Para tu información Estoy usando puntos en los nombres de mis campos para editar árboles de datos agrupados.

26
scipilot

El funcionamiento de esta función es un truco genial que se me ocurrió durante mis vacaciones de verano en 2013. Algún día escribiré una publicación en el blog al respecto.

Esta solución funciona universalmente y tiene soporte de matriz profunda, por ejemplo a.a[x][b.a]=10. Utiliza parse_str() detrás de las escenas con algo de preprocesamiento.

function fix($source) {
    $source = preg_replace_callback(
        '/(^|(?<=&))[^=[&]+/',
        function($key) { return bin2hex(urldecode($key[0])); },
        $source
    );

    parse_str($source, $post);

    $result = array();
    foreach ($post as $key => $val) {
        $result[hex2bin($key)] = $val;
    }
    return $result;
}

Y luego puedes llamar a esta función así, dependiendo de la fuente:

$_POST   = fix(file_get_contents('php://input'));
$_GET    = fix($_SERVER['QUERY_STRING']);
$_COOKIE = fix($_SERVER['HTTP_COOKIE']);

Para PHP debajo de 5.4: use base64_encode en lugar de bin2hex y base64_decode en lugar de hex2bin.

17
Rok Kralj

Esto sucede porque un punto es un carácter no válido en el nombre de una variable, el razón para el cual se encuentra muy en la implementación de PHP, por lo que no hay soluciones fáciles (todavía).

Mientras tanto, puede solucionar este problema:

  1. Acceder a los datos de consulta sin procesar a través de php://input para POST data o $_SERVER['QUERY_STRING'] para obtener datos GET
  2. Usando una función de conversión.

La siguiente función de conversión (PHP> = 5.4) codifica los nombres de cada par clave-valor en una representación hexadecimal y luego realiza una parse_str() regular; Una vez hecho esto, los nombres hexadecimales vuelven a su forma original:

function parse_qs($data)
{
    $data = preg_replace_callback('/(?:^|(?<=&))[^=[]+/', function($match) {
        return bin2hex(urldecode($match[0]));
    }, $data);

    parse_str($data, $values);

    return array_combine(array_map('hex2bin', array_keys($values)), $values);
}

// work with the raw query string
$data = parse_qs($_SERVER['QUERY_STRING']);

O:

// handle posted data (this only works with application/x-www-form-urlencoded)
$data = parse_qs(file_get_contents('php://input'));
6
Ja͢ck

Este enfoque es una versión alterada de Rok Kralj, pero con algunos ajustes para funcionar, para mejorar la eficiencia (evita las devoluciones de llamada innecesarias, la codificación y la decodificación de claves no afectadas) y para manejar correctamente las claves de matriz.

A Gist with tests está disponible y cualquier comentario o sugerencia es bienvenido aquí o allá.

public function fix(&$target, $source, $keep = false) {                        
    if (!$source) {                                                            
        return;                                                                
    }                                                                          
    $keys = array();                                                           

    $source = preg_replace_callback(                                           
        '/                                                                     
        # Match at start of string or &                                        
        (?:^|(?<=&))                                                           
        # Exclude cases where the period is in brackets, e.g. foo[bar.blarg]
        [^=&\[]*                                                               
        # Affected cases: periods and spaces                                   
        (?:\.|%20)                                                             
        # Keep matching until assignment, next variable, end of string or   
        # start of an array                                                    
        [^=&\[]*                                                               
        /x',                                                                   
        function ($key) use (&$keys) {                                         
            $keys[] = $key = base64_encode(urldecode($key[0]));                
            return urlencode($key);                                            
        },                                                                     
    $source                                                                    
    );                                                                         

    if (!$keep) {                                                              
        $target = array();                                                     
    }                                                                          

    parse_str($source, $data);                                                 
    foreach ($data as $key => $val) {                                          
        // Only unprocess encoded keys                                      
        if (!in_array($key, $keys)) {                                          
            $target[$key] = $val;                                              
            continue;                                                          
        }                                                                      

        $key = base64_decode($key);                                            
        $target[$key] = $val;                                                  

        if ($keep) {                                                           
            // Keep a copy in the underscore key version                       
            $key = preg_replace('/(\.| )/', '_', $key);                        
            $target[$key] = $val;                                              
        }                                                                      
    }                                                                          
}                                                                              
5
El Yobo

La razón por la que esto sucede es debido a la antigua funcionalidad register_globals de PHP. Los . el carácter no es un carácter válido en el nombre de una variable, por lo que PHP lo cubre con un guión bajo para asegurarse de que haya compatibilidad.

En resumen, no es una buena práctica hacer periodos en las variables de URL.

4
Jeremy Privett

Si busca cualquier forma de literalmente obtener PHP para detener el reemplazo '. ' los caracteres de las matrices $ _GET o $ _POST, entonces una de esas formas es modificar la fuente de PHP (y en este caso es relativamente sencillo).

ADVERTENCIA: ¡La modificación de PHP C fuente es una opción avanzada!

También vea esto informe de error de PHP que sugiere la misma modificación.

Para explorar necesitarás:

  • descargar código fuente de PHP C
  • deshabilitar la verificación de reemplazo .
  • ./ configure , make e implemente su compilación personalizada de PHP

El cambio de fuente en sí es trivial e implica actualizar solo la mitad de una línea en main/php_variables.c:

....
/* ensure that we don't have spaces or dots in the variable name (not binary safe) */
for (p = var; *p; p++) {
    if (*p == ' ' /*|| *p == '.'*/) {
        *p='_';
....

Nota: en comparación con || *p == '.' original se ha comentado


Ejemplo de salida:

dado un QUERY_STRING de a.a[]=bb&a.a[]=BB&c%20c=dd, ejecutar <?php print_r($_GET); ahora produce:

 Array 
 (
 [Aa] => Array 
 (
 [0] => bb 
 [1] => BB 
) 
 
 [C_c] => dd 
) 

Notas:

  • este parche solo aborda la pregunta original (detiene el reemplazo de puntos, no espacios).
  • la ejecución de este parche será más rápida que las soluciones a nivel de script, pero esas respuestas puras .php siguen siendo generalmente preferibles (porque evitan cambiar PHP en sí).
  • en teoría, aquí es posible un enfoque de relleno múltiple y podría combinar enfoques: pruebe el cambio de nivel C utilizando parse_str() y (si no está disponible) recurra a métodos más lentos.
3
humbletim

Después de ver la solución de Rok, se me ocurrió una versión que aborda las limitaciones en mi respuesta a continuación, las de Crb arriba y la de Rok también. Ver un mi versión mejorada .


La respuesta de @crb arriba es un buen comienzo, pero hay un par de problemas.

  • Reprocesa todo, lo que es excesivo; solo aquellos campos que tengan un "." En el nombre hay que reprocesar.
  • No puede manejar las matrices de la misma manera que lo hace el procesamiento nativo PHP, por ejemplo. para teclas como "foo.bar []".

La solución a continuación resuelve ambos problemas ahora (tenga en cuenta que se ha actualizado desde que se publicó originalmente). Esto es aproximadamente un 50% más rápido que mi respuesta anterior en mis pruebas, pero no manejará situaciones donde los datos tienen la misma clave (o una clave que se extrae de la misma forma, por ejemplo, foo.bar y foo_bar se extraen como foo_bar).

<?php

public function fix2(&$target, $source, $keep = false) {                       
    if (!$source) {                                                            
        return;                                                                
    }                                                                          
    preg_match_all(                                                            
        '/                                                                     
        # Match at start of string or &                                        
        (?:^|(?<=&))                                                           
        # Exclude cases where the period is in brackets, e.g. foo[bar.blarg]
        [^=&\[]*                                                               
        # Affected cases: periods and spaces                                   
        (?:\.|%20)                                                             
        # Keep matching until assignment, next variable, end of string or   
        # start of an array                                                    
        [^=&\[]*                                                               
        /x',                                                                   
        $source,                                                               
        $matches                                                               
    );                                                                         

    foreach (current($matches) as $key) {                                      
        $key    = urldecode($key);                                             
        $badKey = preg_replace('/(\.| )/', '_', $key);                         

        if (isset($target[$badKey])) {                                         
            // Duplicate values may have already unset this                    
            $target[$key] = $target[$badKey];                                  

            if (!$keep) {                                                      
                unset($target[$badKey]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              
2
El Yobo

Mi solución a este problema fue rápida y sucia, pero todavía me gusta. Simplemente quería publicar una lista de nombres de archivos que se verificaron en el formulario. Utilicé base64_encode para codificar los nombres de archivo dentro del marcado y luego lo descodifiqué con base64_decode antes de usarlos.

2
Jason

Mi solución actual (basada en las respuestas del tema anterior):

function parseQueryString($data)
{
    $data = rawurldecode($data);   
    $pattern = '/(?:^|(?<=&))[^=&\[]*[^=&\[]*/';       
    $data = preg_replace_callback($pattern, function ($match){
        return bin2hex(urldecode($match[0]));
    }, $data);
    parse_str($data, $values);

    return array_combine(array_map('hex2bin', array_keys($values)), $values);
}

$_GET = parseQueryString($_SERVER['QUERY_STRING']);
0
sasha-ch

Usando crb's quería recrear la matriz $_POST como un todo, pero tenga en cuenta que todavía tendrá que asegurarse de que está codificando y decodificando correctamente tanto en el cliente como en el servidor. Es importante entender cuándo un personaje es verdaderamente inválido y es verdaderamente válido. Además, las personas deben aún y siempre escapar de los datos del cliente antes de usarlos con cualquier comando de base de datos sin excepción.

<?php
unset($_POST);
$_POST = array();
$p0 = explode('&',file_get_contents('php://input'));
foreach ($p0 as $key => $value)
{
 $p1 = explode('=',$value);
 $_POST[$p1[0]] = $p1[1];
 //OR...
 //$_POST[urldecode($p1[0])] = urldecode($p1[1]);
}
print_r($_POST);
?>

Recomiendo usar esto solo para casos individuales, pero no estoy seguro de los puntos negativos de poner esto en la parte superior de su archivo de encabezado principal.

0
John

Bueno, la función que incluyo a continuación, "getRealPostArray ()", no es una solución bonita, pero maneja matrices y admite ambos nombres: "alpha_beta" y "alpha.beta":

  <input type='text' value='First-.' name='alpha.beta[a.b][]' /><br>
  <input type='text' value='Second-.' name='alpha.beta[a.b][]' /><br>
  <input type='text' value='First-_' name='alpha_beta[a.b][]' /><br>
  <input type='text' value='Second-_' name='alpha_beta[a.b][]' /><br>

mientras que var_dump ($ _ POST) produce:

  'alpha_beta' => 
    array (size=1)
      'a.b' => 
        array (size=4)
          0 => string 'First-.' (length=7)
          1 => string 'Second-.' (length=8)
          2 => string 'First-_' (length=7)
          3 => string 'Second-_' (length=8)

var_dump (getRealPostArray ()) produce:

  'alpha.beta' => 
    array (size=1)
      'a.b' => 
        array (size=2)
          0 => string 'First-.' (length=7)
          1 => string 'Second-.' (length=8)
  'alpha_beta' => 
    array (size=1)
      'a.b' => 
        array (size=2)
          0 => string 'First-_' (length=7)
          1 => string 'Second-_' (length=8)

La función, por lo que vale:

function getRealPostArray() {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST') {#Nothing to do
      return null;
  }
  $neverANamePart = '~#~'; #Any arbitrary string never expected in a 'name'
  $postdata = file_get_contents("php://input");
  $post = [];
  $rebuiltpairs = [];
  $postraws = explode('&', $postdata);
  foreach ($postraws as $postraw) { #Each is a string like: 'xxxx=yyyy'
    $keyvalpair = explode('=',$postraw);
    if (empty($keyvalpair[1])) {
      $keyvalpair[1] = '';
    }
    $pos = strpos($keyvalpair[0],'%5B');
    if ($pos !== false) {
      $str1 = substr($keyvalpair[0], 0, $pos);
      $str2 = substr($keyvalpair[0], $pos);
      $str1 = str_replace('.',$neverANamePart,$str1);
      $keyvalpair[0] = $str1.$str2;
    } else {
      $keyvalpair[0] = str_replace('.',$neverANamePart,$keyvalpair[0]);
    }
    $rebuiltpair = implode('=',$keyvalpair);
    $rebuiltpairs[]=$rebuiltpair;
  }
  $rebuiltpostdata = implode('&',$rebuiltpairs);
  parse_str($rebuiltpostdata, $post);
  $fixedpost = [];
  foreach ($post as $key => $val) {
    $fixedpost[str_replace($neverANamePart,'.',$key)] = $val;
  }
  return $fixedpost;
}
0
ChrisNY