Получите PHP, чтобы остановить замену. символов в массивах $_GET или $_POST?

Если я передаю переменные PHP с . в своих именах через $_GET PHP, автоматически заменяет их символами _. Например:

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

... выводит следующее:

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

... мой вопрос таков: существует ли любой способ остановить это? Невозможно для жизни меня выяснить, что я сделал, чтобы заслужить это

PHP-версия, с которой я работаю, - 5.2.4-2ubuntu5.3.

Ответ 1

Здесь объяснение PHP.net о том, почему оно это делает:

Точки в именах входящих переменных

Как правило, PHP не изменяет имена переменных, когда они перешел в script. Однако это следует отметить, что точка (период, полная остановка) не является допустимым символом в имя переменной PHP. По причине, посмотрите на это:

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

Теперь, что синтаксический анализатор видит переменную с именем $varname, за которым следует строка оператор конкатенации, за которым следует запрет (т.е. некорректная строка который не соответствует ни одному известному ключу или зарезервированные слова) 'ext'. Очевидно, что это не имеет ожидаемого результата.

По этой причине важно обратите внимание, что PHP автоматически замените любые точки входящей переменной имена с символами подчеркивания.

Что из http://ca.php.net/variables.external.

Кроме того, согласно этот комментарий, эти другие символы преобразуются в символы подчеркивания:

Полный список символов имени поля, который PHP преобразует в _ (подчеркивание), следующий (а не только точка):

  • chr (32)() (пробел)
  • chr (46) (.) (точка)
  • chr (91) ([) (открытая квадратная скобка)
  • chr (128) - chr (159) (различные)

Итак, похоже, что вы застряли в нем, поэтому вам нужно будет преобразовать символы подчеркивания обратно в точки script с помощью предложения dawnerd (I 'd просто используйте str_replace, хотя.)

Ответ 2

Долгое время ответил на вопрос, но на самом деле есть лучший ответ (или обход). PHP позволяет вам использовать исходный поток потока, поэтому вы можете сделать что-то вроде этого:

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

который даст вам массив $_POST в формате строки запроса, периоды, как они должны быть.

Затем вы можете проанализировать его, если вам нужно (согласно комментарий POSTer)

<?php
// Function to fix up PHP 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'); }
?>

Очень полезно для параметров OpenID, которые содержат как.. и "_", каждый из которых имеет определенный смысл!

Ответ 3

Выделение фактического ответа Johan в комментарии выше - я просто завернул весь свой пост в массиве верхнего уровня, который полностью обходит проблему без необходимости тяжелой обработки.

В форме, которую вы делаете

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

вместо

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

и в обработчике сообщения просто разверните его:

$posdata = $_POST['data'];

Для меня это было двухстрочное изменение, так как мои взгляды были полностью замаскированы.

FYI. Я использую точки в именах полей для редактирования деревьев сгруппированных данных.

Ответ 4

Это исправление работает повсеместно и поддерживает массив, например a[2][5]=10.

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

    parse_str($source, $post);
    return array_combine(array_map('hex2bin', array_keys($post)), $post);
}

И тогда вы можете вызвать эту функцию, как это, в зависимости от источника:

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

Для PHP ниже 5.4: используйте base64_encode вместо bin2hex и base64_decode вместо hex2bin.

Ответ 5

Это происходит потому, что период является недопустимым символом в имени переменной, причина, которая очень глубока в реализации PHP, поэтому нет простых исправлений (пока).

Тем временем вы можете обойти эту проблему:

  • Доступ к необработанным данным запроса через php://input для данных POST или $_SERVER['QUERY_STRING'] для данных GET
  • Использование функции преобразования.

Следующая функция преобразования (PHP >= 5.4) кодирует имена каждой пары ключ-значение в шестнадцатеричное представление и затем выполняет регулярный parse_str(); после выполнения он возвращает шестнадцатеричные имена обратно в их исходную форму:

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

Или:

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

Ответ 6

Причина, по которой это происходит, связана с функциональностью PHP old register_globals.. символ не является допустимым символом в имени переменной, поэтому PHP скрывает его до подчеркивания, чтобы убедиться в его совместимости.

Короче говоря, это не очень хорошая практика, чтобы делать периоды в переменных URL.

Ответ 7

Этот подход представляет собой измененную версию Rok Kralj's, но с некоторой настройкой для работы, чтобы повысить эффективность (избегает ненужных обратных вызовов, кодирования и декодирования на незатронутых ключах) и правильно обрабатывать ключи массива.

A gist with tests доступен, и любые отзывы или предложения приветствуются здесь или там.

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

Ответ 8

Если вы ищете любой способ буквально заставить PHP перестать заменять '.' символов в массивах $_GET или $_POST, то одним из таких способов является изменение источника PHP (и в этом случае это относительно просто).

ПРЕДУПРЕЖДЕНИЕ: изменение источника PHP C является расширенным вариантом!

Также см. этот отчет об ошибке PHP, который предлагает ту же самую модификацию.

Для изучения вам понадобится:

  • скачать PHP C исходный код
  • отключить проверку замены .
  • ./настроить, создать и развернуть свою настроенную сборку PHP

Само изменение источника тривиально и включает обновление одной половины одной строки в 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='_';
....

Примечание: по сравнению с оригиналом || *p == '.' был закомментирован


Результат:

задав QUERY_STRING a.a[]=bb&a.a[]=BB&c%20c=dd, запуск <?php print_r($_GET); теперь производит:

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

    [c_c] => dd
)

Примечания:

  • этот патч предназначен только для исходного вопроса (он останавливает замещение точек, а не пробелов).
  • работа над этим патчем будет быстрее, чем script -уровневые решения, но эти ответы pure-.php по-прежнему предпочтительнее (потому что они не меняют самого PHP).
  • в теории возможен подход polyfill и может сочетать подходы - тест для изменения уровня C с использованием parse_str() и (если он недоступен) отпадает назад к более медленным методам.

Ответ 9

Посмотрев на решение Rok, я придумал версию, в которой рассматриваются ограничения в моем ответе ниже, crb выше и решение Rok. См. мою улучшенную версию.


@crb answer выше - хорошее начало, но есть несколько проблем.

  • Он перерабатывает все, что излишне; только те поля, у которых есть "." в названии необходимо перерабатывать.
  • Он не может обрабатывать массивы так же, как это делает встроенная обработка PHP. для таких клавиш, как "foo.bar []".

В приведенном ниже решении рассматриваются обе эти проблемы (обратите внимание, что он был обновлен с момента публикации). Это примерно на 50% быстрее, чем мой ответ выше в моем тестировании, но не будет обрабатывать ситуации, когда данные имеют один и тот же ключ (или ключ, который извлекается одинаково, например, foo.bar и foo_bar оба извлекаются как 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              

Ответ 10

Мое решение этой проблемы было быстрым и грязным, но мне все еще нравится. Я просто хотел опубликовать список имен файлов, которые были проверены в форме. Я использовал base64_encode для кодирования имен файлов в разметке, а затем просто расшифровал его с помощью base64_decode до их использования.

Ответ 11

..

Почему бы вам просто не преобразовать все точки в какой-то токен, например, (~ # ~), а затем опубликовать его? При приеме варов вы можете восстановить их обратно. Это потому, что нам иногда нужно размещать символы подчеркивания.. и мы потеряли бы их, если бы перевернули все "_" на "." S...

Ответ 12

Ну, функция, которую я включил ниже, "getRealPostArray()", не является довольно решением, но она обрабатывает массивы и поддерживает оба имени: "alpha_beta" и "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>

тогда как var_dump ($ _ POST) производит:

  '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()) производит:

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

Функция, для чего она стоит:

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

Ответ 13

Используя crb, я хотел воссоздать массив $_POST в целом, но имейте в виду, что вам все равно нужно будет правильно кодировать и декодировать как на клиенте, так и на сервере. Важно понимать, когда персонаж действительно недействителен, и он действительно действителен. Кроме того, люди должны все равно и всегда избегать клиентских данных, прежде чем использовать их без каких-либо исключений из любой команды базы данных.

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

Я рекомендую использовать это только для отдельных случаев, но я не уверен в отрицательных точках размещения этого в верхней части основного файла заголовка.

Ответ 14

Мое текущее решение (на основе ответов на предыдущие темы):

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