web-dev-qa-db-fra.com

Obtenez PHP pour arrêter de remplacer les caractères '.' Dans les tableaux $ _GET ou $ _POST?

Si je passe PHP variables avec . dans leurs noms via $ _GET PHP les remplace automatiquement par _ personnages. Par exemple:

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

... génère les éléments suivants:

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

... ma question est la suivante: y a-t-il tout moyen pour que cela s'arrête? Je ne peux pas pour la vie de moi comprendre ce que j'ai fait pour mériter ça

La version PHP avec laquelle je cours est 5.2.4-2ubuntu5.3.

70
Dave Carpeneto

Voici pourquoi PHP.net explique pourquoi il le fait:

Points dans les noms de variables entrants

En règle générale, PHP ne modifie pas les noms des variables lorsqu'elles sont passées dans un script. Cependant, il convient de noter que le point (point, point final) n'est pas un caractère valide dans a = PHP nom de variable. Pour la raison, regardez-le:

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

Maintenant, ce que l'analyseur voit est une variable nommée $ varname, suivie de l'opérateur de concaténation de chaîne, suivie de la barre de chaîne (c'est-à-dire une chaîne sans guillemets qui ne correspond à aucune clé connue ou mot réservé) `` ext ''. De toute évidence, cela n'a pas le résultat escompté.

Pour cette raison, il est important de noter que PHP remplacera automatiquement tous les points des noms de variables entrants par des traits de soulignement).

Cela vient de http://ca.php.net/variables.external .

De plus, selon ce commentaire ces autres caractères sont convertis en traits de soulignement:

La liste complète des caractères de nom de champ que PHP convertit en _ (soulignement) est la suivante (pas seulement un point):

  • chr (32) () (espace)
  • chr (46) (.) (point)
  • chr (91) ([) (crochet carré ouvert)
  • chr (128) - chr (159) (divers)

Il semble donc que vous y soyez coincé, vous devrez donc reconvertir les traits de soulignement en points dans votre script en utilisant suggestion de dawnerd (je voudrais simplement utiliser str_replace bien que.)

63
Jeremy Ruten

Réponse à la question depuis longtemps, mais il existe en fait une meilleure réponse (ou solution de rechange). PHP vous permet au flux d'entrée brut , vous pouvez donc faire quelque chose comme ceci:

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

qui vous donnera le tableau $ _POST au format de chaîne de requête, les périodes comme elles devraient l'être.

Vous pouvez ensuite l'analyser si vous en avez besoin (selon commentaire de l'AFFICHE )

<?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'); }
?>

Extrêmement utile pour les paramètres OpenID, qui contiennent les deux '.' et '_', chacun avec une certaine signification!

57
crb

Soulignant une réponse réelle de Johan dans un commentaire ci-dessus - je viens d'envelopper tout mon message dans un tableau de niveau supérieur qui contourne complètement le problème sans aucun traitement lourd requis.

Dans le formulaire que vous faites

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

au lieu de

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

et dans le gestionnaire de messages, décompressez-le simplement:

$posdata = $_POST['data'];

Pour moi, c'était un changement sur deux lignes, car mes opinions étaient entièrement basées sur des modèles.

Pour info. J'utilise des points dans les noms de mes champs pour modifier les arbres de données groupées.

26
scipilot

Le fonctionnement de cette fonction est un hack de génie que j'ai trouvé pendant mes vacances d'été en 2013. J'écrirai un blog à ce sujet un jour.

Ce correctif fonctionne de manière universelle et prend en charge un réseau étendu, par exemple a.a[x][b.a]=10. Il utilise parse_str() en arrière-plan avec un certain prétraitement.

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;
}

Et puis vous pouvez appeler cette fonction comme ceci, selon la source:

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

Pour PHP ci-dessous 5.4: utilisez base64_encode Au lieu de bin2hex Et base64_decode Au lieu de hex2bin.

18
Rok Kralj

Cela se produit car un point est un caractère invalide dans le nom d'une variable, le raison pour lequel se trouve très profondément dans l'implémentation de PHP, donc il n'y a pas encore de correctifs faciles.

En attendant, vous pouvez contourner ce problème en:

  1. Accès aux données de requête brutes via php://input Pour POST données ou $_SERVER['QUERY_STRING'] Pour les données GET
  2. Utilisation d'une fonction de conversion.

La fonction de conversion ci-dessous (PHP> = 5.4) code les noms de chaque paire clé-valeur en une représentation hexadécimale, puis exécute une parse_str() régulière; une fois cela fait, il rétablit les noms hexadécimaux dans leur forme d'origine:

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']);

Ou:

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

Cette approche est une version modifiée de Rok Kralj, mais avec quelques ajustements pour fonctionner, pour améliorer l'efficacité (évite les rappels inutiles, l'encodage et le décodage sur les clés non affectées) et pour gérer correctement les clés de tableau.

Un Gist with tests est disponible et tous les commentaires ou suggestions sont les bienvenus ici ou là.

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 raison pour laquelle cela se produit est à cause de l'ancienne fonctionnalité register_globals de PHP. Le . caractère n'est pas un caractère valide dans un nom de variable, donc PHP le recouvre en un trait de soulignement afin de s'assurer qu'il y a compatibilité.

En bref, ce n'est pas une bonne pratique de faire des périodes dans les variables URL.

4
Jeremy Privett

Si vous recherchez n'importe quel moyen de littéralement obtenez PHP pour arrêter de remplacer '. 'dans les tableaux $ _GET ou $ _POST, alors une de ces façons est de modifier la source de PHP (et dans ce cas, c'est relativement simple).

AVERTISSEMENT: Modifier PHP La source C est une option avancée !

Voir aussi ceci rapport de bogue PHP qui suggère la même modification.

Pour explorer, vous devrez:

  • télécharger code source C de PHP
  • désactiver la vérification de remplacement .
  • ./configure, make et déployez votre build personnalisé de PHP

Le changement de source lui-même est trivial et implique une mise à jour juste la moitié d'une ligne dans 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='_';
....

Remarque: par rapport à l'original || *p == '.' A été commenté


Exemple de sortie:

étant donné un QUERY_STRING de a.a[]=bb&a.a[]=BB&c%20c=dd, l'exécution de <?php print_r($_GET); produit maintenant:

 Tableau 
 (
 [Aa] => Tableau 
 (
 [0] => bb 
 [1] => BB 
) 
 
 [C_c] => jj 
) 

Remarques:

  • ce patch ne répond qu'à la question d'origine (il arrête le remplacement des points, pas des espaces).
  • l'exécution de ce correctif sera plus rapide que les solutions de niveau script, mais ces réponses au format .php pur sont toujours préférables (car elles évitent de changer PHP lui-même).
  • en théorie, une approche polyfill est possible ici et pourrait combiner des approches - tester le changement de niveau C en utilisant parse_str() et (si non disponible) revenir à des méthodes plus lentes.
3
humbletim

Après avoir examiné la solution de Rok, j'ai trouvé une version qui corrige les limitations dans ma réponse ci-dessous, crb ci-dessus et la solution de Rok également. Voir un ma version améliorée .


La réponse de @ crb ci-dessus est un bon début, mais il y a quelques problèmes.

  • Il retire tout, ce qui est exagéré; seuls les champs qui ont un "." au nom doivent être retraités.
  • Il ne parvient pas à gérer les tableaux de la même manière que le traitement natif PHP, par exemple pour les clés comme "foo.bar []").

La solution ci-dessous résout maintenant ces deux problèmes (notez qu'elle a été mise à jour depuis sa publication initiale). C'est environ 50% plus rapide que ma réponse ci-dessus dans mes tests, mais ne gérera pas les situations où les données ont la même clé (ou une clé qui est extraite de la même manière, par exemple foo.bar et foo_bar sont toutes deux extraites en tant que 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

Ma solution à ce problème a été rapide et sale, mais je l'aime toujours. Je voulais simplement publier une liste de noms de fichiers qui ont été vérifiés sur le formulaire. J'ai utilisé base64_encode pour encoder les noms de fichiers dans le balisage, puis juste les décoder avec base64_decode avant de les utiliser.

2
Jason

Ma solution actuelle (basée sur les réponses au sujet précédent):

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

En utilisant crb, je voulais recréer le $_POST tableau dans son ensemble, mais gardez à l'esprit que vous devrez toujours vous assurer que vous codez et décodez correctement à la fois sur le client et sur le serveur. Il est important de comprendre quand un caractère est vraiment invalide et c'est vraiment valide. De plus, les utilisateurs doivent toujours et toujours échapper les données du client avant de les utiliser avec tout commande de base de données sans exception.

<?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);
?>

Je recommande de n'utiliser cela que pour des cas individuels, mais je ne suis pas sûr des points négatifs de la placer en haut de votre fichier d'en-tête principal.

0
John

Eh bien, la fonction que j'inclus ci-dessous, "getRealPostArray ()", n'est pas une jolie solution, mais elle gère les tableaux et prend en charge les deux noms: "alpha_beta" et "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>

tandis que var_dump ($ _ POST) produit:

  '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 ()) produit:

  '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 fonction, pour ce qu'elle vaut:

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