关于正则表达式:让PHP停止替换”。”。 $ _GET或$ _POST数组中的字符?

关于正则表达式:让PHP停止替换”。”。 $ _GET或$ _POST数组中的字符?

Get PHP to stop replacing '.' characters in $_GET or $_POST arrays?

如果我通过$ _GET传递名称为.的PHP变量,PHP将自动替换为_字符。例如:

1
2
3
4
<?php
echo"url is".$_SERVER['REQUEST_URI']."<p>";
echo"x.y is".$_GET['x.y'].".<p>";
echo"x_y is".$_GET['x_y'].".<p>";

...输出以下内容:

1
2
3
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解释为什么这样做的原因:

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:

1
2
3
<?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.

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

此外,根据此注释,这些其他字符也将转换为下划线:

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)

因此,看起来好像您坚持使用它,因此您必须使用awnerd的建议将下划线转换回脚本中的点(不过我只是使用str_replace。)


长期以来一直在回答问题,但实际上有更好的答案(或解决方法)。 PHP使您可以进入原始输入流,因此可以执行以下操作:

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

,它将以查询字符串格式为您提供$ _POST数组,其句点应为原先的句点。

然后您可以根据需要对其进行解析(根据POSTer的评论)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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的实际答案-我只是将我的整个帖子包装在一个顶级数组中,从而完全绕开了该问题,而无需进行大量处理。

以表格形式进行

1
2
3
<input name="data[database.username]">  
<input name="data[database.password]">  
<input name="data[something.else.really.deep]">

而不是

1
2
3
<input name="database.username">
<input name="database.password">
<input name="something.else.really.deep">

,然后在发布处理程序中将其解包:

1
$posdata = $_POST['data'];

对我来说,这是两行更改,因为我的视图完全是模板化的。

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


这个功能的工作是一个很好的主意,我在2013年暑假期间就想到了。

它符合标准并具有深层阵列支持,例如a.a[x][b.a]=10。它在后台使用parse_str()并进行了一些有针对性的预处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

然后您可以像这样应用它,具体取决于来源:

1
2
3
$_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


之所以会发生这种情况,是因为句点是变量名称中的无效字符,其原因在于PHP的实现非常深入,因此尚无简单的解决方法(尚未解决)。

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

  • 通过php://input(用于POST数据)或$_SERVER['QUERY_STRING'](用于GET数据)访问原始查询数据
  • 使用转换功能。
  • 下面的转换函数(PHP> = 5.4)将每个键值对的名称编码为十六进制表示形式,然后执行常规的parse_str();完成后,它将十六进制名称恢复为原始格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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']);

    或:

    1
    2
    // handle posted data (this only works with application/x-www-form-urlencoded)
    $data = parse_qs(file_get_contents('php://input'));

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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源代码
    • 禁用.替换检查
    • ./configure,制作和部署您的自定义PHP版本

    源代码更改本身是微不足道的,仅涉及更新main/php_variables.c中一行的一半:

    1
    2
    3
    4
    5
    6
    ....
    /* 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);现在会产生:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Array
    (
        [a.a] => Array
            (
                [0] => bb
                [1] => BB
            )

        [c_c] => dd
    )

    注意事项:

    • 该补丁仅解决原始问题(它停止点的替换,而不是空格的替换)。
    • 在此修补程序上运行将比脚本级解决方案更快,但是那些纯-.php答案通常还是可取的(因为它们避免更改PHP本身)。
    • 从理论上讲,这里可以使用polyfill方法,并且可以结合使用多种方法-使用parse_str()测试C级别的更改,以及(如果无法使用)回退到较慢的方法。


    在查看了Rok的解决方案后,我想出了一个版本,该版本解决了我下面的答案,上面的crb和Rok的解决方案的局限性。请参阅我的改进版本。

    上面的

    @crb答案是一个好的开始,但是有两个问题。

    • 它会重新处理所有东西,这是过大的;仅那些带有"。"的字段名称中的字母需要重新处理。
    • 它无法以与本地PHP处理相同的方式处理数组,例如用于" foo.bar []"之类的键。

    下面的解决方案现在解决了这两个问题(请注意,自最初发布以来已更新)。这比我在测试中的回答要快50%,但是将无法处理数据具有相同键(或被提取相同键的键,例如foo.bar和foo_bar都被提取为foo_bar)的情况。铅>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <?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]);                                      
                }                                                                  
            }                                                                      
        }                                                                          
    }

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


    我当前的解决方案(基于上一主题的答复):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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']);

    使用crb,我想整体上重新创建$_POST数组,但是请记住,您仍然必须确保在客户端和服务器端都正确地编码和解码。重要的是要了解何时一个字符真正无效并且真正有效。另外,在与任何数据库命令一起使用客户端数据之前,人们应始终且始终对客户端数据进行转义,这毫无例外。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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);
    ?>

    我建议仅将此方法用于个别情况,但我不确定将其放在主头文件顶部的不利之处。


    好吧,我下面包含的函数" getRealPostArray()"并不是一个不错的解决方案,但是它可以处理数组并支持两个名称:" alpha_beta"和" alpha.beta":

    1
    2
    3
    4
      <input type='text' value='First-.' name='alpha.beta[a.b][]' />
      <input type='text' value='Second-.' name='alpha.beta[a.b][]' />
      <input type='text' value='First-_' name='alpha_beta[a.b][]' />
      <input type='text' value='Second-_' name='alpha_beta[a.b][]' />

    而var_dump($ _ POST)产生:

    1
    2
    3
    4
    5
    6
    7
    8
      '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())产生:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
      '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)

    该函数的价值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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;
    }

    推荐阅读