特定のタグだけ許可したい

PHPで特定のタグだけを許可したい場合

サイト運営をしていると、コメント欄などで「特定のタグだけを許可したい場合」がたびたびあると思います。

例えば・・・・・
<b>タグや<u>タグ<strong>タグなんか許可したい場合があると思います。

PHPには、タグを取り除く「strip_tags関数」なる便利な物があるのですが・・・・


strip_tags関数
HTMLタグおよびPHPタグを取り除く
strip_tags($text); strip_tags($text,'<a><b>');//'<a>タグと<b>タグだけ許可 //上のように第二引数で使用可能なタグを指定できるが //脆弱性があるので使わないように・・・

詳しくは書きませんが、strip_tags関数には、虚弱性がありまして
第2引数で、指定したタグ以外を取り除く事ができるんですが
第2引数を使用した場合「XSS(クロスサイトスクリプティング)」という攻撃によって、WEBサイトが攻撃される可能性があります
(ネット上を探せばいくらでもでてくるので気になる方は検索してください)


では、どうすればいいのか?
まず 「HTMLタグを使用させない」のが1つの方法です。
つまり「htmlspecialchars関数でタグを無効」にするのが1番良い方法です。

どうしても HTMLタグの一部を許可したいんだよ!

「どうしても特定のHTMLタグだけを許可したい!」という時が来ると思います

私がいつも使用している方法を書いておきたいと思います。

一部のHTMLタグだけを許可する方法

1. まず、「htmlspecialchars関数」でエスケープ処理します

2. 表示させる時に「特定のHTMLタグだけ元にもどす」です
(str_replace関数で、置換して戻します)



htmlspecialchars_decode関数というhtmlspecialchars関数とは、反対の関数があるのですが
これを使用すると全部のタグをそのまま戻すのでそもそも意味がありません!

では具体的な方法を下記にまとめておきます

一部のタグを許可するオリジナル関数
特定のHTMLタグおよびPHPタグを取り除く
//<b>タグ、<u>タグ、<strong>タグだけを元に戻す関数 function tag_kyoka($str){ $search = array('&lt;b&gt;','&lt;/b&gt;','&lt;u&gt;','&lt;/u&gt;','&lt;strong&gt;','&lt;/strong&gt;'); $replace = array('<b>','</b>','<u>','</u>','<strong>','</strong>'); return str_replace($search,$replace,$str); }

一部のタグだけを許可する(実験)
先ほど作成した関数を実行してみる

function h($str) { return htmlspecialchars($string,ENT_QUOTES,'UTF-8'); } //↑htmlspecialchars関数は長いので h($str)で発動するようにした $str = '<b>おはよう</b><u>こんにちは</u><strong>こんばんは</strong><i>さようなら</i>'; $str = h($str); echo $str; 結果 <b>おはよう</b><u>こんにちは</u><strong>こんばんは</strong><i>さようなら</i> $str2 = '<b>おはよう</b><u>こんにちは</u><strong>こんばんは</strong><i>さようなら</i>'; $str2 = h($str2); $str2 = tag_kyoka($str2); echo $str2; 結果 おはようこんにちはこんばんは<i>さようなら</i>

実験結果より、<b>タグ、<u>タグ、<strong>タグは使用できてるようになりました。
一方、<i>タグはそのまま「htmlspecialchars関数」によって、エスケープされているのがわかります。


<a>タグだけ許可したい

<a>タグの場合は、
<a href="この中の内容は常に一定ではない">ここも一定でない</a>となり
上記のような手法が使えません よって正規表現をつかって、置換します

<a>タグの始まりから終り(</a>)までを正規表現であらわすと
/<a href.*<\/a>/ となります

エスケープされた<a>タグは 「&lt;a&gt;」となり
エスケープされた<a>タグの始まりから終り(</a>)までを正規表現で書くと 「/&lt;a href=.*&lt;\/a&gt;/」となります

以上を踏まえて、<a>タグのみを変換したいと思います

<a>タグのみを許可するスクリプト
//まずはタグのいっぱい入った適当な文字列を用意します
$text =<<< END
<u>リンク文字変換テスト</u><a href="http://www.php.net/">PHP公式ホームページ</a>
2行目 <a href="http://www.php.net/manual/ja/">PHP日本語マニュアル</a>2行目終り
<i>3行目</i><del>テスト</del><strong>リンク文字変換テスト</strong>
4行目 <a href="危険な文字列">不正なタグ</a>
END;
$textをそのまま出力してみます

リンク文字変換テストPHP公式ホームページ
2行目 PHP日本語マニュアル2行目終り
3行目テストリンク文字変換テスト
4行目 不正なタグ

タグが有効になっているのがわかります

$textをエスケープ処理します
$text = htmlspecialchars($text,ENT_QUOTES,'UTF-8');
echo $text;

エスケープ処理が効いているのか確認します
<u>リンク文字変換テスト</u><a href="http://www.php.net/">PHP公式ホームページ</a>
2行目 <a href="http://www.php.net/manual/ja/">PHP日本語マニュアル</a>2行目終り
<i>3行目</i><del>テスト</del><strong>リンク文字変換テスト</strong>
4行目 <a href="危険な文字列">不正なタグ</a>

ここから<a>タグのみを正規表現で置換します
preg_match_all("/&lt;a href=.*&lt;\/a&gt;/iu",$text,$pattarn);
//<a>タグにマッチする文字列を$pattarn[0][0]から順番に格納した

foreach ($pattarn[0] as $key=>$val){
if(preg_match("/(http|https):\/\/[-\w\.]+(:\d+)?(\/[^\s]*)?$/",$pattarn[0][$key])){
//urlとして正しいものが含まれている場合は処理をする
	$replace[] = htmlspecialchars_decode($pattarn[0][$key]);
//htmlspecialchars_decodeでエスケープ処理を解除した
	}else{
	$replace[] = $pattarn[0][$key];
//urlとして正しいものが含まれていない場合は処理しない
	}
}
$text = str_replace($pattarn[0],$replace,$text);
//パターンにマッチした部分(<a>タグ)だけを元にもどした
echo $text;

結果
<u>リンク文字変換テスト</u>PHP公式ホームページ
2行目 PHP日本語マニュアル2行目終り
<i>3行目</i><del>テスト</del><strong>リンク文字変換テスト</strong>
4行目 <a href="危険な文字列">不正なタグ</a>

あとがき

このページを作ってみて思った事は、HTMLタグをいちいちエスケープ処理して書かないといけなかったので
ホームページ作成がものすごいだるかった・・・・・・

例えば、「&lt;や&gt;」などは、htmlspecialcharsを2回かけたり・・・・
ある意味PHPプログラムよりややこしかった;;
なぜ、HTMLタグを元に戻すような正規表現のページがあまりないのかよくわかりましたw

あと、<a>タグだけを許可するスクリプトですが
「preg_replace()関数 - 正規表現で置換する」というのを使えばもっとスマートにできると思います

ただ、preg_replace()はただでさえややこしい正規表現を正確に置換しないといけないので
場合によっては結構やっかいな関数です

私も最初は preg_replace()で置換しようとしていましたが、<a>タグはややこしいので断念しました


<a>タグだけを許可ですが・・・
<a>タグを許可した時点でセキュリティー的に問題があると思うので
「 strip_tags($text,'<a>') 」を使えばいいんではないかと個人的には思います
(上のプログラムは無意味??)

会員制の掲示板でログインした会員しか書き込みできないような掲示板の場合は、
「 strip_tags($text,'<a>') 」を使用すれば良いのではないでしょうか?

URLを自動で変換したい場合はURL自動変換するPHPスクリプトをご覧下さい