通过将元素列入白名单来清理 SVG 文档
Posted
技术标签:
【中文标题】通过将元素列入白名单来清理 SVG 文档【英文标题】:Sanitizing an SVG document by whitelisting elements 【发布时间】:2011-06-20 08:42:44 【问题描述】:我想从一些 SVG 文档中提取大约 20 种元素类型来形成一个新的 SVG。
rect
、circle
、polygon
、text
、polyline
,基本上是一组视觉部件在白名单中。
javascript、cmets、动画和外链都需要去。
想到了三种方法:
-
Regex:我完全熟悉,显然不想去那里。
php DOM:可能一年前使用过一次。
XSLT:我刚刚看了一眼。
如果 XSLT 是适合这项工作的工具,我需要什么 xsl:stylesheet? 否则,您会使用哪种方法?
示例输入:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2">
<title>Mostly harmless</title>
<metadata id="metadata7">Some metadata</metadata>
<script type="text/ecmascript">
<![CDATA[
alert('Hax!');
]]>
</script>
<style type="text/css">
<![CDATA[ svgdisplay:none ]]>
</style>
<defs id="defs4">
<circle id="my_circle" cx="100" cy="50" r="40" fill="red"/>
</defs>
<g id="layer1">
<a xlink:href="www.hax.ru">
<use xlink:href="#my_circle" x="20" y="20"/>
<use xlink:href="#my_circle" x="100" y="50"/>
</a>
</g>
<text>
<tspan>It was the best of times</tspan>
<tspan dx="-140" dy="15">It was the worst of times.</tspan>
</text>
</svg>
示例输出。显示完全相同的图像:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<circle id="my_circle" cx="100" cy="50" r="40" fill="red"/>
</defs>
<g id="layer1">
<use xlink:href="#my_circle" x="20" y="20"/>
<use xlink:href="#my_circle" x="100" y="50"/>
</g>
<text>
<tspan>It was the best of times</tspan>
<tspan dx="-140" dy="15">It was the worst of times.</tspan>
</text>
</svg>
keeper 元素的大概列表是:g, rect, circle, ellipse, line, polyline, polygon, path, text, tspan, tref, textpath, linearGradient+stop, radialGradient, defs, clippath, path
。
如果不是特别是 SVG tiny,那么肯定是 SVG lite。
【问题讨论】:
XSLT 可能是适合这项工作的工具。如果您可以提供一个简短的示例并描述您想要编辑或保留的内容,您可能会通过 XSLT 获得答案以帮助您入门。 好问题,+1。请参阅我的答案以获取完全产生所需输出的完整解决方案并进行详细说明。 :) 额外背景:如果 SVG 就像一个论坛页面,你自然只允许人们使用一小部分 html,否则各种脚本和破坏行为都会得到解决。这是一个共享的 SVG 文档,在概念上就像一个网络论坛。 【参考方案1】:这种转变:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s="http://www.w3.org/2000/svg"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:element name="name()" namespace="namespace-uri()">
<xsl:copy-of select="namespace::xlink"/>
<xsl:apply-templates select="node()|@*"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="name()"
namespace="namespace-uri()">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="s:a">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match=
"s:title|s:metadata|s:script|s:style|
s:svg/@version|s:svg/@id"/>
</xsl:stylesheet>
应用于提供的 XML 文档时:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg" version="1.1"
id="svg2">
<title>Mostly harmless</title>
<metadata id="metadata7">Some metadata</metadata>
<script type="text/ecmascript"><![CDATA[ alert('Hax!'); ]]></script>
<style type="text/css"><![CDATA[ svgdisplay:none ]]></style>
<defs id="defs4">
<circle id="my_circle" cx="100" cy="50" r="40" fill="red"/>
</defs>
<g id="layer1">
<a xlink:href="www.hax.ru">
<use xlink:href="#my_circle" x="20" y="20"/>
<use xlink:href="#my_circle" x="100" y="50"/>
</a>
</g>
<text>
<tspan>It was the best of times</tspan>
<tspan dx="-140" dy="15">It was the worst of times.</tspan>
</text>
</svg>
产生想要的正确结果:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs id="defs4">
<circle id="my_circle" cx="100" cy="50" r="40" fill="red"/>
</defs>
<g id="layer1">
<use xlink:href="#my_circle" x="20" y="20"/>
<use xlink:href="#my_circle" x="100" y="50"/>
</g>
<text>
<tspan>It was the best of times</tspan>
<tspan dx="-140" dy="15">It was the worst of times.</tspan>
</text>
</svg>
解释:
两个模板,具有类似于身份规则的组合效果,匹配所有“白名单节点并实质上复制它们(仅消除不需要的命名空间节点)。
没有正文的模板匹配所有“黑名单”节点(元素和一些属性)。这些已被有效删除。
必须有匹配特定“灰名单”节点的模板(在我们的例子中模板匹配s:a
)。 “灰名单节点不会被完全删除——它可能会被重命名或以其他方式修改,或者至少它的内容可能仍然包含在输出中。
很可能随着你对问题的理解越来越清晰,三个列表会不断增长,所以修改黑名单删除模板的匹配模式以容纳新发现的列入黑名单的元素。新发现的白名单节点根本不需要工作。仅处理新的灰名单元素(如果有的话)将需要更多的工作。
【讨论】:
您的结果包含应该被过滤的 。这证明了黑名单是徒劳的。虽然很好的简单英语解释。 @SamG:没有解决方案 is 的声音,我根本没有看到a
元素。现已修复 - 看看吧,我们现在有一个处理灰名单元素的示例。
我不会争论,因为我对学习 xsl 更感兴趣。虽然考虑这个网站的 HTML 政策。 “我们不允许所有 HTML 标签,因为那将是 XSS 的天堂” SVG 通常不是这样的公共文档,但这个恰好是。所以威胁模型是一样的,与悲观无关。 meta.stackexchange.com/questions/1777/…
@SamG:撇开哲学讨论不谈,这个解决方案不仅完全解决了您的问题,而且是构建任何此类更清洁应用程序的可靠方法——为什么不考虑接受这个答案?
@Dimitre 在大多数情况下,我认为您的方法是最干净和最有效的。我认为问题在于他可能不提前知道他想要编辑哪些节点,因为它是用户生成的内容。他只知道他想保留什么内容(“白名单”)。【参考方案2】:
Dimitre Novatchev 的解决方案更加“干净”和优雅,但如果您需要“白名单”解决方案(因为您无法预测用户可能输入的哪些内容需要您将其列入“黑名单”),那么您需要充分充实“白名单”。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:svg="http://www.w3.org/2000/svg">
<xsl:output indent="yes" />
<!--The "whitelist" template that will copy matched nodes forward and apply-templates
for any attributes or child nodes -->
<xsl:template match="svg:svg
| svg:defs | svg:defs/text()
| svg:g | svg:g/text()
| svg:a | svg:a/text()
| svg:use | svg:use/text()
| svg:rect | svg:rect/text()
| svg:circle | svg:circle/text()
| svg:ellipse | svg:ellipse/text()
| svg:line | svg:line/text()
| svg:polyline | svg:polyline/text()
| svg:polygon | svg:polygon/text()
| svg:path | svg:path/text()
| svg:text | svg:text/text()
| svg:tspan | svg:tspan/text()
| svg:tref | svg:tref/text()
| svg:textpath | svg:textpath/text()
| svg:linearGradient | svg:linearGradient/text()
| svg:radialGradient | svg:radialGradient/text()
| svg:clippath | svg:clippath/text()
| svg:text | svg:text/text()">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<!--The "blacklist" template, which does nothing except apply templates for the
matched node's attributes and child nodes -->
<xsl:template match="@* | node()">
<xsl:apply-templates select="@* | node()" />
</xsl:template>
</xsl:stylesheet>
【讨论】:
它匹配附加到任何绑定到 SVG 命名空间的元素的属性。 我以为我明白这一点,但是当我删除“svg:a |”从列表中,我得到一个错误。 “必须在元素的任何子节点之前添加属性节点。”我使用 xsltproc 进行实际处理。 另外我猜它需要一个 text() 某处,因为它丢弃了所有的文本节点。 复制一个本身没有复制的元素的属性对我来说似乎是有问题的。它们要么最终出现在“错误”元素上,要么由于文本节点已写入父元素而导致失败。 我是否正确理解@* 输出所有属性?也应该有一个属性的白名单,否则像svgfig 是完成这项工作的好工具。您可以加载 SVG 文件并选择您喜欢的部分来制作新文档。或者您可以删除您不喜欢的部分并重新保存。
【讨论】:
【参考方案4】:作为已接受的 XSLT 答案的替代方案,您可以使用 Ruby 和 Nokogiri:
require 'nokogiri'
svg = Nokogiri::XML( IO.read( "myfile.svg" ) )
svg.xpath( '//*[not(name()="rect" or name()="circle" or ...)]' ).each do |node|
node.remove
end
File.open( "myfile_clean.svg", "w" ) do |file|
file << svg.to_xml
end
【讨论】:
以上是关于通过将元素列入白名单来清理 SVG 文档的主要内容,如果未能解决你的问题,请参考以下文章