转义序列
用于表示特殊字符的字符串。反斜杠后跟字符表示换行、制表符等。
转义序列是一种表示机制:把普通字符无法表示的特殊字符或控制字符,用反斜杠 (\) 之类的转义字符后跟若干字符的组合来表达。它是编程语言、数据格式、终端等所有处理文本的场景中都会用到的基本概念。
有代表性的转义序列包括 \n (换行)、\t (制表符)、\\ (反斜杠)、\" (双引号)、\0 (空字符) 等。作为 Unicode 转义,还有 \u0041 (字符 A) 和 \u{1F600} (表情符号 😀) 这样直接指定码位的写法。C 语言系的语言还可以使用八进制转义 (\101) 和十六进制转义 (\x41),两者表示的都是 A。不过像 \u0041 这样的 4 位形式只能写到 U+FFFF。超出这一范围的字符,必须用 ES2015 之后的 JavaScript 和 Rust 中可用的 \u{1F600} 这类花括号形式,或者写成并列代理对的 \uD83D\uDE00 形式 (两者都等价于 😀);在没有花括号形式的 Java 等语言里,只能选后者。
转义的机制因上下文而异。JSON 中双引号和反斜杠的转义是必须的,控制字符 (U+0000 - U+001F) 也需要转义。HTML 中由 < (<)、& (&)、" (") 等字符引用承担转义的角色,这对于防止 XSS (跨站脚本) 攻击也很重要。URL 中使用的是 %20 (空格) 这样的百分号编码。
正则表达式中的转义尤其复杂。\. 是字面量的点号 (让元字符失效),\d 是表示数字的元序列。在编程语言的字符串字面量中书写正则表达式时,语言的转义和正则表达式的转义会双重生效,因此有时为了表示 1 个反斜杠不得不写成 \\\\。这个「双重转义」问题是让许多开发者头疼的地方。
从安全的角度看,转义处理不完备会引出严重的漏洞。SQL 注入的典型入口就是单引号的转义遗漏,XSS 则源于 HTML 特殊字符的转义遗漏。把用户输入嵌入其他上下文 (SQL、HTML、JavaScript、Shell 命令等) 时,与该上下文相符的处理不可或缺。不过就 SQL 而言,靠自己写转义来守住全部漏洞这一方针本身就不被推荐。原因是字符集的解释差异和引号写法的不同容易留下绕过的空隙,所以判断的基本做法是:用占位符实现参数化查询,把值和代码在结构上分开,只对表名这类无法使用占位符的位置改用允许列表来校验。
从字符计数的角度看,需要注意转义序列在源代码中的字符数与实际输出的字符数并不相同。\n 在源代码中是 2 个字符 (\ 和 n),运行时则作为 1 个字符 (换行) 处理。\u0041 是 6 个字符的源代码,输出却是 1 个字符 (A)。在表情符号上,这一差距还会进一步拉大。把 😀 写成 \u{1F600} 时源代码是 9 个字符,写成 \uD83D\uDE00 时是 12 个字符,而输出在两种写法下看上去都是 1 个字符,用 JavaScript 的 length 数则是 2。用字符计数工具分析源代码时,是显示展开转义序列之后的字符数还是展开之前的字符数,需要根据用户的用途分别对待。