htmlspecialchars の実装を学ぶために、preg_replace_callback を使って HTML の特殊文字をエンティティに置き換える関数を定義する。不正なバイト列は考慮しないものとする。正規表現は「perl - 勝手に添削 - 40行で作るPerl用テンプレートエンジン」からいただいた。& をエスケープ対象外から除外する必要がある。さらにほかの既存のエンティティのエスケープを避けるかどうかを考慮する必要がある。htmlspecialchars の第4引数に false を指定して処理された文字列を比較対象とした。
function escapeHTML($string)
{
if ('' === $string) {
return $string;
}
$escaped = ['&' => 'amp', '<' => 'lt', '>' => 'gt', '"' => 'quot'];
$regex = '/([&<>"])(?!(amp|lt|gt|quot);)/s';
return preg_replace_callback($regex,
function($matches) use ($escaped) {
return '&'.$escaped[$matches[1]].';';
}, $string);
}
$string = '<b>テスト&<>"</b>';
var_dump(
escapeHTML($string),
htmlspecialchars($string, ENT_QUOTES, 'UTF-8', false),
htmlspecialchars($string, ENT_QUOTES, 'UTF-8')
);
最新の iconv では IGNORE オプションがサポートされなくなったことに伴い、IGONRE オプションを指定するとエラーが発生するようにようになった。PHP 5.4.7 では修正されていない。
$string = [
implode(array_map('chr',
[0x61, 0xF1, 0x80, 0x80, 0xE1, 0x80, 0xC2, 0x62, 0x80, 0x63, 0x80, 0xBF, 0x64]
)),
chr(0x2F),
implode(array_map('chr', [0xC0, 0xAF])),
implode(array_map('chr', [0xE0, 0x80, 0xAF])),
implode(array_map('chr', [0xF0, 0x80, 0x80, 0xAF]))
];
var_dump(array_map(
function($v) { return iconv($v, 'UTF-8', 'UTF-8//IGNORE'); },
$string
));
PHP 5.3 以降であれば、ENT_IGNORE オプションを使って、不正なバイト列を削除できる。
$string = "\x61\xF1\x80\x80\xE1\x80\xC2\x62\x80\x63\x80\xBF\x64";
htmlspecialchars_decode(htmlspecialchars($string, ENT_IGNORE));
不正なバイト列を削除する場合、バリデーションの前に実行しなければならない。さもなければ、危険な文字列に不正なバイト列が差し込まれた場合、バリデーションのチェックをすり抜けてしまう。マニュアルの ENT_IGNORE オプションのリンク先に記載されている Unicode.org のレポートに関して、JPCERT でコードの例をまじえた日本語の解説が公開されている。
hex2bin は
実装には strlen と for 文の組み合わせか、while と isset の組み合わせの選択肢がある。ord、chr の代わりに unpack、pack を使って定義することもできる。hex2bin は PHP 5.4.1 で文字列の長さが奇数である場合は、警告を発するようになった。
function bin2hex2($str) {
$length = strlen($str);
$ret = '';
for ($i = 0; $i < $length; $i += 1) {
$ret .= dechex(ord($str[$i]));
}
return $ret;
}
function bin2hex3($str) {
$ret = '';
$i = 0;
while(true) {
if(!isset($str[$i])) {
break;
}
$ret .= dechex(ord($str[$i]));
$i += 1;
}
return $ret;
}
function hex2bin2($hex) {
$length = strlen($hex);
$ret = '';
for ($i = 0; $i < $length; $i += 2) {
$ret .= chr(hexdec($hex[$i].$hex[$i + 1]));
}
return $ret;
}
function hex2bin3($hex) {
$ret = '';
$i = 0;
while(true) {
if (!isset($hex[$i]) || !isset($hex[$i + 1])) {
break;
}
$ret .= chr(hexdec($hex[$i].$hex[$i + 1]));
$i += 2;
}
return $ret;
}
$str = 'あいうえお';
$hex = 'e38182e38184e38186e38188e3818a';
var_dump(
bin2hex($str),
bin2hex2($str),
bin2hex3($str),
hex2bin($hex),
hex2bin2($hex),
hex2bin3($hex)
);
「strlen を使わずにバイトのサイズを求める」の記事で while 文からバイトサイズを求める方法を示したが、少し書き換えれば、文字列を配列に変換する str_split として定義することもできる。
function str_split2($str) {
$ret = [];
$n = 0;
while(true) {
if (!isset($str[$n])) {
break;
}
$ret[$n] = $str[$n];
$n += 1;
}
return $ret;
}
$str = 'abcde';
var_dump(str_split2($str));