Faça com que o PHP pare de substituir '.' caracteres em matrizes $ _Get ou $ _Post?

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

  •  09-06-2019
  •  | 
  •  

Pergunta

Se eu passar variáveis ​​PHP com . em seus nomes via $_GET PHP os substitui automaticamente por _ personagens.Por exemplo:

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

...gera o seguinte:

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

...minha pergunta é esta:existe qualquer maneira de fazer com que isso pare?Não consigo descobrir o que fiz para merecer isso

A versão do PHP que estou executando é 5.2.4-2ubuntu5.3.

Foi útil?

Solução

Aqui está a explicação do PHP.net sobre por que isso acontece:

Pontos em nomes de variáveis ​​recebidas

Normalmente, o PHP não altera os nomes das variáveis ​​quando são passadas para um script.No entanto, deve -se notar que o ponto (período, parada completa) não é um caractere válido em um nome de variável php.Pelo motivo, olhe para ele:

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

Agora, o que o analisador vê é uma variável chamada $ varname, seguido pelo operador de concatenação de string, seguido pelo barestring (ou seja,string não cotada que não corresponde a nenhuma chave conhecida ou palavras reservadas) 'ext'.Obviamente, isso não tem o resultado pretendido.

Por esse motivo, é importante observar que o PHP substituirá automaticamente quaisquer pontos nos nomes de variáveis ​​recebidos por sublinhados.

Isso é de http://ca.php.net/variables.external.

Também, de acordo com este comentário esses outros caracteres são convertidos em sublinhados:

A lista completa de caracteres de nome de campo que o PHP converte para _ (sublinhado) é a seguinte (não apenas ponto):

  • chr(32) ( ) (espaço)
  • chr(46) (.) (ponto)
  • chr(91) ([) (colchete aberto)
  • chr(128) - chr(159) (vários)

Parece que você está preso a isso, então você terá que converter os sublinhados novamente em pontos no seu script usando sugestão de Dawnerd (eu usaria apenas str_replace no entanto.)

Outras dicas

Pergunta respondida há muito tempo, mas na verdade existe uma resposta melhor (ou solução alternativa).PHP permite que você no fluxo de entrada bruto, então você pode fazer algo assim:

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

que fornecerá o array $_POST no formato de string de consulta, com períodos como deveriam ser.

Você pode então analisá-lo se precisar (conforme Comentário do 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'); }
?>

Muito útil para parâmetros OpenID, que contêm os dois ''. e '_', cada um com um certo significado!

Destacando uma resposta real de Johan em um comentário acima - acabei de agrupar toda a minha postagem em uma matriz de nível superior que contorna completamente o problema sem a necessidade de processamento pesado.

Na forma que você faz

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

em vez de

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

e no manipulador de postagem, apenas desembrulhe:

$posdata = $_POST['data'];

Para mim, esta foi uma mudança de duas linhas, já que minhas opiniões eram inteiramente padronizadas.

PARA SUA INFORMAÇÃO.Estou usando pontos nos nomes dos meus campos para editar árvores de dados agrupados.

O funcionamento desta função é um truque genial que criei durante minhas férias de verão em 2013.Escreverei um post no blog sobre isso algum dia.

Esta correção funciona universalmente e tem suporte profundo a array, por exemplo a.a[x][b.a]=10.Ele usa parse_str() nos bastidores com algum pré-processamento.

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

E então você pode chamar essa função assim, dependendo da fonte:

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

Para PHP abaixo de 5.4: usar base64_encode em vez de bin2hex e base64_decode em vez de hex2bin.

Isso acontece porque um ponto final é um caractere inválido no nome de uma variável, o razão para o qual está muito profundo na implementação do PHP, portanto não há soluções fáceis (ainda).

Enquanto isso, você pode contornar esse problema:

  1. Acessando os dados brutos da consulta por meio de php://input para dados POST ou $_SERVER['QUERY_STRING'] para obter dados
  2. Usando uma função de conversão.

A função de conversão abaixo (PHP >= 5.4) codifica os nomes de cada par de valores-chave em uma representação hexadecimal e, em seguida, executa uma operação regular parse_str();uma vez feito isso, ele reverte os nomes hexadecimais à sua 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']);

Ou:

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

Esta abordagem é uma versão alterada de Rok Kralj, mas com alguns ajustes para funcionar, para melhorar a eficiência (evita retornos de chamada desnecessários, codificação e decodificação em chaves não afetadas) e para lidar corretamente com chaves de array.

A essência com testes está disponível e qualquer feedback ou sugestão é bem-vindo aqui ou ali.

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

A razão pela qual isso acontece é por causa da antiga funcionalidade register_globals do PHP.O .caractere não é um caractere válido em um nome de variável, então o PHP o converte em um sublinhado para garantir que haja compatibilidade.

Resumindo, não é uma boa prática colocar pontos em variáveis ​​de URL.

Se procura qualquer forma de literalmente Faça com que o PHP pare de substituir '.' caracteres em matrizes $ _Get ou $ _Post, então uma dessas maneiras é modificar a fonte do PHP (e, nesse caso, é relativamente simples).

AVISO:Modificar a fonte PHP C é uma opção avançada!

Veja também isso Relatório de bug do PHP o que sugere a mesma modificação.

Para explorar você precisará:

  • download Código fonte C do PHP
  • desabilitar o . verificação de substituição
  • ./configure, fazer e implante sua versão personalizada de PHP

A mudança de origem em si é trivial e envolve a atualização apenas metade de uma linha em 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='_';
....

Observação:comparado ao original || *p == '.' foi comentado


Exemplo de saída:

recebeu uma QUERY_STRING de a.a[]=bb&a.a[]=BB&c%20c=dd, correndo <?php print_r($_GET); agora produz:

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

    [c_c] => dd
)

Notas:

  • este patch aborda apenas a questão original (interrompe a substituição de pontos, não de espaços).
  • a execução neste patch será mais rápida do que as soluções em nível de script, mas essas respostas pure-.php ainda são geralmente preferíveis (porque evitam alterar o próprio PHP).
  • em teoria, uma abordagem polyfill é possível aqui e poderia combinar abordagens - teste a mudança de nível C usando parse_str() e (se não estiver disponível) recorrer a métodos mais lentos.

Minha solução para esse problema foi rápida e suja, mas ainda gosto dela.Eu simplesmente queria postar uma lista de nomes de arquivos que foram verificados no formulário.eu usei base64_encode para codificar os nomes dos arquivos dentro da marcação e depois apenas decodificá-los com base64_decode antes de usá-los.

Depois de analisar a solução de Rok, criei uma versão que aborda as limitações da minha resposta abaixo, do crb acima e da solução de Rok também.Ver um minha versão melhorada.


Resposta do @crb acima é um bom começo, mas há alguns problemas.

  • Reprocessa tudo, o que é um exagero;Somente aqueles campos que têm um "". Em nome, precisa ser reprocessado.
  • Ele não consegue lidar com matrizes da mesma maneira que o processamento nativo do PHP, por exemplo.para chaves como "foo.bar[]".

A solução abaixo resolve esses dois problemas agora (observe que ela foi atualizada desde a publicação original).Isso é cerca de 50% mais rápido do que minha resposta acima em meus testes, mas não irá lidar com situações em que os dados tenham a mesma chave (ou uma chave que seja extraída da mesma forma, por exemplofoo.bar e foo_bar são extraídos 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              

Bem, a função que incluo abaixo, "getRealPostArray()", não é uma solução bonita, mas lida com arrays e suporta ambos os nomes:"alpha_beta" e "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>

enquanto var_dump($_POST) produz:

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

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

A função, pelo 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;
}

Usando crb's eu queria recriar o $_POST array como um todo, porém, lembre-se de que você ainda precisará garantir que está codificando e decodificando corretamente tanto no cliente quanto no servidor.É importante entender quando um personagem é verdadeiramente inválido e é verdadeiramente válido.Além disso, as pessoas deveriam ainda e sempre escapar dos dados do cliente antes de usá-los com qualquer comando de banco de dados sem exceção.

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

Eu recomendo usar isso apenas para casos individuais. De imediato, não tenho certeza sobre os pontos negativos de colocar isso no topo do seu arquivo de cabeçalho principal.

Minha solução atual (com base nas respostas do tópico 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']);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top