CoolCMS API

SvgSanitizer
in package

FinalYes

Sanitizes SVG to a safe subset before the file reaches `www/`.

SVG is XML that can run scripts: <script>, on* handlers, <foreignObject> with HTML, href="javascript:", CSS url(). Assets are served by the web server straight from the file, so an SVG opened on its own URL runs as a same-origin document on the domain where the admin has a session cookie. Only an admin can upload, but the victim of a stored script is again an admin.

Hence a whitelist, not a blacklist: whatever is not in ALLOWED_ELEMENTS / ALLOWED_ATTRIBUTES is removed. The lists cover common icon exports (shapes, gradients, masks, filters); <image> and <a> are deliberately absent because icons do not need them and both can link outside.

Table of Contents

Constants

ALLOWED_ATTRIBUTES  : array<string|int, mixed> = ['id', 'class', 'style', 'transform', 'd', 'poi...
ALLOWED_ELEMENTS  : array<string|int, mixed> = ['svg', 'g', 'defs', 'symbol', 'use', 'title',...
DANGEROUS_CSS  : string = '~(?:url\s*\(\s*[\'"]?(?!\#)|expression\s*\(|@i...
Values in attributes and in `<style>` that have no business in an icon.

Methods

getDimensions()  : array{width: int, height: int}|null
Dimensions from the root `<svg>`: `width`/`height`, otherwise the 3rd and 4th `viewBox` value.
sanitize()  : string|null
Returns the sanitized SVG, or null when the input is not a usable SVG.
cleanElement()  : void
Cleans the attributes of one element and the content of `<style>`.
cleanNode()  : void
Recursively walks the tree and removes disallowed nodes and attributes.
isDangerousCss()  : bool
Normalizes a value to the form the CSS tokenizer sees, before the {@see DANGEROUS_CSS} mask runs.
normalizeCss()  : string
CSS Syntax Level 3, section 4.3.7 "consume an escaped code point": `\` plus 1-6 hex digits (and one optional space) is a code point; `\` plus a line break is a line continuation (nothing); `\` plus any other character is that character. CSS comments (even an unterminated one at the end) are dropped.
parseLength()  : int|null
`"32"`, `"32px"` and `"32.5pt"` give 32; percentages and other relative units give null.

Constants

ALLOWED_ATTRIBUTES

private array<string|int, mixed> ALLOWED_ATTRIBUTES = ['id', 'class', 'style', 'transform', 'd', 'points', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'dx', 'dy', 'width', 'height', 'viewbox', 'preserveaspectratio', 'version', 'fill', 'fill-opacity', 'fill-rule', 'clip-rule', 'clip-path', 'mask', 'filter', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity', 'stroke-miterlimit', 'opacity', 'color', 'display', 'visibility', 'overflow', 'offset', 'stop-color', 'stop-opacity', 'spreadmethod', 'gradientunits', 'gradienttransform', 'patternunits', 'patterncontentunits', 'maskunits', 'maskcontentunits', 'clippathunits', 'filterunits', 'primitiveunits', 'markerwidth', 'markerheight', 'refx', 'refy', 'orient', 'markerunits', 'font-family', 'font-size', 'font-weight', 'font-style', 'letter-spacing', 'text-anchor', 'dominant-baseline', 'baseline-shift', 'in', 'in2', 'result', 'stddeviation', 'mode', 'values', 'type', 'operator', 'k1', 'k2', 'k3', 'k4', 'flood-color', 'flood-opacity', 'radius', 'href', 'xlink:href', 'xml:space']

ALLOWED_ELEMENTS

private array<string|int, mixed> ALLOWED_ELEMENTS = ['svg', 'g', 'defs', 'symbol', 'use', 'title', 'desc', 'style', 'metadata', 'path', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'text', 'tspan', 'textpath', 'lineargradient', 'radialgradient', 'stop', 'pattern', 'clippath', 'mask', 'marker', 'filter', 'fegaussianblur', 'feoffset', 'feblend', 'fecolormatrix', 'fecomposite', 'feflood', 'femerge', 'femergenode', 'fedropshadow', 'femorphology']

DANGEROUS_CSS

Values in attributes and in `<style>` that have no business in an icon.

private string DANGEROUS_CSS = '~(?:url\s*\(\s*[\'"]?(?!\#)|expression\s*\(|@import|javascript\s*:|behavior\s*:|-moz-binding)~i'

url(#...) is deliberately allowed: a reference to a gradient or mask inside the same document (fill: url(#grad)) is common in exported icons. A url() pointing anywhere else (external resource, data:, javascript:) is blocked.

IMPORTANT: the mask runs on every attribute, not only on style. Presentation attributes take url() exactly like CSS, so filter="url(https://example.com/x.svg#f)" or fill="url(//example.com/g.svg#a)" would pass unchecked; a link out of an icon makes no sense and is a leak channel.

Methods

getDimensions()

Dimensions from the root `<svg>`: `width`/`height`, otherwise the 3rd and 4th `viewBox` value.

public getDimensions(string $svg) : array{width: int, height: int}|null

Used only for Asset.meta; for a vector it is informational, not the file dimensions.

Parameters
$svg : string
Return values
array{width: int, height: int}|null

sanitize()

Returns the sanitized SVG, or null when the input is not a usable SVG.

public sanitize(string $svg) : string|null

Null means "do not accept it" and the caller discards the file. Reasons: invalid XML, the root is not <svg>, or an internal DTD subset (the way entity bombs and XXE get in).

Parameters
$svg : string
Return values
string|null

cleanElement()

Cleans the attributes of one element and the content of `<style>`.

private cleanElement(DOMElement $element) : void
Parameters
$element : DOMElement

cleanNode()

Recursively walks the tree and removes disallowed nodes and attributes.

private cleanNode(DOMNode $node) : void

Iterates over a copy of the child list; deleting during iteration would make the live NodeList skip nodes.

Parameters
$node : DOMNode

isDangerousCss()

Normalizes a value to the form the CSS tokenizer sees, before the {@see DANGEROUS_CSS} mask runs.

private isDangerousCss(string $value) : bool

IMPORTANT: running the mask on raw text lets a CSS escape such as @\69 mport "https://example.com/x.css" through (the browser reads \69 as i, giving @import). So escapes are decoded and comments dropped first. Dropping comments is deliberately fail-closed: @im, a comment, port is not an @import for a browser, but an icon needs no such form, so rejecting it is safer than relying on an exact match with the browser tokenizer.

Parameters
$value : string
Return values
bool

normalizeCss()

CSS Syntax Level 3, section 4.3.7 "consume an escaped code point": `\` plus 1-6 hex digits (and one optional space) is a code point; `\` plus a line break is a line continuation (nothing); `\` plus any other character is that character. CSS comments (even an unterminated one at the end) are dropped.

private normalizeCss(string $value) : string
Parameters
$value : string
Return values
string

parseLength()

`"32"`, `"32px"` and `"32.5pt"` give 32; percentages and other relative units give null.

private parseLength(string $value) : int|null
Parameters
$value : string
Return values
int|null
On this page

Search results