Заставить PHP прекратить замену символов '.' в массивах $_GET или $ _POST?

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

  •  09-06-2019
  •  | 
  •  

Вопрос

Если я передам переменные 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.

Это было полезно?

Решение

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

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

Как правило, PHP не изменяет имена переменных, когда они передаются в скрипт.Однако следует отметить, что точка (точка, полная остановка) не является допустимым символом в имени переменной 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) (разные)

Похоже, вы застряли на этом, поэтому вам придется преобразовать подчеркивания обратно в точки в вашем скрипте, используя предложение даунерда (Я бы просто использовал str_replace - str_replace хотя.)

Другие советы

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

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

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

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

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

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

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

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

<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">  

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

$posdata = $_POST['data'];

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

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

Работа этой функции - гениальный хак, который я придумал во время своих летних каникул в 2013 году.Когда-нибудь я напишу об этом пост в блоге.

Это исправление работает универсально и имеет глубокую поддержку массивов, например a.a[x][b.a]=10.Он использует parse_str() за кулисами с некоторой предварительной обработкой.

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

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

$_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.

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

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

  1. Доступ к необработанным данным запроса осуществляется либо через php://input для отправки данных или $_SERVER['QUERY_STRING'] для ПОЛУЧЕНИЯ данных
  2. Использование функции преобразования.

Приведенная ниже функция преобразования (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'));

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

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

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

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

Короче говоря, не рекомендуется ставить точки в переменных URL.

Если вы ищете Любой способ буквально заставьте 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 == '.' был прокомментирован


Пример Вывода:

дана СТРОКА запроса из a.a[]=bb&a.a[]=BB&c%20c=dd, бегущий <?php print_r($_GET); теперь производит:

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

    [c_c] => dd
)

Примечания:

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

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

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


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

  • Он перерабатывает все, что является излишеством;только те поля, в названии которых есть ".", нуждаются в повторной обработке.
  • Он не может обрабатывать массивы таким же образом, как это делает встроенная обработка 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              

Ну, функция, которую я включаю ниже, "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;
}

Используя 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);
?>

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

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

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']);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top