diff --git a/springboot/fastbee-common/pom.xml b/springboot/fastbee-common/pom.xml
index 827eaf96..5ab6f131 100644
--- a/springboot/fastbee-common/pom.xml
+++ b/springboot/fastbee-common/pom.xml
@@ -65,6 +65,12 @@
保留 Quill 编辑器常用的安全格式,删除脚本、事件属性、危险协议和未授权标签。
+ */ +public final class RichTextSanitizer +{ + private static final Pattern IMAGE_DIMENSION_PATTERN = Pattern.compile("[0-9]{1,5}"); + private static final Pattern LINK_TARGET_PATTERN = Pattern.compile("(?i)_(?:blank|self)"); + + /** Quill 输出的安全样式类,禁止通过任意 class 扩大前端样式能力。 */ + private static final Pattern QUIL_CLASS_PATTERN = Pattern.compile( + "(?i)(?:ql-(?:align-(?:center|right|justify)|indent-[1-8]|size-(?:small|large|huge)|" + + "direction-rtl|font-(?:serif|monospace)|syntax))" + + "(?:\\s+ql-(?:align-(?:center|right|justify)|indent-[1-8]|size-(?:small|large|huge)|" + + "direction-rtl|font-(?:serif|monospace)|syntax))*"); + + /** + * 策略对象创建成本较高且本身不可变,所有请求复用同一实例。 + * 不允许 iframe、video、object、embed、svg 等主动内容。 + */ + private static final PolicyFactory POLICY = new HtmlPolicyBuilder() + .allowCommonInlineFormattingElements() + .allowCommonBlockElements() + .allowStyling() + .allowUrlProtocols("http", "https") + .allowElements("a", "img", "br", "pre", "div", "span") + .allowWithoutAttributes("a", "br", "span") + .allowAttributes("href").onElements("a") + .allowAttributes("target").matching(LINK_TARGET_PATTERN).onElements("a") + .allowAttributes("alt", "src").onElements("img") + .allowAttributes("height", "width").matching(IMAGE_DIMENSION_PATTERN).onElements("img") + .allowAttributes("class") + .matching(QUIL_CLASS_PATTERN) + .onElements("p", "div", "pre", "ol", "ul", "li", "span") + .requireRelNofollowOnLinks() + .toFactory(); + + private RichTextSanitizer() + { + } + + /** + * 净化不可信富文本。 + * + * @param html 待净化的富文本 + * @return 可安全嵌入 HTML 正文上下文的富文本 + */ + public static String sanitize(String html) + { + if (html == null || html.isEmpty()) + { + return html; + } + return POLICY.sanitize(html); + } +} diff --git a/springboot/fastbee-common/src/test/java/com/fastbee/common/utils/html/RichTextSanitizerTest.java b/springboot/fastbee-common/src/test/java/com/fastbee/common/utils/html/RichTextSanitizerTest.java new file mode 100644 index 00000000..eac1f9bb --- /dev/null +++ b/springboot/fastbee-common/src/test/java/com/fastbee/common/utils/html/RichTextSanitizerTest.java @@ -0,0 +1,75 @@ +package com.fastbee.common.utils.html; + +import org.junit.Assert; +import org.junit.Test; + +/** + * 富文本白名单净化测试。 + */ +public class RichTextSanitizerTest +{ + @Test + public void shouldRemoveExecutableContent() + { + String input = "正文"
+ + "
正文
"); + Assert.assertEquals(sanitized, RichTextSanitizer.sanitize(sanitized)); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java index 448b77ae..6ead4e26 100644 --- a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java @@ -3,6 +3,7 @@ package com.fastbee.iot.service.impl; import java.util.ArrayList; import java.util.List; import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.html.RichTextSanitizer; import com.fastbee.iot.mapper.NewsCategoryMapper; import com.fastbee.iot.model.CategoryNews; import com.fastbee.iot.model.IdAndName; @@ -88,6 +89,7 @@ public class NewsServiceImpl implements INewsService @Override public int insertNews(News news) { + news.setContent(RichTextSanitizer.sanitize(news.getContent())); news.setCreateTime(DateUtils.getNowDate()); return newsMapper.insertNews(news); } @@ -101,6 +103,7 @@ public class NewsServiceImpl implements INewsService @Override public int updateNews(News news) { + news.setContent(RichTextSanitizer.sanitize(news.getContent())); news.setUpdateTime(DateUtils.getNowDate()); return newsMapper.updateNews(news); } diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml index c4eeb2a1..252cf074 100644 --- a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml @@ -96,7 +96,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update news