Получить PHP, чтобы остановить замену "." символы в массивах $ _GET или $ _POST? - PullRequest
69 голосов
/ 16 сентября 2008

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

Ответы [ 13 ]

63 голосов
/ 16 сентября 2008

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

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

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

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

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

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

Это от http://ca.php.net/variables.external.

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

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

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

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

55 голосов
/ 21 декабря 2009

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

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

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

Затем вы можете разобрать его, если вам нужно (согласно Комментарий 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'); }
?>

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

25 голосов
/ 04 декабря 2013

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

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

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

17 голосов
/ 13 августа 2013

Работа этой функции - гениальный хак, который я придумал во время моих летних каникул в 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.

6 голосов
/ 21 января 2013

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

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

  1. Доступ к необработанным данным запроса через php://input для данных POST или $_SERVER['QUERY_STRING'] для данных GET
  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'));
5 голосов
/ 10 августа 2013

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

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

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;                                              
        }                                                                      
    }                                                                          
}                                                                              
4 голосов
/ 16 сентября 2008

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

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

3 голосов
/ 15 августа 2013

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

ПРЕДУПРЕЖДЕНИЕ. Изменение исходного кода PHP C является расширенной опцией!

См. Также Отчет об ошибках PHP , в котором предлагается то же изменение.

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

  • скачать Исходный код PHP C
  • отключить проверку . замены
  • . / Configure , make и развертывание собственной сборки 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
)

Примечания:

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

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


@ ответ 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              
2 голосов
/ 08 февраля 2011

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

...