textarea无法阻止非法字符输入,需用input事件实时清洗并服务端双重校验。前端清洗应覆盖粘贴、IME等所有路径,服务端须按输出上下文选择转义策略,禁用strip_tags等不安全方法。

textarea 本身不阻止任何字符输入,所谓“防止”只能靠 JavaScript 实时拦截或服务端强制清理。浏览器不会替你过滤控制符、零宽空格、编码实体或组合字符——这些正是绕过前端校验的常见手段。
用 input 事件监听并清洗值,不是 keydown 或 change
只有 input 事件能覆盖粘贴、拖入、自动填充、IME 输入(如中文拼音上屏)等全部路径。其他事件要么漏掉粘贴,要么在组合输入未完成时就触发,导致 iOS/Android 上光标错乱或内容丢失。
- 对
textarea绑定addEventListener('input', handler),别用内联oninput - 清洗逻辑示例:
e.target.value = e.target.value.replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u2028-\u202f]/g, '')—— 去掉控制字符、零宽空格、行分隔符 - 如果需保留换行但禁用富文本,加
.replace(/]*>/g, '')(仅作预处理,不能替代后端净化) - 清洗后不调用
setSelectionRange()的话,iOS 下光标会跳到开头,用户继续输入就覆盖前面内容
服务端必须重做清洗,且按输出位置选择策略
前端改 value 只影响当前页面 DOM,抓包重放、禁用 JS、curl 提交原始数据,都会绕过所有前端逻辑。服务端拿到的永远是原始字节流。
- 若输出到纯文本上下文(如
<p>{{ content }}</p><div class="aritcle_card flexRow"> <div class="artcardd flexRow"> <a class="aritcle_card_img" href="/xiazai/skill12116" title="DeepBI DHTML 页面生成器"><img src="https://img.php.cn/upload/skill/001/246/273/6adaf20d3191a227.jpg" alt="DeepBI DHTML 页面生成器" ></a> <div class="aritcle_card_info flexColumn"> <a href="/xiazai/skill12116" title="DeepBI DHTML 页面生成器">DeepBI DHTML 页面生成器</a> <p>生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur</p> </div> <a href="/xiazai/skill12116" title="DeepBI DHTML 页面生成器" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div> <p><span>立即学习</span>“<a href="https://pan.quark.cn/s/cb6835dc7db1" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">前端免费学习笔记(深入)</a>”;</p>),用htmlspecialchars($str, ENT_QUOTES, 'UTF-8'),缺参数会导致单引号逃逸 - 若输出到 HTML 属性(如
<div title="{{ content }}">),<code>htmlspecialchars不够,得用上下文专用转义,比如htmlentities($str, ENT_QUOTES | ENT_HTML5, 'UTF-8')并额外校验是否含双引号 - 若允许有限 HTML(如评论支持
<b></b>、<a></a>),必须用白名单库(如 PHP 的HTMLPurifier、Python 的bleach.clean()),禁用strip_tags()—— 它不检查<a onclick="..."></a> - 数据库存储前不要删掉所有特殊字符,否则无法还原语义;应保留原始输入 + 记录清洗动作(如 “移除了 U+200B”),用于人工复核
-
<textarea pattern="[a-zA-Z0-9\u4e00-\u9fa5\r\n]"></textarea>对粘贴进来的<script></script>完全无效 -
maxlength="100"挡不住 100 个\u200b(零宽空格),它们不占长度但能破坏渲染或绕过规则 - 移动端键盘类型切换(如
type="email")只是提示,不影响实际可输入字符集
别信 pattern 或 maxlength 能防特殊字符
pattern 属性只在调用 reportValidity() 或原生表单提交时校验,既不阻止输入,也不修改 value;maxlength 按 UTF-16 码元计数,一个 emoji 可能占 2 个长度,而零宽字符根本不算长度。
真正难处理的是那些“看起来正常”的字符:全角标点、同形字母(如拉丁 a 和西里尔 а)、带组合符的汉字(U+0065 U+0301 → é)。它们在视觉上无异,但 Unicode 码位不同,简单正则匹配会漏掉。这类问题必须靠 Unicode 规范化(NFC/NFD)+ 白名单比对,而不是靠删减。


















