preg_replace_callback を使って HTML の特殊文字をエンティティに変換する

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>テスト&amp;&lt;&gt;&quot;</b>';

var_dump(
    escapeHTML($string),
    htmlspecialchars($string, ENT_QUOTES, 'UTF-8', false),
    htmlspecialchars($string, ENT_QUOTES, 'UTF-8')
);

iconv の IGNORE オプションが利用できなくなった

最新の 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
));

htmlspecialchars を使って不正なバイト列を削除する

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 でコードの例をまじえた日本語の解説が公開されている。

ord、chr を使って bin2hex、hex2bin を定義する

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

str_split を配列アクセスと while 文から定義する

「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));
アーカイブ ランダム ホーム 次のページ ページ 1 / 5