让PHP停止replace'。' $ _GET或$ _POST数组中的字符?

如果我使用'。'传递PHPvariables 在他们的名字通过$ _GET PHP自动replace他们与'_'字符。 例如:

<?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为什么这样做的解释:

传入variables名称中的点

通常,PHP在传递到脚本时不会更改variables的名称。 但是,应该注意的是,点(句点,句号)不是PHPvariables名称中的有效字符。 为什么呢,看看吧:

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

现在,parsing器看到的是名为$ varname的variables,后跟string连接运算符,后跟barestring(即,不带任何已知键或保留字的未加引号的string)“ext”。 显然,这没有预期的结果。

出于这个原因,重要的是要注意,PHP会自动用下划线replace传入variables名中的任何点。

这是从http://ca.php.net/variables.external

另外,根据这个评论,这些其他字符被转换为下划线:

PHP转换为_(下划线)的字段名称字符的完整列表如下(不只是点):

  • chr(32)()(空格)
  • chr(46)(。)(dot)
  • chr(91)([)(空心方括号)
  • chr(128) – chr(159)(各种)

所以它看起来像你坚持下去,所以你必须使用dawnerd的build议将下划线转换回脚本中的点(尽pipe我只是使用str_replace )。

长久以来回答的问题,但实际上有一个更好的答案(或解决方法)。 PHP可以让你在原始inputstream ,所以你可以做这样的事情:

 $query_string = file_get_contents('php://input'); 

这将给你查询string格式的$ _POST数组,它们应该是。

你可以parsing它,如果你需要(根据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参数,其中包含'。' 和'_',每个都有一定的意义!

Johan在上面的评论中突出显示了一个实际的答案 – 我只是把我的整篇文章包装在一个顶级的数组中,这个数组完全绕过了这个问题,不需要大量的处理。

在你做的forms

 <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']; 

对我来说,这是一个两线的变化,因为我的观点完全是模板化的。

仅供参考。 我在字段名称中使用点来编辑分组数据的树。

此修补程序通用,并有arrays支持,例如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']); 

对于5.4以下的PHP:使用base64_encode而不是bin2hexbase64_decode而不是hex2bin

发生这种情况的原因是variables的名称中有一个无效字符,其原因在于PHP的实现非常深入,因此没有简单的修复(还没有)。

在此期间,您可以通过以下方式解决此问题:

  1. 通过用于POST数据的php://input或用于GET数据的$_SERVER['QUERY_STRING']访问原始查询数据
  2. 使用转换function。

下面的转换函数(PHP> = 5.4)将每个键值对的名称编码为hex表示,然后执行一个常规的parse_str() ; 一旦完成,它将hex名称恢复为原来的forms:

 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_globalsfunction。 的。 字符不是variables名中的有效字符,所以PHP将其转换为下划线,以确保兼容性。

总之,在URLvariables中做周期并不是一个好的做法。

这种方法是Rok Kralj的一个改变版本,但有一些调整工作,以提高效率(避免不必要的callback,对未受影响的密钥进行编码和解码)并正确处理数组密钥。

一个testing的要点是可用的,任何意见或build议,欢迎在这里或那里。

 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的错误报告 ,build议相同的修改。

探索你需要:

  • 下载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
 )

笔记:

  • 这个补丁只能解决原来的问题(停止replace点而不是空格)。
  • 运行在这个补丁上比脚本级别的解决scheme要快,但是这些纯粹的.php答案仍然通常是可取的(因为它们避免了改变PHP本身)。
  • 理论上polyfill方法在这里是可能的,可以结合使用方法 – 使用parse_str()和(如果不可用)回退到较慢的方法来testingC级更改。

在看了Rok的解决scheme之后,我想出了一个解决以下问题的限制的版本,crb以及Rok的解决scheme。 看到我的改进版本 。


@ crb 上面的答案是一个好的开始,但是还有一些问题。

  • 它重新处理一切,这是过度的; 只有那些有“。”的字段 在名字需要重新处理。
  • 它不能像本地PHP处理一样处理数组,例如像“foo.bar []”这样的键。

下面的解决scheme现在解决了这两个问题(请注意,自从最初发布以来,它已经被更新)。 在我的testing中,这大概比我上面的答案快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]); } } } } 

..

你为什么不把所有的点都转换成某种types的标记,例如(〜#〜)然后发布呢? 当接收到variables时,你可以重新将它们恢复。这是因为有时我们需要发布下划线..如果将所有的“_”都转换为“。”,我们将会丢失它们。

我对这个问题的解决办法是快速和肮脏的,但我仍然喜欢它。 我只是想发布在表单上检查的文件名列表。 我使用base64_encode对标记内的文件名进行编码,然后在使用base64_decode之前对其进行解码。

那么,下面的函数“getRealPostArray()”并不是一个漂亮的解决scheme,但它处理数组并支持两个名称:“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,它的价值:

 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我想重新创build$_POST数组作为一个整体,但请记住,您仍然必须确保您在客户端和服务器上正确编码和解码。 了解一个angular色是否真的无效并且确实是有效的是很重要的。 另外,在使用任何数据库命令之前,人们仍然应该始终转义客户端数据。

 <?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); ?> 

我build议使用这只适用于个别情况下,副手,我不知道把它放在您的主头文件的顶部的负面点。

我目前的解决scheme(根据prev主题回复):

 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']);