it-swarm-ru.tech

Получите 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.

70
Dave Carpeneto

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

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

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

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

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

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

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

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

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

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

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

63
Jeremy Ruten

Давно ответил на вопрос, но на самом деле есть лучший ответ (или обходной путь). 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, которые содержат оба символа '.' и '_', каждый с определенным значением!

57
crb

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

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

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

26
scipilot

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

17
Rok Kralj

Это происходит из-за того, что точка является недопустимым символом в имени переменной, причина , для которой очень глубоко заложена реализация 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'));
6
Ja͢ck

Этот подход представляет собой измененную версию 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;                                              
        }                                                                      
    }                                                                          
}                                                                              
5
El Yobo

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

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

4
Jeremy Privett

Если вы ищете любой способ буквально получить 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 
 (
 [Aa] => Array 
 (
 [0] => bb 
 [1] => BB 
) 
 
 [C_c] => дд 
) 

Примечания:

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

Посмотрев на решение 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              
2
El Yobo

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

2
Jason

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

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

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

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

0
John

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