如果我通过$ _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。
答案 0 :(得分:63)
以下是PHP.net对其原因的解释:
传入变量名称中的点
通常,PHP不会改变 变量的名称 传入剧本。但是,它 应该注意点(期间, 完全停止)不是一个有效的角色 一个PHP变量名。由于这个原因, 看看它:
<?php $varname.ext; /* invalid variable name */ ?>
现在,什么 解析器看到的是一个名为的变量 $ varname,后跟字符串 连接运算符,后跟 barestring(即未加引号的字符串 与任何已知密钥或 保留字)'ext'。显然,这个 没有预期的结果。
因此,重要的是 请注意PHP将自动 替换传入变量中的任何点 带下划线的名字。
那来自http://ca.php.net/variables.external。
此外,根据this comment,这些其他字符将转换为下划线:
PHP转换为_(下划线)的字段名称字符的完整列表如下(不仅仅是点):
- chr(32)()(空格)
- chr(46)(。)(dot)
- chr(91)([)(方括号)
- chr(128) - chr(159)(各种)
所以看起来你已经陷入困境,所以你必须使用dawnerd's suggestion将下划线转换回脚本中的点(我只是使用str_replace。)< / p>
答案 1 :(得分:55)
很久以来一直回答问题,但实际上有更好的答案(或解决方法)。 PHP允许您访问raw input stream,因此您可以执行以下操作:
$query_string = file_get_contents('php://input');
它将以查询字符串格式提供$ _POST数组,它们应该是句点。
然后,您可以根据需要进行解析(根据POSTer's comment)
<?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参数非常有用。和'_',每个都有一定的含义!
答案 2 :(得分:25)
在上面的评论中突出显示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'];
对我而言,这是一个两线的变化,因为我的观点完全是模板化的。
FYI。我在字段名称中使用点来编辑分组数据树。
答案 3 :(得分:17)
这个功能的工作是我在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']);
对于低于5.4的PHP:使用base64_encode
代替bin2hex
和base64_decode
代替hex2bin
。
答案 4 :(得分:6)
这是因为句点是变量名称中的无效字符,reason对于PHP的实现非常深入,因此没有简单的修复(尚未)。
与此同时,您可以通过以下方式解决此问题:
php://input
访问POST数据或$_SERVER['QUERY_STRING']
获取GET数据以下转换函数(PHP&gt; = 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 :(得分:5)
这种方法是Rok Kralj的改进版本,但需要进行一些调整,以提高效率(避免不必要的回调,对不受影响的键进行编码和解码)并正确处理数组键。
可以使用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;
}
}
}
答案 6 :(得分:4)
发生这种情况的原因是因为PHP的旧register_globals功能。的。 character不是变量名中的有效字符,因此PHP会将其转换为下划线以确保兼容性。
简而言之,在URL变量中执行句点不是一个好习惯。
答案 7 :(得分:3)
如果正在寻找任何 字面的方法,请让PHP停止替换“。” $ _GET或$ _POST数组中的字符,然后一种方法是修改PHP的源(在这种情况下,它相对简单)。
警告:修改PHP C源代码是一种高级选项!
另请参阅此PHP bug report,其中提出相同的修改。
要探索你需要:
.
替换检查源更改本身很简单,只涉及main/php_variables.c
中的one half of one line更新:
....
/* 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 == '.'
相比已被注释掉
示例输出:
给定a.a[]=bb&a.a[]=BB&c%20c=dd
的QUERY_STRING,
正在运行<?php print_r($_GET);
会产生:
Array ( [a.a] => Array ( [0] => bb [1] => BB ) [c_c] => dd )
注意:
parse_str()
测试C级别更改和(如果不可用)回退到较慢的方法。
答案 8 :(得分:2)
我对这个问题的解决方法既快又脏,但我还是喜欢它。我只是想发布一个在表单上检查过的文件名列表。我使用base64_encode
对标记中的文件名进行编码,然后在使用之前使用base64_decode
对其进行解码。
答案 9 :(得分:2)
在查看了Rok的解决方案之后,我提出了一个版本,它解决了我在下面的答案中的限制,crb以及Rok的解决方案。请参阅my improved version。
@ crb的回答above是一个好的开始,但有一些问题。
下面的解决方案现在解决了这两个问题(请注意,自最初发布以来它已经更新)。这比我在测试中的答案快了大约50%,但是不会处理数据具有相同键的情况(或者提取相同键的键,例如foo.bar和foo_bar都被提取为foo_bar)。 / p>
<?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]);
}
}
}
}
答案 10 :(得分:0)
嗯,我在下面包含的函数&#34; getRealPostArray()&#34;,不是一个很好的解决方案,但它处理数组并支持两个名称:&#34; alpha_beta&#34;和#34; alpha.beta&#34;:
<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;
}
答案 11 :(得分:0)
使用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);
?>
我建议仅在个别情况下使用此功能,但我不确定将其置于主头文件顶部的负面影响。
答案 12 :(得分:0)
我目前的解决方案(基于上一主题回复):
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']);