86

如果我通过 $_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>";

...输出以下内容:

url is /SpShipTool/php/testGetUrl.php?x.y=a.b
x.y is .
x_y is a.b.

...我的问题是:有什么办法可以让这停止?无法为我的生活弄清楚我做了什么值得这个

我运行的 PHP 版本是 5.2.4-2ubuntu5.3。

4

13 回答 13

77

这是 PHP.net 对它为什么这样做的解释:

传入变量名中的点

通常,PHP 在将变量传递到脚本时不会更改它们的名称。但是,应该注意点(句点、句号)不是 PHP 变量名中的有效字符。究其原因,请看:

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

现在,解析器看到的是一个名为 $varname 的变量,后跟字符串连接运算符,然后是裸字符串(即不匹配任何已知键或保留字的未加引号的字符串)'ext'。显然,这并没有达到预期的结果。

出于这个原因,重要的是要注意 PHP 会自动将传入变量名中的任何点替换为下划线。

那来自http://ca.php.net/variables.external

此外,根据此评论,这些其他字符将转换为下划线:

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

  • chr(32) ( ) (空格)
  • chr(46) (.) (点)
  • chr(91) ([)(开方括号)
  • chr(128) - chr(159)(各种)

所以看起来你被困住了,所以你必须使用黎明的建议将下划线转换回脚本中的点(不过我只是使用str_replace。)

于 2008-09-16T02:01:36.047 回答
62

很久以来回答的问题,但实际上有一个更好的答案(或解决方法)。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 参数非常有用 和'_',每个都有一定的含义!

于 2009-12-21T12:47:21.840 回答
29

在上面的评论中突出显示 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">  

在 post 处理程序中,只需打开它:

$posdata = $_POST['data'];

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

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

于 2013-12-04T01:36:13.060 回答
18

您是否想要一个符合标准并适用于深度数组的解决方案(例如:) ?param[2][5]=10

要修复此问题的所有可能来源,您可以在 PHP 代码的最顶部应用:

$_GET    = fix( $_SERVER['QUERY_STRING'] );
$_POST   = fix( file_get_contents('php://input') );
$_COOKIE = fix( $_SERVER['HTTP_COOKIE'] );

这个函数的工作是我在 2013 年暑假期间提出的一个好主意。不要被一个简单的正则表达式气馁,它只是抓取所有查询名称,对它们进行编码(因此保留点),然后使用普通的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;
}

ps:如果您在项目中使用此解决方案,请使用@author Rok Kralj.

于 2013-08-13T12:59:30.670 回答
7

发生这种情况是因为句点是变量名称中的无效字符,其原因在于 PHP 的实现非常深入,因此(目前)还没有简单的修复方法。

同时,您可以通过以下方式解决此问题:

  1. 通过php://inputPOST 数据或$_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'));
于 2013-01-21T04:52:26.717 回答
5

这种方法是 Rok Kralj 的修改版本,但需要进行一些调整以提高效率(避免对未受影响的键进行不必要的回调、编码和解码)并正确处理数组键。

提供了带有测试的要点,欢迎在这里或那里提供任何反馈或建议。

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;                                              
        }                                                                      
    }                                                                          
}                                                                              
于 2013-08-10T15:31:14.197 回答
4

发生这种情况的原因是 PHP 的旧 register_globals 功能。这 。字符不是变量名中的有效字符,因此 PHP 将其转换为下划线以确保兼容性。

简而言之,在 URL 变量中添加句点不是一个好习惯。

于 2008-09-16T01:56:15.687 回答
3

如果正在寻找任何方式 让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 == '.' 已注释掉


示例输出:

给定一个 QUERY_STRING a.a[]=bb&a.a[]=BB&c%20c=dd,现在运行<?php print_r($_GET);会产生:

大批
(
    [aa] => 数组
        (
            [0] => bb
            [1] => BB
        )

    [c_c] => dd
)

笔记:

  • 此补丁仅解决原始问题(它停止替换点,而不是空格)。
  • 在这个补丁上运行将比脚本级解决方案更快,但那些纯 .php 答案仍然是普遍首选的(因为它们避免更改 PHP 本身)。
  • parse_str()理论上,这里可以使用 polyfill 方法,并且可以组合方法——使用和(如果不可用)回退到较慢的方法来 测试 C 级更改。
于 2013-08-15T00:28:57.950 回答
2

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

于 2011-02-07T22:35:54.737 回答
2

在查看了 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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              
于 2013-08-03T01:54:05.590 回答
0

好吧,我在下面包含的函数“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;
}
于 2014-09-12T01:27:53.107 回答
0

使用 crb's 我想重新创建整个$_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);
?>

我建议仅将其仅用于个别情况,我不确定将其放在主头文件顶部的负面影响。

于 2014-10-05T20:26:30.073 回答
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']);
于 2015-11-16T12:36:11.030 回答