让 PHP 停止替换 '.' $_GET 或 $_POST 数组中的字符?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/68651/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get PHP to stop replacing '.' characters in $_GET or $_POST arrays?
提问by
If I pass PHP variables with .in their names via $_GET PHP auto-replaces them with _characters. For example:
如果我.通过 $_GET 在他们的名字中传递 PHP 变量,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>";
... outputs the following:
...输出以下内容:
url is /SpShipTool/php/testGetUrl.php?x.y=a.b
x.y is .
x_y is a.b.
... my question is this: is there anyway I can get this to stop? Cannot for the life of me figure out what I've done to deserve this
...我的问题是:有什么办法可以让它停止?无法为我的生活弄清楚我做了什么值得这个
PHP version I'm running with is 5.2.4-2ubuntu5.3.
我正在运行的 PHP 版本是 5.2.4-2ubuntu5.3。
采纳答案by Jeremy Ruten
Here's PHP.net's explanation of why it does it:
这是 PHP.net 对其原因的解释:
Dots in incoming variable names
Typically, PHP does not alter the names of variables when they are passed into a script. However, it should be noted that the dot (period, full stop) is not a valid character in a PHP variable name. For the reason, look at it:
<?php $varname.ext; /* invalid variable name */ ?>Now, what the parser sees is a variable named $varname, followed by the string concatenation operator, followed by the barestring (i.e. unquoted string which doesn't match any known key or reserved words) 'ext'. Obviously, this doesn't have the intended result.
For this reason, it is important to note that PHP will automatically replace any dots in incoming variable names with underscores.
传入变量名称中的点
通常,PHP 在将变量传递给脚本时不会更改变量的名称。但是,应该注意点(句点、句号)不是 PHP 变量名中的有效字符。究其原因,请看:
<?php $varname.ext; /* invalid variable name */ ?>现在,解析器看到的是一个名为 $varname 的变量,后跟字符串连接运算符,后跟裸字符串(即不匹配任何已知键或保留字的未加引号的字符串)'ext'。显然,这并没有达到预期的结果。
出于这个原因,重要的是要注意 PHP 会自动用下划线替换传入变量名称中的任何点。
That's from http://ca.php.net/variables.external.
这是来自http://ca.php.net/variables.external。
Also, according to this commentthese other characters are converted to underscores:
此外,根据此评论,这些其他字符将转换为下划线:
The full list of field-name characters that PHP converts to _ (underscore) is the following (not just dot):
- chr(32) ( ) (space)
- chr(46) (.) (dot)
- chr(91) ([) (open square bracket)
- chr(128) - chr(159) (various)
PHP 转换为 _(下划线)的字段名称字符的完整列表如下(不仅仅是点):
- chr(32) ( ) (空格)
- chr(46) (.)(点)
- chr(91) ([)(左方括号)
- chr(128) - chr(159)(各种)
So it looks like you're stuck with it, so you'll have to convert the underscores back to dots in your script using dawnerd's suggestion(I'd just use str_replacethough.)
所以看起来你被它卡住了,所以你必须使用dawnerd的建议将下划线转换回脚本中的点(虽然我只是使用str_replace。)
回答by crb
Long-since answered question, but there is actually a better answer (or work-around). PHP lets you at the raw input stream, so you can do something like this:
很久以来就已回答的问题,但实际上有更好的答案(或解决方法)。PHP 允许您使用原始输入流,因此您可以执行以下操作:
$query_string = file_get_contents('php://input');
which will give you the $_POST array in query string format, periods as they should be.
这将为您提供查询字符串格式的 $_POST 数组,句点应该是。
You can then parse it if you need (as per POSTer's comment)
然后,您可以根据需要对其进行解析(根据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'); }
?>
Hugely useful for OpenID parameters, which contain both '.' and '_', each with a certain meaning!
对于包含“.”的 OpenID 参数非常有用。和'_',每一个都有一定的含义!
回答by scipilot
Highlighting an actual answer by Johan in a comment above - I just wrapped my entire post in a top-level array which completely bypasses the problem with no heavy processing required.
在上面的评论中突出显示 Johan 的实际答案 - 我只是将我的整个帖子包装在一个顶级数组中,它完全绕过了这个问题,不需要繁重的处理。
In the form you do
在你做的表格中
<input name="data[database.username]">
<input name="data[database.password]">
<input name="data[something.else.really.deep]">
instead of
代替
<input name="database.username">
<input name="database.password">
<input name="something.else.really.deep">
and in the post handler, just unwrap it:
在后处理程序中,只需打开它:
$posdata = $_POST['data'];
For me this was a two-line change, as my views were entirely templated.
对我来说,这是一个两行的变化,因为我的观点完全是模板化的。
FYI. I am using dots in my field names to edit trees of grouped data.
供参考。我在字段名称中使用点来编辑分组数据树。
回答by Rok Kralj
The working of this function is a neat idea that I came up during my summer vacation of 2013.
这个功能的工作是我在 2013 年暑假期间想到的一个巧妙的想法。
It is standards compliant and has deep array support, for example a.a[x][b.a]=10. It uses parse_str()behind the scenes with some targeted preprocessing.
它符合标准并具有深入的阵列支持,例如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;
}
And then you can apply it like this, depending on the source:
然后你可以像这样应用它,具体取决于来源:
$_POST = fix(file_get_contents('php://input'));
$_GET = fix($_SERVER['QUERY_STRING']);
$_COOKIE = fix($_SERVER['HTTP_COOKIE']);
For PHP below 5.4:use base64_encodeinstead of bin2hexand base64_decodeinstead of hex2bin.
对于低于 5.4 的 PHP:使用base64_encode代替bin2hex和base64_decode代替hex2bin.
回答by Ja?ck
This happens because a period is an invalid character in a variable's name, the reasonfor which lies very deep in the implementation of PHP, so there are no easy fixes (yet).
发生这种情况是因为句点是变量名称中的无效字符,其原因在 PHP 的实现中非常深,因此没有简单的修复(目前)。
In the meantime you can work around this issue by:
同时,您可以通过以下方式解决此问题:
- Accessing the raw query data via either
php://inputfor POST data or$_SERVER['QUERY_STRING']for GET data - Using a conversion function.
- 通过
php://inputPOST 数据或$_SERVER['QUERY_STRING']GET 数据访问原始查询数据 - 使用转换函数。
The below conversion function (PHP >= 5.4) encodes the names of each key-value pair into a hexadecimal representation and then performs a regular parse_str(); once done, it reverts the hexadecimal names back into their original form:
下面的转换函数 (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']);
Or:
或者:
// handle posted data (this only works with application/x-www-form-urlencoded)
$data = parse_qs(file_get_contents('php://input'));
回答by El Yobo
This approach is an altered version of Rok Kralj's, but with some tweaking to work, to improve efficiency (avoids unnecessary callbacks, encoding and decoding on unaffected keys) and to correctly handle array keys.
这种方法是 Rok Kralj 的更改版本,但进行了一些调整以提高效率(避免不必要的回调,对未受影响的键进行编码和解码)并正确处理数组键。
A gist with testsis available and any feedback or suggestions are welcome here or there.
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;
}
}
}
回答by Jeremy Privett
The reason this happens is because of PHP's old register_globals functionality. The . character is not a valid character in a variable name, so PHP coverts it to an underscore in order to make sure there's compatibility.
发生这种情况的原因是 PHP 的旧 register_globals 功能。这 。字符不是变量名中的有效字符,因此 PHP 将其转换为下划线以确保兼容性。
In short, it's not a good practice to do periods in URL variables.
简而言之,在 URL 变量中使用句点并不是一个好习惯。
回答by humbletim
If looking for anyway to literallyget PHP to stop replacing '.' characters in $_GET or $_POST arrays, then one such way is to modify PHP's source (and in this case it is relatively straightforward).
如果寻找任何方式 从字面上让PHP停止更换“” $_GET 或 $_POST 数组中的字符,那么一种方法是修改 PHP 的源代码(在这种情况下它相对简单)。
WARNING: Modifying PHP C source is an advanced option!
警告:修改 PHP C 源代码是一个高级选项!
Also see this PHP bug reportwhich suggests the same modification.
另请参阅此PHP 错误报告,其中建议进行相同的修改。
To explore you'll need to:
要探索,您需要:
- download PHP's C source code
- disable the
.replacement check - ./configure, makeand deploy your customized build of PHP
- 下载PHP 的 C 源代码
- 禁用
.替换检查 - ./configure,制作和部署您定制的 PHP 版本
The source change itself is trivial and involves updating just one half of one linein main/php_variables.c:
源变化本身是微不足道,涉及到更新只是一半一条线之一的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='_';
....
Note: compared to original || *p == '.'has been commented-out
注:对比原版|| *p == '.'已被注释掉
Example Output:
示例输出:
given a QUERY_STRING of a.a[]=bb&a.a[]=BB&c%20c=dd,
running <?php print_r($_GET);now produces:
给定 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
)
Notes:
笔记:
- this patch addresses the original question only (it stops replacement of dots, not spaces).
- running on this patch will be faster than script-level solutions, but those pure-.php answers are still generally-preferable (because they avoid changing PHP itself).
- in theory a polyfill approach is possible here and could combine approaches -- test for the C-level change using
parse_str()and (if unavailable) fall-back to slower methods.
- 此补丁仅解决原始问题(它停止替换点,而不是空格)。
- 在此补丁上运行将比脚本级解决方案更快,但那些纯 .php 答案仍然通常是首选(因为它们避免更改 PHP 本身)。
- 理论上,polyfill 方法在这里是可能的,并且可以结合方法——使用
parse_str()和(如果不可用)回退到较慢的方法来测试 C 级更改。
回答by El Yobo
After looking at Rok's solution I have come up with a version which addresses the limitations in my answer below, crb's above and Rok's solution as well. See a my improved version.
在查看了 Rok 的解决方案后,我提出了一个版本,该版本解决了下面我的答案中的限制,上面的 crb 和 Rok 的解决方案也是如此。请参阅我的改进版本。
@crb's answer aboveis a good start, but there are a couple of problems.
@crb上面的回答是一个好的开始,但存在一些问题。
- It reprocesses everything, which is overkill; only those fields that have a "." in the name need to be reprocessed.
- It fails to handle arrays in the same way that native PHP processing does, e.g. for keys like "foo.bar[]".
- 它重新处理一切,这是矫枉过正;只有那些带有“.”的字段 在名称中需要重新处理。
- 它无法以与原生 PHP 处理相同的方式处理数组,例如,对于像“foo.bar[]”这样的键。
The solution below addresses both of these problems now (note that it has been updated since originally posted). This is about 50% faster than my answer above in my testing, but will not handle situations where the data has the same key (or a key which gets extracted the same, e.g. foo.bar and foo_bar are both extracted as 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]);
}
}
}
}
回答by Jason
My solution to this problem was quick and dirty, but I still like it. I simply wanted to post a list of filenames that were checked on the form. I used base64_encodeto encode the filenames within the markup and then just decoded it with base64_decodeprior to using them.
我对这个问题的解决既快速又肮脏,但我仍然喜欢它。我只是想发布在表单上检查过的文件名列表。我曾经base64_encode在标记中对文件名进行编码,然后base64_decode在使用它们之前对其进行解码。

