在 ThinkPHP 8 中,模板变量的 HTML 转义是一个重要的安全特性。以下是详细的转义与关闭转义方法:

1. 默认转义行为

ThinkPHP 8 默认对模板输出的变量进行 HTML 转义,防止 XSS 攻击。

// 控制器
public function index()
{
    $data = [
        'content' => '<script>alert("xss")</script><b>标题</b>'
    ];
    return view('index', $data);
}
<!-- 模板文件 -->
{$content}
<!-- 输出:&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;&lt;b&gt;标题&lt;/b&gt; -->
<!-- 所有HTML标签都被转义为实体 -->

2. 关闭转义的方法

方法1:使用 raw 过滤器

{$content|raw}
<!-- 输出原始HTML:<script>alert("xss")</script><b>标题</b> -->

方法2:使用 |raw 简写

{$content|raw}

方法3:在 {php} 标签中使用 html_entity_decode

{php}
    echo html_entity_decode($content);
{/php}

3. 批量关闭转义

在控制器中设置不转义:

public function index()
{
    $this->filter([]); // 禁用所有过滤器

    // 或者指定变量不转义
    $content = '<b>标题</b>';
    $this->assign([
        'content' => $content,
        'raw_content' => htmlspecialchars_decode($content)
    ]);
}

配置全局转义行为:

// config/view.php
return [
    // 默认过滤规则
    'default_filter' => 'htmlspecialchars',

    // 或关闭默认过滤
    'default_filter' => '',
];

4. 部分转义场景

转义特定字符:

<!-- 只转义特殊字符 -->
{$content|escape}

使用 nl2br 转换换行:

{$content|nl2br}
<!-- 将换行符转为 <br> 标签 -->

JSON 编码输出:

{$data|json_encode}

5. 自定义过滤函数

// 在控制器中定义过滤函数
public function index()
{
    // 自定义安全输出
    $safeOutput = function($value) {
        // 允许部分安全标签
        $allowedTags = '<b><i><u><p><br><span><div>';
        return strip_tags($value, $allowedTags);
    };

    $content = '<b>加粗</b><script>alert("xss")</script>';
    $this->assign('content', $safeOutput($content));
}

6. Blade 模板引擎的转义

如果使用 ThinkPHP 8 的 Blade 模板:

<!-- 默认转义 -->
{{ $content }}

<!-- 不转义 -->
{!! $content !!}

<!-- 转义JSON -->
@json($array)

<!-- 在JS中安全输出 -->
<script>
    var data = @json($array);
    var content = "{{ $content }}"; // 自动转义
</script>

7. 最佳实践建议

安全建议:

  1. 永远不要信任用户输入
  2. 默认开启转义
  3. 只在必要时关闭转义
  4. 对用户内容进行白名单过滤

示例代码:

// 控制器:对用户输入进行预处理
public function save()
{
    $content = input('post.content');

    // 1. 清理危险标签
    $content = strip_tags($content, '<p><br><b><i><u><a><img>');

    // 2. 验证URL
    $content = $this->filterUrls($content);

    // 3. 分配给视图
    $this->assign('content', $content);
}

// 模板:安全输出
{if condition="$isSafe"}
    {$content|raw}
{else}
    {$content}  <!-- 默认转义 -->
{/if}

自定义安全过滤器:

// 创建自定义助手函数
function safe_html($html, $allowedTags = '')
{
    if (empty($allowedTags)) {
        return htmlspecialchars($html);
    }

    $html = strip_tags($html, $allowedTags);

    // 移除危险属性
    $html = preg_replace('/on\w+\s*=/i', '', $html);
    $html = preg_replace('/javascript:/i', '', $html);

    return $html;
}

// 在模板中使用
{$content|safe_html="<p><a><b><i>"}

8. 常见问题解决

问题1:双重转义

<!-- 错误:已经转义的内容再次转义 -->
{$content|escape|raw}  <!-- 错误用法 -->

<!-- 正确:直接输出 -->
{$content}

问题2:富文本编辑器内容

对于富文本编辑器(如UEditor、CKEditor)的内容:

// 1. 入库时只做基本过滤
// 2. 出库时信任内容,但限制允许的标签

// 使用HTMLPurifier等专业库
$purifier = new HTMLPurifier();
$cleanHtml = $purifier->purify($dirtyHtml);
<!-- 模板中直接输出 -->
{$cleanHtml|raw}

总结

  1. 安全性优先:默认开启HTML转义
  2. 明确控制:只在明确安全的情况下使用 |raw
  3. 多层防护:结合输入验证、数据处理和输出转义
  4. 使用专业工具:处理复杂HTML时使用HTMLPurifier等专业库

记住: 永远不要直接输出未经处理的用户输入,即使你认为它是安全的。