当前位置: 首页 > news >正文

仓颉三方库开发实战:sanitize_html 实现详解

仓颉三方库开发实战:sanitize_html 实现详解

项目背景

在现代Web开发中,HTML内容清理和消毒是防止XSS(跨站脚本)攻击的关键技术。sanitize_html项目使用仓颉编程语言实现了一个功能强大的HTML内容清理和消毒库,不仅提供了完善的HTML过滤功能,也探索了仓颉语言在Web安全领域的应用潜力。

本文将详细介绍sanitize_html的设计理念、核心功能实现、技术挑战与解决方案,为使用仓颉语言进行Web安全开发的开发者提供参考。

技术栈

  • 开发语言:仓颉编程语言 (cjc >= 1.0.3)
  • 核心库
    • std.collection: 数据结构(ArrayList, HashMap)
    • std.option: Option类型错误处理
    • 字符串处理:Rune类型处理、字符串操作

核心功能实现

1. 库架构设计

sanitize_html采用分层模块化设计,将功能分解为多个独立的模块:

sanitize_html
├── 数据结构定义模块          # Frame、Tag、SanitizeOptions等核心数据结构
├── 字符串工具模块            # 字符串处理、转义、匹配等工具函数
├── URL解析和验证模块          # URL解析、协议验证、域名检查等
├── HTML解析器模块            # HTML标签解析、属性提取、文本处理等
├── 标签过滤模块              # 标签白名单、属性过滤、类名过滤等
├── 标签转换模块              # 标签转换器、simpleTransform函数等
├── 文本过滤模块              # 文本过滤器、文本处理模式等
├── 排除过滤模块              # 排除过滤器、排除规则匹配等
└── 公共API模块               # sanitize、sanitizeHtml等公共接口

设计亮点

  • 模块化设计,提高代码可维护性和可测试性
  • 接口驱动设计,支持灵活的扩展机制
  • 类型安全,充分利用仓颉语言的类型系统

2. 核心数据结构设计

Frame类:标签层次结构跟踪

Frame类用于跟踪HTML标签的层次结构,确保标签正确闭合和嵌套:

public class Frame {public var tag: String  // 原始标签名(用于匹配结束标签)public var transformedTag: String  // 转换后的标签名(用于输出结束标签)public var attribs: HashMap<String, String>public var tagPosition: Int64public var text: Stringpublic var openingTagLength: Int64public var mediaChildren: ArrayList<String>public var innerText: Option<String>public init(tag: String, attribs: HashMap<String, String>) {this.tag = tagthis.transformedTag = tagthis.attribs = attribsthis.tagPosition = 0this.text = ""this.openingTagLength = 0this.mediaChildren = ArrayList<String>()this.innerText = None}
}

设计考虑

  • 使用栈(ArrayList)跟踪标签嵌套关系
  • 区分原始标签名和转换后的标签名,确保结束标签匹配正确
  • 使用Option类型处理可选的innerText,避免空指针异常
Tag类:标签转换数据结构

Tag类用于标签转换器返回完整信息:

public class Tag {public var tagName: Stringpublic var attribs: HashMap<String, String>public var text: Option<String>  // 可选的文本内容public init(tagName: String, attribs: HashMap<String, String>, text: Option<String>) {this.tagName = tagNamethis.attribs = attribsthis.text = text}// 便捷构造函数public static func of(tagName: String, attribs: HashMap<String, String>): Tag {return Tag(tagName, attribs, None)}public static func of(tagName: String, attribs: HashMap<String, String>, text: String): Tag {return Tag(tagName, attribs, Some(text))}
}

设计考虑

  • 支持标签名转换、属性修改、文本内容设置三种转换方式
  • 使用便捷构造函数简化Tag对象创建
  • Option类型处理可选的文本内容
SanitizeOptions类:配置选项管理

SanitizeOptions类包含所有可配置的选项:

public class SanitizeOptions {public var allowedTags: Option<ArrayList<String>>public var allowedAttributes: Option<HashMap<String, ArrayList<String>>>public var allowedSchemes: ArrayList<String>// ... 更多配置选项public init() {// 初始化所有字段为默认值}public static func defaults(): SanitizeOptions {let opts = SanitizeOptions()// 设置安全的默认配置opts.allowedTags = Some(SanitizeOptions.getDefaultTags())opts.allowedAttributes = Some(SanitizeOptions.getDefaultAttributes())// ... 设置其他默认值return opts}
}

设计考虑

  • 使用Option类型表示可选配置,None表示使用默认行为
  • 提供defaults()静态方法,返回安全的默认配置
  • 支持用户配置与默认配置的智能合并

3. HTML解析器实现

HTML解析器是sanitize_html的核心模块,负责解析HTML标签、属性、文本内容等。

解析流程
private func parseAndSanitize(html: String, options: SanitizeOptions): String {// 1. 预处理CDATA(如果启用)var processedHtml = htmlif (options.recognizeCDATA) {processedHtml = preprocessCDATA(html)}// 2. 初始化状态var result = ""let stack = ArrayList<Frame>()var depth: Int64 = 0var skipText = falsevar skipTextDepth: Int64 = 0let htmlRunes = processedHtml.toRuneArray()var i: Int64 = 0// 3. 遍历HTML字符while (i < htmlRunes.size) {if (htmlRunes[i] == r'<') {// 处理标签let tagEnd = findTagEnd(htmlRunes, i + 1)if (tagEnd.isSome()) {let end = tagEnd.unwrap()let tagContentStr = runesToStringSlice(htmlRunes, i+1, end)if (stringStartsWith(tagContentStr, "/")) {// 处理结束标签handleClosingTag(...)} else {// 处理开始标签handleOpeningTag(...)}}} else {// 处理文本内容if (!skipText) {handleText(...)}}}// 4. 关闭所有未闭合的标签closeUnclosedTags(stack, result)// 5. 后处理CDATA(如果启用)if (options.recognizeCDATA) {result = postprocessCDATA(result)}return result
}

实现要点

  • 使用Rune数组进行字符处理,正确支持Unicode字符
  • 使用栈跟踪标签嵌套关系,确保标签正确闭合
  • 支持CDATA段的预处理和后处理
  • 支持skipText机制,跳过某些标签的内容(如script、style)
标签解析
private func parseTag(tagContent: String): (String, HashMap<String, String>) {let tagName = extractTagName(tagContent)let attrs = HashMap<String, String>()let runes = tagContent.toRuneArray()var i = stringSize(tagName)// 解析属性while (i < runes.size) {let oldI = ii = parseAttribute(runes, i, attrs)// 防止无限循环if (i <= oldI) {i = oldI + 1}}return (tagName, attrs)
}

实现要点

  • 支持带引号和不带引号的属性值
  • 处理属性的各种格式(单引号、双引号、无引号)
  • 防止解析死循环,确保解析进度

4. 标签过滤机制

标签过滤是sanitize_html的核心安全机制之一。

标签白名单检查
private func tagAllowed(name: String, options: SanitizeOptions): Bool {match (options.allowedTags) {case None => return true  // 未指定则允许所有case Some(allowed) => return isTagInList(name, allowed)}
}
禁用标签处理模式

sanitize_html支持三种禁用标签处理模式:

  • discard(默认):丢弃禁用标签,保留其内容
  • escape:转义禁用标签,子标签如果不被禁用则不转义
  • recursiveEscape:转义禁用标签及其所有子标签,不管子标签是否被允许
if (!tagAllowed(finalTagName, options)) {if (options.disallowedTagsMode == "discard" || options.disallowedTagsMode == "completelyDiscard") {if (containsInList(options.nonTextTags, tagName)) {newSkipText = truenewSkipTextDepth = newDepth}} else if (options.disallowedTagsMode == "escape" || options.disallowedTagsMode == "recursiveEscape") {let tagStr = runesToStringSlice(htmlRunes, i, end+1)result = escapeHtml(tagStr, false)}
}

5. 属性过滤机制

属性过滤是sanitize_html的另一个核心安全机制。

属性白名单检查
private func attributeAllowed(tagName: String, attrName: String, options: SanitizeOptions): Bool {match (options.allowedAttributes) {case None => return truecase Some(allowedAttrs) => return checkAttributesInMap(allowedAttrs, tagName, attrName)}
}
URL属性验证

对于href、src等URL属性,需要进行协议验证:

private func naughtyHref(tagName: String, href: String, options: SanitizeOptions): Bool {// 移除控制字符var cleaned = cleanUrl(href)// 移除注释cleaned = removeComments(cleaned)// 提取协议let scheme = extractScheme(cleaned)match (scheme) {case Some(sch) => return handleSchemeExists(tagName, sch, options)case None => return handleSchemeNone(cleaned, options)}
}

实现要点

  • 移除URL中的控制字符和注释,防止XSS攻击
  • 验证URL协议是否在允许列表中
  • 支持协议相对URL(//example.com)的处理
  • 支持按标签配置不同的协议规则
类名过滤

对于class属性,支持类名白名单过滤:

private func filterClasses(classes: String, allowed: Option<HashMap<String, ArrayList<String>>>, tagName: String): String {match (allowed) {case None => classes  // 没有限制,返回原值case Some(allowedMap) => filterClassesWithMap(classes, allowedMap, tagName)}
}

实现要点

  • 支持按标签配置允许的类名列表
  • 支持通配符匹配(如 “class-*” 匹配所有以 “class-” 开头的类名)
  • 支持正则表达式匹配(以 “regex:” 开头)

6. 标签转换功能

sanitize_html支持两种标签转换方式:

字符串映射

简单的标签名映射:

options.transformTags["ol"] = "ul"  // 将ol标签转换为ul
函数转换器

通过TagTransformer接口实现复杂的转换逻辑:

public interface TagTransformer {func transform(tagName: String, attributes: HashMap<String, String>): Option<Tag>
}// 使用simpleTransform创建转换器
let transformer = simpleTransform("div", None, None)
options.transformTagFunctions["p"] = transformer

实现要点

  • 支持精确匹配和通配符匹配(如 “h*” 匹配所有h开头的标签)
  • 支持属性合并和替换两种模式
  • 支持在转换时设置标签的innerText

7. 文本过滤功能

sanitize_html支持两种文本过滤方式:

预设模式
options.textFilterMode = "trim|upper"  // 去除首尾空白并转大写

支持的预设模式:

  • trim:去除首尾空白
  • upper:转大写
  • lower:转小写
函数形式

通过TextFilter接口实现自定义文本过滤逻辑:

public interface TextFilter {func filter(text: String, tagName: String): String
}class UpperCaseFilter <: TextFilter {public func filter(text: String, _: String): String {// 转换为大写return toUpperCase(text)}
}

8. 排除过滤功能

sanitize_html支持两种排除过滤方式:

配置化规则

通过ExclusionRule实现配置化的排除规则:

public class ExclusionRule {public var tag: String  // 要匹配的标签名public var attributeConditions: HashMap<String, String>  // 属性条件public var textCondition: Option<String>  // 文本内容条件public var mode: String  // "excludeTag" 或 "excludeAll"
}let rule = ExclusionRule("a", HashMap<String, String>(), None, "excludeTag")
options.exclusiveFilterRules.add(rule)
函数形式

通过ExclusiveFilter接口实现自定义排除逻辑:

public interface ExclusiveFilter {func shouldExclude(frame: Frame): ExcludeResult
}class EmptyLinkFilter <: ExclusiveFilter {public func shouldExclude(frame: Frame): ExcludeResult {if (frame.tag == "a" && frame.text == "") {return ExcludeResult.excludeTag()}return ExcludeResult.none()}
}

实现要点

  • 支持excludeTag(只排除标签)和excludeAll(排除标签和内容)两种模式
  • 支持按标签名、属性条件、文本内容进行匹配
  • 支持正则表达式匹配(属性值和文本内容)

技术挑战与解决方案

1. Unicode字符处理

挑战:HTML可能包含各种Unicode字符,需要正确处理。

解决方案:使用Rune类型进行字符处理:

private func toLowerCase(s: String): String {var result = ""for (r in s.runes()) {if (r >= r'A' && r <= r'Z') {result = result + String(Rune(UInt32(r) + 32))} else {result = result + String(r)}}return result
}

优势

  • 正确处理多字节Unicode字符
  • 避免字符串编码问题
  • 提高字符串处理性能

2. 标签层次结构管理

挑战:HTML标签可能嵌套,需要正确跟踪标签的层次结构,确保标签正确闭合。

解决方案:使用栈(ArrayList)跟踪标签嵌套关系:

let stack = ArrayList<Frame>()// 开始标签时入栈
let frame = Frame(tagName, filteredAttrs)
stack.add(frame)// 结束标签时出栈
let popResult = popMatchingTag(stack, tagName)

实现细节

  • 使用Frame类存储标签信息,包括原始标签名和转换后的标签名
  • popMatchingTag函数从栈顶向下查找匹配的标签,找到后删除从该位置到栈顶的所有元素(这是HTML标签匹配的标准行为)
  • 处理未闭合标签,在解析结束时自动关闭

3. URL协议验证

挑战:需要验证URL协议的安全性,防止javascript:、data:等危险协议。

解决方案:实现URL解析和协议验证机制:

private func extractScheme(url: String): Option<String> {// 首先查找 :// 模式(标准协议格式)let colonSlashIndex = findSubstring(url, "://")match (colonSlashIndex) {case Some(idx) => return validateAndExtractScheme(url, idx)case None => ()}// 如果没有找到 ://,查找单独的 :(用于javascript:等协议)let colonIndex = findSubstring(url, ":")match (colonIndex) {case Some(idx) => return checkColonScheme(url, idx)case None => return None}
}

安全措施

  • 移除URL中的控制字符和注释
  • 验证协议格式(字母开头,包含字母、数字、点、减号、加号)
  • 支持协议白名单机制
  • 支持按标签配置不同的协议规则

4. 标签转换的复杂性

挑战:标签转换需要支持多种转换方式,包括字符串映射、函数转换器、通配符匹配等。

解决方案:实现多层次的转换机制:

private func applyTransformTag(tagName: String, attributes: HashMap<String, String>,transformTags: HashMap<String, String>,transformTagFunctions: HashMap<String, TagTransformer>): Option<Tag> {// 1. 首先尝试函数转换器(精确匹配)// 2. 尝试函数转换器(通配符匹配)// 3. 尝试字符串映射(精确匹配)// 4. 尝试字符串映射(通配符匹配)// 5. 没有匹配的规则,返回None
}

实现要点

  • 优先级:函数转换器 > 字符串映射(精确匹配 > 通配符匹配)
  • 支持通配符模式匹配(如 “h*” 匹配所有h开头的标签)
  • 支持属性合并和替换两种模式
  • 支持在转换时设置标签的innerText

5. 性能优化

挑战:大型HTML文档的解析性能可能受到影响。

解决方案

  1. 使用HashMap存储配置:提高查找效率
  2. 使用Rune数组进行字符处理:避免多次字符串复制
  3. 使用ArrayList作为标签栈:高效管理标签层次结构
  4. 延迟字符串构建:只在需要时构建HTML字符串
// 使用HashMap存储配置
let allowedAttrs = HashMap<String, ArrayList<String>>()// 使用Rune数组进行字符处理
let htmlRunes = processedHtml.toRuneArray()// 使用ArrayList作为标签栈
let stack = ArrayList<Frame>()

使用示例

示例1:基本使用

import sanitize_html.*main() {let dirty = "<script>alert('XSS')</script><p>Hello World</p>"let clean = sanitize(dirty)println(clean)  // 输出: <p>Hello World</p>
}

示例2:自定义配置

import sanitize_html.*
import std.collection.ArrayList
import std.collection.HashMapmain() {let options = SanitizeOptions()let allowedTags = ArrayList<String>()allowedTags.add("p")allowedTags.add("b")allowedTags.add("i")options.allowedTags = Some(allowedTags)let allowedAttrs = HashMap<String, ArrayList<String>>()let pAttrs = ArrayList<String>()pAttrs.add("class")allowedAttrs["p"] = pAttrsoptions.allowedAttributes = Some(allowedAttrs)let dirty = "<p class=\"text\">Hello <b>World</b></p><script>alert('XSS')</script>"let clean = sanitizeHtml(dirty, Some(options))println(clean)  // 输出: <p class="text">Hello <b>World</b></p>
}

示例3:标签转换

import sanitize_html.*
import std.collection.HashMapmain() {let options = SanitizeOptions()// 字符串映射options.transformTags["ol"] = "ul"// 函数转换器let transformer = simpleTransform("div", None, None)options.transformTagFunctions["p"] = transformerlet dirty = "<ol><li>Item</li></ol><p>Hello</p>"let clean = sanitizeHtml(dirty, Some(options))println(clean)  // 输出: <ul><li>Item</li></ul><div>Hello</div>
}

示例4:文本过滤

import sanitize_html.*class UpperCaseFilter <: TextFilter {public func filter(text: String, _: String): String {var result = ""for (r in text.runes()) {if (r >= r'a' && r <= r'z') {result = result + String(Rune(UInt32(r) - 32))} else {result = result + String(r)}}return result}
}main() {let options = SanitizeOptions()options.textFilter = Some(UpperCaseFilter())let dirty = "<p>Hello World</p>"let clean = sanitizeHtml(dirty, Some(options))println(clean)  // 输出: <p>HELLO WORLD</p>
}

示例5:排除过滤

import sanitize_html.*
import std.collection.ArrayList
import std.collection.HashMapclass EmptyLinkFilter <: ExclusiveFilter {public func shouldExclude(frame: Frame): ExcludeResult {if (frame.tag == "a" && frame.text == "") {return ExcludeResult.excludeTag()}return ExcludeResult.none()}
}main() {let options = SanitizeOptions()options.exclusiveFilter = Some(EmptyLinkFilter())let dirty = "<a href=\"#\"></a><a href=\"https://example.com\">Link</a>"let clean = sanitizeHtml(dirty, Some(options))println(clean)  // 输出: <a href="https://example.com">Link</a>
}

最佳实践

1. 使用默认配置

对于大多数场景,使用默认配置即可获得良好的安全保护:

let clean = sanitize(dirty)

2. 最小权限原则

只允许必要的标签和属性:

let options = SanitizeOptions()
let allowedTags = ArrayList<String>()
allowedTags.add("p")
allowedTags.add("b")
options.allowedTags = Some(allowedTags)

3. URL协议白名单

严格限制允许的URL协议:

options.allowedSchemes.add("http")
options.allowedSchemes.add("https")
// 不包含javascript:、data:等危险协议

4. 域名白名单

对于script和iframe标签,使用域名白名单:

options.allowedScriptDomains.add("cdn.example.com")
options.allowedIframeDomains.add("youtube.com")

5. 测试覆盖

为自定义配置编写测试用例:

@Test
public class SanitizeHtmlTests {@TestCasefunc testBasicSanitize(): Unit {let dirty = "<script>alert('XSS')</script><p>Hello</p>"let clean = sanitize(dirty)@Assert(clean == "<p>Hello</p>")}
}

总结

sanitize_html是一个功能强大的HTML内容清理和消毒库,使用仓颉编程语言实现。通过模块化设计、接口驱动架构、类型安全机制,实现了完善的HTML过滤功能,有效防止XSS攻击。

核心优势

  1. 类型安全:充分利用仓颉语言的类型系统,使用Option类型进行安全的错误处理
  2. 模块化设计:将功能分解为多个独立模块,提高代码可维护性和可测试性
  3. 接口驱动:提供丰富的接口(TagTransformer、TextFilter等),支持灵活的扩展机制
  4. 安全可靠:内置多层安全机制,有效防止XSS攻击
  5. 易于使用:提供简洁的API和安全的默认配置,开箱即用

适用场景

  • Web应用的用户输入过滤
  • 内容管理系统(CMS)的HTML清理
  • 富文本编辑器的内容处理
  • 邮件系统的HTML内容过滤
  • 任何需要HTML内容清理和消毒的场景

相关学习资源

仓颉标准库:https://gitcode.com/Cangjie/cangjie_runtime/tree/main/stdlib

仓颉扩展库:https://gitcode.com/Cangjie/cangjie_stdx

仓颉命令行工具:https://gitcode.com/Cangjie/cangjie_tools

仓颉语言测试用例:https://gitcode.com/Cangjie/cangjie_test

仓颉语言示例代码:https://gitcode.com/Cangjie/Cangjie-Examples

仓颉鸿蒙示例应用:https://gitcode.com/Cangjie/HarmonyOS-Examples

精品三方库:https://gitcode.com/org/Cangjie-TPC/repos

SIG 孵化库:https://gitcode.com/org/Cangjie-SIG/repos


sanitize_html展示了仓颉语言在Web安全领域的应用潜力,为使用仓颉语言进行Web开发的开发者提供了有价值的参考。

http://www.dtcms.com/a/601289.html

相关文章:

  • 逻辑回归以及python(sklearn)详解
  • RESTful规范
  • 四川高端网站建设女生做网站开发
  • PDF转图片:轻松实现工程图纸的高效共享与高清展示
  • 【ZeroRange WebRTC】ICE 服务器列表解析(KVS WebRTC)
  • 【考证资讯】注意!2026 年HCIE实验考试内容重要调整!
  • uni-app中表格分页
  • LeetCode hot100:142 环形链表 II:寻找环的入口节点
  • vue下载依赖报错npm ERR node-sass@4.14.1 postinstall: `node scripts/build.js`的解决方法
  • 二分查找专题(十三):“答案二分”的“三连击”——「制作m束花所需的最少天数」
  • 快3网站制作 优帮云简述网站建设的方法
  • Java1112 基类 c#vscode使用 程序结构
  • 第30节:大规模地形渲染与LOD技术
  • Goer-Docker系列1-容器数据持久化
  • 天机学堂——day1(修改bug)
  • 国外网站设计欣赏长沙网页设计哪家专业
  • php做图片交互网站代码成都制作网站公司
  • AI应用开发神器coze(扣子):使用智能体生成文案和图片
  • Java·如何区别多态中的“重写”与“重载”。
  • B端系统自动化MCP工具开发指南
  • 外贸整合营销网站如何快速开发手机app
  • 谢赛宁×李飞飞×LeCun联手重磅|Cambrian-S:「视频空间超感知」新范式,实现真正持续视频感知
  • 在服务器网站上做跳转网站运营推广方式
  • Ansible 安装与入门
  • VMMap 学习笔记(8.7):命令行模式与自动抓取——无界面采集内存证据的正确姿势
  • 大型网站服务器得多少钱app大全免费软件排行榜
  • AXI-5.3.2~5.3.5
  • Anaconda安装与配置:构建人工智能开发环境
  • 从入门到精通:周志华《机器学习》第一、二章深度解析
  • 网站建设品牌策划装修设计软件排名