web-dev-qa-db-ja.com

Get 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.

...私の質問はこれです:anyこれを止める方法はありますか?私の人生のために、私はこれに値するために何をしたか理解することはできません

実行しているPHPバージョンは5.2.4-2ubuntu5.3です。

70
Dave Carpeneto

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)(さまざま)

だから、あなたはそれにこだわっているように見えるので、 dawnerdの提案 (私は str_replace しかし。)

63
Jeremy Ruten

長い間答えられた質問ですが、実際にはより良い答え(または回避策)があります。 PHPでは、 raw入力ストリーム で次のようなことができます。

$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パラメーターに非常に便利です。および「_」、それぞれ特定の意味があります!

57
crb

上記のコメントで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'];

私の意見は完全にテンプレート化されていたので、これは2行の変更でした。

ご参考までに。グループ化されたデータのツリーを編集するために、フィールド名にドットを使用しています。

26
scipilot

この機能の動作は、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']);
_

For PHP below 5.4: _base64_encode_の代わりに_bin2hex_を使用し、_base64_decode_の代わりに_hex2bin_を使用します。

18
Rok Kralj

これは、ピリオドが変数名の無効な文字であるために発生します。 reason は、PHPの実装に非常に深く存在するため、簡単な修正は(まだ)ありません。

それまでの間、次の方法でこの問題を回避できます。

  1. _php://input_ for POST dataまたは_$_SERVER['QUERY_STRING']_ for GET data)を介して生のクエリデータにアクセス
  2. 変換関数を使用します。

以下の変換関数(PHP> = 5.4)は、各キーと値のペアの名前を16進表現にエンコードしてから、通常のparse_str();を実行します。完了すると、16進数名を元の形式に戻します。

_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'));
_
6
Ja͢ck

このアプローチは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;                                              
        }                                                                      
    }                                                                          
}                                                                              
5
El Yobo

これが起こる理由は、PHPの古いregister_globals機能のためです。 。 characterは変数名の有効な文字ではないため、PHPは互換性があることを確認するためにアンダースコアに変換します。

つまり、URL変数でピリオドを実行することはお勧めできません。

4
Jeremy Privett

anyを探している場合literallyget PHPの置換を停止するには ' 。 '文字を$ _GETまたは$ _POST配列で使用する場合、そのような方法の1つはPHPのソースを変更することです(この場合は比較的簡単です)。

警告:変更PHP Cソースは高度なオプションです!

こちらもご覧ください PHPバグレポート これは同じ修正を示唆しています。

探索するには、次のことが必要です。

  • ダウンロード PHPのCソースコード
  • _._置換チェックを無効にします
  • ./ configuremakeおよびカスタマイズしたPHPビルドをデプロイします

ソースの変更自体は簡単で、_main/php_variables.c_の 1行の半分 だけを更新する必要があります。

_....
/* 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 
(
 [aa] => Array 
(
 [0] => bb 
 [1] => BB 
)
 
 [c_c] => dd 
)

注:

  • このパッチは元の質問のみに対処します(スペースではなくドットの置換を停止します)。
  • このパッチで実行すると、スクリプトレベルのソリューションよりも高速になりますが、これらのpure-.phpの回答は依然として一般的に好まれます(PHP自体)を変更しないためです)。
  • 理論的にはポリフィルアプローチが可能であり、アプローチを組み合わせることができます-parse_str()および(利用できない場合)遅いメソッドへのフォールバックを使用してCレベルの変更をテストします。
3
humbletim

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]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              
2
El Yobo

この問題に対する私の解決策は迅速で汚いものでしたが、それでも気に入っています。フォームでチェックされたファイル名のリストを投稿したかっただけです。 base64_encodeを使用してマークアップ内のファイル名をエンコードし、使用する前にbase64_decodeでデコードしました。

2
Jason

私の現在のソリューション(前のトピックの返信に基づく):

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

Crbを使用して、$_POST配列全体ですが、クライアントとサーバーの両方で正しくエンコードとデコードを行う必要があることに注意してください。文字がtruly無効であり、本当にvalidである場合を理解することが重要です。さらに、人々はstillおよびalwaysanyデータベースコマンドで使用する前にクライアントデータをエスケープする必要があります例外なし

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

これを個別のケースにのみ使用することをお勧めします。これをプライマリヘッダーファイルの先頭に配置することのマイナス点についてはわかりません。

0
John

さて、以下に含める関数「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;
}
0
ChrisNY