-
-
Notifications
You must be signed in to change notification settings - Fork 624
Expand file tree
/
Copy pathSvgTest.php
More file actions
161 lines (142 loc) · 6.08 KB
/
SvgTest.php
File metadata and controls
161 lines (142 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<?php
namespace Tests\Support;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Support\Svg;
use Tests\TestCase;
class SvgTest extends TestCase
{
#[Test]
#[DataProvider('sanitizeCssProvider')]
public function it_sanitizes_css(string $input, string $expected)
{
$this->assertSame($expected, trim(Svg::sanitizeCss($input)));
}
public static function sanitizeCssProvider()
{
return [
'strips @import with url()' => [
'@import url("https://evil.com/x.css");',
'',
],
'strips @import with bare string' => [
'@import "https://evil.com/x.css";',
'',
],
'strips @import with protocol-relative url' => [
'@import url(//evil.com/x.css);',
'',
],
'strips @import without semicolon' => [
"@import url('https://evil.com/x.css')",
'',
],
'strips @import using hex escapes' => [
'@\\69mport url("https://evil.com/x.css");',
'',
],
'strips @import using non-hex backslash escapes' => [
'@\import url("https://evil.com/x.css");',
'',
],
'strips @import using mixed hex and non-hex escapes' => [
'@\\69\mport url("https://evil.com/x.css");',
'',
],
'neutralizes external url' => [
'.cls { background: url(https://evil.com/beacon.gif); }',
'.cls { background: url(); }',
],
'neutralizes protocol-relative url' => [
'.cls { background: url(//evil.com/x); }',
'.cls { background: url(); }',
],
'neutralizes quoted external url' => [
'.cls { background: url("http://evil.com/x"); }',
'.cls { background: url(); }',
],
'neutralizes external url using hex escapes' => [
'.cls { background: url(\\68\\74\\74\\70\\73://evil.com/beacon.gif); }',
'.cls { background: url(); }',
],
'neutralizes external url using non-hex backslash escapes' => [
'.cls { background: url(\https://evil.com/x); }',
'.cls { background: url(); }',
],
'neutralizes external url using non-breaking space escape' => [
'.cls { background: url(\\a0 https://evil.com/x); }',
'.cls { background: url(); }',
],
'neutralizes external url using zero-width space escape' => [
'.cls { background: url(\\200B https://evil.com/x); }',
'.cls { background: url(); }',
],
'neutralizes external url using BOM escape' => [
'.cls { background: url(\\FEFF https://evil.com/x); }',
'.cls { background: url(); }',
],
'neutralizes external url in @font-face src' => [
'@font-face { font-family: "x"; src: url("https://evil.com/font.woff"); }',
'@font-face { font-family: "x"; src: url(); }',
],
'preserves normal css' => [
'.cls-1 { fill: #333; stroke: red; }',
'.cls-1 { fill: #333; stroke: red; }',
],
'preserves internal url references' => [
'.cls { fill: url(#myGradient); }',
'.cls { fill: url(#myGradient); }',
],
'preserves data uris' => [
'.cls { background: url(data:image/png;base64,abc123); }',
'.cls { background: url(data:image/png;base64,abc123); }',
],
'handles mixed legitimate and malicious css' => [
".cls-1 { fill: #333; }\n@import url(\"https://evil.com/track.css\");\n.cls-2 { stroke: url(#grad); background: url(https://evil.com/bg.gif); }",
".cls-1 { fill: #333; }\n\n.cls-2 { stroke: url(#grad); background: url(); }",
],
];
}
#[Test]
public function it_sanitizes_style_tags_in_full_svg()
{
$svg = '<svg xmlns="http://www.w3.org/2000/svg"><style>@import url("https://evil.com/track.css"); .cls-1 { fill: #333; }</style><rect class="cls-1"/></svg>';
$result = Svg::sanitize($svg);
$this->assertStringNotContainsString('@import', $result);
$this->assertStringNotContainsString('evil.com', $result);
$this->assertStringContainsString('.cls-1', $result);
$this->assertStringContainsString('fill:', $result);
}
#[Test]
public function it_passes_through_svg_without_style_tags()
{
$svg = '<svg xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1" fill="white"/></svg>';
$result = Svg::sanitize($svg);
$this->assertStringContainsString('<rect', $result);
$this->assertStringContainsString('<svg', $result);
}
#[Test]
public function it_preserves_xml_declaration()
{
$svg = '<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>';
$result = Svg::sanitize($svg);
$this->assertStringStartsWith('<?xml', $result);
}
#[Test]
public function it_does_not_add_xml_declaration()
{
$svg = '<svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>';
$result = Svg::sanitize($svg);
$this->assertStringStartsWith('<svg', $result);
}
#[Test]
public function it_sanitizes_css_inside_cdata_sections()
{
$svg = '<svg xmlns="http://www.w3.org/2000/svg"><style><![CDATA[@import url("https://evil.com/track.css"); .cls-1 { fill: url(https://evil.com/bg.gif); }]]></style><rect class="cls-1"/></svg>';
$result = Svg::sanitize($svg);
$this->assertStringNotContainsString('@import', $result);
$this->assertStringNotContainsString('evil.com', $result);
$this->assertStringContainsString('.cls-1', $result);
$this->assertStringContainsString('fill:', $result);
}
}