让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。
解决方案
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)(各种)
所以看起来你已经被它困住了,所以你必须使用以下命令将下划线转换回脚本中的点 达纳德的建议 (我只想用 字符串替换 尽管。)
其他提示
早已回答的问题,但实际上有一个更好的答案(或解决方法)。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参数非常有用。和“ _”,每个都有一定的含义!
在上面的评论中突出显示 Johan 的实际答案 - 我只是将整个帖子包装在一个顶级数组中,这完全绕过了问题,无需进行繁重的处理。
以你做的形式
<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'];
对我来说,这是两行的改变,因为我的观点完全是模板化的。
供参考。我在字段名称中使用点来编辑分组数据树。
这个函数的工作原理是我在 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 的实现中非常深入,所以还没有简单的修复方法。
同时,您可以通过以下方式解决此问题:
- 通过以下任一方式访问原始查询数据
php://input
对于 POST 数据或$_SERVER['QUERY_STRING']
用于获取数据 - 使用转换函数。
下面的转换函数(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 == '.'
已被注释掉
示例输出:
给定 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 本身)。
- 理论上,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']);