LoginSignup
19
17

More than 5 years have passed since last update.

u (PCRE_UTF8) オプションをつけないと \h、\s、\v で想定しない文字を破壊するおそれがある

Last updated at Posted at 2014-11-13

PCRE 関数のエスケープシーケンスのうち、\h (水平方向の空白文字)、\s (空白文字)、\v (垂直方向の空白文字) を使う場合、UTF-8 のオプション (u)をつけないと、preg_replace などで不要な文字を削除する際に想定しない文字を破壊するおそれがあります。

次のコードはひらがなの「だ」(U+3060) が破壊される様子を示したものです。「だ」の3番目のバイト (0xA0) がノーブレークスペース (U+00A0) と誤検出されてしまいます。

<?php

$str = "だ";

var_dump(
  "\xE3\x81\xA0" === $str,
  "\xE3\x81" === preg_replace("/\h/", "", $str),
  "e381" === bin2hex(preg_replace("/\h/", "", $str)),
  $str === preg_replace("/\h/u", "", $str)
);

Unicode で定義されるすべての文字 (U+0000 から U+10FFFF) に対してこれらのエスケープシーケンスを試すと、膨大な数の文字がマッチすることがわかります。u オプションをつけた場合でも、複数の文字がマッチしますので、それらの1つ1つが本当に想定した種類の文字であるのかどうかを検討する必要があります。u オプションをつけると、次の文字がマッチします。

U+0009 U+0020 U+00A0 U+1680 U+180E U+2000
U+2001 U+2002 U+2003 U+2004 U+2005 U+2006
U+2007 U+2008 U+2009 U+200A U+202F U+205F
U+3000 
<?php

print_matched_chars(function($char) {
    return preg_match('/\h/', $char);
});

function print_matched_chars(callable $callable) {

    for ($i = 0; $i < 0x110000; ++$i) {

        if ($i > 0xD7FF && $i < 0xE000) {
            continue;
        }

        $char = utf8_chr($i);
        $hex = strtoupper(dechex($i));

        if ($callable($char)) {
            echo 'U+',
            $i < 0x1000 ? str_repeat('0', 4 - strlen($hex)) : '',
            $hex, ': ' , $char, PHP_EOL;
        }

    }
}

function utf8_chr($cp) {

    if (!is_int($cp)) {
        exit("$cp is not integer\n");
    }

    if ($cp < 0 || (0xD7FF < $cp && $cp < 0xE000) || 0x10FFFF < $cp) {
        exit("$cp is out of range\n");
    }

    return mb_decode_numericentity('&#'.$cp.';', [0, 0x10FFFF, 0, 0x10FFFF], 'UTF-8');
}
19
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
17