it-swarm-pt.tech

Obter PHP para parar de substituir caracteres '.' Em matrizes $ _GET ou $ _POST?

Se eu passar PHP variáveis ​​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 que eu possa fazer isso parar? Não posso pela minha vida descobrir o que fiz para merecer isso

A versão PHP com a qual estou executando é 5.2.4-2ubuntu5.3.

70
Dave Carpeneto

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

Pontos nos nomes de variáveis ​​recebidos

Normalmente, PHP não altera os nomes das variáveis ​​quando elas são passadas para um script. No entanto, deve-se notar que o ponto (ponto, ponto final) não é um caractere válido em um = PHP nome da variável. Por esse motivo, veja:

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

Agora, o que o analisador vê é uma variável chamada $ varname, seguida pelo operador de concatenação de cadeias, seguida pela restrição de barras (ou seja, cadeia não citada 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 PHP substituirá automaticamente qualquer ponto nos nomes de variáveis ​​recebidos por sublinhados.

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

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

A lista completa de caracteres de nome de campo que 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 em pontos no seu script usando sugestão de dawnerd (eu usaria str_replace Apesar.)

63
Jeremy Ruten

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

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

que fornecerá a matriz $ _POST no formato de sequência de consultas, períodos como deveriam ser.

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

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

57
crb

Destacando uma resposta real de Johan em um comentário acima - apenas envolvi minha postagem inteira em uma matriz de nível superior que ignora 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]">  

ao invés de

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

e no manipulador de postagem, basta desembrulhá-lo:

$posdata = $_POST['data'];

Para mim, essa foi uma mudança de duas linhas, pois minhas opiniões foram totalmente modeladas.

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

26
scipilot

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

Essa correção funciona universalmente e possui suporte a matriz profunda, 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: use base64_encode Em vez de bin2hex E base64_decode Em vez de hex2bin.

18
Rok Kralj

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

Enquanto isso, você pode solucionar esse problema:

  1. Acessando os dados brutos da consulta por meio de php://input Para POST data ou $_SERVER['QUERY_STRING'] Para dados GET
  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 parse_str() regular; uma vez feito, ele reverte os nomes hexadecimais de volta à 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'));
6
Ja͢ck

Essa abordagem é uma versão alterada do 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 manipular corretamente as chaves da matriz.

R Gist with tests 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;                                              
        }                                                                      
    }                                                                          
}                                                                              
5
El Yobo

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

Em resumo, não é uma boa prática fazer períodos em variáveis ​​de URL.

4
Jeremy Privett

Se estiver procurando qualquer maneira de literalmente get PHP para parar de substituir '). 'caracteres nas matrizes $ _GET ou $ _POST, então uma dessas maneiras é modificar a fonte do PHP (e, nesse caso, é relativamente simples).

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

Veja também este relatório de erros do PHP que sugere a mesma modificação.

Para explorar, você precisará:

  • download código fonte C do PHP
  • desative a verificação de substituição .
  • ./configure, make e implemente sua construção personalizada do PHP

A alteração da fonte 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='_';
....

Nota: em comparação com o original || *p == '.' Foi comentado


Saída de exemplo:

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

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

Notas:

  • este patch aborda apenas a pergunta original (para a substituição de pontos, não de espaços).
  • rodar neste patch será mais rápido que as soluções em nível de script, mas essas respostas .php puras ainda são geralmente preferíveis (porque evitam alterar PHP).
  • em teoria, uma abordagem de polyfill é possível aqui e pode combinar abordagens - teste a alteração no nível C usando parse_str() e (se não estiver disponível) retorne a métodos mais lentos.
3
humbletim

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


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

  • Ele reprocessa tudo o que é exagero; somente os campos que possuem um "." no nome precisa ser reprocessado.
  • Ele falha ao manipular matrizes da mesma maneira que o processamento nativo PHP, por exemplo, para chaves como "foo.bar []".

A solução abaixo aborda esses dois problemas agora (observe que ela foi atualizada desde a publicação original). Isso é cerca de 50% mais rápido que minha resposta acima em meus testes, mas não trata de situações em que os dados têm a mesma chave (ou uma chave extraída da mesma forma, por exemplo, foo.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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              
2
El Yobo

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

2
Jason

Minha solução atual (com base nas respostas de tópicos anteriores):

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 o crb, eu queria recriar o $_POST array como um todo, mas lembre-se de que você ainda precisará garantir que está codificando e decodificando corretamente no cliente e no servidor. É importante entender quando um personagem é verdadeiramente inválido e é verdadeiramente válido. Além disso, as pessoas devem ainda e sempre escapar dos dados do cliente antes de usá-los com any comando do 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 dos pontos negativos de colocá-lo no topo do seu arquivo de cabeçalho principal.

0
John

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

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