summaryrefslogtreecommitdiff
path: root/vendor/github.com/microcosm-cc/bluemonday
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/microcosm-cc/bluemonday')
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/.coveralls.yml1
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/.editorconfig4
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/.gitattributes1
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/.gitignore15
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/.travis.yml26
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/CONTRIBUTING.md52
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/CREDITS.md8
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/LICENSE.md31
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/Makefile48
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/README.md418
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/SECURITY.md15
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/css/handlers.go2015
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/doc.go104
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/helpers.go304
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/policies.go253
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/policy.go952
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/sanitize.go1116
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_go1.12.go11
-rw-r--r--vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_ltgo1.12.go15
19 files changed, 5389 insertions, 0 deletions
diff --git a/vendor/github.com/microcosm-cc/bluemonday/.coveralls.yml b/vendor/github.com/microcosm-cc/bluemonday/.coveralls.yml
new file mode 100644
index 0000000..e0c8760
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/.coveralls.yml
@@ -0,0 +1 @@
repo_token: x2wlA1x0X8CK45ybWpZRCVRB4g7vtkhaw
diff --git a/vendor/github.com/microcosm-cc/bluemonday/.editorconfig b/vendor/github.com/microcosm-cc/bluemonday/.editorconfig
new file mode 100644
index 0000000..006bc2f
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/.editorconfig
@@ -0,0 +1,4 @@
1root = true
2
3[*]
4end_of_line = lf
diff --git a/vendor/github.com/microcosm-cc/bluemonday/.gitattributes b/vendor/github.com/microcosm-cc/bluemonday/.gitattributes
new file mode 100644
index 0000000..6313b56
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/.gitattributes
@@ -0,0 +1 @@
* text=auto eol=lf
diff --git a/vendor/github.com/microcosm-cc/bluemonday/.gitignore b/vendor/github.com/microcosm-cc/bluemonday/.gitignore
new file mode 100644
index 0000000..c3df40e
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/.gitignore
@@ -0,0 +1,15 @@
1 # Binaries for programs and plugins
2*.exe
3*.exe~
4*.dll
5*.so
6*.dylib
7
8# Test binary, built with `go test -c`
9*.test
10
11# Output of the go coverage tool, specifically when used with LiteIDE
12*.out
13
14# goland idea folder
15*.idea \ No newline at end of file
diff --git a/vendor/github.com/microcosm-cc/bluemonday/.travis.yml b/vendor/github.com/microcosm-cc/bluemonday/.travis.yml
new file mode 100644
index 0000000..97175fb
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/.travis.yml
@@ -0,0 +1,26 @@
1language: go
2go:
3 - 1.2.x
4 - 1.3.x
5 - 1.4.x
6 - 1.5.x
7 - 1.6.x
8 - 1.7.x
9 - 1.8.x
10 - 1.9.x
11 - 1.10.x
12 - 1.11.x
13 - 1.12.x
14 - 1.13.x
15 - 1.14.x
16 - 1.15.x
17 - 1.16.x
18 - tip
19matrix:
20 allow_failures:
21 - go: tip
22 fast_finish: true
23install:
24 - go get .
25script:
26 - go test -v ./...
diff --git a/vendor/github.com/microcosm-cc/bluemonday/CONTRIBUTING.md b/vendor/github.com/microcosm-cc/bluemonday/CONTRIBUTING.md
new file mode 100644
index 0000000..1d4b244
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/CONTRIBUTING.md
@@ -0,0 +1,52 @@
1# Contributing to bluemonday
2
3Third-party patches are essential for keeping bluemonday secure and offering the features developers want. However there are a few guidelines that we need contributors to follow so that we can maintain the quality of work that developers who use bluemonday expect.
4
5## Getting Started
6
7* Make sure you have a [Github account](https://github.com/signup/free)
8
9## Guidelines
10
111. Do not vendor dependencies. As a security package, were we to vendor dependencies the projects that then vendor bluemonday may not receive the latest security updates to the dependencies. By not vendoring dependencies the project that implements bluemonday will vendor the latest version of any dependent packages. Vendoring is a project problem, not a package problem. bluemonday will be tested against the latest version of dependencies periodically and during any PR/merge.
122. I do not care about spelling mistakes or whitespace and I do not believe that you should either. PRs therefore must be functional in their nature or be substantial and impactful if documentation or examples.
13
14## Submitting an Issue
15
16* Submit a ticket for your issue, assuming one does not already exist
17* Clearly describe the issue including the steps to reproduce (with sample input and output) if it is a bug
18
19If you are reporting a security flaw, you may expect that we will provide the code to fix it for you. Otherwise you may want to submit a pull request to ensure the resolution is applied sooner rather than later:
20
21* Fork the repository on Github
22* Issue a pull request containing code to resolve the issue
23
24## Submitting a Pull Request
25
26* Submit a ticket for your issue, assuming one does not already exist
27* Describe the reason for the pull request and if applicable show some example inputs and outputs to demonstrate what the patch does
28* Fork the repository on Github
29* Before submitting the pull request you should
30 1. Include tests for your patch, 1 test should encapsulate the entire patch and should refer to the Github issue
31 1. If you have added new exposed/public functionality, you should ensure it is documented appropriately
32 1. If you have added new exposed/public functionality, you should consider demonstrating how to use it within one of the helpers or shipped policies if appropriate or within a test if modifying a helper or policy is not appropriate
33 1. Run all of the tests `go test -v ./...` or `make test` and ensure all tests pass
34 1. Run gofmt `gofmt -w ./$*` or `make fmt`
35 1. Run vet `go tool vet *.go` or `make vet` and resolve any issues
36 1. Install golint using `go get -u github.com/golang/lint/golint` and run vet `golint *.go` or `make lint` and resolve every warning
37* When submitting the pull request you should
38 1. Note the issue(s) it resolves, i.e. `Closes #6` in the pull request comment to close issue #6 when the pull request is accepted
39
40Once you have submitted a pull request, we *may* merge it without changes. If we have any comments or feedback, or need you to make changes to your pull request we will update the Github pull request or the associated issue. We expect responses from you within two weeks, and we may close the pull request is there is no activity.
41
42### Contributor Licence Agreement
43
44We haven't gone for the formal "Sign a Contributor Licence Agreement" thing that projects like [puppet](https://cla.puppetlabs.com/), [Mojito](https://developer.yahoo.com/cocktails/mojito/cla/) and companies like [Google](http://code.google.com/legal/individual-cla-v1.0.html) are using.
45
46But we do need to know that we can accept and merge your contributions, so for now the act of contributing a pull request should be considered equivalent to agreeing to a contributor licence agreement, specifically:
47
48You accept that the act of submitting code to the bluemonday project is to grant a copyright licence to the project that is perpetual, worldwide, non-exclusive, no-charge, royalty free and irrevocable.
49
50You accept that all who comply with the licence of the project (BSD 3-clause) are permitted to use your contributions to the project.
51
52You accept, and by submitting code do declare, that you have the legal right to grant such a licence to the project and that each of the contributions is your own original creation.
diff --git a/vendor/github.com/microcosm-cc/bluemonday/CREDITS.md b/vendor/github.com/microcosm-cc/bluemonday/CREDITS.md
new file mode 100644
index 0000000..68fa88d
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/CREDITS.md
@@ -0,0 +1,8 @@
11. John Graham-Cumming http://jgc.org/
21. Mohammad Gufran https://github.com/Gufran
31. Steven Gutzwiller https://github.com/StevenGutzwiller
41. Andrew Krasichkov @buglloc https://github.com/buglloc
51. Mike Samuel mikesamuel@gmail.com
61. Dmitri Shuralyov shurcooL@gmail.com
71. opennota https://github.com/opennota https://gitlab.com/opennota
81. Tom Anthony https://www.tomanthony.co.uk/ \ No newline at end of file
diff --git a/vendor/github.com/microcosm-cc/bluemonday/LICENSE.md b/vendor/github.com/microcosm-cc/bluemonday/LICENSE.md
new file mode 100644
index 0000000..2e6c493
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/LICENSE.md
@@ -0,0 +1,31 @@
1SPDX short identifier: BSD-3-Clause
2https://opensource.org/licenses/BSD-3-Clause
3
4Copyright (c) 2014, David Kitchen <david@buro9.com>
5
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are met:
10
11* Redistributions of source code must retain the above copyright notice, this
12 list of conditions and the following disclaimer.
13
14* Redistributions in binary form must reproduce the above copyright notice,
15 this list of conditions and the following disclaimer in the documentation
16 and/or other materials provided with the distribution.
17
18* Neither the name of the organisation (Microcosm) nor the names of its
19 contributors may be used to endorse or promote products derived from
20 this software without specific prior written permission.
21
22THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/microcosm-cc/bluemonday/Makefile b/vendor/github.com/microcosm-cc/bluemonday/Makefile
new file mode 100644
index 0000000..dcd042a
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/Makefile
@@ -0,0 +1,48 @@
1# Targets:
2#
3# all: Builds the code locally after testing
4#
5# fmt: Formats the source files
6# fmt-check: Check if the source files are formated
7# build: Builds the code locally
8# vet: Vets the code
9# lint: Runs lint over the code (you do not need to fix everything)
10# test: Runs the tests
11# cover: Gives you the URL to a nice test coverage report
12#
13# install: Builds, tests and installs the code locally
14
15GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./.git/*")
16
17.PHONY: all fmt build vet lint test cover install
18
19# The first target is always the default action if `make` is called without
20# args we build and install into $GOPATH so that it can just be run
21
22all: fmt vet test install
23
24fmt:
25 @gofmt -s -w ${GOFILES_NOVENDOR}
26
27fmt-check:
28 @([ -z "$(shell gofmt -d $(GOFILES_NOVENDOR) | head)" ]) || (echo "Source is unformatted"; exit 1)
29
30build:
31 @go build
32
33vet:
34 @go vet
35
36lint:
37 @golint *.go
38
39test:
40 @go test -v ./...
41
42cover: COVERAGE_FILE := coverage.out
43cover:
44 @go test -coverprofile=$(COVERAGE_FILE) && \
45 cover -html=$(COVERAGE_FILE) && rm $(COVERAGE_FILE)
46
47install:
48 @go install ./...
diff --git a/vendor/github.com/microcosm-cc/bluemonday/README.md b/vendor/github.com/microcosm-cc/bluemonday/README.md
new file mode 100644
index 0000000..8e658fe
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/README.md
@@ -0,0 +1,418 @@
1# bluemonday [![GoDoc](https://godoc.org/github.com/microcosm-cc/bluemonday?status.png)](https://godoc.org/github.com/microcosm-cc/bluemonday) [![Sourcegraph](https://sourcegraph.com/github.com/microcosm-cc/bluemonday/-/badge.svg)](https://sourcegraph.com/github.com/microcosm-cc/bluemonday?badge)
2
3bluemonday is a HTML sanitizer implemented in Go. It is fast and highly configurable.
4
5bluemonday takes untrusted user generated content as an input, and will return HTML that has been sanitised against an allowlist of approved HTML elements and attributes so that you can safely include the content in your web page.
6
7If you accept user generated content, and your server uses Go, you **need** bluemonday.
8
9The default policy for user generated content (`bluemonday.UGCPolicy().Sanitize()`) turns this:
10```html
11Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
12```
13
14Into a harmless:
15```html
16Hello World
17```
18
19And it turns this:
20```html
21<a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
22```
23
24Into this:
25```html
26XSS
27```
28
29Whilst still allowing this:
30```html
31<a href="http://www.google.com/">
32 <img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
33</a>
34```
35
36To pass through mostly unaltered (it gained a rel="nofollow" which is a good thing for user generated content):
37```html
38<a href="http://www.google.com/" rel="nofollow">
39 <img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
40</a>
41```
42
43It protects sites from [XSS](http://en.wikipedia.org/wiki/Cross-site_scripting) attacks. There are many [vectors for an XSS attack](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet) and the best way to mitigate the risk is to sanitize user input against a known safe list of HTML elements and attributes.
44
45You should **always** run bluemonday **after** any other processing.
46
47If you use [blackfriday](https://github.com/russross/blackfriday) or [Pandoc](http://johnmacfarlane.net/pandoc/) then bluemonday should be run after these steps. This ensures that no insecure HTML is introduced later in your process.
48
49bluemonday is heavily inspired by both the [OWASP Java HTML Sanitizer](https://code.google.com/p/owasp-java-html-sanitizer/) and the [HTML Purifier](http://htmlpurifier.org/).
50
51## Technical Summary
52
53Allowlist based, you need to either build a policy describing the HTML elements and attributes to permit (and the `regexp` patterns of attributes), or use one of the supplied policies representing good defaults.
54
55The policy containing the allowlist is applied using a fast non-validating, forward only, token-based parser implemented in the [Go net/html library](https://godoc.org/golang.org/x/net/html) by the core Go team.
56
57We expect to be supplied with well-formatted HTML (closing elements for every applicable open element, nested correctly) and so we do not focus on repairing badly nested or incomplete HTML. We focus on simply ensuring that whatever elements do exist are described in the policy allowlist and that attributes and links are safe for use on your web page. [GIGO](http://en.wikipedia.org/wiki/Garbage_in,_garbage_out) does apply and if you feed it bad HTML bluemonday is not tasked with figuring out how to make it good again.
58
59### Supported Go Versions
60
61bluemonday is tested on all versions since Go 1.2 including tip.
62
63We do not support Go 1.0 as we depend on `golang.org/x/net/html` which includes a reference to `io.ErrNoProgress` which did not exist in Go 1.0.
64
65We support Go 1.1 but Travis no longer tests against it.
66
67## Is it production ready?
68
69*Yes*
70
71We are using bluemonday in production having migrated from the widely used and heavily field tested OWASP Java HTML Sanitizer.
72
73We are passing our extensive test suite (including AntiSamy tests as well as tests for any issues raised). Check for any [unresolved issues](https://github.com/microcosm-cc/bluemonday/issues?page=1&state=open) to see whether anything may be a blocker for you.
74
75We invite pull requests and issues to help us ensure we are offering comprehensive protection against various attacks via user generated content.
76
77## Usage
78
79Install in your `${GOPATH}` using `go get -u github.com/microcosm-cc/bluemonday`
80
81Then call it:
82```go
83package main
84
85import (
86 "fmt"
87
88 "github.com/microcosm-cc/bluemonday"
89)
90
91func main() {
92 // Do this once for each unique policy, and use the policy for the life of the program
93 // Policy creation/editing is not safe to use in multiple goroutines
94 p := bluemonday.UGCPolicy()
95
96 // The policy can then be used to sanitize lots of input and it is safe to use the policy in multiple goroutines
97 html := p.Sanitize(
98 `<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
99 )
100
101 // Output:
102 // <a href="http://www.google.com" rel="nofollow">Google</a>
103 fmt.Println(html)
104}
105```
106
107We offer three ways to call Sanitize:
108```go
109p.Sanitize(string) string
110p.SanitizeBytes([]byte) []byte
111p.SanitizeReader(io.Reader) bytes.Buffer
112```
113
114If you are obsessed about performance, `p.SanitizeReader(r).Bytes()` will return a `[]byte` without performing any unnecessary casting of the inputs or outputs. Though the difference is so negligible you should never need to care.
115
116You can build your own policies:
117```go
118package main
119
120import (
121 "fmt"
122
123 "github.com/microcosm-cc/bluemonday"
124)
125
126func main() {
127 p := bluemonday.NewPolicy()
128
129 // Require URLs to be parseable by net/url.Parse and either:
130 // mailto: http:// or https://
131 p.AllowStandardURLs()
132
133 // We only allow <p> and <a href="">
134 p.AllowAttrs("href").OnElements("a")
135 p.AllowElements("p")
136
137 html := p.Sanitize(
138 `<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
139 )
140
141 // Output:
142 // <a href="http://www.google.com">Google</a>
143 fmt.Println(html)
144}
145```
146
147We ship two default policies:
148
1491. `bluemonday.StrictPolicy()` which can be thought of as equivalent to stripping all HTML elements and their attributes as it has nothing on its allowlist. An example usage scenario would be blog post titles where HTML tags are not expected at all and if they are then the elements *and* the content of the elements should be stripped. This is a *very* strict policy.
1502. `bluemonday.UGCPolicy()` which allows a broad selection of HTML elements and attributes that are safe for user generated content. Note that this policy does *not* allow iframes, object, embed, styles, script, etc. An example usage scenario would be blog post bodies where a variety of formatting is expected along with the potential for TABLEs and IMGs.
151
152## Policy Building
153
154The essence of building a policy is to determine which HTML elements and attributes are considered safe for your scenario. OWASP provide an [XSS prevention cheat sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet) to help explain the risks, but essentially:
155
1561. Avoid anything other than the standard HTML elements
1571. Avoid `script`, `style`, `iframe`, `object`, `embed`, `base` elements that allow code to be executed by the client or third party content to be included that can execute code
1581. Avoid anything other than plain HTML attributes with values matched to a regexp
159
160Basically, you should be able to describe what HTML is fine for your scenario. If you do not have confidence that you can describe your policy please consider using one of the shipped policies such as `bluemonday.UGCPolicy()`.
161
162To create a new policy:
163```go
164p := bluemonday.NewPolicy()
165```
166
167To add elements to a policy either add just the elements:
168```go
169p.AllowElements("b", "strong")
170```
171
172Or using a regex:
173
174_Note: if an element is added by name as shown above, any matching regex will be ignored_
175
176It is also recommended to ensure multiple patterns don't overlap as order of execution is not guaranteed and can result in some rules being missed.
177```go
178p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
179```
180
181Or add elements as a virtue of adding an attribute:
182```go
183// Note the recommended pattern, see the recommendation on using .Matching() below
184p.AllowAttrs("nowrap").OnElements("td", "th")
185```
186
187Again, this also supports a regex pattern match alternative:
188```go
189p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
190```
191
192Attributes can either be added to all elements:
193```go
194p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
195```
196
197Or attributes can be added to specific elements:
198```go
199// Not the recommended pattern, see the recommendation on using .Matching() below
200p.AllowAttrs("value").OnElements("li")
201```
202
203It is **always** recommended that an attribute be made to match a pattern. XSS in HTML attributes is very easy otherwise:
204```go
205// \p{L} matches unicode letters, \p{N} matches unicode numbers
206p.AllowAttrs("title").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).Globally()
207```
208
209You can stop at any time and call .Sanitize():
210```go
211// string htmlIn passed in from a HTTP POST
212htmlOut := p.Sanitize(htmlIn)
213```
214
215And you can take any existing policy and extend it:
216```go
217p := bluemonday.UGCPolicy()
218p.AllowElements("fieldset", "select", "option")
219```
220
221### Inline CSS
222
223Although it's possible to handle inline CSS using `AllowAttrs` with a `Matching` rule, writing a single monolithic regular expression to safely process all inline CSS which you wish to allow is not a trivial task. Instead of attempting to do so, you can allow the `style` attribute on whichever element(s) you desire and use style policies to control and sanitize inline styles.
224
225It is strongly recommended that you use `Matching` (with a suitable regular expression)
226`MatchingEnum`, or `MatchingHandler` to ensure each style matches your needs,
227but default handlers are supplied for most widely used styles.
228
229Similar to attributes, you can allow specific CSS properties to be set inline:
230```go
231p.AllowAttrs("style").OnElements("span", "p")
232// Allow the 'color' property with valid RGB(A) hex values only (on any element allowed a 'style' attribute)
233p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
234```
235
236Additionally, you can allow a CSS property to be set only to an allowed value:
237```go
238p.AllowAttrs("style").OnElements("span", "p")
239// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
240// on 'span' elements only
241p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
242```
243
244Or you can specify elements based on a regex pattern match:
245```go
246p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
247// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
248// on 'span' elements only
249p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
250```
251
252If you need more specific checking, you can create a handler that takes in a string and returns a bool to
253validate the values for a given property. The string parameter has been
254converted to lowercase and unicode code points have been converted.
255```go
256myHandler := func(value string) bool{
257 // Validate your input here
258 return true
259}
260p.AllowAttrs("style").OnElements("span", "p")
261// Allow the 'color' property with values validated by the handler (on any element allowed a 'style' attribute)
262p.AllowStyles("color").MatchingHandler(myHandler).Globally()
263```
264
265### Links
266
267Links are difficult beasts to sanitise safely and also one of the biggest attack vectors for malicious content.
268
269It is possible to do this:
270```go
271p.AllowAttrs("href").Matching(regexp.MustCompile(`(?i)mailto|https?`)).OnElements("a")
272```
273
274But that will not protect you as the regular expression is insufficient in this case to have prevented a malformed value doing something unexpected.
275
276We provide some additional global options for safely working with links.
277
278`RequireParseableURLs` will ensure that URLs are parseable by Go's `net/url` package:
279```go
280p.RequireParseableURLs(true)
281```
282
283If you have enabled parseable URLs then the following option will `AllowRelativeURLs`. By default this is disabled (bluemonday is an allowlist tool... you need to explicitly tell us to permit things) and when disabled it will prevent all local and scheme relative URLs (i.e. `href="localpage.html"`, `href="../home.html"` and even `href="//www.google.com"` are relative):
284```go
285p.AllowRelativeURLs(true)
286```
287
288If you have enabled parseable URLs then you can allow the schemes (commonly called protocol when thinking of `http` and `https`) that are permitted. Bear in mind that allowing relative URLs in the above option will allow for a blank scheme:
289```go
290p.AllowURLSchemes("mailto", "http", "https")
291```
292
293Regardless of whether you have enabled parseable URLs, you can force all URLs to have a rel="nofollow" attribute. This will be added if it does not exist, but only when the `href` is valid:
294```go
295// This applies to "a" "area" "link" elements that have a "href" attribute
296p.RequireNoFollowOnLinks(true)
297```
298
299Similarly, you can force all URLs to have "noreferrer" in their rel attribute.
300```go
301// This applies to "a" "area" "link" elements that have a "href" attribute
302p.RequireNoReferrerOnLinks(true)
303```
304
305
306We provide a convenience method that applies all of the above, but you will still need to allow the linkable elements for the URL rules to be applied to:
307```go
308p.AllowStandardURLs()
309p.AllowAttrs("cite").OnElements("blockquote", "q")
310p.AllowAttrs("href").OnElements("a", "area")
311p.AllowAttrs("src").OnElements("img")
312```
313
314An additional complexity regarding links is the data URI as defined in [RFC2397](http://tools.ietf.org/html/rfc2397). The data URI allows for images to be served inline using this format:
315
316```html
317<img src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=">
318```
319
320We have provided a helper to verify the mimetype followed by base64 content of data URIs links:
321
322```go
323p.AllowDataURIImages()
324```
325
326That helper will enable GIF, JPEG, PNG and WEBP images.
327
328It should be noted that there is a potential [security](http://palizine.plynt.com/issues/2010Oct/bypass-xss-filters/) [risk](https://capec.mitre.org/data/definitions/244.html) with the use of data URI links. You should only enable data URI links if you already trust the content.
329
330We also have some features to help deal with user generated content:
331```go
332p.AddTargetBlankToFullyQualifiedLinks(true)
333```
334
335This will ensure that anchor `<a href="" />` links that are fully qualified (the href destination includes a host name) will get `target="_blank"` added to them.
336
337Additionally any link that has `target="_blank"` after the policy has been applied will also have the `rel` attribute adjusted to add `noopener`. This means a link may start like `<a href="//host/path"/>` and will end up as `<a href="//host/path" rel="noopener" target="_blank">`. It is important to note that the addition of `noopener` is a security feature and not an issue. There is an unfortunate feature to browsers that a browser window opened as a result of `target="_blank"` can still control the opener (your web page) and this protects against that. The background to this can be found here: [https://dev.to/ben/the-targetblank-vulnerability-by-example](https://dev.to/ben/the-targetblank-vulnerability-by-example)
338
339### Policy Building Helpers
340
341We also bundle some helpers to simplify policy building:
342```go
343
344// Permits the "dir", "id", "lang", "title" attributes globally
345p.AllowStandardAttributes()
346
347// Permits the "img" element and its standard attributes
348p.AllowImages()
349
350// Permits ordered and unordered lists, and also definition lists
351p.AllowLists()
352
353// Permits HTML tables and all applicable elements and non-styling attributes
354p.AllowTables()
355```
356
357### Invalid Instructions
358
359The following are invalid:
360```go
361// This does not say where the attributes are allowed, you need to add
362// .Globally() or .OnElements(...)
363// This will be ignored without error.
364p.AllowAttrs("value")
365
366// This does not say where the attributes are allowed, you need to add
367// .Globally() or .OnElements(...)
368// This will be ignored without error.
369p.AllowAttrs(
370 "type",
371).Matching(
372 regexp.MustCompile("(?i)^(circle|disc|square|a|A|i|I|1)$"),
373)
374```
375
376Both examples exhibit the same issue, they declare attributes but do not then specify whether they are allowed globally or only on specific elements (and which elements). Attributes belong to one or more elements, and the policy needs to declare this.
377
378## Limitations
379
380We are not yet including any tools to help allow and sanitize CSS. Which means that unless you wish to do the heavy lifting in a single regular expression (inadvisable), **you should not allow the "style" attribute anywhere**.
381
382In the same theme, both `<script>` and `<style>` are considered harmful. These elements (and their content) will not be rendered by default, and require you to explicitly set `p.AllowUnsafe(true)`. You should be aware that allowing these elements defeats the purpose of using a HTML sanitizer as you would be explicitly allowing either JavaScript (and any plainly written XSS) and CSS (which can modify a DOM to insert JS), and additionally but limitations in this library mean it is not aware of whether HTML is validly structured and that can allow these elements to bypass some of the safety mechanisms built into the [WhatWG HTML parser standard](https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselect).
383
384It is not the job of bluemonday to fix your bad HTML, it is merely the job of bluemonday to prevent malicious HTML getting through. If you have mismatched HTML elements, or non-conforming nesting of elements, those will remain. But if you have well-structured HTML bluemonday will not break it.
385
386## TODO
387
388* Investigate whether devs want to blacklist elements and attributes. This would allow devs to take an existing policy (such as the `bluemonday.UGCPolicy()` ) that encapsulates 90% of what they're looking for but does more than they need, and to remove the extra things they do not want to make it 100% what they want
389* Investigate whether devs want a validating HTML mode, in which the HTML elements are not just transformed into a balanced tree (every start tag has a closing tag at the correct depth) but also that elements and character data appear only in their allowed context (i.e. that a `table` element isn't a descendent of a `caption`, that `colgroup`, `thead`, `tbody`, `tfoot` and `tr` are permitted, and that character data is not permitted)
390
391## Development
392
393If you have cloned this repo you will probably need the dependency:
394
395`go get golang.org/x/net/html`
396
397Gophers can use their familiar tools:
398
399`go build`
400
401`go test`
402
403I personally use a Makefile as it spares typing the same args over and over whilst providing consistency for those of us who jump from language to language and enjoy just typing `make` in a project directory and watch magic happen.
404
405`make` will build, vet, test and install the library.
406
407`make clean` will remove the library from a *single* `${GOPATH}/pkg` directory tree
408
409`make test` will run the tests
410
411`make cover` will run the tests and *open a browser window* with the coverage report
412
413`make lint` will run golint (install via `go get github.com/golang/lint/golint`)
414
415## Long term goals
416
4171. Open the code to adversarial peer review similar to the [Attack Review Ground Rules](https://code.google.com/p/owasp-java-html-sanitizer/wiki/AttackReviewGroundRules)
4181. Raise funds and pay for an external security review
diff --git a/vendor/github.com/microcosm-cc/bluemonday/SECURITY.md b/vendor/github.com/microcosm-cc/bluemonday/SECURITY.md
new file mode 100644
index 0000000..a344e7c
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/SECURITY.md
@@ -0,0 +1,15 @@
1# Security Policy
2
3## Supported Versions
4
5Latest tag and tip are supported.
6
7Older tags remain present but changes result in new tags and are not back ported... please verify any issue against the latest tag and tip.
8
9## Reporting a Vulnerability
10
11Email: <bluemonday@buro9.com>
12
13Bluemonday is pure OSS and not maintained by a company. As such there is no bug bounty program but security issues will be taken seriously and resolved as soon as possible.
14
15The maintainer lives in the United Kingdom and whilst the email is monitored expect a reply or ACK when the maintainer is awake.
diff --git a/vendor/github.com/microcosm-cc/bluemonday/css/handlers.go b/vendor/github.com/microcosm-cc/bluemonday/css/handlers.go
new file mode 100644
index 0000000..e0429cf
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/css/handlers.go
@@ -0,0 +1,2015 @@
1// Copyright (c) 2019, 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 css
31
32import (
33 "regexp"
34 "strings"
35)
36
37var (
38 defaultStyleHandlers = map[string]func(string) bool{
39 "align-content": AlignContentHandler,
40 "align-items": AlignItemsHandler,
41 "align-self": AlignSelfHandler,
42 "all": AllHandler,
43 "animation": AnimationHandler,
44 "animation-delay": AnimationDelayHandler,
45 "animation-direction": AnimationDirectionHandler,
46 "animation-duration": AnimationDurationHandler,
47 "animation-fill-mode": AnimationFillModeHandler,
48 "animation-iteration-count": AnimationIterationCountHandler,
49 "animation-name": AnimationNameHandler,
50 "animation-play-state": AnimationPlayStateHandler,
51 "animation-timing-function": TimingFunctionHandler,
52 "backface-visibility": BackfaceVisibilityHandler,
53 "background": BackgroundHandler,
54 "background-attachment": BackgroundAttachmentHandler,
55 "background-blend-mode": BackgroundBlendModeHandler,
56 "background-clip": BackgroundClipHandler,
57 "background-color": ColorHandler,
58 "background-image": ImageHandler,
59 "background-origin": BackgroundOriginHandler,
60 "background-position": BackgroundPositionHandler,
61 "background-repeat": BackgroundRepeatHandler,
62 "background-size": BackgroundSizeHandler,
63 "border": BorderHandler,
64 "border-bottom": BorderSideHandler,
65 "border-bottom-color": ColorHandler,
66 "border-bottom-left-radius": BorderSideRadiusHandler,
67 "border-bottom-right-radius": BorderSideRadiusHandler,
68 "border-bottom-style": BorderSideStyleHandler,
69 "border-bottom-width": BorderSideWidthHandler,
70 "border-collapse": BorderCollapseHandler,
71 "border-color": ColorHandler,
72 "border-image": BorderImageHandler,
73 "border-image-outset": BorderImageOutsetHandler,
74 "border-image-repeat": BorderImageRepeatHandler,
75 "border-image-slice": BorderImageSliceHandler,
76 "border-image-source": ImageHandler,
77 "border-image-width": BorderImageWidthHandler,
78 "border-left": BorderSideHandler,
79 "border-left-color": ColorHandler,
80 "border-left-style": BorderSideStyleHandler,
81 "border-left-width": BorderSideWidthHandler,
82 "border-radius": BorderRadiusHandler,
83 "border-right": BorderSideHandler,
84 "border-right-color": ColorHandler,
85 "border-right-style": BorderSideStyleHandler,
86 "border-right-width": BorderSideWidthHandler,
87 "border-spacing": BorderSpacingHandler,
88 "border-style": BorderStyleHandler,
89 "border-top": BorderSideHandler,
90 "border-top-color": ColorHandler,
91 "border-top-left-radius": BorderSideRadiusHandler,
92 "border-top-right-radius": BorderSideRadiusHandler,
93 "border-top-style": BorderSideStyleHandler,
94 "border-top-width": BorderSideWidthHandler,
95 "border-width": BorderWidthHandler,
96 "bottom": SideHandler,
97 "box-decoration-break": BoxDecorationBreakHandler,
98 "box-shadow": BoxShadowHandler,
99 "box-sizing": BoxSizingHandler,
100 "break-after": BreakBeforeAfterHandler,
101 "break-before": BreakBeforeAfterHandler,
102 "break-inside": BreakInsideHandler,
103 "caption-side": CaptionSideHandler,
104 "caret-color": CaretColorHandler,
105 "clear": ClearHandler,
106 "clip": ClipHandler,
107 "color": ColorHandler,
108 "column-count": ColumnCountHandler,
109 "column-fill": ColumnFillHandler,
110 "column-gap": ColumnGapHandler,
111 "column-rule": ColumnRuleHandler,
112 "column-rule-color": ColorHandler,
113 "column-rule-style": BorderSideStyleHandler,
114 "column-rule-width": ColumnRuleWidthHandler,
115 "column-span": ColumnSpanHandler,
116 "column-width": ColumnWidthHandler,
117 "columns": ColumnsHandler,
118 "cursor": CursorHandler,
119 "direction": DirectionHandler,
120 "display": DisplayHandler,
121 "empty-cells": EmptyCellsHandler,
122 "filter": FilterHandler,
123 "flex": FlexHandler,
124 "flex-basis": FlexBasisHandler,
125 "flex-direction": FlexDirectionHandler,
126 "flex-flow": FlexFlowHandler,
127 "flex-grow": FlexGrowHandler,
128 "flex-shrink": FlexGrowHandler,
129 "flex-wrap": FlexWrapHandler,
130 "float": FloatHandler,
131 "font": FontHandler,
132 "font-family": FontFamilyHandler,
133 "font-kerning": FontKerningHandler,
134 "font-language-override": FontLanguageOverrideHandler,
135 "font-size": FontSizeHandler,
136 "font-size-adjust": FontSizeAdjustHandler,
137 "font-stretch": FontStretchHandler,
138 "font-style": FontStyleHandler,
139 "font-synthesis": FontSynthesisHandler,
140 "font-variant": FontVariantHandler,
141 "font-variant-caps": FontVariantCapsHandler,
142 "font-variant-position": FontVariantPositionHandler,
143 "font-weight": FontWeightHandler,
144 "grid": GridHandler,
145 "grid-area": GridAreaHandler,
146 "grid-auto-columns": GridAutoColumnsHandler,
147 "grid-auto-flow": GridAutoFlowHandler,
148 "grid-auto-rows": GridAutoColumnsHandler,
149 "grid-column": GridColumnHandler,
150 "grid-column-end": GridAxisStartEndHandler,
151 "grid-column-gap": LengthHandler,
152 "grid-column-start": GridAxisStartEndHandler,
153 "grid-gap": GridGapHandler,
154 "grid-row": GridRowHandler,
155 "grid-row-end": GridAxisStartEndHandler,
156 "grid-row-gap": LengthHandler,
157 "grid-row-start": GridAxisStartEndHandler,
158 "grid-template": GridTemplateHandler,
159 "grid-template-areas": GridTemplateAreasHandler,
160 "grid-template-columns": GridTemplateColumnsHandler,
161 "grid-template-rows": GridTemplateRowsHandler,
162 "hanging-punctuation": HangingPunctuationHandler,
163 "height": HeightHandler,
164 "hyphens": HyphensHandler,
165 "image-rendering": ImageRenderingHandler,
166 "isolation": IsolationHandler,
167 "justify-content": JustifyContentHandler,
168 "left": SideHandler,
169 "letter-spacing": LetterSpacingHandler,
170 "line-break": LineBreakHandler,
171 "line-height": LineHeightHandler,
172 "list-style": ListStyleHandler,
173 "list-style-image": ImageHandler,
174 "list-style-position": ListStylePositionHandler,
175 "list-style-type": ListStyleTypeHandler,
176 "margin": MarginHandler,
177 "margin-bottom": MarginSideHandler,
178 "margin-left": MarginSideHandler,
179 "margin-right": MarginSideHandler,
180 "margin-top": MarginSideHandler,
181 "max-height": MaxHeightWidthHandler,
182 "max-width": MaxHeightWidthHandler,
183 "min-height": MinHeightWidthHandler,
184 "min-width": MinHeightWidthHandler,
185 "mix-blend-mode": MixBlendModeHandler,
186 "object-fit": ObjectFitHandler,
187 "object-position": ObjectPositionHandler,
188 "opacity": OpacityHandler,
189 "order": OrderHandler,
190 "orphans": OrphansHandler,
191 "outline": OutlineHandler,
192 "outline-color": ColorHandler,
193 "outline-offset": OutlineOffsetHandler,
194 "outline-style": OutlineStyleHandler,
195 "outline-width": OutlineWidthHandler,
196 "overflow": OverflowHandler,
197 "overflow-wrap": OverflowWrapHandler,
198 "overflow-x": OverflowXYHandler,
199 "overflow-y": OverflowXYHandler,
200 "padding": PaddingHandler,
201 "padding-bottom": PaddingSideHandler,
202 "padding-left": PaddingSideHandler,
203 "padding-right": PaddingSideHandler,
204 "padding-top": PaddingSideHandler,
205 "page-break-after": PageBreakBeforeAfterHandler,
206 "page-break-before": PageBreakBeforeAfterHandler,
207 "page-break-inside": PageBreakInsideHandler,
208 "perspective": PerspectiveHandler,
209 "perspective-origin": PerspectiveOriginHandler,
210 "pointer-events": PointerEventsHandler,
211 "position": PositionHandler,
212 "quotes": QuotesHandler,
213 "resize": ResizeHandler,
214 "right": SideHandler,
215 "scroll-behavior": ScrollBehaviorHandler,
216 "tab-size": TabSizeHandler,
217 "table-layout": TableLayoutHandler,
218 "text-align": TextAlignHandler,
219 "text-align-last": TextAlignLastHandler,
220 "text-combine-upright": TextCombineUprightHandler,
221 "text-decoration": TextDecorationHandler,
222 "text-decoration-color": ColorHandler,
223 "text-decoration-line": TextDecorationLineHandler,
224 "text-decoration-style": TextDecorationStyleHandler,
225 "text-indent": TextIndentHandler,
226 "text-justify": TextJustifyHandler,
227 "text-orientation": TextOrientationHandler,
228 "text-overflow": TextOverflowHandler,
229 "text-shadow": TextShadowHandler,
230 "text-transform": TextTransformHandler,
231 "top": SideHandler,
232 "transform": TransformHandler,
233 "transform-origin": TransformOriginHandler,
234 "transform-style": TransformStyleHandler,
235 "transition": TransitionHandler,
236 "transition-delay": TransitionDelayHandler,
237 "transition-duration": TransitionDurationHandler,
238 "transition-property": TransitionPropertyHandler,
239 "transition-timing-function": TimingFunctionHandler,
240 "unicode-bidi": UnicodeBidiHandler,
241 "user-select": UserSelectHandler,
242 "vertical-align": VerticalAlignHandler,
243 "visibility": VisiblityHandler,
244 "white-space": WhiteSpaceHandler,
245 "widows": OrphansHandler,
246 "width": WidthHandler,
247 "word-break": WordBreakHandler,
248 "word-spacing": WordSpacingHandler,
249 "word-wrap": WordWrapHandler,
250 "writing-mode": WritingModeHandler,
251 "z-index": ZIndexHandler,
252 }
253 colorValues = []string{"initial", "inherit", "aliceblue", "antiquewhite",
254 "aqua", "aquamarine", "azure", "beige", "bisque", "black",
255 "blanchedalmond", "blue", "blueviolet", "brown", "burlywood",
256 "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
257 "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
258 "darkgray", "darkgrey", "darkgreen", "darkkhaki", "darkmagenta",
259 "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon",
260 "darkseagreen", "darkslateblue", "darkslategrey", "darkslategray",
261 "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray",
262 "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen",
263 "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
264 "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred",
265 "indigo", "ivory", "khaki", "lavender", "lavenderblush",
266 "lemonchiffon", "lightblue", "lightcoral", "lightcyan",
267 "lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen",
268 "lightpink", "lightsalmon", "lightseagreen", "lightskyblue",
269 "lightslategray", "lightslategrey", "lightsteeelblue", "lightyellow",
270 "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine",
271 "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen",
272 "mediumslateblue", "mediumspringgreen", "mediumturquoise",
273 "mediumvioletred", "midnightblue", "mintcream", "mistyrose",
274 "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab",
275 "orange", "orangered", "orchid", "palegoldenrod", "palegreen",
276 "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
277 "pink", "plum", "powderblue", "purple", "rebeccapurple", "red",
278 "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown",
279 "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue",
280 "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
281 "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
282 "whitesmoke", "yellow", "yellowgreen"}
283
284 Alpha = regexp.MustCompile(`^[a-z]+$`)
285 Blur = regexp.MustCompile(`^blur\([0-9]+px\)$`)
286 BrightnessCont = regexp.MustCompile(`^(brightness|contrast)\([0-9]+\%\)$`)
287 Count = regexp.MustCompile(`^[0-9]+[\.]?[0-9]*$`)
288 CubicBezier = regexp.MustCompile(`^cubic-bezier\(([ ]*(0(.[0-9]+)?|1(.0)?),){3}[ ]*(0(.[0-9]+)?|1)\)$`)
289 Digits = regexp.MustCompile(`^digits [2-4]$`)
290 DropShadow = regexp.MustCompile(`drop-shadow\(([-]?[0-9]+px) ([-]?[0-9]+px)( [-]?[0-9]+px)?( ([-]?[0-9]+px))?`)
291 Font = regexp.MustCompile(`^('[a-z \-]+'|[a-z \-]+)$`)
292 Grayscale = regexp.MustCompile(`^grayscale\(([0-9]{1,2}|100)%\)$`)
293 GridTemplateAreas = regexp.MustCompile(`^['"]?[a-z ]+['"]?$`)
294 HexRGB = regexp.MustCompile(`^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$`)
295 HSL = regexp.MustCompile(`^hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)$`)
296 HSLA = regexp.MustCompile(`^hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)$`)
297 HueRotate = regexp.MustCompile(`^hue-rotate\(([12]?[0-9]{1,2}|3[0-5][0-9]|360)?\)$`)
298 Invert = regexp.MustCompile(`^invert\(([0-9]{1,2}|100)%\)$`)
299 Length = regexp.MustCompile(`^[\-]?([0-9]+|[0-9]*[\.][0-9]+)(%|cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|deg|rad|turn)?$`)
300 Matrix = regexp.MustCompile(`^matrix\(([ ]*[0-9]+[\.]?[0-9]*,){5}([ ]*[0-9]+[\.]?[0-9]*)\)$`)
301 Matrix3D = regexp.MustCompile(`^matrix3d\(([ ]*[0-9]+[\.]?[0-9]*,){15}([ ]*[0-9]+[\.]?[0-9]*)\)$`)
302 NegTime = regexp.MustCompile(`^[\-]?[0-9]+[\.]?[0-9]*(s|ms)?$`)
303 Numeric = regexp.MustCompile(`^[0-9]+$`)
304 NumericDecimal = regexp.MustCompile(`^[0-9\.]+$`)
305 Opactiy = regexp.MustCompile(`^opacity\(([0-9]{1,2}|100)%\)$`)
306 Perspective = regexp.MustCompile(`perspective\(`)
307 Position = regexp.MustCompile(`^[\-]*[0-9]+[cm|mm|in|px|pt|pc\%]* [[\-]*[0-9]+[cm|mm|in|px|pt|pc\%]*]*$`)
308 Opacity = regexp.MustCompile(`^(0[.]?[0-9]*)|(1.0)$`)
309 QuotedAlpha = regexp.MustCompile(`^["'][a-z]+["']$`)
310 Quotes = regexp.MustCompile(`^([ ]*["'][\x{0022}\x{0027}\x{2039}\x{2039}\x{203A}\x{00AB}\x{00BB}\x{2018}\x{2019}\x{201C}-\x{201E}]["'] ["'][\x{0022}\x{0027}\x{2039}\x{2039}\x{203A}\x{00AB}\x{00BB}\x{2018}\x{2019}\x{201C}-\x{201E}]["'])+$`)
311 Rect = regexp.MustCompile(`^rect\([0-9]+px,[ ]*[0-9]+px,[ ]*[0-9]+px,[ ]*[0-9]+px\)$`)
312 RGB = regexp.MustCompile(`^rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)$`)
313 RGBA = regexp.MustCompile(`^rgba\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){3}[ ]*(1(\.0)?|0|(0\.[0-9]+))\)$`)
314 Rotate = regexp.MustCompile(`^rotate(x|y|z)?\(([12]?|3[0-5][0-9]|360)\)$`)
315 Rotate3D = regexp.MustCompile(`^rotate3d\(([ ]?(1(\.0)?|0\.[0-9]+),){3}([12]?|3[0-5][0-9]|360)\)$`)
316 Saturate = regexp.MustCompile(`^saturate\([0-9]+%\)$`)
317 Sepia = regexp.MustCompile(`^sepia\(([0-9]{1,2}|100)%\)$`)
318 Skew = regexp.MustCompile(`skew(x|y)?\(`)
319 Span = regexp.MustCompile(`^span [0-9]+$`)
320 Steps = regexp.MustCompile(`^steps\([ ]*[0-9]+([ ]*,[ ]*(start|end)?)\)$`)
321 Time = regexp.MustCompile(`^[0-9]+[\.]?[0-9]*(s|ms)?$`)
322 TransitionProp = regexp.MustCompile(`^([a-zA-Z]+,[ ]?)*[a-zA-Z]+$`)
323 TranslateScale = regexp.MustCompile(`(translate|translate3d|translatex|translatey|translatez|scale|scale3d|scalex|scaley|scalez)\(`)
324 URL = regexp.MustCompile(`^url\([\"\']?((https|http)[a-z0-9\.\\/_:]+[\"\']?)\)$`)
325 ZIndex = regexp.MustCompile(`^[\-]?[0-9]+$`)
326)
327
328func multiSplit(value string, seps ...string) []string {
329 curArray := []string{value}
330 for _, i := range seps {
331 newArray := []string{}
332 for _, j := range curArray {
333 newArray = append(newArray, strings.Split(j, i)...)
334 }
335 curArray = newArray
336 }
337 return curArray
338}
339
340func recursiveCheck(value []string, funcs []func(string) bool) bool {
341 for i := 0; i < len(value); i++ {
342 tempVal := strings.Join(value[:i+1], " ")
343 for _, j := range funcs {
344 if j(tempVal) && (len(value[i+1:]) == 0 || recursiveCheck(value[i+1:], funcs)) {
345 return true
346 }
347 }
348 }
349 return false
350}
351
352func in(value []string, arr []string) bool {
353 for _, i := range value {
354 foundString := false
355 for _, j := range arr {
356 if j == i {
357 foundString = true
358 }
359 }
360 if !foundString {
361 return false
362 }
363 }
364 return true
365}
366
367func splitValues(value string) []string {
368 values := strings.Split(value, ",")
369 for _, strippedValue := range values {
370 strippedValue = strings.ToLower(strings.TrimSpace(strippedValue))
371 }
372 return values
373}
374
375func GetDefaultHandler(attr string) func(string) bool {
376
377 if defaultStyleHandlers[attr] != nil {
378 return defaultStyleHandlers[attr]
379 }
380 return BaseHandler
381}
382
383func BaseHandler(value string) bool {
384 return false
385}
386
387func AlignContentHandler(value string) bool {
388 values := []string{"stretch", "center", "flex-start",
389 "flex-end", "space-between", "space-around", "initial", "inherit"}
390 splitVals := splitValues(value)
391 return in(splitVals, values)
392}
393
394func AlignItemsHandler(value string) bool {
395 values := []string{"stretch", "center", "flex-start",
396 "flex-end", "baseline", "initial", "inherit"}
397 splitVals := splitValues(value)
398 return in(splitVals, values)
399}
400
401func AlignSelfHandler(value string) bool {
402 values := []string{"auto", "stretch", "center", "flex-start",
403 "flex-end", "baseline", "initial", "inherit"}
404 splitVals := splitValues(value)
405 return in(splitVals, values)
406}
407
408func AllHandler(value string) bool {
409 values := []string{"initial", "inherit", "unset"}
410 splitVals := splitValues(value)
411 return in(splitVals, values)
412}
413
414func AnimationHandler(value string) bool {
415 values := []string{"initial", "inherit"}
416 if in([]string{value}, values) {
417 return true
418 }
419 splitVals := strings.Split(value, " ")
420 usedFunctions := []func(string) bool{
421 AnimationNameHandler,
422 AnimationDurationHandler,
423 TimingFunctionHandler,
424 AnimationDelayHandler,
425 AnimationIterationCountHandler,
426 AnimationDirectionHandler,
427 AnimationFillModeHandler,
428 AnimationPlayStateHandler,
429 }
430 return recursiveCheck(splitVals, usedFunctions)
431}
432
433func AnimationDelayHandler(value string) bool {
434 if NegTime.MatchString(value) {
435 return true
436 }
437 values := []string{"initial", "inherit"}
438 splitVals := splitValues(value)
439 return in(splitVals, values)
440}
441
442func AnimationDirectionHandler(value string) bool {
443 values := []string{"normal", "reverse", "alternate", "alternate-reverse", "initial", "inherit"}
444 splitVals := splitValues(value)
445 return in(splitVals, values)
446}
447
448func AnimationDurationHandler(value string) bool {
449 if Time.MatchString(value) {
450 return true
451 }
452 values := []string{"initial", "inherit"}
453 splitVals := splitValues(value)
454 return in(splitVals, values)
455}
456
457func AnimationFillModeHandler(value string) bool {
458 values := []string{"none", "forwards", "backwards", "both", "initial", "inherit"}
459 splitVals := splitValues(value)
460 return in(splitVals, values)
461}
462
463func AnimationIterationCountHandler(value string) bool {
464 if Count.MatchString(value) {
465 return true
466 }
467 values := []string{"infinite", "initial", "inherit"}
468 splitVals := splitValues(value)
469 return in(splitVals, values)
470}
471
472func AnimationNameHandler(value string) bool {
473 return Alpha.MatchString(value)
474}
475
476func AnimationPlayStateHandler(value string) bool {
477 values := []string{"paused", "running", "initial", "inherit"}
478 splitVals := splitValues(value)
479 return in(splitVals, values)
480}
481
482func TimingFunctionHandler(value string) bool {
483 values := []string{"linear", "ease", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end", "initial", "inherit"}
484 splitVals := splitValues(value)
485 if in(splitVals, values) {
486 return true
487 }
488 if CubicBezier.MatchString(value) {
489 return true
490 }
491 return Steps.MatchString(value)
492}
493
494func BackfaceVisibilityHandler(value string) bool {
495 values := []string{"visible", "hidden", "initial", "inherit"}
496 splitVals := splitValues(value)
497 return in(splitVals, values)
498}
499
500func BackgroundHandler(value string) bool {
501 values := []string{"initial", "inherit"}
502 if in([]string{value}, values) {
503 return true
504 }
505 splitVals := strings.Split(value, " ")
506 newSplitVals := []string{}
507 for _, i := range splitVals {
508 if len(strings.Split(i, "/")) == 2 {
509 newSplitVals = append(newSplitVals, strings.Split(i, "/")...)
510 } else {
511 newSplitVals = append(newSplitVals, i)
512 }
513 }
514 usedFunctions := []func(string) bool{
515 ColorHandler,
516 ImageHandler,
517 BackgroundPositionHandler,
518 BackgroundSizeHandler,
519 BackgroundRepeatHandler,
520 BackgroundOriginHandler,
521 BackgroundClipHandler,
522 BackgroundAttachmentHandler,
523 }
524 return recursiveCheck(newSplitVals, usedFunctions)
525}
526
527func BackgroundAttachmentHandler(value string) bool {
528 values := []string{"scroll", "fixed", "local", "initial", "inherit"}
529 splitVals := splitValues(value)
530 return in(splitVals, values)
531}
532
533func BackgroundClipHandler(value string) bool {
534 values := []string{"border-box", "padding-box", "content-box", "initial", "inherit"}
535 splitVals := splitValues(value)
536 return in(splitVals, values)
537}
538
539func BackgroundBlendModeHandler(value string) bool {
540 values := []string{"normal", "multiply", "screen", "overlay", "darken",
541 "lighten", "color-dodge", "saturation", "color", "luminosity"}
542 splitVals := splitValues(value)
543 return in(splitVals, values)
544}
545
546func ImageHandler(value string) bool {
547 values := []string{"none", "initial", "inherit"}
548 splitVals := splitValues(value)
549 if in(splitVals, values) {
550 return true
551 }
552 return URL.MatchString(value)
553}
554
555func BackgroundOriginHandler(value string) bool {
556 values := []string{"padding-box", "border-box", "content-box", "initial", "inherit"}
557 splitVals := splitValues(value)
558 return in(splitVals, values)
559}
560
561func BackgroundPositionHandler(value string) bool {
562 splitVals := strings.Split(value, ";")
563 values := []string{"left", "left top", "left bottom", "right", "right top", "right bottom", "right center", "center top", "center center", "center bottom", "center", "top", "bottom", "initial", "inherit"}
564 if in(splitVals, values) {
565 return true
566 }
567 return Position.MatchString(value)
568}
569
570func BackgroundRepeatHandler(value string) bool {
571 values := []string{"repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round", "initial", "inherit"}
572 splitVals := splitValues(value)
573 return in(splitVals, values)
574}
575
576func BackgroundSizeHandler(value string) bool {
577 splitVals := strings.Split(value, " ")
578 values := []string{"auto", "cover", "contain", "initial", "inherit"}
579 if in(splitVals, values) {
580 return true
581 }
582 if len(splitVals) > 0 && LengthHandler(splitVals[0]) {
583 if len(splitVals) < 2 || (len(splitVals) == 2 && LengthHandler(splitVals[1])) {
584 return true
585 }
586 }
587 return false
588}
589
590func BorderHandler(value string) bool {
591 values := []string{"initial", "inherit"}
592 if in([]string{value}, values) {
593 return true
594 }
595 splitVals := multiSplit(value, " ", "/")
596 usedFunctions := []func(string) bool{
597 BorderWidthHandler,
598 BorderStyleHandler,
599 ColorHandler,
600 }
601 return recursiveCheck(splitVals, usedFunctions)
602}
603
604func BorderSideHandler(value string) bool {
605 values := []string{"initial", "inherit"}
606 if in([]string{value}, values) {
607 return true
608 }
609 splitVals := strings.Split(value, " ")
610 usedFunctions := []func(string) bool{
611 BorderSideWidthHandler,
612 BorderSideStyleHandler,
613 ColorHandler,
614 }
615 return recursiveCheck(splitVals, usedFunctions)
616}
617
618func BorderSideRadiusHandler(value string) bool {
619 splitVals := strings.Split(value, " ")
620 valid := true
621 for _, i := range splitVals {
622 if !LengthHandler(i) {
623 valid = false
624 break
625 }
626 }
627 if valid {
628 return true
629 }
630 splitVals = splitValues(value)
631 values := []string{"initial", "inherit"}
632 return in(splitVals, values)
633}
634
635func BorderSideStyleHandler(value string) bool {
636 values := []string{"none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset", "initial", "inherit"}
637 splitVals := splitValues(value)
638 return in(splitVals, values)
639}
640
641func BorderSideWidthHandler(value string) bool {
642 if LengthHandler(value) {
643 return true
644 }
645 splitVals := strings.Split(value, ";")
646 values := []string{"medium", "thin", "thick", "initial", "inherit"}
647 return in(splitVals, values)
648}
649
650func BorderCollapseHandler(value string) bool {
651 values := []string{"separate", "collapse", "initial", "inherit"}
652 splitVals := splitValues(value)
653 return in(splitVals, values)
654}
655
656func BorderImageHandler(value string) bool {
657 values := []string{"initial", "inherit"}
658 if in([]string{value}, values) {
659 return true
660 }
661 splitVals := multiSplit(value, " ", " / ")
662 usedFunctions := []func(string) bool{
663 ImageHandler,
664 BorderImageSliceHandler,
665 BorderImageWidthHandler,
666 BorderImageOutsetHandler,
667 BorderImageRepeatHandler,
668 }
669 return recursiveCheck(splitVals, usedFunctions)
670}
671
672func BorderImageOutsetHandler(value string) bool {
673 if LengthHandler(value) {
674 return true
675 }
676 values := []string{"initial", "inherit"}
677 splitVals := splitValues(value)
678 return in(splitVals, values)
679}
680
681func BorderImageRepeatHandler(value string) bool {
682 values := []string{"stretch", "repeat", "round", "space", "initial", "inherit"}
683 splitVals := splitValues(value)
684 return in(splitVals, values)
685}
686
687func BorderImageSliceHandler(value string) bool {
688 values := []string{"fill", "initial", "inherit"}
689 if in([]string{value}, values) {
690 return true
691 }
692 splitVals := strings.Split(value, " ")
693 if len(splitVals) > 4 {
694 return false
695 }
696 usedFunctions := []func(string) bool{
697 LengthHandler,
698 }
699 return recursiveCheck(splitVals, usedFunctions)
700}
701
702func BorderImageWidthHandler(value string) bool {
703 if LengthHandler(value) {
704 return true
705 }
706 values := []string{"auto", "initial", "inherit"}
707 splitVals := splitValues(value)
708 return in(splitVals, values)
709}
710
711func BorderRadiusHandler(value string) bool {
712 values := []string{"initial", "inherit"}
713 if in([]string{value}, values) {
714 return true
715 }
716 splitVals := strings.Split(value, " ")
717 if len(splitVals) > 4 {
718 return false
719 }
720 usedFunctions := []func(string) bool{
721 LengthHandler,
722 }
723 return recursiveCheck(splitVals, usedFunctions)
724}
725
726func BorderSpacingHandler(value string) bool {
727 values := []string{"initial", "inherit"}
728 if in([]string{value}, values) {
729 return true
730 }
731 splitVals := strings.Split(value, " ")
732 if len(splitVals) > 2 {
733 return false
734 }
735 usedFunctions := []func(string) bool{
736 LengthHandler,
737 }
738 return recursiveCheck(splitVals, usedFunctions)
739}
740
741func BorderStyleHandler(value string) bool {
742 values := []string{"initial", "inherit"}
743 if in([]string{value}, values) {
744 return true
745 }
746 splitVals := strings.Split(value, " ")
747 if len(splitVals) > 4 {
748 return false
749 }
750 usedFunctions := []func(string) bool{
751 BorderSideStyleHandler,
752 }
753 return recursiveCheck(splitVals, usedFunctions)
754}
755
756func BorderWidthHandler(value string) bool {
757 values := []string{"initial", "inherit"}
758 if in([]string{value}, values) {
759 return true
760 }
761 splitVals := strings.Split(value, " ")
762 if len(splitVals) > 4 {
763 return false
764 }
765 usedFunctions := []func(string) bool{
766 BorderSideWidthHandler,
767 }
768 return recursiveCheck(splitVals, usedFunctions)
769}
770
771func SideHandler(value string) bool {
772 if LengthHandler(value) {
773 return true
774 }
775 values := []string{"auto", "inherit", "unset"}
776 splitVals := splitValues(value)
777 return in(splitVals, values)
778}
779
780func BoxDecorationBreakHandler(value string) bool {
781 values := []string{"slice", "clone", "initial", "initial", "inherit", "unset"}
782 splitVals := splitValues(value)
783 return in(splitVals, values)
784}
785
786func BoxShadowHandler(value string) bool {
787 values := []string{"none", "initial", "inherit"}
788 if in([]string{value}, values) {
789 return true
790 }
791 commaSplitVals := strings.Split(value, ",")
792 for _, val := range commaSplitVals {
793 splitVals := strings.Split(val, " ")
794 if len(splitVals) > 6 || len(splitVals) < 2 {
795 return false
796 }
797 if !LengthHandler(splitVals[0]) {
798 return false
799 }
800 if !LengthHandler(splitVals[1]) {
801 return false
802 }
803 usedFunctions := []func(string) bool{
804 LengthHandler,
805 ColorHandler,
806 }
807 if len(splitVals) > 2 && !recursiveCheck(splitVals[2:], usedFunctions) {
808 return false
809 }
810 }
811 return true
812}
813
814func BoxSizingHandler(value string) bool {
815 values := []string{"slicontent-box", "border-box", "initial", "inherit"}
816 splitVals := splitValues(value)
817 return in(splitVals, values)
818}
819
820func BreakBeforeAfterHandler(value string) bool {
821 values := []string{"auto", "avoid", "always", "all", "avoid-page", "page", "left", "right", "recto", "verso", "avoid-column", "column", "avoid-region", "region"}
822 splitVals := splitValues(value)
823 return in(splitVals, values)
824}
825
826func BreakInsideHandler(value string) bool {
827 values := []string{"auto", "avoid", "avoid-page", "avoid-column", "avoid-region"}
828 splitVals := splitValues(value)
829 return in(splitVals, values)
830}
831
832func CaptionSideHandler(value string) bool {
833 values := []string{"top", "bottom", "initial", "inherit"}
834 splitVals := splitValues(value)
835 return in(splitVals, values)
836}
837
838func CaretColorHandler(value string) bool {
839 splitVals := splitValues(value)
840 if in(splitVals, colorValues) {
841 return true
842 }
843 if HexRGB.MatchString(value) {
844 return true
845 }
846 if RGB.MatchString(value) {
847 return true
848 }
849 if RGBA.MatchString(value) {
850 return true
851 }
852 if HSL.MatchString(value) {
853 return true
854 }
855 return HSLA.MatchString(value)
856}
857
858func ClearHandler(value string) bool {
859 values := []string{"none", "left", "right", "both", "initial", "inherit"}
860 splitVals := splitValues(value)
861 return in(splitVals, values)
862}
863
864func ClipHandler(value string) bool {
865 if Rect.MatchString(value) {
866 return true
867 }
868 values := []string{"auto", "initial", "inherit"}
869 splitVals := splitValues(value)
870 return in(splitVals, values)
871}
872
873func ColorHandler(value string) bool {
874 splitVals := splitValues(value)
875 if in(splitVals, colorValues) {
876 return true
877 }
878 if HexRGB.MatchString(value) {
879 return true
880 }
881 if RGB.MatchString(value) {
882 return true
883 }
884 if RGBA.MatchString(value) {
885 return true
886 }
887 if HSL.MatchString(value) {
888 return true
889 }
890 return HSLA.MatchString(value)
891}
892
893func ColumnCountHandler(value string) bool {
894 if Numeric.MatchString(value) {
895 return true
896 }
897 values := []string{"auto", "initial", "inherit"}
898 splitVals := splitValues(value)
899 return in(splitVals, values)
900}
901
902func ColumnFillHandler(value string) bool {
903 values := []string{"balance", "auto", "initial", "inherit"}
904 splitVals := splitValues(value)
905 return in(splitVals, values)
906}
907
908func ColumnGapHandler(value string) bool {
909 if LengthHandler(value) {
910 return true
911 }
912 values := []string{"normal", "initial", "inherit"}
913 splitVals := splitValues(value)
914 return in(splitVals, values)
915}
916
917func ColumnRuleHandler(value string) bool {
918 values := []string{"initial", "inherit"}
919 if in([]string{value}, values) {
920 return true
921 }
922 splitVals := strings.Split(value, " ")
923 usedFunctions := []func(string) bool{
924 ColumnRuleWidthHandler,
925 BorderSideStyleHandler,
926 ColorHandler,
927 }
928 return recursiveCheck(splitVals, usedFunctions)
929}
930
931func ColumnRuleWidthHandler(value string) bool {
932 if LengthHandler(value) {
933 return true
934 }
935 splitVals := strings.Split(value, ";")
936 values := []string{"medium", "thin", "thick", "initial", "inherit"}
937 return in(splitVals, values)
938}
939
940func ColumnSpanHandler(value string) bool {
941 values := []string{"none", "all", "initial", "inherit"}
942 splitVals := splitValues(value)
943 return in(splitVals, values)
944}
945
946func ColumnWidthHandler(value string) bool {
947 if LengthHandler(value) {
948 return true
949 }
950 splitVals := strings.Split(value, ";")
951 values := []string{"auto", "initial", "inherit"}
952 return in(splitVals, values)
953}
954
955func ColumnsHandler(value string) bool {
956 values := []string{"auto", "initial", "inherit"}
957 if in([]string{value}, values) {
958 return true
959 }
960 splitVals := strings.Split(value, " ")
961 usedFunctions := []func(string) bool{
962 ColumnWidthHandler,
963 ColumnCountHandler,
964 }
965 return recursiveCheck(splitVals, usedFunctions)
966}
967
968func CursorHandler(value string) bool {
969 values := []string{"alias", "all-scroll", "auto", "cell", "context-menu", "col-resize", "copy", "crosshair", "default", "e-resize", "ew-resize", "grab", "grabbing", "help", "move", "n-resize", "ne-resize", "nesw-resize", "ns-resize", "nw-resize", "nwse-resize", "no-drop", "none", "not-allowed", "pointer", "progress", "row-resize", "s-resize", "se-resize", "sw-resize", "text", "vertical-text", "w-resize", "wait", "zoom-in", "zoom-out", "initial", "inherit"}
970 splitVals := splitValues(value)
971 return in(splitVals, values)
972}
973
974func DirectionHandler(value string) bool {
975 values := []string{"ltr", "rtl", "initial", "inherit"}
976 splitVals := splitValues(value)
977 return in(splitVals, values)
978}
979
980func DisplayHandler(value string) bool {
981 values := []string{"inline", "block", "contents", "flex", "grid", "inline-block", "inline-flex", "inline-grid", "inline-table", "list-item", "run-in", "table", "table-caption", "table-column-group", "table-header-group", "table-footer-group", "table-row-group", "table-cell", "table-column", "table-row", "none", "initial", "inherit"}
982 splitVals := splitValues(value)
983 return in(splitVals, values)
984}
985
986func EmptyCellsHandler(value string) bool {
987 values := []string{"show", "hide", "initial", "inherit"}
988 splitVals := splitValues(value)
989 return in(splitVals, values)
990}
991
992func FilterHandler(value string) bool {
993 values := []string{"none", "initial", "inherit"}
994 splitVals := splitValues(value)
995 if in(splitVals, values) {
996 return true
997 }
998 if Blur.MatchString(value) {
999 return true
1000 }
1001 if BrightnessCont.MatchString(value) {
1002 return true
1003 }
1004 if DropShadow.MatchString(value) {
1005 return true
1006 }
1007 colorValue := strings.TrimSuffix(string(DropShadow.ReplaceAll([]byte(value), []byte{})), ")")
1008 if ColorHandler(colorValue) {
1009 return true
1010 }
1011 if Grayscale.MatchString(value) {
1012 return true
1013 }
1014 if HueRotate.MatchString(value) {
1015 return true
1016 }
1017 if Invert.MatchString(value) {
1018 return true
1019 }
1020 if Opacity.MatchString(value) {
1021 return true
1022 }
1023 if Saturate.MatchString(value) {
1024 return true
1025 }
1026 return Sepia.MatchString(value)
1027}
1028
1029func FlexHandler(value string) bool {
1030 values := []string{"auto", "initial", "initial", "inherit"}
1031 if in([]string{value}, values) {
1032 return true
1033 }
1034 splitVals := strings.Split(value, " ")
1035 usedFunctions := []func(string) bool{
1036 FlexGrowHandler,
1037 FlexBasisHandler,
1038 }
1039 return recursiveCheck(splitVals, usedFunctions)
1040}
1041
1042func FlexBasisHandler(value string) bool {
1043 if LengthHandler(value) {
1044 return true
1045 }
1046 splitVals := strings.Split(value, ";")
1047 values := []string{"auto", "initial", "inherit"}
1048 return in(splitVals, values)
1049}
1050
1051func FlexDirectionHandler(value string) bool {
1052 values := []string{"row", "row-reverse", "column", "column-reverse", "initial", "inherit"}
1053 splitVals := splitValues(value)
1054 return in(splitVals, values)
1055}
1056
1057func FlexFlowHandler(value string) bool {
1058 values := []string{"initial", "inherit"}
1059 if in([]string{value}, values) {
1060 return true
1061 }
1062 splitVals := strings.Split(value, " ")
1063 usedFunctions := []func(string) bool{
1064 FlexDirectionHandler,
1065 FlexWrapHandler,
1066 }
1067 return recursiveCheck(splitVals, usedFunctions)
1068}
1069
1070func FlexGrowHandler(value string) bool {
1071 if NumericDecimal.MatchString(value) {
1072 return true
1073 }
1074 splitVals := strings.Split(value, ";")
1075 values := []string{"initial", "inherit"}
1076 return in(splitVals, values)
1077}
1078
1079func FlexWrapHandler(value string) bool {
1080 values := []string{"nowrap", "wrap", "wrap-reverse", "initial", "inherit"}
1081 splitVals := splitValues(value)
1082 return in(splitVals, values)
1083}
1084
1085func FloatHandler(value string) bool {
1086 values := []string{"none", "left", "right", "initial", "inherit"}
1087 splitVals := splitValues(value)
1088 return in(splitVals, values)
1089}
1090
1091func FontHandler(value string) bool {
1092 values := []string{"caption", "icon", "menu", "message-box", "small-caption", "status-bar", "initial", "inherit"}
1093 if in([]string{value}, values) {
1094 return true
1095 }
1096 splitVals := strings.Split(value, " ")
1097 newSplitVals := []string{}
1098 for _, i := range splitVals {
1099 if len(strings.Split(i, "/")) == 2 {
1100 newSplitVals = append(newSplitVals, strings.Split(i, "/")...)
1101 } else {
1102 newSplitVals = append(newSplitVals, i)
1103 }
1104 }
1105 usedFunctions := []func(string) bool{
1106 FontStyleHandler,
1107 FontVariantHandler,
1108 FontWeightHandler,
1109 FontSizeHandler,
1110 FontFamilyHandler,
1111 }
1112 return recursiveCheck(newSplitVals, usedFunctions)
1113}
1114
1115func FontFamilyHandler(value string) bool {
1116 values := []string{"initial", "inherit"}
1117 splitVals := splitValues(value)
1118 if in(splitVals, values) {
1119 return true
1120 }
1121 for _, i := range splitVals {
1122 i = strings.TrimSpace(i)
1123 if Font.FindString(i) != i {
1124 return false
1125 }
1126 }
1127 return true
1128}
1129
1130func FontKerningHandler(value string) bool {
1131 values := []string{"auto", "normal", "none"}
1132 splitVals := splitValues(value)
1133 return in(splitVals, values)
1134}
1135
1136func FontLanguageOverrideHandler(value string) bool {
1137 return Alpha.MatchString(value)
1138}
1139
1140func FontSizeHandler(value string) bool {
1141 if LengthHandler(value) {
1142 return true
1143 }
1144 values := []string{"medium", "xx-small", "x-small", "small", "large", "x-large", "xx-large", "smaller", "larger", "initial", "inherit"}
1145 splitVals := splitValues(value)
1146 return in(splitVals, values)
1147}
1148
1149func FontSizeAdjustHandler(value string) bool {
1150 if Count.MatchString(value) {
1151 return true
1152 }
1153 values := []string{"auto", "initial", "inherit"}
1154 splitVals := splitValues(value)
1155 return in(splitVals, values)
1156}
1157
1158func FontStretchHandler(value string) bool {
1159 values := []string{"ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "normal", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded", "initial", "inherit"}
1160 splitVals := splitValues(value)
1161 return in(splitVals, values)
1162}
1163
1164func FontStyleHandler(value string) bool {
1165 values := []string{"normal", "italic", "oblique", "initial", "inherit"}
1166 splitVals := splitValues(value)
1167 return in(splitVals, values)
1168}
1169
1170func FontSynthesisHandler(value string) bool {
1171 values := []string{"none", "style", "weight"}
1172 splitVals := splitValues(value)
1173 return in(splitVals, values)
1174}
1175
1176func FontVariantCapsHandler(value string) bool {
1177 values := []string{"normal", "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "unicase", "titling-caps"}
1178 splitVals := splitValues(value)
1179 return in(splitVals, values)
1180}
1181
1182func FontVariantHandler(value string) bool {
1183 values := []string{"normal", "small-caps", "initial", "inherit"}
1184 splitVals := splitValues(value)
1185 return in(splitVals, values)
1186}
1187
1188func FontVariantPositionHandler(value string) bool {
1189 values := []string{"normal", "sub", "super"}
1190 splitVals := splitValues(value)
1191 return in(splitVals, values)
1192}
1193
1194func FontWeightHandler(value string) bool {
1195 values := []string{"normal", "bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900", "initial", "inherit"}
1196 splitVals := splitValues(value)
1197 return in(splitVals, values)
1198}
1199
1200func GridHandler(value string) bool {
1201 values := []string{"none", "initial", "inherit"}
1202 if in([]string{value}, values) {
1203 return true
1204 }
1205 splitVals := strings.Split(value, " ")
1206 newSplitVals := []string{}
1207 for _, i := range splitVals {
1208 if i != "/" {
1209 newSplitVals = append(newSplitVals, i)
1210 }
1211 }
1212 usedFunctions := []func(string) bool{
1213 GridTemplateRowsHandler,
1214 GridTemplateColumnsHandler,
1215 GridTemplateAreasHandler,
1216 GridAutoColumnsHandler,
1217 GridAutoFlowHandler,
1218 }
1219 return recursiveCheck(newSplitVals, usedFunctions)
1220}
1221
1222func GridAreaHandler(value string) bool {
1223 values := []string{"none", "initial", "inherit"}
1224 if in([]string{value}, values) {
1225 return true
1226 }
1227 splitVals := strings.Split(value, " / ")
1228 usedFunctions := []func(string) bool{
1229 GridAxisStartEndHandler,
1230 }
1231 return recursiveCheck(splitVals, usedFunctions)
1232}
1233
1234func GridAutoColumnsHandler(value string) bool {
1235 if LengthHandler(value) {
1236 return true
1237 }
1238 values := []string{"auto", "max-content", "min-content", "initial", "inherit"}
1239 splitVals := splitValues(value)
1240 return in(splitVals, values)
1241}
1242
1243func GridAutoFlowHandler(value string) bool {
1244 values := []string{"row", "column", "dense", "row dense", "column dense"}
1245 splitVals := splitValues(value)
1246 return in(splitVals, values)
1247}
1248
1249func GridColumnHandler(value string) bool {
1250 values := []string{"none", "initial", "inherit"}
1251 if in([]string{value}, values) {
1252 return true
1253 }
1254 splitVals := strings.Split(value, " / ")
1255 if len(splitVals) > 2 {
1256 return false
1257 }
1258 usedFunctions := []func(string) bool{
1259 GridAxisStartEndHandler,
1260 }
1261 return recursiveCheck(splitVals, usedFunctions)
1262}
1263
1264func GridColumnGapHandler(value string) bool {
1265 return LengthHandler(value)
1266}
1267
1268func LengthHandler(value string) bool {
1269 return Length.MatchString(value)
1270}
1271
1272func LineBreakHandler(value string) bool {
1273 values := []string{"auto", "loose", "normal", "strict"}
1274 splitVals := splitValues(value)
1275 return in(splitVals, values)
1276}
1277
1278func GridAxisStartEndHandler(value string) bool {
1279 if Numeric.MatchString(value) {
1280 return true
1281 }
1282 if Span.MatchString(value) {
1283 return true
1284 }
1285 values := []string{"auto"}
1286 splitVals := splitValues(value)
1287 return in(splitVals, values)
1288}
1289
1290func GridGapHandler(value string) bool {
1291 splitVals := strings.Split(value, " ")
1292 if len(splitVals) > 2 {
1293 return false
1294 }
1295 usedFunctions := []func(string) bool{
1296 GridColumnGapHandler,
1297 }
1298 return recursiveCheck(splitVals, usedFunctions)
1299}
1300
1301func GridRowHandler(value string) bool {
1302 splitVals := strings.Split(value, " / ")
1303 if len(splitVals) > 2 {
1304 return false
1305 }
1306 usedFunctions := []func(string) bool{
1307 GridAxisStartEndHandler,
1308 }
1309 return recursiveCheck(splitVals, usedFunctions)
1310}
1311
1312func GridTemplateHandler(value string) bool {
1313 values := []string{"none", "initial", "inherit"}
1314 if in([]string{value}, values) {
1315 return true
1316 }
1317 splitVals := strings.Split(value, " / ")
1318 if len(splitVals) > 2 {
1319 return false
1320 }
1321 usedFunctions := []func(string) bool{
1322 GridTemplateColumnsHandler,
1323 GridTemplateRowsHandler,
1324 }
1325 return recursiveCheck(splitVals, usedFunctions)
1326}
1327
1328func GridTemplateAreasHandler(value string) bool {
1329 values := []string{"none"}
1330 if in([]string{value}, values) {
1331 return true
1332 }
1333 return GridTemplateAreas.MatchString(value)
1334}
1335
1336func GridTemplateColumnsHandler(value string) bool {
1337 splitVals := strings.Split(value, " ")
1338 values := []string{"none", "auto", "max-content", "min-content", "initial", "inherit"}
1339 for _, val := range splitVals {
1340 if LengthHandler(val) {
1341 continue
1342 }
1343 valArr := []string{val}
1344 if !in(valArr, values) {
1345 return false
1346 }
1347 }
1348 return true
1349}
1350
1351func GridTemplateRowsHandler(value string) bool {
1352 splitVals := strings.Split(value, " ")
1353 values := []string{"none", "auto", "max-content", "min-content"}
1354 for _, val := range splitVals {
1355 if LengthHandler(val) {
1356 continue
1357 }
1358 valArr := []string{val}
1359 if !in(valArr, values) {
1360 return false
1361 }
1362 }
1363 return true
1364}
1365
1366func HangingPunctuationHandler(value string) bool {
1367 values := []string{"none", "first", "last", "allow-end", "force-end", "initial", "inherit"}
1368 splitVals := splitValues(value)
1369 return in(splitVals, values)
1370}
1371
1372func HeightHandler(value string) bool {
1373 if LengthHandler(value) {
1374 return true
1375 }
1376 values := []string{"auto", "initial", "inherit"}
1377 splitVals := splitValues(value)
1378 return in(splitVals, values)
1379}
1380
1381func HyphensHandler(value string) bool {
1382 values := []string{"none", "manual", "auto", "initial", "inherit"}
1383 splitVals := splitValues(value)
1384 return in(splitVals, values)
1385}
1386
1387func ImageRenderingHandler(value string) bool {
1388 values := []string{"auto", "smooth", "high-quality", "crisp-edges", "pixelated"}
1389 splitVals := splitValues(value)
1390 return in(splitVals, values)
1391}
1392
1393func IsolationHandler(value string) bool {
1394 values := []string{"auto", "isolate", "initial", "inherit"}
1395 splitVals := splitValues(value)
1396 return in(splitVals, values)
1397}
1398
1399func JustifyContentHandler(value string) bool {
1400 values := []string{"flex-start", "flex-end", "center", "space-between", "space-around", "initial", "inherit"}
1401 splitVals := splitValues(value)
1402 return in(splitVals, values)
1403}
1404
1405func LetterSpacingHandler(value string) bool {
1406 if LengthHandler(value) {
1407 return true
1408 }
1409 values := []string{"normal", "initial", "inherit"}
1410 splitVals := splitValues(value)
1411 return in(splitVals, values)
1412}
1413
1414func LineHeightHandler(value string) bool {
1415 if LengthHandler(value) {
1416 return true
1417 }
1418 values := []string{"normal", "initial", "inherit"}
1419 splitVals := splitValues(value)
1420 return in(splitVals, values)
1421}
1422
1423func ListStyleHandler(value string) bool {
1424 values := []string{"initial", "inherit"}
1425 if in([]string{value}, values) {
1426 return true
1427 }
1428 splitVals := strings.Split(value, " ")
1429 usedFunctions := []func(string) bool{
1430 ListStyleTypeHandler,
1431 ListStylePositionHandler,
1432 ImageHandler,
1433 }
1434 return recursiveCheck(splitVals, usedFunctions)
1435}
1436
1437func ListStylePositionHandler(value string) bool {
1438 values := []string{"inside", "outside", "initial", "inherit"}
1439 splitVals := splitValues(value)
1440 return in(splitVals, values)
1441}
1442
1443func ListStyleTypeHandler(value string) bool {
1444 values := []string{"disc", "armenian", "circle", "cjk-ideographic", "decimal", "decimal-leading-zero", "georgian", "hebrew", "hiragana", "hiragana-iroha", "katakana", "katakana-iroha", "lower-alpha", "lower-greek", "lower-latin", "lower-roman", "none", "square", "upper-alpha", "upper-greek", "upper-latin", "upper-roman", "initial", "inherit"}
1445 splitVals := splitValues(value)
1446 return in(splitVals, values)
1447}
1448
1449func MarginHandler(value string) bool {
1450 values := []string{"auto", "initial", "inherit"}
1451 if in([]string{value}, values) {
1452 return true
1453 }
1454 splitVals := strings.Split(value, " ")
1455 usedFunctions := []func(string) bool{
1456 MarginSideHandler,
1457 }
1458 return recursiveCheck(splitVals, usedFunctions)
1459}
1460
1461func MarginSideHandler(value string) bool {
1462 if LengthHandler(value) {
1463 return true
1464 }
1465 values := []string{"auto", "initial", "inherit"}
1466 splitVals := splitValues(value)
1467 return in(splitVals, values)
1468}
1469
1470func MaxHeightWidthHandler(value string) bool {
1471 if LengthHandler(value) {
1472 return true
1473 }
1474 values := []string{"none", "initial", "inherit"}
1475 splitVals := splitValues(value)
1476 return in(splitVals, values)
1477}
1478
1479func MinHeightWidthHandler(value string) bool {
1480 if LengthHandler(value) {
1481 return true
1482 }
1483 values := []string{"initial", "inherit"}
1484 splitVals := splitValues(value)
1485 return in(splitVals, values)
1486}
1487
1488func MixBlendModeHandler(value string) bool {
1489 values := []string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "difference", "exclusion", "hue", "saturation", "color", "luminosity"}
1490 splitVals := splitValues(value)
1491 return in(splitVals, values)
1492}
1493
1494func ObjectFitHandler(value string) bool {
1495 values := []string{"fill", "contain", "cover", "none", "scale-down", "initial", "inherit"}
1496 splitVals := splitValues(value)
1497 return in(splitVals, values)
1498}
1499
1500func ObjectPositionHandler(value string) bool {
1501 values := []string{"initial", "inherit"}
1502 if in([]string{value}, values) {
1503 return true
1504 }
1505 splitVals := strings.Split(value, " ")
1506 if len(splitVals) > 2 {
1507 return false
1508 }
1509 usedFunctions := []func(string) bool{
1510 LengthHandler,
1511 }
1512 return recursiveCheck(splitVals, usedFunctions)
1513}
1514
1515func OpacityHandler(value string) bool {
1516 if Opacity.MatchString(value) {
1517 return true
1518 }
1519 values := []string{"initial", "inherit"}
1520 splitVals := splitValues(value)
1521 return in(splitVals, values)
1522}
1523
1524func OrderHandler(value string) bool {
1525 if Numeric.MatchString(value) {
1526 return true
1527 }
1528 values := []string{"initial", "inherit"}
1529 splitVals := splitValues(value)
1530 return in(splitVals, values)
1531}
1532
1533func OutlineHandler(value string) bool {
1534 values := []string{"initial", "inherit"}
1535 if in([]string{value}, values) {
1536 return true
1537 }
1538 splitVals := strings.Split(value, " ")
1539 usedFunctions := []func(string) bool{
1540 ColorHandler,
1541 OutlineWidthHandler,
1542 OutlineStyleHandler,
1543 }
1544 return recursiveCheck(splitVals, usedFunctions)
1545}
1546
1547func OutlineOffsetHandler(value string) bool {
1548 if LengthHandler(value) {
1549 return true
1550 }
1551 values := []string{"initial", "inherit"}
1552 splitVals := splitValues(value)
1553 return in(splitVals, values)
1554}
1555
1556func OutlineStyleHandler(value string) bool {
1557 values := []string{"none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset", "initial", "inherit"}
1558 splitVals := splitValues(value)
1559 return in(splitVals, values)
1560}
1561
1562func OutlineWidthHandler(value string) bool {
1563 if LengthHandler(value) {
1564 return true
1565 }
1566 values := []string{"medium", "thin", "thick", "initial", "inherit"}
1567 splitVals := splitValues(value)
1568 return in(splitVals, values)
1569}
1570
1571func OverflowHandler(value string) bool {
1572 values := []string{"visible", "hidden", "scroll", "auto", "initial", "inherit"}
1573 splitVals := splitValues(value)
1574 return in(splitVals, values)
1575}
1576
1577func OverflowXYHandler(value string) bool {
1578 values := []string{"visible", "hidden", "scroll", "auto", "initial", "inherit"}
1579 splitVals := splitValues(value)
1580 return in(splitVals, values)
1581}
1582
1583func OverflowWrapHandler(value string) bool {
1584 values := []string{"normal", "break-word", "anywhere"}
1585 splitVals := splitValues(value)
1586 return in(splitVals, values)
1587}
1588
1589func OrphansHandler(value string) bool {
1590 return Numeric.MatchString(value)
1591}
1592
1593func PaddingHandler(value string) bool {
1594 values := []string{"initial", "inherit"}
1595 if in([]string{value}, values) {
1596 return true
1597 }
1598 splitVals := strings.Split(value, " ")
1599 if len(splitVals) > 4 {
1600 return false
1601 }
1602 usedFunctions := []func(string) bool{
1603 PaddingSideHandler,
1604 }
1605 return recursiveCheck(splitVals, usedFunctions)
1606}
1607
1608func PaddingSideHandler(value string) bool {
1609 if LengthHandler(value) {
1610 return true
1611 }
1612 values := []string{"initial", "inherit"}
1613 splitVals := splitValues(value)
1614 return in(splitVals, values)
1615}
1616
1617func PageBreakBeforeAfterHandler(value string) bool {
1618 values := []string{"auto", "always", "avoid", "left", "right", "initial", "inherit"}
1619 splitVals := splitValues(value)
1620 return in(splitVals, values)
1621}
1622
1623func PageBreakInsideHandler(value string) bool {
1624 values := []string{"auto", "avoid", "initial", "inherit"}
1625 splitVals := splitValues(value)
1626 return in(splitVals, values)
1627}
1628
1629func PerspectiveHandler(value string) bool {
1630 if LengthHandler(value) {
1631 return true
1632 }
1633 values := []string{"none", "initial", "inherit"}
1634 splitVals := splitValues(value)
1635 return in(splitVals, values)
1636}
1637
1638func PerspectiveOriginHandler(value string) bool {
1639 values := []string{"initial", "inherit"}
1640 if in([]string{value}, values) {
1641 return true
1642 }
1643 splitVals := strings.Split(value, " ")
1644 xValues := []string{"left", "center", "right"}
1645 yValues := []string{"top", "center", "bottom"}
1646 if len(splitVals) > 1 {
1647 if !in([]string{splitVals[0]}, xValues) && !LengthHandler(splitVals[0]) {
1648 return false
1649 }
1650 return in([]string{splitVals[1]}, yValues) || LengthHandler(splitVals[1])
1651 } else if len(splitVals) == 1 {
1652 return in(splitVals, xValues) || in(splitVals, yValues) || LengthHandler(splitVals[0])
1653 }
1654 return false
1655}
1656
1657func PointerEventsHandler(value string) bool {
1658 values := []string{"auto", "none", "initial", "inherit"}
1659 splitVals := splitValues(value)
1660 return in(splitVals, values)
1661}
1662
1663func PositionHandler(value string) bool {
1664 values := []string{"static", "absolute", "fixed", "relative", "sticky", "initial", "inherit"}
1665 splitVals := splitValues(value)
1666 return in(splitVals, values)
1667}
1668
1669func QuotesHandler(value string) bool {
1670 values := []string{"none", "initial", "inherit"}
1671 splitVals := splitValues(value)
1672 if in(splitVals, values) {
1673 return true
1674 }
1675 return Quotes.MatchString(value)
1676}
1677
1678func ResizeHandler(value string) bool {
1679 values := []string{"none", "both", "horizontal", "vertical", "initial", "inherit"}
1680 splitVals := splitValues(value)
1681 return in(splitVals, values)
1682}
1683
1684func ScrollBehaviorHandler(value string) bool {
1685 values := []string{"auto", "smooth", "initial", "inherit"}
1686 splitVals := splitValues(value)
1687 return in(splitVals, values)
1688}
1689
1690func TabSizeHandler(value string) bool {
1691 if LengthHandler(value) {
1692 return true
1693 }
1694 values := []string{"initial", "inherit"}
1695 splitVals := splitValues(value)
1696 return in(splitVals, values)
1697}
1698
1699func TableLayoutHandler(value string) bool {
1700 values := []string{"auto", "fixed", "initial", "inherit"}
1701 splitVals := splitValues(value)
1702 return in(splitVals, values)
1703}
1704
1705func TextAlignHandler(value string) bool {
1706 values := []string{"left", "right", "center", "justify", "initial", "inherit"}
1707 splitVals := splitValues(value)
1708 return in(splitVals, values)
1709}
1710
1711func TextAlignLastHandler(value string) bool {
1712 values := []string{"auto", "left", "right", "center", "justify", "start", "end", "initial", "inherit"}
1713 splitVals := splitValues(value)
1714 return in(splitVals, values)
1715}
1716
1717func TextCombineUprightHandler(value string) bool {
1718 values := []string{"none", "all"}
1719 splitVals := splitValues(value)
1720 if in(splitVals, values) {
1721 return true
1722 }
1723 return Digits.MatchString(value)
1724}
1725
1726func TextDecorationHandler(value string) bool {
1727 values := []string{"initial", "inherit"}
1728 if in([]string{value}, values) {
1729 return true
1730 }
1731 splitVals := strings.Split(value, " ")
1732 usedFunctions := []func(string) bool{
1733 TextDecorationStyleHandler,
1734 ColorHandler,
1735 TextDecorationLineHandler,
1736 }
1737 return recursiveCheck(splitVals, usedFunctions)
1738}
1739
1740func TextDecorationLineHandler(value string) bool {
1741 values := []string{"none", "underline", "overline", "line-through", "initial", "inherit"}
1742 splitVals := strings.Split(value, " ")
1743 return in(splitVals, values)
1744}
1745
1746func TextDecorationStyleHandler(value string) bool {
1747 values := []string{"solid", "double", "dotted", "dashed", "wavy", "initial", "inherit"}
1748 splitVals := splitValues(value)
1749 return in(splitVals, values)
1750}
1751
1752func TextIndentHandler(value string) bool {
1753 if LengthHandler(value) {
1754 return true
1755 }
1756 values := []string{"initial", "inherit"}
1757 splitVals := splitValues(value)
1758 return in(splitVals, values)
1759}
1760
1761func TextJustifyHandler(value string) bool {
1762 values := []string{"auto", "inter-word", "inter-character", "none", "initial", "inherit"}
1763 splitVals := splitValues(value)
1764 return in(splitVals, values)
1765}
1766
1767func TextOverflowHandler(value string) bool {
1768 if QuotedAlpha.MatchString(value) {
1769 return true
1770 }
1771 values := []string{"clip", "ellipsis", "initial", "inherit"}
1772 splitVals := splitValues(value)
1773 return in(splitVals, values)
1774}
1775
1776func TextOrientationHandler(value string) bool {
1777 values := []string{"mixed", "upright", "sideways", "sideways-right"}
1778 splitVals := splitValues(value)
1779 return in(splitVals, values)
1780}
1781
1782func TextShadowHandler(value string) bool {
1783 values := []string{"none", "initial", "inherit"}
1784 if in([]string{value}, values) {
1785 return true
1786 }
1787 commaSplitVals := strings.Split(value, ",")
1788 for _, val := range commaSplitVals {
1789 splitVals := strings.Split(val, " ")
1790 if len(splitVals) > 6 || len(splitVals) < 2 {
1791 return false
1792 }
1793 if !LengthHandler(splitVals[0]) {
1794 return false
1795 }
1796 if !LengthHandler(splitVals[1]) {
1797 return false
1798 }
1799 usedFunctions := []func(string) bool{
1800 LengthHandler,
1801 ColorHandler,
1802 }
1803 if len(splitVals) > 2 && !recursiveCheck(splitVals[2:], usedFunctions) {
1804 return false
1805 }
1806 }
1807 return true
1808}
1809
1810func TextTransformHandler(value string) bool {
1811 values := []string{"none", "capitalize", "uppercase", "lowercase", "initial", "inherit"}
1812 splitVals := splitValues(value)
1813 return in(splitVals, values)
1814}
1815
1816func TransformHandler(value string) bool {
1817 values := []string{"none", "initial", "inherit"}
1818 if in([]string{value}, values) {
1819 return true
1820 }
1821 if Matrix.MatchString(value) {
1822 return true
1823 }
1824 if Matrix3D.MatchString(value) {
1825 return true
1826 }
1827 subValue := string(TranslateScale.ReplaceAll([]byte(value), []byte{}))
1828 trimValue := strings.Split(strings.TrimSuffix(subValue, ")"), ",")
1829 valid := true
1830 for _, i := range trimValue {
1831 if !LengthHandler(strings.TrimSpace(i)) {
1832 valid = false
1833 break
1834 }
1835 }
1836 if valid && trimValue != nil {
1837 return true
1838 }
1839 if Rotate.MatchString(value) {
1840 return true
1841 }
1842 if Rotate3D.MatchString(value) {
1843 return true
1844 }
1845 subValue = string(Skew.ReplaceAll([]byte(value), []byte{}))
1846 subValue = strings.TrimSuffix(subValue, ")")
1847 trimValue = strings.Split(subValue, ",")
1848 valid = true
1849 for _, i := range trimValue {
1850 if !LengthHandler(strings.TrimSpace(i)) {
1851 valid = false
1852 break
1853 }
1854 }
1855 if valid {
1856 return true
1857 }
1858 subValue = string(Perspective.ReplaceAll([]byte(value), []byte{}))
1859 subValue = strings.TrimSuffix(subValue, ")")
1860 return LengthHandler(subValue)
1861}
1862
1863func TransformOriginHandler(value string) bool {
1864 values := []string{"initial", "inherit"}
1865 if in([]string{value}, values) {
1866 return true
1867 }
1868 splitVals := strings.Split(value, " ")
1869 xValues := []string{"left", "center", "right"}
1870 yValues := []string{"top", "center", "bottom"}
1871 if len(splitVals) > 2 {
1872 if !in([]string{splitVals[0]}, xValues) && !LengthHandler(splitVals[0]) {
1873 return false
1874 }
1875 if !in([]string{splitVals[1]}, yValues) && !LengthHandler(splitVals[1]) {
1876 return false
1877 }
1878 return LengthHandler(splitVals[2])
1879 } else if len(splitVals) > 1 {
1880 if !in([]string{splitVals[0]}, xValues) && !LengthHandler(splitVals[0]) {
1881 return false
1882 }
1883 return in([]string{splitVals[1]}, yValues) || LengthHandler(splitVals[1])
1884 } else if len(splitVals) == 1 {
1885 return in(splitVals, xValues) || in(splitVals, yValues) || LengthHandler(splitVals[0])
1886 }
1887 return false
1888}
1889
1890func TransformStyleHandler(value string) bool {
1891 values := []string{"flat", "preserve-3d", "initial", "inherit"}
1892 splitVals := splitValues(value)
1893 return in(splitVals, values)
1894}
1895
1896func TransitionHandler(value string) bool {
1897 values := []string{"initial", "inherit"}
1898 if in([]string{value}, values) {
1899 return true
1900 }
1901 splitVals := strings.Split(value, " ")
1902 usedFunctions := []func(string) bool{
1903 TransitionPropertyHandler,
1904 TransitionDurationHandler,
1905 TimingFunctionHandler,
1906 TransitionDelayHandler,
1907 ColorHandler,
1908 }
1909 return recursiveCheck(splitVals, usedFunctions)
1910}
1911
1912func TransitionDelayHandler(value string) bool {
1913 if Time.MatchString(value) {
1914 return true
1915 }
1916 values := []string{"initial", "inherit"}
1917 splitVals := splitValues(value)
1918 return in(splitVals, values)
1919}
1920
1921func TransitionDurationHandler(value string) bool {
1922 if Time.MatchString(value) {
1923 return true
1924 }
1925 values := []string{"initial", "inherit"}
1926 splitVals := splitValues(value)
1927 return in(splitVals, values)
1928}
1929
1930func TransitionPropertyHandler(value string) bool {
1931 if TransitionProp.MatchString(value) {
1932 return true
1933 }
1934 values := []string{"none", "all", "initial", "inherit"}
1935 splitVals := splitValues(value)
1936 return in(splitVals, values)
1937}
1938
1939func UnicodeBidiHandler(value string) bool {
1940 values := []string{"normal", "embed", "bidi-override", "isolate", "isolate-override", "plaintext", "initial", "inherit"}
1941 splitVals := splitValues(value)
1942 return in(splitVals, values)
1943}
1944
1945func UserSelectHandler(value string) bool {
1946 values := []string{"auto", "none", "text", "all"}
1947 splitVals := splitValues(value)
1948 return in(splitVals, values)
1949}
1950
1951func VerticalAlignHandler(value string) bool {
1952 if LengthHandler(value) {
1953 return true
1954 }
1955 values := []string{"baseline", "sub", "super", "top", "text-top", "middle", "bottom", "text-bottom", "initial", "inherit"}
1956 splitVals := splitValues(value)
1957 return in(splitVals, values)
1958}
1959
1960func VisiblityHandler(value string) bool {
1961 values := []string{"visible", "hidden", "collapse", "initial", "inherit"}
1962 splitVals := splitValues(value)
1963 return in(splitVals, values)
1964}
1965
1966func WhiteSpaceHandler(value string) bool {
1967 values := []string{"normal", "nowrap", "pre", "pre-line", "pre-wrap", "initial", "inherit"}
1968 splitVals := splitValues(value)
1969 return in(splitVals, values)
1970}
1971
1972func WidthHandler(value string) bool {
1973 if LengthHandler(value) {
1974 return true
1975 }
1976 values := []string{"auto", "initial", "inherit"}
1977 splitVals := splitValues(value)
1978 return in(splitVals, values)
1979}
1980
1981func WordSpacingHandler(value string) bool {
1982 if LengthHandler(value) {
1983 return true
1984 }
1985 values := []string{"normal", "initial", "inherit"}
1986 splitVals := splitValues(value)
1987 return in(splitVals, values)
1988}
1989
1990func WordBreakHandler(value string) bool {
1991 values := []string{"normal", "break-all", "keep-all", "break-word", "initial", "inherit"}
1992 splitVals := splitValues(value)
1993 return in(splitVals, values)
1994}
1995
1996func WordWrapHandler(value string) bool {
1997 values := []string{"normal", "break-word", "initial", "inherit"}
1998 splitVals := splitValues(value)
1999 return in(splitVals, values)
2000}
2001
2002func WritingModeHandler(value string) bool {
2003 values := []string{"horizontal-tb", "vertical-rl", "vertical-lr"}
2004 splitVals := splitValues(value)
2005 return in(splitVals, values)
2006}
2007
2008func ZIndexHandler(value string) bool {
2009 if ZIndex.MatchString(value) {
2010 return true
2011 }
2012 values := []string{"auto", "initial", "inherit"}
2013 splitVals := splitValues(value)
2014 return in(splitVals, values)
2015}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/doc.go b/vendor/github.com/microcosm-cc/bluemonday/doc.go
new file mode 100644
index 0000000..d95e8a9
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/doc.go
@@ -0,0 +1,104 @@
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
30/*
31Package bluemonday provides a way of describing an allowlist of HTML elements
32and attributes as a policy, and for that policy to be applied to untrusted
33strings from users that may contain markup. All elements and attributes not on
34the allowlist will be stripped.
35
36The default bluemonday.UGCPolicy().Sanitize() turns this:
37
38 Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
39
40Into the more harmless:
41
42 Hello World
43
44And it turns this:
45
46 <a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
47
48Into this:
49
50 XSS
51
52Whilst still allowing this:
53
54 <a href="http://www.google.com/">
55 <img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
56 </a>
57
58To pass through mostly unaltered (it gained a rel="nofollow"):
59
60 <a href="http://www.google.com/" rel="nofollow">
61 <img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
62 </a>
63
64The primary purpose of bluemonday is to take potentially unsafe user generated
65content (from things like Markdown, HTML WYSIWYG tools, etc) and make it safe
66for you to put on your website.
67
68It protects sites against XSS (http://en.wikipedia.org/wiki/Cross-site_scripting)
69and other malicious content that a user interface may deliver. There are many
70vectors for an XSS attack (https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet)
71and the safest thing to do is to sanitize user input against a known safe list
72of HTML elements and attributes.
73
74Note: You should always run bluemonday after any other processing.
75
76If you use blackfriday (https://github.com/russross/blackfriday) or
77Pandoc (http://johnmacfarlane.net/pandoc/) then bluemonday should be run after
78these steps. This ensures that no insecure HTML is introduced later in your
79process.
80
81bluemonday is heavily inspired by both the OWASP Java HTML Sanitizer
82(https://code.google.com/p/owasp-java-html-sanitizer/) and the HTML Purifier
83(http://htmlpurifier.org/).
84
85We ship two default policies, one is bluemonday.StrictPolicy() and can be
86thought of as equivalent to stripping all HTML elements and their attributes as
87it has nothing on its allowlist.
88
89The other is bluemonday.UGCPolicy() and allows a broad selection of HTML
90elements and attributes that are safe for user generated content. Note that
91this policy does not allow iframes, object, embed, styles, script, etc.
92
93The essence of building a policy is to determine which HTML elements and
94attributes are considered safe for your scenario. OWASP provide an XSS
95prevention cheat sheet ( https://www.google.com/search?q=xss+prevention+cheat+sheet )
96to help explain the risks, but essentially:
97
98 1. Avoid allowing anything other than plain HTML elements
99 2. Avoid allowing `script`, `style`, `iframe`, `object`, `embed`, `base`
100 elements
101 3. Avoid allowing anything other than plain HTML elements with simple
102 values that you can match to a regexp
103*/
104package bluemonday
diff --git a/vendor/github.com/microcosm-cc/bluemonday/helpers.go b/vendor/github.com/microcosm-cc/bluemonday/helpers.go
new file mode 100644
index 0000000..2b03d7e
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/helpers.go
@@ -0,0 +1,304 @@
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)
37
38// A selection of regular expressions that can be used as .Matching() rules on
39// HTML attributes.
40var (
41 // CellAlign handles the `align` attribute
42 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td#attr-align
43 CellAlign = regexp.MustCompile(`(?i)^(center|justify|left|right|char)$`)
44
45 // CellVerticalAlign handles the `valign` attribute
46 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td#attr-valign
47 CellVerticalAlign = regexp.MustCompile(`(?i)^(baseline|bottom|middle|top)$`)
48
49 // Direction handles the `dir` attribute
50 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo#attr-dir
51 Direction = regexp.MustCompile(`(?i)^(rtl|ltr)$`)
52
53 // ImageAlign handles the `align` attribute on the `image` tag
54 // http://www.w3.org/MarkUp/Test/Img/imgtest.html
55 ImageAlign = regexp.MustCompile(
56 `(?i)^(left|right|top|texttop|middle|absmiddle|baseline|bottom|absbottom)$`,
57 )
58
59 // Integer describes whole positive integers (including 0) used in places
60 // like td.colspan
61 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td#attr-colspan
62 Integer = regexp.MustCompile(`^[0-9]+$`)
63
64 // ISO8601 according to the W3 group is only a subset of the ISO8601
65 // standard: http://www.w3.org/TR/NOTE-datetime
66 //
67 // Used in places like time.datetime
68 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time#attr-datetime
69 //
70 // Matches patterns:
71 // Year:
72 // YYYY (eg 1997)
73 // Year and month:
74 // YYYY-MM (eg 1997-07)
75 // Complete date:
76 // YYYY-MM-DD (eg 1997-07-16)
77 // Complete date plus hours and minutes:
78 // YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
79 // Complete date plus hours, minutes and seconds:
80 // YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
81 // Complete date plus hours, minutes, seconds and a decimal fraction of a
82 // second
83 // YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
84 ISO8601 = regexp.MustCompile(
85 `^[0-9]{4}(-[0-9]{2}(-[0-9]{2}([ T][0-9]{2}(:[0-9]{2}){1,2}(.[0-9]{1,6})` +
86 `?Z?([\+-][0-9]{2}:[0-9]{2})?)?)?)?$`,
87 )
88
89 // ListType encapsulates the common value as well as the latest spec
90 // values for lists
91 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol#attr-type
92 ListType = regexp.MustCompile(`(?i)^(circle|disc|square|a|A|i|I|1)$`)
93
94 // SpaceSeparatedTokens is used in places like `a.rel` and the common attribute
95 // `class` which both contain space delimited lists of data tokens
96 // http://www.w3.org/TR/html-markup/datatypes.html#common.data.tokens-def
97 // Regexp: \p{L} matches unicode letters, \p{N} matches unicode numbers
98 SpaceSeparatedTokens = regexp.MustCompile(`^([\s\p{L}\p{N}_-]+)$`)
99
100 // Number is a double value used on HTML5 meter and progress elements
101 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-button-element.html#the-meter-element
102 Number = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$`)
103
104 // NumberOrPercent is used predominantly as units of measurement in width
105 // and height attributes
106 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-height
107 NumberOrPercent = regexp.MustCompile(`^[0-9]+[%]?$`)
108
109 // Paragraph of text in an attribute such as *.'title', img.alt, etc
110 // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-title
111 // Note that we are not allowing chars that could close tags like '>'
112 Paragraph = regexp.MustCompile(`^[\p{L}\p{N}\s\-_',\[\]!\./\\\(\)]*$`)
113
114 // dataURIImagePrefix is used by AllowDataURIImages to define the acceptable
115 // prefix of data URIs that contain common web image formats.
116 //
117 // This is not exported as it's not useful by itself, and only has value
118 // within the AllowDataURIImages func
119 dataURIImagePrefix = regexp.MustCompile(
120 `^image/(gif|jpeg|png|svg\+xml|webp);base64,`,
121 )
122)
123
124// AllowStandardURLs is a convenience function that will enable rel="nofollow"
125// on "a", "area" and "link" (if you have allowed those elements) and will
126// ensure that the URL values are parseable and either relative or belong to the
127// "mailto", "http", or "https" schemes
128func (p *Policy) AllowStandardURLs() {
129 // URLs must be parseable by net/url.Parse()
130 p.RequireParseableURLs(true)
131
132 // !url.IsAbs() is permitted
133 p.AllowRelativeURLs(true)
134
135 // Most common URL schemes only
136 p.AllowURLSchemes("mailto", "http", "https")
137
138 // For linking elements we will add rel="nofollow" if it does not already exist
139 // This applies to "a" "area" "link"
140 p.RequireNoFollowOnLinks(true)
141}
142
143// AllowStandardAttributes will enable "id", "title" and the language specific
144// attributes "dir" and "lang" on all elements that are allowed
145func (p *Policy) AllowStandardAttributes() {
146 // "dir" "lang" are permitted as both language attributes affect charsets
147 // and direction of text.
148 p.AllowAttrs("dir").Matching(Direction).Globally()
149 p.AllowAttrs(
150 "lang",
151 ).Matching(regexp.MustCompile(`[a-zA-Z]{2,20}`)).Globally()
152
153 // "id" is permitted. This is pretty much as some HTML elements require this
154 // to work well ("dfn" is an example of a "id" being value)
155 // This does create a risk that JavaScript and CSS within your web page
156 // might identify the wrong elements. Ensure that you select things
157 // accurately
158 p.AllowAttrs("id").Matching(
159 regexp.MustCompile(`[a-zA-Z0-9\:\-_\.]+`),
160 ).Globally()
161
162 // "title" is permitted as it improves accessibility.
163 p.AllowAttrs("title").Matching(Paragraph).Globally()
164}
165
166// AllowStyling presently enables the class attribute globally.
167//
168// Note: When bluemonday ships a CSS parser and we can safely sanitise that,
169// this will also allow sanitized styling of elements via the style attribute.
170func (p *Policy) AllowStyling() {
171
172 // "class" is permitted globally
173 p.AllowAttrs("class").Matching(SpaceSeparatedTokens).Globally()
174}
175
176// AllowImages enables the img element and some popular attributes. It will also
177// ensure that URL values are parseable. This helper does not enable data URI
178// images, for that you should also use the AllowDataURIImages() helper.
179func (p *Policy) AllowImages() {
180
181 // "img" is permitted
182 p.AllowAttrs("align").Matching(ImageAlign).OnElements("img")
183 p.AllowAttrs("alt").Matching(Paragraph).OnElements("img")
184 p.AllowAttrs("height", "width").Matching(NumberOrPercent).OnElements("img")
185
186 // Standard URLs enabled
187 p.AllowStandardURLs()
188 p.AllowAttrs("src").OnElements("img")
189}
190
191// AllowDataURIImages permits the use of inline images defined in RFC2397
192// http://tools.ietf.org/html/rfc2397
193// http://en.wikipedia.org/wiki/Data_URI_scheme
194//
195// Images must have a mimetype matching:
196//
197// image/gif
198// image/jpeg
199// image/png
200// image/webp
201//
202// NOTE: There is a potential security risk to allowing data URIs and you should
203// only permit them on content you already trust.
204// http://palizine.plynt.com/issues/2010Oct/bypass-xss-filters/
205// https://capec.mitre.org/data/definitions/244.html
206func (p *Policy) AllowDataURIImages() {
207
208 // URLs must be parseable by net/url.Parse()
209 p.RequireParseableURLs(true)
210
211 // Supply a function to validate images contained within data URI
212 p.AllowURLSchemeWithCustomPolicy(
213 "data",
214 func(url *url.URL) (allowUrl bool) {
215 if url.RawQuery != "" || url.Fragment != "" {
216 return false
217 }
218
219 matched := dataURIImagePrefix.FindString(url.Opaque)
220 if matched == "" {
221 return false
222 }
223
224 _, err := base64.StdEncoding.DecodeString(url.Opaque[len(matched):])
225 if err != nil {
226 return false
227 }
228
229 return true
230 },
231 )
232}
233
234// AllowLists will enabled ordered and unordered lists, as well as definition
235// lists
236func (p *Policy) AllowLists() {
237 // "ol" "ul" are permitted
238 p.AllowAttrs("type").Matching(ListType).OnElements("ol", "ul")
239
240 // "li" is permitted
241 p.AllowAttrs("type").Matching(ListType).OnElements("li")
242 p.AllowAttrs("value").Matching(Integer).OnElements("li")
243
244 // "dl" "dt" "dd" are permitted
245 p.AllowElements("dl", "dt", "dd")
246}
247
248// AllowTables will enable a rich set of elements and attributes to describe
249// HTML tables
250func (p *Policy) AllowTables() {
251
252 // "table" is permitted
253 p.AllowAttrs("height", "width").Matching(NumberOrPercent).OnElements("table")
254 p.AllowAttrs("summary").Matching(Paragraph).OnElements("table")
255
256 // "caption" is permitted
257 p.AllowElements("caption")
258
259 // "col" "colgroup" are permitted
260 p.AllowAttrs("align").Matching(CellAlign).OnElements("col", "colgroup")
261 p.AllowAttrs("height", "width").Matching(
262 NumberOrPercent,
263 ).OnElements("col", "colgroup")
264 p.AllowAttrs("span").Matching(Integer).OnElements("colgroup", "col")
265 p.AllowAttrs("valign").Matching(
266 CellVerticalAlign,
267 ).OnElements("col", "colgroup")
268
269 // "thead" "tr" are permitted
270 p.AllowAttrs("align").Matching(CellAlign).OnElements("thead", "tr")
271 p.AllowAttrs("valign").Matching(CellVerticalAlign).OnElements("thead", "tr")
272
273 // "td" "th" are permitted
274 p.AllowAttrs("abbr").Matching(Paragraph).OnElements("td", "th")
275 p.AllowAttrs("align").Matching(CellAlign).OnElements("td", "th")
276 p.AllowAttrs("colspan", "rowspan").Matching(Integer).OnElements("td", "th")
277 p.AllowAttrs("headers").Matching(
278 SpaceSeparatedTokens,
279 ).OnElements("td", "th")
280 p.AllowAttrs("height", "width").Matching(
281 NumberOrPercent,
282 ).OnElements("td", "th")
283 p.AllowAttrs(
284 "scope",
285 ).Matching(
286 regexp.MustCompile(`(?i)(?:row|col)(?:group)?`),
287 ).OnElements("td", "th")
288 p.AllowAttrs("valign").Matching(CellVerticalAlign).OnElements("td", "th")
289 p.AllowAttrs("nowrap").Matching(
290 regexp.MustCompile(`(?i)|nowrap`),
291 ).OnElements("td", "th")
292
293 // "tbody" "tfoot"
294 p.AllowAttrs("align").Matching(CellAlign).OnElements("tbody", "tfoot")
295 p.AllowAttrs("valign").Matching(
296 CellVerticalAlign,
297 ).OnElements("tbody", "tfoot")
298}
299
300func (p *Policy) AllowIFrames(vals ...SandboxValue) {
301 p.AllowAttrs("sandbox").OnElements("iframe")
302
303 p.RequireSandboxOnIFrame(vals...)
304}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/policies.go b/vendor/github.com/microcosm-cc/bluemonday/policies.go
new file mode 100644
index 0000000..570bba8
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/policies.go
@@ -0,0 +1,253 @@
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 "regexp"
34)
35
36// StrictPolicy returns an empty policy, which will effectively strip all HTML
37// elements and their attributes from a document.
38func StrictPolicy() *Policy {
39 return NewPolicy()
40}
41
42// StripTagsPolicy is DEPRECATED. Use StrictPolicy instead.
43func StripTagsPolicy() *Policy {
44 return StrictPolicy()
45}
46
47// UGCPolicy returns a policy aimed at user generated content that is a result
48// of HTML WYSIWYG tools and Markdown conversions.
49//
50// This is expected to be a fairly rich document where as much markup as
51// possible should be retained. Markdown permits raw HTML so we are basically
52// providing a policy to sanitise HTML5 documents safely but with the
53// least intrusion on the formatting expectations of the user.
54func UGCPolicy() *Policy {
55
56 p := NewPolicy()
57
58 ///////////////////////
59 // Global attributes //
60 ///////////////////////
61
62 // "class" is not permitted as we are not allowing users to style their own
63 // content
64
65 p.AllowStandardAttributes()
66
67 //////////////////////////////
68 // Global URL format policy //
69 //////////////////////////////
70
71 p.AllowStandardURLs()
72
73 ////////////////////////////////
74 // Declarations and structure //
75 ////////////////////////////////
76
77 // "xml" "xslt" "DOCTYPE" "html" "head" are not permitted as we are
78 // expecting user generated content to be a fragment of HTML and not a full
79 // document.
80
81 //////////////////////////
82 // Sectioning root tags //
83 //////////////////////////
84
85 // "article" and "aside" are permitted and takes no attributes
86 p.AllowElements("article", "aside")
87
88 // "body" is not permitted as we are expecting user generated content to be a fragment
89 // of HTML and not a full document.
90
91 // "details" is permitted, including the "open" attribute which can either
92 // be blank or the value "open".
93 p.AllowAttrs(
94 "open",
95 ).Matching(regexp.MustCompile(`(?i)^(|open)$`)).OnElements("details")
96
97 // "fieldset" is not permitted as we are not allowing forms to be created.
98
99 // "figure" is permitted and takes no attributes
100 p.AllowElements("figure")
101
102 // "nav" is not permitted as it is assumed that the site (and not the user)
103 // has defined navigation elements
104
105 // "section" is permitted and takes no attributes
106 p.AllowElements("section")
107
108 // "summary" is permitted and takes no attributes
109 p.AllowElements("summary")
110
111 //////////////////////////
112 // Headings and footers //
113 //////////////////////////
114
115 // "footer" is not permitted as we expect user content to be a fragment and
116 // not structural to this extent
117
118 // "h1" through "h6" are permitted and take no attributes
119 p.AllowElements("h1", "h2", "h3", "h4", "h5", "h6")
120
121 // "header" is not permitted as we expect user content to be a fragment and
122 // not structural to this extent
123
124 // "hgroup" is permitted and takes no attributes
125 p.AllowElements("hgroup")
126
127 /////////////////////////////////////
128 // Content grouping and separating //
129 /////////////////////////////////////
130
131 // "blockquote" is permitted, including the "cite" attribute which must be
132 // a standard URL.
133 p.AllowAttrs("cite").OnElements("blockquote")
134
135 // "br" "div" "hr" "p" "span" "wbr" are permitted and take no attributes
136 p.AllowElements("br", "div", "hr", "p", "span", "wbr")
137
138 ///////////
139 // Links //
140 ///////////
141
142 // "a" is permitted
143 p.AllowAttrs("href").OnElements("a")
144
145 // "area" is permitted along with the attributes that map image maps work
146 p.AllowAttrs("name").Matching(
147 regexp.MustCompile(`^([\p{L}\p{N}_-]+)$`),
148 ).OnElements("map")
149 p.AllowAttrs("alt").Matching(Paragraph).OnElements("area")
150 p.AllowAttrs("coords").Matching(
151 regexp.MustCompile(`^([0-9]+,)+[0-9]+$`),
152 ).OnElements("area")
153 p.AllowAttrs("href").OnElements("area")
154 p.AllowAttrs("rel").Matching(SpaceSeparatedTokens).OnElements("area")
155 p.AllowAttrs("shape").Matching(
156 regexp.MustCompile(`(?i)^(default|circle|rect|poly)$`),
157 ).OnElements("area")
158 p.AllowAttrs("usemap").Matching(
159 regexp.MustCompile(`(?i)^#[\p{L}\p{N}_-]+$`),
160 ).OnElements("img")
161
162 // "link" is not permitted
163
164 /////////////////////
165 // Phrase elements //
166 /////////////////////
167
168 // The following are all inline phrasing elements
169 p.AllowElements("abbr", "acronym", "cite", "code", "dfn", "em",
170 "figcaption", "mark", "s", "samp", "strong", "sub", "sup", "var")
171
172 // "q" is permitted and "cite" is a URL and handled by URL policies
173 p.AllowAttrs("cite").OnElements("q")
174
175 // "time" is permitted
176 p.AllowAttrs("datetime").Matching(ISO8601).OnElements("time")
177
178 ////////////////////
179 // Style elements //
180 ////////////////////
181
182 // block and inline elements that impart no semantic meaning but style the
183 // document
184 p.AllowElements("b", "i", "pre", "small", "strike", "tt", "u")
185
186 // "style" is not permitted as we are not yet sanitising CSS and it is an
187 // XSS attack vector
188
189 //////////////////////
190 // HTML5 Formatting //
191 //////////////////////
192
193 // "bdi" "bdo" are permitted
194 p.AllowAttrs("dir").Matching(Direction).OnElements("bdi", "bdo")
195
196 // "rp" "rt" "ruby" are permitted
197 p.AllowElements("rp", "rt", "ruby")
198
199 ///////////////////////////
200 // HTML5 Change tracking //
201 ///////////////////////////
202
203 // "del" "ins" are permitted
204 p.AllowAttrs("cite").Matching(Paragraph).OnElements("del", "ins")
205 p.AllowAttrs("datetime").Matching(ISO8601).OnElements("del", "ins")
206
207 ///////////
208 // Lists //
209 ///////////
210
211 p.AllowLists()
212
213 ////////////
214 // Tables //
215 ////////////
216
217 p.AllowTables()
218
219 ///////////
220 // Forms //
221 ///////////
222
223 // By and large, forms are not permitted. However there are some form
224 // elements that can be used to present data, and we do permit those
225 //
226 // "button" "fieldset" "input" "keygen" "label" "output" "select" "datalist"
227 // "textarea" "optgroup" "option" are all not permitted
228
229 // "meter" is permitted
230 p.AllowAttrs(
231 "value",
232 "min",
233 "max",
234 "low",
235 "high",
236 "optimum",
237 ).Matching(Number).OnElements("meter")
238
239 // "progress" is permitted
240 p.AllowAttrs("value", "max").Matching(Number).OnElements("progress")
241
242 //////////////////////
243 // Embedded content //
244 //////////////////////
245
246 // Vast majority not permitted
247 // "audio" "canvas" "embed" "iframe" "object" "param" "source" "svg" "track"
248 // "video" are all not permitted
249
250 p.AllowImages()
251
252 return p
253}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/policy.go b/vendor/github.com/microcosm-cc/bluemonday/policy.go
new file mode 100644
index 0000000..995f46c
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/policy.go
@@ -0,0 +1,952 @@
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
32//TODO sgutzwiller create map of styles to default handlers
33//TODO sgutzwiller create handlers for various attributes
34import (
35 "net/url"
36 "regexp"
37 "strings"
38
39 "github.com/microcosm-cc/bluemonday/css"
40)
41
42// Policy encapsulates the allowlist of HTML elements and attributes that will
43// be applied to the sanitised HTML.
44//
45// You should use bluemonday.NewPolicy() to create a blank policy as the
46// unexported fields contain maps that need to be initialized.
47type Policy struct {
48
49 // Declares whether the maps have been initialized, used as a cheap check to
50 // ensure that those using Policy{} directly won't cause nil pointer
51 // exceptions
52 initialized bool
53
54 // If true then we add spaces when stripping tags, specifically the closing
55 // tag is replaced by a space character.
56 addSpaces bool
57
58 // When true, add rel="nofollow" to HTML a, area, and link tags
59 requireNoFollow bool
60
61 // When true, add rel="nofollow" to HTML a, area, and link tags
62 // Will add for href="http://foo"
63 // Will skip for href="/foo" or href="foo"
64 requireNoFollowFullyQualifiedLinks bool
65
66 // When true, add rel="noreferrer" to HTML a, area, and link tags
67 requireNoReferrer bool
68
69 // When true, add rel="noreferrer" to HTML a, area, and link tags
70 // Will add for href="http://foo"
71 // Will skip for href="/foo" or href="foo"
72 requireNoReferrerFullyQualifiedLinks bool
73
74 // When true, add crossorigin="anonymous" to HTML audio, img, link, script, and video tags
75 requireCrossOriginAnonymous bool
76
77 // When true, add and filter sandbox attribute on iframe tags
78 requireSandboxOnIFrame map[string]bool
79
80 // When true add target="_blank" to fully qualified links
81 // Will add for href="http://foo"
82 // Will skip for href="/foo" or href="foo"
83 addTargetBlankToFullyQualifiedLinks bool
84
85 // When true, URLs must be parseable by "net/url" url.Parse()
86 requireParseableURLs bool
87
88 // When true, u, _ := url.Parse("url"); !u.IsAbs() is permitted
89 allowRelativeURLs bool
90
91 // When true, allow data attributes.
92 allowDataAttributes bool
93
94 // When true, allow comments.
95 allowComments bool
96
97 // map[htmlElementName]map[htmlAttributeName][]attrPolicy
98 elsAndAttrs map[string]map[string][]attrPolicy
99
100 // elsMatchingAndAttrs stores regex based element matches along with attributes
101 elsMatchingAndAttrs map[*regexp.Regexp]map[string][]attrPolicy
102
103 // map[htmlAttributeName][]attrPolicy
104 globalAttrs map[string][]attrPolicy
105
106 // map[htmlElementName]map[cssPropertyName][]stylePolicy
107 elsAndStyles map[string]map[string][]stylePolicy
108
109 // map[regex]map[cssPropertyName][]stylePolicy
110 elsMatchingAndStyles map[*regexp.Regexp]map[string][]stylePolicy
111
112 // map[cssPropertyName][]stylePolicy
113 globalStyles map[string][]stylePolicy
114
115 // If urlPolicy is nil, all URLs with matching schema are allowed.
116 // Otherwise, only the URLs with matching schema and urlPolicy(url)
117 // returning true are allowed.
118 allowURLSchemes map[string][]urlPolicy
119
120 // These regexps are used to match allowed URL schemes, for example
121 // if one would want to allow all URL schemes, they would add `.+`
122 allowURLSchemeRegexps []*regexp.Regexp
123
124 // If an element has had all attributes removed as a result of a policy
125 // being applied, then the element would be removed from the output.
126 //
127 // However some elements are valid and have strong layout meaning without
128 // any attributes, i.e. <table>. To prevent those being removed we maintain
129 // a list of elements that are allowed to have no attributes and that will
130 // be maintained in the output HTML.
131 setOfElementsAllowedWithoutAttrs map[string]struct{}
132
133 // If an element has had all attributes removed as a result of a policy
134 // being applied, then the element would be removed from the output.
135 //
136 // However some elements are valid and have strong layout meaning without
137 // any attributes, i.e. <table>.
138 //
139 // In this case, any element matching a regular expression will be accepted without
140 // attributes added.
141 setOfElementsMatchingAllowedWithoutAttrs []*regexp.Regexp
142
143 setOfElementsToSkipContent map[string]struct{}
144
145 // Permits fundamentally unsafe elements.
146 //
147 // If false (default) then elements such as `style` and `script` will not be
148 // permitted even if declared in a policy. These elements when combined with
149 // untrusted input cannot be safely handled by bluemonday at this point in
150 // time.
151 //
152 // If true then `style` and `script` would be permitted by bluemonday if a
153 // policy declares them. However this is not recommended under any circumstance
154 // and can lead to XSS being rendered thus defeating the purpose of using a
155 // HTML sanitizer.
156 allowUnsafe bool
157}
158
159type attrPolicy struct {
160
161 // optional pattern to match, when not nil the regexp needs to match
162 // otherwise the attribute is removed
163 regexp *regexp.Regexp
164}
165
166type stylePolicy struct {
167 // handler to validate
168 handler func(string) bool
169
170 // optional pattern to match, when not nil the regexp needs to match
171 // otherwise the property is removed
172 regexp *regexp.Regexp
173
174 // optional list of allowed property values, for properties which
175 // have a defined list of allowed values; property will be removed
176 // if the value is not allowed
177 enum []string
178}
179
180type attrPolicyBuilder struct {
181 p *Policy
182
183 attrNames []string
184 regexp *regexp.Regexp
185 allowEmpty bool
186}
187
188type stylePolicyBuilder struct {
189 p *Policy
190
191 propertyNames []string
192 regexp *regexp.Regexp
193 enum []string
194 handler func(string) bool
195}
196
197type urlPolicy func(url *url.URL) (allowUrl bool)
198
199type SandboxValue int64
200
201const (
202 SandboxAllowDownloads SandboxValue = iota
203 SandboxAllowDownloadsWithoutUserActivation
204 SandboxAllowForms
205 SandboxAllowModals
206 SandboxAllowOrientationLock
207 SandboxAllowPointerLock
208 SandboxAllowPopups
209 SandboxAllowPopupsToEscapeSandbox
210 SandboxAllowPresentation
211 SandboxAllowSameOrigin
212 SandboxAllowScripts
213 SandboxAllowStorageAccessByUserActivation
214 SandboxAllowTopNavigation
215 SandboxAllowTopNavigationByUserActivation
216)
217
218// init initializes the maps if this has not been done already
219func (p *Policy) init() {
220 if !p.initialized {
221 p.elsAndAttrs = make(map[string]map[string][]attrPolicy)
222 p.elsMatchingAndAttrs = make(map[*regexp.Regexp]map[string][]attrPolicy)
223 p.globalAttrs = make(map[string][]attrPolicy)
224 p.elsAndStyles = make(map[string]map[string][]stylePolicy)
225 p.elsMatchingAndStyles = make(map[*regexp.Regexp]map[string][]stylePolicy)
226 p.globalStyles = make(map[string][]stylePolicy)
227 p.allowURLSchemes = make(map[string][]urlPolicy)
228 p.allowURLSchemeRegexps = make([]*regexp.Regexp, 0)
229 p.setOfElementsAllowedWithoutAttrs = make(map[string]struct{})
230 p.setOfElementsToSkipContent = make(map[string]struct{})
231 p.initialized = true
232 }
233}
234
235// NewPolicy returns a blank policy with nothing allowed or permitted. This
236// is the recommended way to start building a policy and you should now use
237// AllowAttrs() and/or AllowElements() to construct the allowlist of HTML
238// elements and attributes.
239func NewPolicy() *Policy {
240
241 p := Policy{}
242
243 p.addDefaultElementsWithoutAttrs()
244 p.addDefaultSkipElementContent()
245
246 return &p
247}
248
249// AllowAttrs takes a range of HTML attribute names and returns an
250// attribute policy builder that allows you to specify the pattern and scope of
251// the allowed attribute.
252//
253// The attribute policy is only added to the core policy when either Globally()
254// or OnElements(...) are called.
255func (p *Policy) AllowAttrs(attrNames ...string) *attrPolicyBuilder {
256
257 p.init()
258
259 abp := attrPolicyBuilder{
260 p: p,
261 allowEmpty: false,
262 }
263
264 for _, attrName := range attrNames {
265 abp.attrNames = append(abp.attrNames, strings.ToLower(attrName))
266 }
267
268 return &abp
269}
270
271// AllowDataAttributes permits all data attributes. We can't specify the name
272// of each attribute exactly as they are customized.
273//
274// NOTE: These values are not sanitized and applications that evaluate or process
275// them without checking and verification of the input may be at risk if this option
276// is enabled. This is a 'caveat emptor' option and the person enabling this option
277// needs to fully understand the potential impact with regards to whatever application
278// will be consuming the sanitized HTML afterwards, i.e. if you know you put a link in a
279// data attribute and use that to automatically load some new window then you're giving
280// the author of a HTML fragment the means to open a malicious destination automatically.
281// Use with care!
282func (p *Policy) AllowDataAttributes() {
283 p.allowDataAttributes = true
284}
285
286// AllowComments allows comments.
287//
288// Please note that only one type of comment will be allowed by this, this is the
289// the standard HTML comment <!-- --> which includes the use of that to permit
290// conditionals as per https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)?redirectedfrom=MSDN
291//
292// What is not permitted are CDATA XML comments, as the x/net/html package we depend
293// on does not handle this fully and we are not choosing to take on that work:
294// https://pkg.go.dev/golang.org/x/net/html#Tokenizer.AllowCDATA . If the x/net/html
295// package changes this then these will be considered, otherwise if you AllowComments
296// but provide a CDATA comment, then as per the documentation in x/net/html this will
297// be treated as a plain HTML comment.
298func (p *Policy) AllowComments() {
299 p.allowComments = true
300}
301
302// AllowNoAttrs says that attributes on element are optional.
303//
304// The attribute policy is only added to the core policy when OnElements(...)
305// are called.
306func (p *Policy) AllowNoAttrs() *attrPolicyBuilder {
307
308 p.init()
309
310 abp := attrPolicyBuilder{
311 p: p,
312 allowEmpty: true,
313 }
314 return &abp
315}
316
317// AllowNoAttrs says that attributes on element are optional.
318//
319// The attribute policy is only added to the core policy when OnElements(...)
320// are called.
321func (abp *attrPolicyBuilder) AllowNoAttrs() *attrPolicyBuilder {
322
323 abp.allowEmpty = true
324
325 return abp
326}
327
328// Matching allows a regular expression to be applied to a nascent attribute
329// policy, and returns the attribute policy.
330func (abp *attrPolicyBuilder) Matching(regex *regexp.Regexp) *attrPolicyBuilder {
331
332 abp.regexp = regex
333
334 return abp
335}
336
337// OnElements will bind an attribute policy to a given range of HTML elements
338// and return the updated policy
339func (abp *attrPolicyBuilder) OnElements(elements ...string) *Policy {
340
341 for _, element := range elements {
342 element = strings.ToLower(element)
343
344 for _, attr := range abp.attrNames {
345
346 if _, ok := abp.p.elsAndAttrs[element]; !ok {
347 abp.p.elsAndAttrs[element] = make(map[string][]attrPolicy)
348 }
349
350 ap := attrPolicy{}
351 if abp.regexp != nil {
352 ap.regexp = abp.regexp
353 }
354
355 abp.p.elsAndAttrs[element][attr] = append(abp.p.elsAndAttrs[element][attr], ap)
356 }
357
358 if abp.allowEmpty {
359 abp.p.setOfElementsAllowedWithoutAttrs[element] = struct{}{}
360
361 if _, ok := abp.p.elsAndAttrs[element]; !ok {
362 abp.p.elsAndAttrs[element] = make(map[string][]attrPolicy)
363 }
364 }
365 }
366
367 return abp.p
368}
369
370// OnElementsMatching will bind an attribute policy to all elements matching a given regex
371// and return the updated policy
372func (abp *attrPolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
373 for _, attr := range abp.attrNames {
374 if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok {
375 abp.p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy)
376 }
377 ap := attrPolicy{}
378 if abp.regexp != nil {
379 ap.regexp = abp.regexp
380 }
381 abp.p.elsMatchingAndAttrs[regex][attr] = append(abp.p.elsMatchingAndAttrs[regex][attr], ap)
382 }
383
384 if abp.allowEmpty {
385 abp.p.setOfElementsMatchingAllowedWithoutAttrs = append(abp.p.setOfElementsMatchingAllowedWithoutAttrs, regex)
386 if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok {
387 abp.p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy)
388 }
389 }
390
391 return abp.p
392}
393
394// Globally will bind an attribute policy to all HTML elements and return the
395// updated policy
396func (abp *attrPolicyBuilder) Globally() *Policy {
397
398 for _, attr := range abp.attrNames {
399 if _, ok := abp.p.globalAttrs[attr]; !ok {
400 abp.p.globalAttrs[attr] = []attrPolicy{}
401 }
402
403 ap := attrPolicy{}
404 if abp.regexp != nil {
405 ap.regexp = abp.regexp
406 }
407
408 abp.p.globalAttrs[attr] = append(abp.p.globalAttrs[attr], ap)
409 }
410
411 return abp.p
412}
413
414// AllowStyles takes a range of CSS property names and returns a
415// style policy builder that allows you to specify the pattern and scope of
416// the allowed property.
417//
418// The style policy is only added to the core policy when either Globally()
419// or OnElements(...) are called.
420func (p *Policy) AllowStyles(propertyNames ...string) *stylePolicyBuilder {
421
422 p.init()
423
424 abp := stylePolicyBuilder{
425 p: p,
426 }
427
428 for _, propertyName := range propertyNames {
429 abp.propertyNames = append(abp.propertyNames, strings.ToLower(propertyName))
430 }
431
432 return &abp
433}
434
435// Matching allows a regular expression to be applied to a nascent style
436// policy, and returns the style policy.
437func (spb *stylePolicyBuilder) Matching(regex *regexp.Regexp) *stylePolicyBuilder {
438
439 spb.regexp = regex
440
441 return spb
442}
443
444// MatchingEnum allows a list of allowed values to be applied to a nascent style
445// policy, and returns the style policy.
446func (spb *stylePolicyBuilder) MatchingEnum(enum ...string) *stylePolicyBuilder {
447
448 spb.enum = enum
449
450 return spb
451}
452
453// MatchingHandler allows a handler to be applied to a nascent style
454// policy, and returns the style policy.
455func (spb *stylePolicyBuilder) MatchingHandler(handler func(string) bool) *stylePolicyBuilder {
456
457 spb.handler = handler
458
459 return spb
460}
461
462// OnElements will bind a style policy to a given range of HTML elements
463// and return the updated policy
464func (spb *stylePolicyBuilder) OnElements(elements ...string) *Policy {
465
466 for _, element := range elements {
467 element = strings.ToLower(element)
468
469 for _, attr := range spb.propertyNames {
470
471 if _, ok := spb.p.elsAndStyles[element]; !ok {
472 spb.p.elsAndStyles[element] = make(map[string][]stylePolicy)
473 }
474
475 sp := stylePolicy{}
476 if spb.handler != nil {
477 sp.handler = spb.handler
478 } else if len(spb.enum) > 0 {
479 sp.enum = spb.enum
480 } else if spb.regexp != nil {
481 sp.regexp = spb.regexp
482 } else {
483 sp.handler = css.GetDefaultHandler(attr)
484 }
485 spb.p.elsAndStyles[element][attr] = append(spb.p.elsAndStyles[element][attr], sp)
486 }
487 }
488
489 return spb.p
490}
491
492// OnElementsMatching will bind a style policy to any HTML elements matching the pattern
493// and return the updated policy
494func (spb *stylePolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
495
496 for _, attr := range spb.propertyNames {
497
498 if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok {
499 spb.p.elsMatchingAndStyles[regex] = make(map[string][]stylePolicy)
500 }
501
502 sp := stylePolicy{}
503 if spb.handler != nil {
504 sp.handler = spb.handler
505 } else if len(spb.enum) > 0 {
506 sp.enum = spb.enum
507 } else if spb.regexp != nil {
508 sp.regexp = spb.regexp
509 } else {
510 sp.handler = css.GetDefaultHandler(attr)
511 }
512 spb.p.elsMatchingAndStyles[regex][attr] = append(spb.p.elsMatchingAndStyles[regex][attr], sp)
513 }
514
515 return spb.p
516}
517
518// Globally will bind a style policy to all HTML elements and return the
519// updated policy
520func (spb *stylePolicyBuilder) Globally() *Policy {
521
522 for _, attr := range spb.propertyNames {
523 if _, ok := spb.p.globalStyles[attr]; !ok {
524 spb.p.globalStyles[attr] = []stylePolicy{}
525 }
526
527 // Use only one strategy for validating styles, fallback to default
528 sp := stylePolicy{}
529 if spb.handler != nil {
530 sp.handler = spb.handler
531 } else if len(spb.enum) > 0 {
532 sp.enum = spb.enum
533 } else if spb.regexp != nil {
534 sp.regexp = spb.regexp
535 } else {
536 sp.handler = css.GetDefaultHandler(attr)
537 }
538 spb.p.globalStyles[attr] = append(spb.p.globalStyles[attr], sp)
539 }
540
541 return spb.p
542}
543
544// AllowElements will append HTML elements to the allowlist without applying an
545// attribute policy to those elements (the elements are permitted
546// sans-attributes)
547func (p *Policy) AllowElements(names ...string) *Policy {
548 p.init()
549
550 for _, element := range names {
551 element = strings.ToLower(element)
552
553 if _, ok := p.elsAndAttrs[element]; !ok {
554 p.elsAndAttrs[element] = make(map[string][]attrPolicy)
555 }
556 }
557
558 return p
559}
560
561// AllowElementsMatching will append HTML elements to the allowlist if they
562// match a regexp.
563func (p *Policy) AllowElementsMatching(regex *regexp.Regexp) *Policy {
564 p.init()
565 if _, ok := p.elsMatchingAndAttrs[regex]; !ok {
566 p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy)
567 }
568 return p
569}
570
571// AllowURLSchemesMatching will append URL schemes to the allowlist if they
572// match a regexp.
573func (p *Policy) AllowURLSchemesMatching(r *regexp.Regexp) *Policy {
574 p.allowURLSchemeRegexps = append(p.allowURLSchemeRegexps, r)
575 return p
576}
577
578// RequireNoFollowOnLinks will result in all a, area, link tags having a
579// rel="nofollow"added to them if one does not already exist
580//
581// Note: This requires p.RequireParseableURLs(true) and will enable it.
582func (p *Policy) RequireNoFollowOnLinks(require bool) *Policy {
583
584 p.requireNoFollow = require
585 p.requireParseableURLs = true
586
587 return p
588}
589
590// RequireNoFollowOnFullyQualifiedLinks will result in all a, area, and link
591// tags that point to a non-local destination (i.e. starts with a protocol and
592// has a host) having a rel="nofollow" added to them if one does not already
593// exist
594//
595// Note: This requires p.RequireParseableURLs(true) and will enable it.
596func (p *Policy) RequireNoFollowOnFullyQualifiedLinks(require bool) *Policy {
597
598 p.requireNoFollowFullyQualifiedLinks = require
599 p.requireParseableURLs = true
600
601 return p
602}
603
604// RequireNoReferrerOnLinks will result in all a, area, and link tags having a
605// rel="noreferrrer" added to them if one does not already exist
606//
607// Note: This requires p.RequireParseableURLs(true) and will enable it.
608func (p *Policy) RequireNoReferrerOnLinks(require bool) *Policy {
609
610 p.requireNoReferrer = require
611 p.requireParseableURLs = true
612
613 return p
614}
615
616// RequireNoReferrerOnFullyQualifiedLinks will result in all a, area, and link
617// tags that point to a non-local destination (i.e. starts with a protocol and
618// has a host) having a rel="noreferrer" added to them if one does not already
619// exist
620//
621// Note: This requires p.RequireParseableURLs(true) and will enable it.
622func (p *Policy) RequireNoReferrerOnFullyQualifiedLinks(require bool) *Policy {
623
624 p.requireNoReferrerFullyQualifiedLinks = require
625 p.requireParseableURLs = true
626
627 return p
628}
629
630// RequireCrossOriginAnonymous will result in all audio, img, link, script, and
631// video tags having a crossorigin="anonymous" added to them if one does not
632// already exist
633func (p *Policy) RequireCrossOriginAnonymous(require bool) *Policy {
634
635 p.requireCrossOriginAnonymous = require
636
637 return p
638}
639
640// AddTargetBlankToFullyQualifiedLinks will result in all a, area and link tags
641// that point to a non-local destination (i.e. starts with a protocol and has a
642// host) having a target="_blank" added to them if one does not already exist
643//
644// Note: This requires p.RequireParseableURLs(true) and will enable it.
645func (p *Policy) AddTargetBlankToFullyQualifiedLinks(require bool) *Policy {
646
647 p.addTargetBlankToFullyQualifiedLinks = require
648 p.requireParseableURLs = true
649
650 return p
651}
652
653// RequireParseableURLs will result in all URLs requiring that they be parseable
654// by "net/url" url.Parse()
655// This applies to:
656// - a.href
657// - area.href
658// - blockquote.cite
659// - img.src
660// - link.href
661// - script.src
662func (p *Policy) RequireParseableURLs(require bool) *Policy {
663
664 p.requireParseableURLs = require
665
666 return p
667}
668
669// AllowRelativeURLs enables RequireParseableURLs and then permits URLs that
670// are parseable, have no schema information and url.IsAbs() returns false
671// This permits local URLs
672func (p *Policy) AllowRelativeURLs(require bool) *Policy {
673
674 p.RequireParseableURLs(true)
675 p.allowRelativeURLs = require
676
677 return p
678}
679
680// AllowURLSchemes will append URL schemes to the allowlist
681// Example: p.AllowURLSchemes("mailto", "http", "https")
682func (p *Policy) AllowURLSchemes(schemes ...string) *Policy {
683 p.init()
684
685 p.RequireParseableURLs(true)
686
687 for _, scheme := range schemes {
688 scheme = strings.ToLower(scheme)
689
690 // Allow all URLs with matching scheme.
691 p.allowURLSchemes[scheme] = nil
692 }
693
694 return p
695}
696
697// AllowURLSchemeWithCustomPolicy will append URL schemes with
698// a custom URL policy to the allowlist.
699// Only the URLs with matching schema and urlPolicy(url)
700// returning true will be allowed.
701func (p *Policy) AllowURLSchemeWithCustomPolicy(
702 scheme string,
703 urlPolicy func(url *url.URL) (allowUrl bool),
704) *Policy {
705
706 p.init()
707
708 p.RequireParseableURLs(true)
709
710 scheme = strings.ToLower(scheme)
711
712 p.allowURLSchemes[scheme] = append(p.allowURLSchemes[scheme], urlPolicy)
713
714 return p
715}
716
717// RequireSandboxOnIFrame will result in all iframe tags having a sandbox="" tag
718// Any sandbox values not specified here will be filtered from the generated HTML
719func (p *Policy) RequireSandboxOnIFrame(vals ...SandboxValue) {
720 p.requireSandboxOnIFrame = make(map[string]bool)
721
722 for _, val := range vals {
723 switch SandboxValue(val) {
724 case SandboxAllowDownloads:
725 p.requireSandboxOnIFrame["allow-downloads"] = true
726
727 case SandboxAllowDownloadsWithoutUserActivation:
728 p.requireSandboxOnIFrame["allow-downloads-without-user-activation"] = true
729
730 case SandboxAllowForms:
731 p.requireSandboxOnIFrame["allow-forms"] = true
732
733 case SandboxAllowModals:
734 p.requireSandboxOnIFrame["allow-modals"] = true
735
736 case SandboxAllowOrientationLock:
737 p.requireSandboxOnIFrame["allow-orientation-lock"] = true
738
739 case SandboxAllowPointerLock:
740 p.requireSandboxOnIFrame["allow-pointer-lock"] = true
741
742 case SandboxAllowPopups:
743 p.requireSandboxOnIFrame["allow-popups"] = true
744
745 case SandboxAllowPopupsToEscapeSandbox:
746 p.requireSandboxOnIFrame["allow-popups-to-escape-sandbox"] = true
747
748 case SandboxAllowPresentation:
749 p.requireSandboxOnIFrame["allow-presentation"] = true
750
751 case SandboxAllowSameOrigin:
752 p.requireSandboxOnIFrame["allow-same-origin"] = true
753
754 case SandboxAllowScripts:
755 p.requireSandboxOnIFrame["allow-scripts"] = true
756
757 case SandboxAllowStorageAccessByUserActivation:
758 p.requireSandboxOnIFrame["allow-storage-access-by-user-activation"] = true
759
760 case SandboxAllowTopNavigation:
761 p.requireSandboxOnIFrame["allow-top-navigation"] = true
762
763 case SandboxAllowTopNavigationByUserActivation:
764 p.requireSandboxOnIFrame["allow-top-navigation-by-user-activation"] = true
765 }
766 }
767}
768
769// AddSpaceWhenStrippingTag states whether to add a single space " " when
770// removing tags that are not allowed by the policy.
771//
772// This is useful if you expect to strip tags in dense markup and may lose the
773// value of whitespace.
774//
775// For example: "<p>Hello</p><p>World</p>"" would be sanitized to "HelloWorld"
776// with the default value of false, but you may wish to sanitize this to
777// " Hello World " by setting AddSpaceWhenStrippingTag to true as this would
778// retain the intent of the text.
779func (p *Policy) AddSpaceWhenStrippingTag(allow bool) *Policy {
780
781 p.addSpaces = allow
782
783 return p
784}
785
786// SkipElementsContent adds the HTML elements whose tags is needed to be removed
787// with its content.
788func (p *Policy) SkipElementsContent(names ...string) *Policy {
789
790 p.init()
791
792 for _, element := range names {
793 element = strings.ToLower(element)
794
795 if _, ok := p.setOfElementsToSkipContent[element]; !ok {
796 p.setOfElementsToSkipContent[element] = struct{}{}
797 }
798 }
799
800 return p
801}
802
803// AllowElementsContent marks the HTML elements whose content should be
804// retained after removing the tag.
805func (p *Policy) AllowElementsContent(names ...string) *Policy {
806
807 p.init()
808
809 for _, element := range names {
810 delete(p.setOfElementsToSkipContent, strings.ToLower(element))
811 }
812
813 return p
814}
815
816// AllowUnsafe permits fundamentally unsafe elements.
817//
818// If false (default) then elements such as `style` and `script` will not be
819// permitted even if declared in a policy. These elements when combined with
820// untrusted input cannot be safely handled by bluemonday at this point in
821// time.
822//
823// If true then `style` and `script` would be permitted by bluemonday if a
824// policy declares them. However this is not recommended under any circumstance
825// and can lead to XSS being rendered thus defeating the purpose of using a
826// HTML sanitizer.
827func (p *Policy) AllowUnsafe(allowUnsafe bool) *Policy {
828 p.init()
829 p.allowUnsafe = allowUnsafe
830 return p
831}
832
833// addDefaultElementsWithoutAttrs adds the HTML elements that we know are valid
834// without any attributes to an internal map.
835// i.e. we know that <table> is valid, but <bdo> isn't valid as the "dir" attr
836// is mandatory
837func (p *Policy) addDefaultElementsWithoutAttrs() {
838 p.init()
839
840 p.setOfElementsAllowedWithoutAttrs["abbr"] = struct{}{}
841 p.setOfElementsAllowedWithoutAttrs["acronym"] = struct{}{}
842 p.setOfElementsAllowedWithoutAttrs["address"] = struct{}{}
843 p.setOfElementsAllowedWithoutAttrs["article"] = struct{}{}
844 p.setOfElementsAllowedWithoutAttrs["aside"] = struct{}{}
845 p.setOfElementsAllowedWithoutAttrs["audio"] = struct{}{}
846 p.setOfElementsAllowedWithoutAttrs["b"] = struct{}{}
847 p.setOfElementsAllowedWithoutAttrs["bdi"] = struct{}{}
848 p.setOfElementsAllowedWithoutAttrs["blockquote"] = struct{}{}
849 p.setOfElementsAllowedWithoutAttrs["body"] = struct{}{}
850 p.setOfElementsAllowedWithoutAttrs["br"] = struct{}{}
851 p.setOfElementsAllowedWithoutAttrs["button"] = struct{}{}
852 p.setOfElementsAllowedWithoutAttrs["canvas"] = struct{}{}
853 p.setOfElementsAllowedWithoutAttrs["caption"] = struct{}{}
854 p.setOfElementsAllowedWithoutAttrs["center"] = struct{}{}
855 p.setOfElementsAllowedWithoutAttrs["cite"] = struct{}{}
856 p.setOfElementsAllowedWithoutAttrs["code"] = struct{}{}
857 p.setOfElementsAllowedWithoutAttrs["col"] = struct{}{}
858 p.setOfElementsAllowedWithoutAttrs["colgroup"] = struct{}{}
859 p.setOfElementsAllowedWithoutAttrs["datalist"] = struct{}{}
860 p.setOfElementsAllowedWithoutAttrs["dd"] = struct{}{}
861 p.setOfElementsAllowedWithoutAttrs["del"] = struct{}{}
862 p.setOfElementsAllowedWithoutAttrs["details"] = struct{}{}
863 p.setOfElementsAllowedWithoutAttrs["dfn"] = struct{}{}
864 p.setOfElementsAllowedWithoutAttrs["div"] = struct{}{}
865 p.setOfElementsAllowedWithoutAttrs["dl"] = struct{}{}
866 p.setOfElementsAllowedWithoutAttrs["dt"] = struct{}{}
867 p.setOfElementsAllowedWithoutAttrs["em"] = struct{}{}
868 p.setOfElementsAllowedWithoutAttrs["fieldset"] = struct{}{}
869 p.setOfElementsAllowedWithoutAttrs["figcaption"] = struct{}{}
870 p.setOfElementsAllowedWithoutAttrs["figure"] = struct{}{}
871 p.setOfElementsAllowedWithoutAttrs["footer"] = struct{}{}
872 p.setOfElementsAllowedWithoutAttrs["h1"] = struct{}{}
873 p.setOfElementsAllowedWithoutAttrs["h2"] = struct{}{}
874 p.setOfElementsAllowedWithoutAttrs["h3"] = struct{}{}
875 p.setOfElementsAllowedWithoutAttrs["h4"] = struct{}{}
876 p.setOfElementsAllowedWithoutAttrs["h5"] = struct{}{}
877 p.setOfElementsAllowedWithoutAttrs["h6"] = struct{}{}
878 p.setOfElementsAllowedWithoutAttrs["head"] = struct{}{}
879 p.setOfElementsAllowedWithoutAttrs["header"] = struct{}{}
880 p.setOfElementsAllowedWithoutAttrs["hgroup"] = struct{}{}
881 p.setOfElementsAllowedWithoutAttrs["hr"] = struct{}{}
882 p.setOfElementsAllowedWithoutAttrs["html"] = struct{}{}
883 p.setOfElementsAllowedWithoutAttrs["i"] = struct{}{}
884 p.setOfElementsAllowedWithoutAttrs["ins"] = struct{}{}
885 p.setOfElementsAllowedWithoutAttrs["kbd"] = struct{}{}
886 p.setOfElementsAllowedWithoutAttrs["li"] = struct{}{}
887 p.setOfElementsAllowedWithoutAttrs["mark"] = struct{}{}
888 p.setOfElementsAllowedWithoutAttrs["marquee"] = struct{}{}
889 p.setOfElementsAllowedWithoutAttrs["nav"] = struct{}{}
890 p.setOfElementsAllowedWithoutAttrs["ol"] = struct{}{}
891 p.setOfElementsAllowedWithoutAttrs["optgroup"] = struct{}{}
892 p.setOfElementsAllowedWithoutAttrs["option"] = struct{}{}
893 p.setOfElementsAllowedWithoutAttrs["p"] = struct{}{}
894 p.setOfElementsAllowedWithoutAttrs["picture"] = struct{}{}
895 p.setOfElementsAllowedWithoutAttrs["pre"] = struct{}{}
896 p.setOfElementsAllowedWithoutAttrs["q"] = struct{}{}
897 p.setOfElementsAllowedWithoutAttrs["rp"] = struct{}{}
898 p.setOfElementsAllowedWithoutAttrs["rt"] = struct{}{}
899 p.setOfElementsAllowedWithoutAttrs["ruby"] = struct{}{}
900 p.setOfElementsAllowedWithoutAttrs["s"] = struct{}{}
901 p.setOfElementsAllowedWithoutAttrs["samp"] = struct{}{}
902 p.setOfElementsAllowedWithoutAttrs["script"] = struct{}{}
903 p.setOfElementsAllowedWithoutAttrs["section"] = struct{}{}
904 p.setOfElementsAllowedWithoutAttrs["select"] = struct{}{}
905 p.setOfElementsAllowedWithoutAttrs["small"] = struct{}{}
906 p.setOfElementsAllowedWithoutAttrs["span"] = struct{}{}
907 p.setOfElementsAllowedWithoutAttrs["strike"] = struct{}{}
908 p.setOfElementsAllowedWithoutAttrs["strong"] = struct{}{}
909 p.setOfElementsAllowedWithoutAttrs["style"] = struct{}{}
910 p.setOfElementsAllowedWithoutAttrs["sub"] = struct{}{}
911 p.setOfElementsAllowedWithoutAttrs["summary"] = struct{}{}
912 p.setOfElementsAllowedWithoutAttrs["sup"] = struct{}{}
913 p.setOfElementsAllowedWithoutAttrs["svg"] = struct{}{}
914 p.setOfElementsAllowedWithoutAttrs["table"] = struct{}{}
915 p.setOfElementsAllowedWithoutAttrs["tbody"] = struct{}{}
916 p.setOfElementsAllowedWithoutAttrs["td"] = struct{}{}
917 p.setOfElementsAllowedWithoutAttrs["textarea"] = struct{}{}
918 p.setOfElementsAllowedWithoutAttrs["tfoot"] = struct{}{}
919 p.setOfElementsAllowedWithoutAttrs["th"] = struct{}{}
920 p.setOfElementsAllowedWithoutAttrs["thead"] = struct{}{}
921 p.setOfElementsAllowedWithoutAttrs["title"] = struct{}{}
922 p.setOfElementsAllowedWithoutAttrs["time"] = struct{}{}
923 p.setOfElementsAllowedWithoutAttrs["tr"] = struct{}{}
924 p.setOfElementsAllowedWithoutAttrs["tt"] = struct{}{}
925 p.setOfElementsAllowedWithoutAttrs["u"] = struct{}{}
926 p.setOfElementsAllowedWithoutAttrs["ul"] = struct{}{}
927 p.setOfElementsAllowedWithoutAttrs["var"] = struct{}{}
928 p.setOfElementsAllowedWithoutAttrs["video"] = struct{}{}
929 p.setOfElementsAllowedWithoutAttrs["wbr"] = struct{}{}
930
931}
932
933// addDefaultSkipElementContent adds the HTML elements that we should skip
934// rendering the character content of, if the element itself is not allowed.
935// This is all character data that the end user would not normally see.
936// i.e. if we exclude a <script> tag then we shouldn't render the JavaScript or
937// anything else until we encounter the closing </script> tag.
938func (p *Policy) addDefaultSkipElementContent() {
939 p.init()
940
941 p.setOfElementsToSkipContent["frame"] = struct{}{}
942 p.setOfElementsToSkipContent["frameset"] = struct{}{}
943 p.setOfElementsToSkipContent["iframe"] = struct{}{}
944 p.setOfElementsToSkipContent["noembed"] = struct{}{}
945 p.setOfElementsToSkipContent["noframes"] = struct{}{}
946 p.setOfElementsToSkipContent["noscript"] = struct{}{}
947 p.setOfElementsToSkipContent["nostyle"] = struct{}{}
948 p.setOfElementsToSkipContent["object"] = struct{}{}
949 p.setOfElementsToSkipContent["script"] = struct{}{}
950 p.setOfElementsToSkipContent["style"] = struct{}{}
951 p.setOfElementsToSkipContent["title"] = struct{}{}
952}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/sanitize.go b/vendor/github.com/microcosm-cc/bluemonday/sanitize.go
new file mode 100644
index 0000000..9121aef
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/sanitize.go
@@ -0,0 +1,1116 @@
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 "bytes"
34 "fmt"
35 "io"
36 "net/url"
37 "regexp"
38 "strconv"
39 "strings"
40
41 "golang.org/x/net/html"
42
43 "github.com/aymerick/douceur/parser"
44)
45
46var (
47 dataAttribute = regexp.MustCompile("^data-.+")
48 dataAttributeXMLPrefix = regexp.MustCompile("^xml.+")
49 dataAttributeInvalidChars = regexp.MustCompile("[A-Z;]+")
50 cssUnicodeChar = regexp.MustCompile(`\\[0-9a-f]{1,6} ?`)
51 dataURIbase64Prefix = regexp.MustCompile(`^data:[^,]*;base64,`)
52)
53
54// Sanitize takes a string that contains a HTML fragment or document and applies
55// the given policy allowlist.
56//
57// It returns a HTML string that has been sanitized by the policy or an empty
58// string if an error has occurred (most likely as a consequence of extremely
59// malformed input)
60func (p *Policy) Sanitize(s string) string {
61 if strings.TrimSpace(s) == "" {
62 return s
63 }
64
65 return p.sanitizeWithBuff(strings.NewReader(s)).String()
66}
67
68// SanitizeBytes takes a []byte that contains a HTML fragment or document and applies
69// the given policy allowlist.
70//
71// It returns a []byte containing the HTML that has been sanitized by the policy
72// or an empty []byte if an error has occurred (most likely as a consequence of
73// extremely malformed input)
74func (p *Policy) SanitizeBytes(b []byte) []byte {
75 if len(bytes.TrimSpace(b)) == 0 {
76 return b
77 }
78
79 return p.sanitizeWithBuff(bytes.NewReader(b)).Bytes()
80}
81
82// SanitizeReader takes an io.Reader that contains a HTML fragment or document
83// and applies the given policy allowlist.
84//
85// It returns a bytes.Buffer containing the HTML that has been sanitized by the
86// policy. Errors during sanitization will merely return an empty result.
87func (p *Policy) SanitizeReader(r io.Reader) *bytes.Buffer {
88 return p.sanitizeWithBuff(r)
89}
90
91// SanitizeReaderToWriter takes an io.Reader that contains a HTML fragment or document
92// and applies the given policy allowlist and writes to the provided writer returning
93// an error if there is one.
94func (p *Policy) SanitizeReaderToWriter(r io.Reader, w io.Writer) error {
95 return p.sanitize(r, w)
96}
97
98const escapedURLChars = "'<>\"\r"
99
100func escapeUrlComponent(w stringWriterWriter, val string) error {
101 i := strings.IndexAny(val, escapedURLChars)
102 for i != -1 {
103 if _, err := w.WriteString(val[:i]); err != nil {
104 return err
105 }
106 var esc string
107 switch val[i] {
108 case '\'':
109 // "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
110 esc = "&#39;"
111 case '<':
112 esc = "&lt;"
113 case '>':
114 esc = "&gt;"
115 case '"':
116 // "&#34;" is shorter than "&quot;".
117 esc = "&#34;"
118 case '\r':
119 esc = "&#13;"
120 default:
121 panic("unrecognized escape character")
122 }
123 val = val[i+1:]
124 if _, err := w.WriteString(esc); err != nil {
125 return err
126 }
127 i = strings.IndexAny(val, escapedURLChars)
128 }
129 _, err := w.WriteString(val)
130 return err
131}
132
133// Query represents a single part of the query string, a query param
134type Query struct {
135 Key string
136 Value string
137 HasValue bool
138}
139
140func parseQuery(query string) (values []Query, err error) {
141 // This is essentially a copy of parseQuery from
142 // https://golang.org/src/net/url/url.go but adjusted to build our values
143 // based on our type, which we need to preserve the ordering of the query
144 // string
145 for query != "" {
146 key := query
147 if i := strings.IndexAny(key, "&;"); i >= 0 {
148 key, query = key[:i], key[i+1:]
149 } else {
150 query = ""
151 }
152 if key == "" {
153 continue
154 }
155 value := ""
156 hasValue := false
157 if i := strings.Index(key, "="); i >= 0 {
158 key, value = key[:i], key[i+1:]
159 hasValue = true
160 }
161 key, err1 := url.QueryUnescape(key)
162 if err1 != nil {
163 if err == nil {
164 err = err1
165 }
166 continue
167 }
168 value, err1 = url.QueryUnescape(value)
169 if err1 != nil {
170 if err == nil {
171 err = err1
172 }
173 continue
174 }
175 values = append(values, Query{
176 Key: key,
177 Value: value,
178 HasValue: hasValue,
179 })
180 }
181 return values, err
182}
183
184func encodeQueries(queries []Query) string {
185 var buff bytes.Buffer
186 for i, query := range queries {
187 buff.WriteString(url.QueryEscape(query.Key))
188 if query.HasValue {
189 buff.WriteString("=")
190 buff.WriteString(url.QueryEscape(query.Value))
191 }
192 if i < len(queries)-1 {
193 buff.WriteString("&")
194 }
195 }
196 return buff.String()
197}
198
199func sanitizedURL(val string) (string, error) {
200 u, err := url.Parse(val)
201 if err != nil {
202 return "", err
203 }
204
205 // we use parseQuery but not u.Query to keep the order not change because
206 // url.Values is a map which has a random order.
207 queryValues, err := parseQuery(u.RawQuery)
208 if err != nil {
209 return "", err
210 }
211 // sanitize the url query params
212 for i, query := range queryValues {
213 queryValues[i].Key = html.EscapeString(query.Key)
214 }
215 u.RawQuery = encodeQueries(queryValues)
216 // u.String() will also sanitize host/scheme/user/pass
217 return u.String(), nil
218}
219
220// Performs the actual sanitization process.
221func (p *Policy) sanitizeWithBuff(r io.Reader) *bytes.Buffer {
222 var buff bytes.Buffer
223 if err := p.sanitize(r, &buff); err != nil {
224 return &bytes.Buffer{}
225 }
226 return &buff
227}
228
229type asStringWriter struct {
230 io.Writer
231}
232
233func (a *asStringWriter) WriteString(s string) (int, error) {
234 return a.Write([]byte(s))
235}
236
237func (p *Policy) sanitize(r io.Reader, w io.Writer) error {
238 // It is possible that the developer has created the policy via:
239 // p := bluemonday.Policy{}
240 // rather than:
241 // p := bluemonday.NewPolicy()
242 // If this is the case, and if they haven't yet triggered an action that
243 // would initialize the maps, then we need to do that.
244 p.init()
245
246 buff, ok := w.(stringWriterWriter)
247 if !ok {
248 buff = &asStringWriter{w}
249 }
250
251 var (
252 skipElementContent bool
253 skippingElementsCount int64
254 skipClosingTag bool
255 closingTagToSkipStack []string
256 mostRecentlyStartedToken string
257 )
258
259 tokenizer := html.NewTokenizer(r)
260 for {
261 if tokenizer.Next() == html.ErrorToken {
262 err := tokenizer.Err()
263 if err == io.EOF {
264 // End of input means end of processing
265 return nil
266 }
267
268 // Raw tokenizer error
269 return err
270 }
271
272 token := tokenizer.Token()
273 switch token.Type {
274 case html.DoctypeToken:
275
276 // DocType is not handled as there is no safe parsing mechanism
277 // provided by golang.org/x/net/html for the content, and this can
278 // be misused to insert HTML tags that are not then sanitized
279 //
280 // One might wish to recursively sanitize here using the same policy
281 // but I will need to do some further testing before considering
282 // this.
283
284 case html.CommentToken:
285
286 // Comments are ignored by default
287 if p.allowComments {
288 // But if allowed then write the comment out as-is
289 buff.WriteString(token.String())
290 }
291
292 case html.StartTagToken:
293
294 mostRecentlyStartedToken = normaliseElementName(token.Data)
295
296 switch normaliseElementName(token.Data) {
297 case `script`:
298 if !p.allowUnsafe {
299 continue
300 }
301 case `style`:
302 if !p.allowUnsafe {
303 continue
304 }
305 }
306
307 aps, ok := p.elsAndAttrs[token.Data]
308 if !ok {
309 aa, matched := p.matchRegex(token.Data)
310 if !matched {
311 if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {
312 skipElementContent = true
313 skippingElementsCount++
314 }
315 if p.addSpaces {
316 if _, err := buff.WriteString(" "); err != nil {
317 return err
318 }
319 }
320 break
321 }
322 aps = aa
323 }
324 if len(token.Attr) != 0 {
325 token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)
326 }
327
328 if len(token.Attr) == 0 {
329 if !p.allowNoAttrs(token.Data) {
330 skipClosingTag = true
331 closingTagToSkipStack = append(closingTagToSkipStack, token.Data)
332 if p.addSpaces {
333 if _, err := buff.WriteString(" "); err != nil {
334 return err
335 }
336 }
337 break
338 }
339 }
340
341 if !skipElementContent {
342 if _, err := buff.WriteString(token.String()); err != nil {
343 return err
344 }
345 }
346
347 case html.EndTagToken:
348
349 if mostRecentlyStartedToken == normaliseElementName(token.Data) {
350 mostRecentlyStartedToken = ""
351 }
352
353 switch normaliseElementName(token.Data) {
354 case `script`:
355 if !p.allowUnsafe {
356 continue
357 }
358 case `style`:
359 if !p.allowUnsafe {
360 continue
361 }
362 }
363
364 if skipClosingTag && closingTagToSkipStack[len(closingTagToSkipStack)-1] == token.Data {
365 closingTagToSkipStack = closingTagToSkipStack[:len(closingTagToSkipStack)-1]
366 if len(closingTagToSkipStack) == 0 {
367 skipClosingTag = false
368 }
369 if p.addSpaces {
370 if _, err := buff.WriteString(" "); err != nil {
371 return err
372 }
373 }
374 break
375 }
376 if _, ok := p.elsAndAttrs[token.Data]; !ok {
377 match := false
378 for regex := range p.elsMatchingAndAttrs {
379 if regex.MatchString(token.Data) {
380 skipElementContent = false
381 match = true
382 break
383 }
384 }
385 if _, ok := p.setOfElementsToSkipContent[token.Data]; ok && !match {
386 skippingElementsCount--
387 if skippingElementsCount == 0 {
388 skipElementContent = false
389 }
390 }
391 if !match {
392 if p.addSpaces {
393 if _, err := buff.WriteString(" "); err != nil {
394 return err
395 }
396 }
397 break
398 }
399 }
400
401 if !skipElementContent {
402 if _, err := buff.WriteString(token.String()); err != nil {
403 return err
404 }
405 }
406
407 case html.SelfClosingTagToken:
408
409 switch normaliseElementName(token.Data) {
410 case `script`:
411 if !p.allowUnsafe {
412 continue
413 }
414 case `style`:
415 if !p.allowUnsafe {
416 continue
417 }
418 }
419
420 aps, ok := p.elsAndAttrs[token.Data]
421 if !ok {
422 aa, matched := p.matchRegex(token.Data)
423 if !matched {
424 if p.addSpaces && !matched {
425 if _, err := buff.WriteString(" "); err != nil {
426 return err
427 }
428 }
429 break
430 }
431 aps = aa
432 }
433
434 if len(token.Attr) != 0 {
435 token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)
436 }
437
438 if len(token.Attr) == 0 && !p.allowNoAttrs(token.Data) {
439 if p.addSpaces {
440 if _, err := buff.WriteString(" "); err != nil {
441 return err
442 }
443 }
444 break
445 }
446 if !skipElementContent {
447 if _, err := buff.WriteString(token.String()); err != nil {
448 return err
449 }
450 }
451
452 case html.TextToken:
453
454 if !skipElementContent {
455 switch mostRecentlyStartedToken {
456 case `script`:
457 // not encouraged, but if a policy allows JavaScript we
458 // should not HTML escape it as that would break the output
459 //
460 // requires p.AllowUnsafe()
461 if p.allowUnsafe {
462 if _, err := buff.WriteString(token.Data); err != nil {
463 return err
464 }
465 }
466 case "style":
467 // not encouraged, but if a policy allows CSS styles we
468 // should not HTML escape it as that would break the output
469 //
470 // requires p.AllowUnsafe()
471 if p.allowUnsafe {
472 if _, err := buff.WriteString(token.Data); err != nil {
473 return err
474 }
475 }
476 default:
477 // HTML escape the text
478 if _, err := buff.WriteString(token.String()); err != nil {
479 return err
480 }
481 }
482 }
483
484 default:
485 // A token that didn't exist in the html package when we wrote this
486 return fmt.Errorf("unknown token: %v", token)
487 }
488 }
489}
490
491// sanitizeAttrs takes a set of element attribute policies and the global
492// attribute policies and applies them to the []html.Attribute returning a set
493// of html.Attributes that match the policies
494func (p *Policy) sanitizeAttrs(
495 elementName string,
496 attrs []html.Attribute,
497 aps map[string][]attrPolicy,
498) []html.Attribute {
499
500 if len(attrs) == 0 {
501 return attrs
502 }
503
504 hasStylePolicies := false
505 sps, elementHasStylePolicies := p.elsAndStyles[elementName]
506 if len(p.globalStyles) > 0 || (elementHasStylePolicies && len(sps) > 0) {
507 hasStylePolicies = true
508 }
509 // no specific element policy found, look for a pattern match
510 if !hasStylePolicies {
511 for k, v := range p.elsMatchingAndStyles {
512 if k.MatchString(elementName) {
513 if len(v) > 0 {
514 hasStylePolicies = true
515 break
516 }
517 }
518 }
519 }
520
521 // Builds a new attribute slice based on the whether the attribute has been
522 // allowed explicitly or globally.
523 cleanAttrs := []html.Attribute{}
524attrsLoop:
525 for _, htmlAttr := range attrs {
526 if p.allowDataAttributes {
527 // If we see a data attribute, let it through.
528 if isDataAttribute(htmlAttr.Key) {
529 cleanAttrs = append(cleanAttrs, htmlAttr)
530 continue
531 }
532 }
533 // Is this a "style" attribute, and if so, do we need to sanitize it?
534 if htmlAttr.Key == "style" && hasStylePolicies {
535 htmlAttr = p.sanitizeStyles(htmlAttr, elementName)
536 if htmlAttr.Val == "" {
537 // We've sanitized away any and all styles; don't bother to
538 // output the style attribute (even if it's allowed)
539 continue
540 } else {
541 cleanAttrs = append(cleanAttrs, htmlAttr)
542 continue
543 }
544 }
545
546 // Is there an element specific attribute policy that applies?
547 if apl, ok := aps[htmlAttr.Key]; ok {
548 for _, ap := range apl {
549 if ap.regexp != nil {
550 if ap.regexp.MatchString(htmlAttr.Val) {
551 cleanAttrs = append(cleanAttrs, htmlAttr)
552 continue attrsLoop
553 }
554 } else {
555 cleanAttrs = append(cleanAttrs, htmlAttr)
556 continue attrsLoop
557 }
558 }
559 }
560
561 // Is there a global attribute policy that applies?
562 if apl, ok := p.globalAttrs[htmlAttr.Key]; ok {
563 for _, ap := range apl {
564 if ap.regexp != nil {
565 if ap.regexp.MatchString(htmlAttr.Val) {
566 cleanAttrs = append(cleanAttrs, htmlAttr)
567 }
568 } else {
569 cleanAttrs = append(cleanAttrs, htmlAttr)
570 }
571 }
572 }
573 }
574
575 if len(cleanAttrs) == 0 {
576 // If nothing was allowed, let's get out of here
577 return cleanAttrs
578 }
579 // cleanAttrs now contains the attributes that are permitted
580
581 if linkable(elementName) {
582 if p.requireParseableURLs {
583 // Ensure URLs are parseable:
584 // - a.href
585 // - area.href
586 // - link.href
587 // - blockquote.cite
588 // - q.cite
589 // - img.src
590 // - script.src
591 tmpAttrs := []html.Attribute{}
592 for _, htmlAttr := range cleanAttrs {
593 switch elementName {
594 case "a", "area", "base", "link":
595 if htmlAttr.Key == "href" {
596 if u, ok := p.validURL(htmlAttr.Val); ok {
597 htmlAttr.Val = u
598 tmpAttrs = append(tmpAttrs, htmlAttr)
599 }
600 break
601 }
602 tmpAttrs = append(tmpAttrs, htmlAttr)
603 case "blockquote", "del", "ins", "q":
604 if htmlAttr.Key == "cite" {
605 if u, ok := p.validURL(htmlAttr.Val); ok {
606 htmlAttr.Val = u
607 tmpAttrs = append(tmpAttrs, htmlAttr)
608 }
609 break
610 }
611 tmpAttrs = append(tmpAttrs, htmlAttr)
612 case "audio", "embed", "iframe", "img", "script", "source", "track", "video":
613 if htmlAttr.Key == "src" {
614 if u, ok := p.validURL(htmlAttr.Val); ok {
615 htmlAttr.Val = u
616 tmpAttrs = append(tmpAttrs, htmlAttr)
617 }
618 break
619 }
620 tmpAttrs = append(tmpAttrs, htmlAttr)
621 default:
622 tmpAttrs = append(tmpAttrs, htmlAttr)
623 }
624 }
625 cleanAttrs = tmpAttrs
626 }
627
628 if (p.requireNoFollow ||
629 p.requireNoFollowFullyQualifiedLinks ||
630 p.requireNoReferrer ||
631 p.requireNoReferrerFullyQualifiedLinks ||
632 p.addTargetBlankToFullyQualifiedLinks) &&
633 len(cleanAttrs) > 0 {
634
635 // Add rel="nofollow" if a "href" exists
636 switch elementName {
637 case "a", "area", "base", "link":
638 var hrefFound bool
639 var externalLink bool
640 for _, htmlAttr := range cleanAttrs {
641 if htmlAttr.Key == "href" {
642 hrefFound = true
643
644 u, err := url.Parse(htmlAttr.Val)
645 if err != nil {
646 continue
647 }
648 if u.Host != "" {
649 externalLink = true
650 }
651
652 continue
653 }
654 }
655
656 if hrefFound {
657 var (
658 noFollowFound bool
659 noReferrerFound bool
660 targetBlankFound bool
661 )
662
663 addNoFollow := (p.requireNoFollow ||
664 externalLink && p.requireNoFollowFullyQualifiedLinks)
665
666 addNoReferrer := (p.requireNoReferrer ||
667 externalLink && p.requireNoReferrerFullyQualifiedLinks)
668
669 addTargetBlank := (externalLink &&
670 p.addTargetBlankToFullyQualifiedLinks)
671
672 tmpAttrs := []html.Attribute{}
673 for _, htmlAttr := range cleanAttrs {
674
675 var appended bool
676 if htmlAttr.Key == "rel" && (addNoFollow || addNoReferrer) {
677
678 if addNoFollow && !strings.Contains(htmlAttr.Val, "nofollow") {
679 htmlAttr.Val += " nofollow"
680 }
681 if addNoReferrer && !strings.Contains(htmlAttr.Val, "noreferrer") {
682 htmlAttr.Val += " noreferrer"
683 }
684 noFollowFound = addNoFollow
685 noReferrerFound = addNoReferrer
686 tmpAttrs = append(tmpAttrs, htmlAttr)
687 appended = true
688 }
689
690 if elementName == "a" && htmlAttr.Key == "target" {
691 if htmlAttr.Val == "_blank" {
692 targetBlankFound = true
693 }
694 if addTargetBlank && !targetBlankFound {
695 htmlAttr.Val = "_blank"
696 targetBlankFound = true
697 tmpAttrs = append(tmpAttrs, htmlAttr)
698 appended = true
699 }
700 }
701
702 if !appended {
703 tmpAttrs = append(tmpAttrs, htmlAttr)
704 }
705 }
706 if noFollowFound || noReferrerFound || targetBlankFound {
707 cleanAttrs = tmpAttrs
708 }
709
710 if (addNoFollow && !noFollowFound) || (addNoReferrer && !noReferrerFound) {
711 rel := html.Attribute{}
712 rel.Key = "rel"
713 if addNoFollow {
714 rel.Val = "nofollow"
715 }
716 if addNoReferrer {
717 if rel.Val != "" {
718 rel.Val += " "
719 }
720 rel.Val += "noreferrer"
721 }
722 cleanAttrs = append(cleanAttrs, rel)
723 }
724
725 if elementName == "a" && addTargetBlank && !targetBlankFound {
726 rel := html.Attribute{}
727 rel.Key = "target"
728 rel.Val = "_blank"
729 targetBlankFound = true
730 cleanAttrs = append(cleanAttrs, rel)
731 }
732
733 if targetBlankFound {
734 // target="_blank" has a security risk that allows the
735 // opened window/tab to issue JavaScript calls against
736 // window.opener, which in effect allow the destination
737 // of the link to control the source:
738 // https://dev.to/ben/the-targetblank-vulnerability-by-example
739 //
740 // To mitigate this risk, we need to add a specific rel
741 // attribute if it is not already present.
742 // rel="noopener"
743 //
744 // Unfortunately this is processing the rel twice (we
745 // already looked at it earlier ^^) as we cannot be sure
746 // of the ordering of the href and rel, and whether we
747 // have fully satisfied that we need to do this. This
748 // double processing only happens *if* target="_blank"
749 // is true.
750 var noOpenerAdded bool
751 tmpAttrs := []html.Attribute{}
752 for _, htmlAttr := range cleanAttrs {
753 var appended bool
754 if htmlAttr.Key == "rel" {
755 if strings.Contains(htmlAttr.Val, "noopener") {
756 noOpenerAdded = true
757 tmpAttrs = append(tmpAttrs, htmlAttr)
758 } else {
759 htmlAttr.Val += " noopener"
760 noOpenerAdded = true
761 tmpAttrs = append(tmpAttrs, htmlAttr)
762 }
763
764 appended = true
765 }
766 if !appended {
767 tmpAttrs = append(tmpAttrs, htmlAttr)
768 }
769 }
770 if noOpenerAdded {
771 cleanAttrs = tmpAttrs
772 } else {
773 // rel attr was not found, or else noopener would
774 // have been added already
775 rel := html.Attribute{}
776 rel.Key = "rel"
777 rel.Val = "noopener"
778 cleanAttrs = append(cleanAttrs, rel)
779 }
780
781 }
782 }
783 default:
784 }
785 }
786 }
787
788 if p.requireCrossOriginAnonymous && len(cleanAttrs) > 0 {
789 switch elementName {
790 case "audio", "img", "link", "script", "video":
791 var crossOriginFound bool
792 for _, htmlAttr := range cleanAttrs {
793 if htmlAttr.Key == "crossorigin" {
794 crossOriginFound = true
795 htmlAttr.Val = "anonymous"
796 }
797 }
798
799 if !crossOriginFound {
800 crossOrigin := html.Attribute{}
801 crossOrigin.Key = "crossorigin"
802 crossOrigin.Val = "anonymous"
803 cleanAttrs = append(cleanAttrs, crossOrigin)
804 }
805 }
806 }
807
808 if p.requireSandboxOnIFrame != nil && elementName == "iframe" {
809 var sandboxFound bool
810 for i, htmlAttr := range cleanAttrs {
811 if htmlAttr.Key == "sandbox" {
812 sandboxFound = true
813 var cleanVals []string
814 cleanValsSet := make(map[string]bool)
815 for _, val := range strings.Fields(htmlAttr.Val) {
816 if p.requireSandboxOnIFrame[val] {
817 if !cleanValsSet[val] {
818 cleanVals = append(cleanVals, val)
819 cleanValsSet[val] = true
820 }
821 }
822 }
823 cleanAttrs[i].Val = strings.Join(cleanVals, " ")
824 }
825 }
826
827 if !sandboxFound {
828 sandbox := html.Attribute{}
829 sandbox.Key = "sandbox"
830 sandbox.Val = ""
831 cleanAttrs = append(cleanAttrs, sandbox)
832 }
833 }
834
835 return cleanAttrs
836}
837
838func (p *Policy) sanitizeStyles(attr html.Attribute, elementName string) html.Attribute {
839 sps := p.elsAndStyles[elementName]
840 if len(sps) == 0 {
841 sps = map[string][]stylePolicy{}
842 // check for any matching elements, if we don't already have a policy found
843 // if multiple matches are found they will be overwritten, it's best
844 // to not have overlapping matchers
845 for regex, policies := range p.elsMatchingAndStyles {
846 if regex.MatchString(elementName) {
847 for k, v := range policies {
848 sps[k] = append(sps[k], v...)
849 }
850 }
851 }
852 }
853
854 //Add semi-colon to end to fix parsing issue
855 attr.Val = strings.TrimRight(attr.Val, " ")
856 if len(attr.Val) > 0 && attr.Val[len(attr.Val)-1] != ';' {
857 attr.Val = attr.Val + ";"
858 }
859 decs, err := parser.ParseDeclarations(attr.Val)
860 if err != nil {
861 attr.Val = ""
862 return attr
863 }
864 clean := []string{}
865 prefixes := []string{"-webkit-", "-moz-", "-ms-", "-o-", "mso-", "-xv-", "-atsc-", "-wap-", "-khtml-", "prince-", "-ah-", "-hp-", "-ro-", "-rim-", "-tc-"}
866
867decLoop:
868 for _, dec := range decs {
869 tempProperty := strings.ToLower(dec.Property)
870 tempValue := removeUnicode(strings.ToLower(dec.Value))
871 for _, i := range prefixes {
872 tempProperty = strings.TrimPrefix(tempProperty, i)
873 }
874 if spl, ok := sps[tempProperty]; ok {
875 for _, sp := range spl {
876 if sp.handler != nil {
877 if sp.handler(tempValue) {
878 clean = append(clean, dec.Property+": "+dec.Value)
879 continue decLoop
880 }
881 } else if len(sp.enum) > 0 {
882 if stringInSlice(tempValue, sp.enum) {
883 clean = append(clean, dec.Property+": "+dec.Value)
884 continue decLoop
885 }
886 } else if sp.regexp != nil {
887 if sp.regexp.MatchString(tempValue) {
888 clean = append(clean, dec.Property+": "+dec.Value)
889 continue decLoop
890 }
891 }
892 }
893 }
894 if spl, ok := p.globalStyles[tempProperty]; ok {
895 for _, sp := range spl {
896 if sp.handler != nil {
897 if sp.handler(tempValue) {
898 clean = append(clean, dec.Property+": "+dec.Value)
899 continue decLoop
900 }
901 } else if len(sp.enum) > 0 {
902 if stringInSlice(tempValue, sp.enum) {
903 clean = append(clean, dec.Property+": "+dec.Value)
904 continue decLoop
905 }
906 } else if sp.regexp != nil {
907 if sp.regexp.MatchString(tempValue) {
908 clean = append(clean, dec.Property+": "+dec.Value)
909 continue decLoop
910 }
911 }
912 }
913 }
914 }
915 if len(clean) > 0 {
916 attr.Val = strings.Join(clean, "; ")
917 } else {
918 attr.Val = ""
919 }
920 return attr
921}
922
923func (p *Policy) allowNoAttrs(elementName string) bool {
924 _, ok := p.setOfElementsAllowedWithoutAttrs[elementName]
925 if !ok {
926 for _, r := range p.setOfElementsMatchingAllowedWithoutAttrs {
927 if r.MatchString(elementName) {
928 ok = true
929 break
930 }
931 }
932 }
933 return ok
934}
935
936func (p *Policy) validURL(rawurl string) (string, bool) {
937 if p.requireParseableURLs {
938 // URLs are valid if when space is trimmed the URL is valid
939 rawurl = strings.TrimSpace(rawurl)
940
941 // URLs cannot contain whitespace, unless it is a data-uri
942 if strings.Contains(rawurl, " ") ||
943 strings.Contains(rawurl, "\t") ||
944 strings.Contains(rawurl, "\n") {
945 if !strings.HasPrefix(rawurl, `data:`) {
946 return "", false
947 }
948
949 // Remove \r and \n from base64 encoded data to pass url.Parse.
950 matched := dataURIbase64Prefix.FindString(rawurl)
951 if matched != "" {
952 rawurl = matched + strings.Replace(
953 strings.Replace(
954 rawurl[len(matched):],
955 "\r",
956 "",
957 -1,
958 ),
959 "\n",
960 "",
961 -1,
962 )
963 }
964 }
965
966 // URLs are valid if they parse
967 u, err := url.Parse(rawurl)
968 if err != nil {
969 return "", false
970 }
971
972 if u.Scheme != "" {
973 for _, r := range p.allowURLSchemeRegexps {
974 if r.MatchString(u.Scheme) {
975 return u.String(), true
976 }
977 }
978
979 urlPolicies, ok := p.allowURLSchemes[u.Scheme]
980 if !ok {
981 return "", false
982 }
983
984 if len(urlPolicies) == 0 {
985 return u.String(), true
986 }
987
988 for _, urlPolicy := range urlPolicies {
989 if urlPolicy(u) == true {
990 return u.String(), true
991 }
992 }
993
994 return "", false
995 }
996
997 if p.allowRelativeURLs {
998 if u.String() != "" {
999 return u.String(), true
1000 }
1001 }
1002
1003 return "", false
1004 }
1005
1006 return rawurl, true
1007}
1008
1009func linkable(elementName string) bool {
1010 switch elementName {
1011 case "a", "area", "base", "link":
1012 // elements that allow .href
1013 return true
1014 case "blockquote", "del", "ins", "q":
1015 // elements that allow .cite
1016 return true
1017 case "audio", "embed", "iframe", "img", "input", "script", "track", "video":
1018 // elements that allow .src
1019 return true
1020 default:
1021 return false
1022 }
1023}
1024
1025// stringInSlice returns true if needle exists in haystack
1026func stringInSlice(needle string, haystack []string) bool {
1027 for _, straw := range haystack {
1028 if strings.ToLower(straw) == strings.ToLower(needle) {
1029 return true
1030 }
1031 }
1032 return false
1033}
1034
1035func isDataAttribute(val string) bool {
1036 if !dataAttribute.MatchString(val) {
1037 return false
1038 }
1039 rest := strings.Split(val, "data-")
1040 if len(rest) == 1 {
1041 return false
1042 }
1043 // data-xml* is invalid.
1044 if dataAttributeXMLPrefix.MatchString(rest[1]) {
1045 return false
1046 }
1047 // no uppercase or semi-colons allowed.
1048 if dataAttributeInvalidChars.MatchString(rest[1]) {
1049 return false
1050 }
1051 return true
1052}
1053
1054func removeUnicode(value string) string {
1055 substitutedValue := value
1056 currentLoc := cssUnicodeChar.FindStringIndex(substitutedValue)
1057 for currentLoc != nil {
1058
1059 character := substitutedValue[currentLoc[0]+1 : currentLoc[1]]
1060 character = strings.TrimSpace(character)
1061 if len(character) < 4 {
1062 character = strings.Repeat("0", 4-len(character)) + character
1063 } else {
1064 for len(character) > 4 {
1065 if character[0] != '0' {
1066 character = ""
1067 break
1068 } else {
1069 character = character[1:]
1070 }
1071 }
1072 }
1073 character = "\\u" + character
1074 translatedChar, err := strconv.Unquote(`"` + character + `"`)
1075 translatedChar = strings.TrimSpace(translatedChar)
1076 if err != nil {
1077 return ""
1078 }
1079 substitutedValue = substitutedValue[0:currentLoc[0]] + translatedChar + substitutedValue[currentLoc[1]:]
1080 currentLoc = cssUnicodeChar.FindStringIndex(substitutedValue)
1081 }
1082 return substitutedValue
1083}
1084
1085func (p *Policy) matchRegex(elementName string) (map[string][]attrPolicy, bool) {
1086 aps := make(map[string][]attrPolicy, 0)
1087 matched := false
1088 for regex, attrs := range p.elsMatchingAndAttrs {
1089 if regex.MatchString(elementName) {
1090 matched = true
1091 for k, v := range attrs {
1092 aps[k] = append(aps[k], v...)
1093 }
1094 }
1095 }
1096 return aps, matched
1097}
1098
1099// normaliseElementName takes a HTML element like <script> which is user input
1100// and returns a lower case version of it that is immune to UTF-8 to ASCII
1101// conversion tricks (like the use of upper case cyrillic i scrİpt which a
1102// strings.ToLower would convert to script). Instead this func will preserve
1103// all non-ASCII as their escaped equivalent, i.e. \u0130 which reveals the
1104// characters when lower cased
1105func normaliseElementName(str string) string {
1106 // that useful QuoteToASCII put quote marks at the start and end
1107 // so those are trimmed off
1108 return strings.TrimSuffix(
1109 strings.TrimPrefix(
1110 strings.ToLower(
1111 strconv.QuoteToASCII(str),
1112 ),
1113 `"`),
1114 `"`,
1115 )
1116}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_go1.12.go b/vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_go1.12.go
new file mode 100644
index 0000000..5d96b97
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_go1.12.go
@@ -0,0 +1,11 @@
1//go:build go1.12
2// +build go1.12
3
4package bluemonday
5
6import "io"
7
8type stringWriterWriter interface {
9 io.Writer
10 io.StringWriter
11}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_ltgo1.12.go b/vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_ltgo1.12.go
new file mode 100644
index 0000000..ecdaa92
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/stringwriterwriter_ltgo1.12.go
@@ -0,0 +1,15 @@
1//go:build go1.1 && !go1.12
2// +build go1.1,!go1.12
3
4package bluemonday
5
6import "io"
7
8type stringWriterWriter interface {
9 io.Writer
10 StringWriter
11}
12
13type StringWriter interface {
14 WriteString(s string) (n int, err error)
15}