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

StackOverflow https://stackoverflow.com/questions/68651

  •  09-06-2019
  •  | 
  •  

Question

Si je transmets des variables PHP avec . leurs noms via $ _GET, PHP les remplace automatiquement par _ caractères. 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: existe-t-il une manière de faire en sorte que cela cesse? Je ne peux pas comprendre ce que j’ai fait pour mériter cela

La version de PHP que je lance est la 5.2.4-2ubuntu5.3.

Était-ce utile?

La solution

Voici l'explication de PHP.net expliquant pourquoi il le fait:

  

Points dans les noms de variables entrantes

     

Typiquement, PHP ne modifie pas la   noms de variables quand ils sont   passé dans un script. Cependant, il   Il convient de noter que le point (période,   point final) n’est pas un caractère valide dans   un nom de variable PHP. Pour la raison,   regardez-le:

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

Maintenant, quoi   l'analyseur voit est une variable nommée   $ varname, suivi de la chaîne   concaténation, suivi de   le barestring (c'est-à-dire une chaîne non citée)   qui ne correspond à aucune clé connue ou   mots réservés) 'ext'. De toute évidence, cette   n'a pas le résultat escompté.

     

Pour cette raison, il est important de   notez que PHP sera automatiquement   remplacer les points dans la variable entrante   noms avec des traits de soulignement.

Il s'agit de http://ca.php.net/variables.external .

En outre, selon ce commentaire , ces autres les caractères sont convertis en caractères de soulignement:

  

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

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

On dirait donc que vous êtes coincé avec cela, vous devrez donc reconvertir les traits de soulignement en points dans votre script en utilisant La suggestion de dawnerd (j'utiliserais simplement str_replace cependant.)

Autres conseils

Cela fait longtemps que la question a été répondue, mais il existe une meilleure réponse (ou solution de contournement). PHP vous permet au flux d'entrée brut de vous permettre d'effectuer les opérations suivantes:

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

qui vous donnera le tableau $ _POST au format chaîne de requête, les points comme il se doit.

Vous pouvez ensuite l'analyser si nécessaire (conformément à de POSTer commentaire )

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

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

Mettre en évidence une réponse 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 traitement lourd requis.

Sous la forme 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 publication, il suffit de le dérouler:

$posdata = $_POST['data'];

Pour moi, il s’agissait d’un changement de deux lignes, mes vues étant entièrement basées sur un modèle.

FYI. J'utilise des points dans mes noms de champs pour éditer des arbres de données groupées.

Le fonctionnement de cette fonction est un coup génial que j'ai découvert pendant mes vacances d'été en 2013. Je rédigerai un article à ce sujet un jour sur le blog.

Ce correctif fonctionne de manière universelle et prend en charge les baies profondes, par exemple a.a[x][b.a]=10. Il utilise parse_str() dans les coulisses avec certains prétraitements.

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

Ensuite, vous pouvez appeler cette fonction comme suit, en fonction de la source:

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

Pour PHP inférieur à 5.4: , utilisez base64_encode au lieu de bin2hex et base64_decode au lieu de hex2bin.

Cela se produit car un point est un caractère non valide dans le nom d'une variable, le raison pour laquelle réside très profondément dans la mise en œuvre de PHP, donc il n'y a pas de solution facile (pour le moment).

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

  1. Accès aux données de requête brutes via php://input pour les données POST 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 dans une représentation hexadécimale, puis effectue une analyse régulière parse_str(); Une fois cela fait, il rétablit les noms hexadécimaux dans leur forme originale:

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

Cette approche est une version modifiée de celle de Rok Kralj, mais avec quelques ajustements nécessaires pour améliorer l'efficacité (évite les rappels inutiles, l'encodage et le décodage sur des clés non affectées) et la gestion correcte des clés de matrice.

Un résumé avec des tests est disponible et tout commentaire ou suggestion est le bienvenu 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;                                              
        }                                                                      
    }                                                                          
}                                                                              

Cela s’explique par 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 d'un trait de soulignement afin de s'assurer de la compatibilité.

En bref, ce n'est pas une bonne pratique de faire des points dans les variables d'URL.

Si vous recherchez une méthode pour littéralement , PHP cesse de remplacer "." caractères dans les tableaux $ _GET ou $ _POST, un de ces moyens consiste à modifier le code source de PHP (et dans ce cas, il est relativement simple).

AVERTISSEMENT: la modification de la source PHP C est une option avancée!

Voir également ce rapport de bogue PHP suggérant la même modification.

Pour explorer, vous devez:

  • télécharger le code source C de PHP
  • désactiver le . contrôle de remplacement
  • ./ configure , fabriquez et déployez votre version personnalisée de PHP

Le changement de source lui-même est trivial et implique une mise à jour du site une moitié de 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é une QUERY_STRING de a.a[]=bb&a.a[]=BB&c%20c=dd, en cours d’exécution <?php print_r($_GET); produit maintenant:

Array
(
    [a.a] => Array
        (
            [0] => bb
            [1] => BB
        )

    [c_c] => dd
)

Notes:

  • ce correctif n’adresse que la question initiale (il arrête le remplacement des points, pas des espaces).
  • s'exécuter sur ce correctif sera plus rapide que les solutions au niveau script, mais ces réponses purement .php restent généralement 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 - testez le changement de niveau C en utilisant parse_str() et (si non disponible), utilisez les méthodes plus lentes.

Ma solution à ce problème était rapide et sale, mais je l’aime toujours. Je voulais simplement poster une liste de noms de fichiers cochés sur le formulaire. J'ai utilisé base64_encode pour encoder les noms de fichiers dans le balisage, puis je les ai décodés avec base64_decode avant de les utiliser.

Après avoir examiné la solution de Rok, je suis parvenu à une version qui répond aux limitations de ma réponse ci-dessous, à celle de crb ci-dessus et à celle de Rok. Voir un version améliorée .

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

  • Il retraite tout ce qui est excessif; 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 PHP natif, par exemple. pour des touches comme & "; foo.bar [] &";.

La solution ci-dessous résout maintenant ces deux problèmes (notez qu'elle a été mise à jour depuis la publication initiale). Ceci est environ 50% plus rapide que ma réponse ci-dessus dans mes tests, mais ne gérera pas les situations dans lesquelles 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 tous deux extraits sous la forme 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              

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 &. quot; 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>

alors 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 que ça 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;
}

À l'aide de crb, je souhaitais recréer l'ensemble $_POST tout en gardant à l'esprit que vous deviez toujours vous assurer que vous encodiez et décodiez correctement à la fois sur le client et sur le serveur. Il est important de comprendre quand un personnage est vraiment invalide et qu'il est vraiment valide . En outre, les utilisateurs doivent toujours et toujours échapper aux données du client avant de les utiliser avec une 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 vous recommande de ne l'utiliser que dans des cas individuels, mais je ne suis pas sûr des avantages de placer ceci en haut de votre fichier d'en-tête principal.

Ma solution actuelle (en fonction des réponses aux sujets précédents):

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']);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top