Coverage for src/backend/InvenTree/InvenTree/sanitizer.py: 45%
16 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""Functions to sanitize user input files."""
3import nh3
5# Allowed CSS properties for SVG sanitization (combines general CSS and SVG-specific properties)
6_SVG_ALLOWED_CSS_PROPERTIES = frozenset([
7 # General CSS (matching bleach's original ALLOWED_CSS_PROPERTIES)
8 'azimuth',
9 'background-color',
10 'border-bottom-color',
11 'border-collapse',
12 'border-color',
13 'border-left-color',
14 'border-right-color',
15 'border-top-color',
16 'clear',
17 'color',
18 'cursor',
19 'direction',
20 'display',
21 'elevation',
22 'float',
23 'font',
24 'font-family',
25 'font-size',
26 'font-style',
27 'font-variant',
28 'font-weight',
29 'height',
30 'letter-spacing',
31 'line-height',
32 'overflow',
33 'pause',
34 'pause-after',
35 'pause-before',
36 'pitch',
37 'pitch-range',
38 'richness',
39 'speak',
40 'speak-header',
41 'speak-numeral',
42 'speak-punctuation',
43 'speech-rate',
44 'stress',
45 'text-align',
46 'text-decoration',
47 'text-indent',
48 'unicode-bidi',
49 'vertical-align',
50 'voice-family',
51 'volume',
52 'white-space',
53 'width',
54 # SVG-specific CSS (matching bleach's ALLOWED_SVG_PROPERTIES)
55 'fill',
56 'fill-opacity',
57 'fill-rule',
58 'stroke',
59 'stroke-linecap',
60 'stroke-linejoin',
61 'stroke-opacity',
62 'stroke-width',
63])
65ALLOWED_ELEMENTS_SVG = [
66 'a',
67 'animate',
68 'animateColor',
69 'animateMotion',
70 'animateTransform',
71 'circle',
72 'defs',
73 'desc',
74 'ellipse',
75 'font-face',
76 'font-face-name',
77 'font-face-src',
78 'g',
79 'glyph',
80 'hkern',
81 'linearGradient',
82 'line',
83 'marker',
84 'metadata',
85 'missing-glyph',
86 'mpath',
87 'path',
88 'polygon',
89 'polyline',
90 'radialGradient',
91 'rect',
92 'set',
93 'stop',
94 'svg',
95 'switch',
96 'text',
97 'title',
98 'tspan',
99 'use',
100]
102ALLOWED_ATTRIBUTES_SVG = [
103 'accent-height',
104 'accumulate',
105 'additive',
106 'alphabetic',
107 'arabic-form',
108 'ascent',
109 'attributeName',
110 'attributeType',
111 'baseProfile',
112 'bbox',
113 'begin',
114 'by',
115 'calcMode',
116 'cap-height',
117 'class',
118 'color',
119 'color-rendering',
120 'content',
121 'cx',
122 'cy',
123 'd',
124 'dx',
125 'dy',
126 'descent',
127 'display',
128 'dur',
129 'end',
130 'fill',
131 'fill-opacity',
132 'fill-rule',
133 'font-family',
134 'font-size',
135 'font-stretch',
136 'font-style',
137 'font-variant',
138 'font-weight',
139 'from',
140 'fx',
141 'fy',
142 'g1',
143 'g2',
144 'glyph-name',
145 'gradientUnits',
146 'hanging',
147 'height',
148 'horiz-adv-x',
149 'horiz-origin-x',
150 'id',
151 'ideographic',
152 'k',
153 'keyPoints',
154 'keySplines',
155 'keyTimes',
156 'lang',
157 'marker-end',
158 'marker-mid',
159 'marker-start',
160 'markerHeight',
161 'markerUnits',
162 'markerWidth',
163 'mathematical',
164 'max',
165 'min',
166 'name',
167 'offset',
168 'opacity',
169 'orient',
170 'origin',
171 'overline-position',
172 'overline-thickness',
173 'panose-1',
174 'path',
175 'pathLength',
176 'points',
177 'preserveAspectRatio',
178 'r',
179 'refX',
180 'refY',
181 'repeatCount',
182 'repeatDur',
183 'requiredExtensions',
184 'requiredFeatures',
185 'restart',
186 'rotate',
187 'rx',
188 'ry',
189 'slope',
190 'stemh',
191 'stemv',
192 'stop-color',
193 'stop-opacity',
194 'strikethrough-position',
195 'strikethrough-thickness',
196 'stroke',
197 'stroke-dasharray',
198 'stroke-dashoffset',
199 'stroke-linecap',
200 'stroke-linejoin',
201 'stroke-miterlimit',
202 'stroke-opacity',
203 'stroke-width',
204 'systemLanguage',
205 'target',
206 'text-anchor',
207 'to',
208 'transform',
209 'type',
210 'u1',
211 'u2',
212 'underline-position',
213 'underline-thickness',
214 'unicode',
215 'unicode-range',
216 'units-per-em',
217 'values',
218 'version',
219 'viewBox',
220 'visibility',
221 'width',
222 'widths',
223 'x',
224 'x-height',
225 'x1',
226 'x2',
227 'xlink:actuate',
228 'xlink:arcrole',
229 'xlink:href',
230 'xlink:role',
231 'xlink:show',
232 'xlink:title',
233 'xlink:type',
234 'xml:base',
235 'xml:lang',
236 'xml:space',
237 'xmlns',
238 'xmlns:xlink',
239 'y',
240 'y1',
241 'y2',
242 'zoomAndPan',
243 'style',
244]
246# Default allowlists (matching bleach's original defaults)
247# TODO: I do not see us needing a bunch of these but I do not want to introduce a breaking change; we might want to narroy this down with the next breaking change
248DEFAULT_TAGS = frozenset([
249 'a',
250 'abbr',
251 'acronym',
252 'b',
253 'blockquote',
254 'code',
255 'em',
256 'i',
257 'li',
258 'ol',
259 'strong',
260 'ul',
261])
262DEAFAULT_ATTRS = {'a': {'href', 'title'}, 'abbr': {'title'}, 'acronym': {'title'}}
263DEFAULT_CSS = frozenset([
264 'azimuth',
265 'background-color',
266 'border-bottom-color',
267 'border-collapse',
268 'border-color',
269 'border-left-color',
270 'border-right-color',
271 'border-top-color',
272 'clear',
273 'color',
274 'cursor',
275 'direction',
276 'display',
277 'elevation',
278 'float',
279 'font',
280 'font-family',
281 'font-size',
282 'font-style',
283 'font-variant',
284 'font-weight',
285 'height',
286 'letter-spacing',
287 'line-height',
288 'overflow',
289 'pause',
290 'pause-after',
291 'pause-before',
292 'pitch',
293 'pitch-range',
294 'richness',
295 'speak',
296 'speak-header',
297 'speak-numeral',
298 'speak-punctuation',
299 'speech-rate',
300 'stress',
301 'text-align',
302 'text-decoration',
303 'text-indent',
304 'unicode-bidi',
305 'vertical-align',
306 'voice-family',
307 'volume',
308 'white-space',
309 'width',
310])
311# TODO: We might want to respect the setting EXTRA_URL_SCHEMES here but that would be breaking
312DEFAULT_PROTOCOLS = frozenset(['http', 'https', 'mailto'])
315def sanitize_svg(
316 file_data,
317 strip: bool = True,
318 elements: list[str] = ALLOWED_ELEMENTS_SVG,
319 attributes: list[str] = ALLOWED_ATTRIBUTES_SVG,
320) -> str:
321 """Sanitize a SVG file.
323 Args:
324 file_data (str): SVG as string.
325 strip (bool, optional): Should invalid elements get removed. Defaults to True.
326 elements (str, optional): Allowed elements. Defaults to ALLOWED_ELEMENTS_SVG.
327 attributes (str, optional): Allowed attributes. Defaults to ALLOWED_ATTRIBUTES_SVG.
329 Returns:
330 str: Sanitzied SVG file.
331 """
332 # Handle byte-encoded data
333 if isinstance(file_data, bytes):
334 file_data = file_data.decode('utf-8')
336 # nh3 requires attributes as dict[str, set[str]]; convert from list (allowed for all elements)
337 attrs_dict = {elem: set(attributes) for elem in elements}
339 cleaned = nh3.clean(
340 file_data,
341 tags=set(elements),
342 attributes=attrs_dict,
343 filter_style_properties=_SVG_ALLOWED_CSS_PROPERTIES,
344 strip_comments=strip,
345 link_rel=None,
346 )
348 # Replace non-breaking spaces with regular spaces to prevent SVG rendering issues
349 for nbsp in [' ', ' ']:
350 cleaned = cleaned.replace(nbsp, ' ')
352 return cleaned