Drupal 8 の #markup のエスケープを避ける方法

Drupal 開発ネタです。

Drupal 8 には前のバージョン Drupal 7 と同じ #markup という要素タイプが存在しますが、この挙動は Drupal 7 とは少し異なり、 #markup の中身にも一定のオートエスケープがかかるようになっています。 今回はこのオートエスケープを避ける方法をかんたんにまとめておきたいと思います。

#markup のオートエスケープを避ける方法は、大きく分けて 3 つのパターンがあります。

  • a. #markup + #allowed_tags
  • b. #markup + Markup クラス
  • c. inline_template + raw フィルタ

順番に見ていきましょう。

a. #markup + #allowed_tags

HTML のオートエスケープの機能を大別すると「タグの無害化」と「タグの中の属性の無害化」の 2 つに分けられるのですが、このうちの前者「タグの無害化」だけが無効化できればそれでよいという場合は、この #markup + #allowed_tags パターンを使う方法がシンプルでかんたんです。

$build = [
  '#markup' => file_get_contents($file),
  // input といくつかのタグのエスケープを防ぐ
  '#allowed_tags' => ['input', 'ul', 'li', 'div', 'span'],
];

このパターンの注意点は、後者の「タグの中の属性の無害化」がコントロールできない点です。 近年大きな盛り上がりを見せているフロントエンドの View (of MVC) 関連ライブラリやウェブコンポーネントなどで特殊な名前の属性を使う場合にはこの方法は使えないことも多いでしょう。

b. #markup + Markup クラス

#markup には、オートエスケープをすべて無効化する使い方もあります。

#markup の値に Drupal\Core\Render\Markup のインスタンスを渡すとその中に格納されている HTML がオートエスケープを経ずにそのまま出力されます。

use Drupal\Core\Render\Markup;

$build = [
  '#markup' => Markup::create(file_get_contents($file)),
];

すでに無害化済みであったり、確実に信頼できる HTML を表示したりしたいような場合にはこの方法を使うとよいでしょう。

「タグの無害化」と「タグの中の属性の無害化」の両方が完全にスキップされるので、正しく理解して注意して使う必要があります。 Drupal 8 のプロジェクトで XSS 関係の脆弱性が最もよく入り込みそうなところです。

c. inline_template + raw フィルタ

b の方法とできることは同じでアプローチが異なるものとして inline_template を使った方法があります。 これは #markup は使わずに inline_template というタイプを使う形になります。

$build = [
  '#type' => 'inline_template',
  '#template' => '{{ content|raw }}',
  '#context' => [
    'content' => file_get_contents($file),
  ],
];

何らかの理由で b の方法が使えなかったり、込み入ったことをしたい場合はこちらを使うとよいでしょう。

こちらも、もともと備わっているセキュリティの機能を迂回する形になるので、挙動と影響を十分に理解した上で使う必要があります。

...

知ってしまうと単純ですが、このあたりはまだまだわかりづらいところなので正しく理解して正しく使うようにしたいものです。