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.
...私の質問はこれです:ありますか どれでも どうすればこれを止めることができますか?これに値するほど自分が何をしたのか、一生わかりません
私が実行しているPHPのバージョンは5.2.4-2ubuntu5.3です。
解決
PHP.net がその理由を説明するのは次のとおりです。
受信変数名のドット
通常、PHPは、変数がスクリプトに渡されたときに変数の名前を変更しません。ただし、DOT(ピリオド、フルストップ)は、PHP変数名の有効な文字ではないことに注意する必要があります。理由から、それを見てください:
<?php $varname.ext; /* invalid variable name */ ?>
さて、パーサーが見るものは、$ varnameという名前の変数であり、その後に文字列連結演算子が続き、その後にBarestringが続きます(つまり既知のキーまたは予約された単語に一致しない引用されていない文字列)「ext」。明らかに、これには意図した結果がありません。
このため、PHPは着信変数名のドットを自動的にアンダースコアに置き換えることに注意することが重要です。
それはからです http://ca.php.net/variables.external.
また、によると、 このコメント これらの他の文字はアンダースコアに変換されます。
PHP が _ (アンダースコア) に変換するフィールド名文字の完全なリストは次のとおりです (ドットだけではありません)。
- chr(32) ( ) (スペース)
- chr(46) (.) (ドット)
- chr(91) ([) (開き角括弧)
- chr(128)~chr(159)(各種)
それで行き詰まっているようですので、スクリプト内でアンダースコアをドットに変換し直す必要があります。 ドナードの提案 (私はただ使います str_replace けれど。)
他のヒント
長い間答えられた質問ですが、実際にはより良い答え(または回避策)があります。PHP では、 生の入力ストリーム, したがって、次のようなことができます。
$query_string = file_get_contents('php://input');
これにより、クエリ文字列形式の $_POST 配列が得られます。ピリオドは必要に応じて付けられます。
その後、必要に応じてそれを解析できます(次のように) 投稿者のコメント)
<?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 による実際の回答を強調表示します。私は投稿全体をトップレベルの配列でラップしただけで、重い処理を必要とせずに問題を完全に回避します。
あなたが行う形式では
<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 行の変更でした。
ご参考までに。グループ化されたデータのツリーを編集するためにフィールド名にドットを使用しています。
この関数の仕組みは、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']);
PHP 5.4 未満の場合: 使用 base64_encode
の代わりに bin2hex
そして base64_decode
の代わりに hex2bin
.
これは、変数名にピリオドが無効な文字であるために発生します。 理由 これは PHP の実装の非常に深いところにあるため、簡単に修正する方法は (まだ) ありません。
それまでは、次の方法でこの問題を回避できます。
- 次のいずれかを介して生のクエリ データにアクセスする
php://input
POSTデータの場合、または$_SERVER['QUERY_STRING']
データをGETする場合 - 変換関数を使用します。
以下の変換関数 (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'));
このアプローチは 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;
}
}
}
この問題が発生する理由は、PHP の古い register_globals 機能にあります。。文字は変数名では有効な文字ではないため、PHP は互換性を確保するために文字をアンダースコアに変換します。
つまり、URL 変数でピリオドを使用するのは良い習慣ではありません。
探しているなら どれでも する方法 文字通り PHPを取得して「」の交換を停止します。 $ _GETまたは$ _POSTアレイの文字、そのような方法の1つは、PHPのソースを変更することです(この場合、比較的簡単です)。
警告:PHP C ソースの変更は高度なオプションです。
こちらもご覧ください PHPのバグレポート これは同じ変更を示唆しています。
探索するには、次のことを行う必要があります。
- ダウンロード PHPのCソースコード
- を無効にする
.
交換チェック - 。/構成、設定, 作る カスタマイズした 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);
現在、以下が生成されます。
Array ( [a.a] => Array ( [0] => bb [1] => BB ) [c_c] => dd )
ノート:
- このパッチは元の質問のみに対処します (スペースではなくドットの置換を停止します)。
- このパッチで実行すると、スクリプトレベルのソリューションよりも高速になりますが、純粋な .php の回答は依然として一般的に好まれます (PHP 自体の変更を回避できるため)。
- 理論的には、ポリフィル アプローチはここで可能であり、アプローチを組み合わせることができます -- を使用して C レベルの変更をテストします
parse_str()
そして (利用できない場合は) より遅い方法にフォールバックします。
この問題に対する私の解決策は手っ取り早く汚いものでしたが、それでも気に入っています。フォーム上でチェックされたファイル名のリストを投稿したかっただけです。私が使用した base64_encode
マークアップ内のファイル名をエンコードし、それを次のようにデコードします。 base64_decode
使用する前に。
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]);
}
}
}
}
さて、以下に含める関数「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;
}
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);
?>
私はこれを個別の場合にのみ使用することをお勧めします。ちなみに、これをプライマリヘッダーファイルの先頭に置くことのマイナス点についてはわかりません。
私の現在の解決策(前のトピックの返信に基づく):
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']);