Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Blade の {{ }} と {!! !!} とは結局どう違うのか、および特定のタグだけ許可したい場合

More than 1 year has passed since last update.

この記事について

こちらの記事を拝読して興味が湧き、もう少し深掘りしたいと思ったので、書くことにしました。

【Laravel】Bladeテンプレートの{!! !!}とは結局何なのか - Qiita

(@ike-taka さん、勝手に便乗してしまい申し訳ありません)

はじめに

  • Laravel: 6.17.1
  • PHP: 7.3.11

Blade の {{ }} および {!! !!} ステートメントに関する公式ドキュメントの記載は以下のページにあります。

{{ }} と {!! !!} の違い

{{ }} でくくった場合は htmlspecialchars でエスケープされる、 {!! !!} はされない、という違いではあるんですが、直接 htmlspecialchars を呼んでるわけじゃないんですね。

Illuminate/Support/helpers.php に e() という関数があり、まずはそいつが呼ばれるようになっています。

helpers.php
function e($value, $doubleEncode = true)
{
    if ($value instanceof Htmlable) {
        return $value->toHtml();
    }

    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', $doubleEncode);
}

どの文字がどのようにエンコードされるかは、以下のコマンドを実行することで確認できます。

$ php -r 'var_dump(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES));'
array(5) {
  ["""]=>
  string(6) """
  ["&"]=>
  string(5) "&"
  ["'"]=>
  string(6) "'"
  ["<"]=>
  string(4) "&lt;"
  [">"]=>
  string(4) "&gt;"
}

第二引数の $doubleEncode は、すでにエンコードされたエンティティをさらにエンコードするかどうか、を指定します。

e('&amp;', true);  // -> &amp;amp;
e('&amp;', false); // => &amp;

まぁ、通常はデフォルトでいいでしょう。

e() の第一引数に Htmlable インタフェースを持つクラスのインスタンスが渡されると、そいつの toHtml() メソッドが呼ばれます。

なので、以下のような渡し方をすると、HTML タグ(正確には get_html_translation_table 実行時に列挙される特殊文字)はエスケープされません。

class MyContent implements Htmlable {
    private $content;

    public function __construct(string $content) {
        $this->content = $content;
    }
    public function toHtml() {
        return $this->content;
    }
}

// Controller
$content = '<script>alert("hi")</script>';
return view('index', ['my_content' => new MyContent($content)]);

// View (JavaScript が実行されてしまう)
{{ $my_content }}

さすがに上のような使い方はされないとは思いますが、 e という関数名は encode の e っぽいので、ミスリードされる恐れはあると思います。

より詳しいことは、Illuminate\View\Compilers\BladeCompiler クラスおよび Illuminate\View\Compilers\Concerns\CompilesEchos トレイトを読むと、どのようなロジックで {{ }}{!! !!} がパース/変換されているか理解できると思いますので、興味ある方は覗いてみてください。

蛇足ではありますが、上の2つのステートメントの他に {{{ }}} という三重ブレースのステートメントが存在します。

こちらも {{ }} 同様エスケープされるんですが、 {{ }} がエスケープの手段を変更できるのに対し(BladeCompiler クラスを拡張し、 $echoFormat というプロパティをオーバライドすれば差し替えられます)、 {{{ }}} は強制的に e() が呼ばれます。

なぜ逆にしなかったのかという疑問はありますし、公式ドキュメントに記載もないので、特に理由がないのであれば {{ }} のみに留めておいたほうがよさそうです(ご存知の方がいたらコメント欄にて教えていただけると助かります)。

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

CMS 的なアプリケーションの場合、ユーザーが入力したコンテントであっても、HTML をエスケープせずにそのまま使いたいシチュエーション(WYSIWYG エディタを提供などしているケース)もあろうかと思います。

そういうケースで使われる、HTML タグおよび PHP タグを除去する strip_tags という関数があります。

PHP: strip_tags - Manual

ただし、マニュアルに下記の記載があります。

警告
strip_tags() は HTML の検証を行わないため、 不完全または壊れたタグにより予想以上に多くのテキスト/データが削除される可能性があります。

警告
この関数は、allowable_tags で許可した全てのタグの属性を修正しません。 これには、style および onmouseover属性が含まれており、 悪意のあるユーザーが他のユーザーに見せるようなテキストを投稿する際に危険な行為を行う可能性があります。

ということなので、ユーザー自身にではなくプログラムで自動的にタグ付けを行ってやる必要があるでしょう。

さて、使い方は以下のようなかんじになります。

Controller

IndexController.php
public function __invoke() {
    $content =<<<EOD
<p>
  <b>alert("hi!")</b>
  <br>
  <script>alert("hi")</script>
  <span style="text-decoration-line: line-through;">hi</span>
  <!-- comment -->
  <span style="color:red;">bye</span>
</p>
EOD;

    $allowedTags = '<b><br><p><span>';

    return view('index', [
        'content' => strip_tags($content, $allowedTags),
    ]);
}

View

<h1>escaped</h1>
<div>{{ $content }}</div>
<h1>raw</h1>
<div>{!! $content !!}</div>

出力

image.png

Htmlable と組み合わせて使うのであれば、

class WysiwygContent implements Htmlable {
    private const DEFAULT_ALLOWED_TAGS = '<b><br><p><span>';

    private $content;
    private $allowedTags;

    public function __construct(string $content, ?string $allowedTags = null) {
        $this->content = $content;
        $this->allowedTags = $allowedTags ?? self::DEFAULT_ALLOWED_TAGS;
    }
    public function toHtml() {
        return strip_tags($this->content, $this->allowedTags);
    }
}

// Controller
    $content =<<<EOD
<p>
  <b>alert("hi!")</b>
  <br>
  <script>alert("hi")</script>
  <span style="text-decoration-line: line-through;">hi</span>
  <!-- comment -->
  <span style="color:red;">bye</span>
</p>
EOD;
return view('index', ['content' => new WysiwygContent($content)]);

// View
{{ $content }}

Htmlable を使う場合は、自前でエンコードするか、危険なタグを取り除くかする必要があります。

おまけ

改行だけして他はエスケープしたい場合

{!! nl2br(e($content)) !!}

おわりに

普段なにげなく使っている Blade の書式ですが、調べてみるといくつか面白い発見があったので、みなさんもぜひソースコードを読んでみてください。

nunulk
PHP, Laravel, オブジェクト指向プログラミング, デザインパターン, リファクタリング, 関数プログラミング, etc.
http://nunulk.hatenablog.com
phper-oop
ペチオブはオブジェクト指向ワーキンググループです。様々なエンジニアの方に参加頂いております。
https://phper-oop.connpass.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away