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

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

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

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

 url is /SpShipTool/php/testGetUrl.php?xy=ab xy is . x_y is ab 

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

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

Вот объяснение 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 ).

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

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

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

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

Это исправление работает повсеместно и поддерживает массив, например 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 .

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

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

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

Этот подход является измененной версией 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, eg 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 перестать заменять «.» символов в массивах $ _GET или $ _POST, то одним из таких способов является изменение источника PHP (и в этом случае он относительно прост).

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

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

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

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


Пример:

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

 массив
 (
     [aa] => Массив
         (
             [0] => bb
             [1] => BB
         )

     [c_c] => dd
 )

Заметки:

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

Посмотрев на решение Рока, я придумал версию, в которой рассматриваются ограничения в моем ответе ниже: crb's above и Rok's solution. См. Мою улучшенную версию .


@ Ответ 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, eg 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]); } } } } с <?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, eg 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]); } } } } 

..

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

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

Ну, функция, которую я включил ниже, «getRealPostArray ()», не является довольно решением, но она обрабатывает массивы и поддерживает оба имени: «alpha_beta» и «alpha.beta»:

  <input type='text' value='First-.' name='alpha.beta[ab][]' /><br> <input type='text' value='Second-.' name='alpha.beta[ab][]' /><br> <input type='text' value='First-_' name='alpha_beta[ab][]' /><br> <input type='text' value='Second-_' name='alpha_beta[ab][]' /><br> 

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

  'alpha_beta' => array (size=1) 'ab' => 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) 'ab' => array (size=2) 0 => string 'First-.' (length=7) 1 => string 'Second-.' (length=8) 'alpha_beta' => array (size=1) 'ab' => 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); ?> с <?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']);