]> source.dussan.org Git - gitea.git/commitdiff
Update to bluemonday-1.0.6 (#15294)
authorzeripath <art27@cantab.net>
Mon, 5 Apr 2021 21:38:31 +0000 (22:38 +0100)
committerGitHub <noreply@github.com>
Mon, 5 Apr 2021 21:38:31 +0000 (17:38 -0400)
Signed-off-by: Andrew Thornton <art27@cantab.net>
15 files changed:
go.mod
go.sum
modules/markup/sanitizer.go
modules/markup/sanitizer_test.go
vendor/github.com/aymerick/douceur/parser/parser.go [new file with mode: 0644]
vendor/github.com/chris-ramon/douceur/LICENSE [deleted file]
vendor/github.com/chris-ramon/douceur/parser/parser.go [deleted file]
vendor/github.com/microcosm-cc/bluemonday/SECURITY.md [new file with mode: 0644]
vendor/github.com/microcosm-cc/bluemonday/go.mod
vendor/github.com/microcosm-cc/bluemonday/go.sum
vendor/github.com/microcosm-cc/bluemonday/handlers.go
vendor/github.com/microcosm-cc/bluemonday/policy.go
vendor/github.com/microcosm-cc/bluemonday/sanitize.go
vendor/golang.org/x/net/internal/socket/rawconn.go
vendor/modules.txt

diff --git a/go.mod b/go.mod
index 8b1135921c8e0abef28f64882899e0237e8c95fc..af67337179b717712d4ef138aaa950a37402cb15 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -86,7 +86,7 @@ require (
        github.com/mgechev/revive v1.0.3
        github.com/mholt/acmez v0.1.3 // indirect
        github.com/mholt/archiver/v3 v3.5.0
-       github.com/microcosm-cc/bluemonday v1.0.5
+       github.com/microcosm-cc/bluemonday v1.0.6
        github.com/miekg/dns v1.1.40 // indirect
        github.com/minio/md5-simd v1.1.2 // indirect
        github.com/minio/minio-go/v7 v7.0.10
@@ -136,7 +136,7 @@ require (
        go.uber.org/multierr v1.6.0 // indirect
        go.uber.org/zap v1.16.0 // indirect
        golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
-       golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c
+       golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
        golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
        golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
        golang.org/x/text v0.3.5
@@ -153,5 +153,3 @@ require (
 )
 
 replace github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4
-
-replace github.com/microcosm-cc/bluemonday => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8
diff --git a/go.sum b/go.sum
index 9526a811688ad6b0b0a20d23bd128f287725c21e..68a3fe1c4e616304dd0cf82fb6719a505bd204da 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -196,8 +196,6 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
 github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
-github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
-github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -776,8 +774,6 @@ github.com/libdns/libdns v0.2.0 h1:ewg3ByWrdUrxrje8ChPVMBNcotg7H9LQYg+u5De2RzI=
 github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8 h1:1omo92DLtxQu6VwVPSZAmduHaK5zssed6cvkHyl1XOg=
-github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
 github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
 github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
 github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
@@ -834,6 +830,8 @@ github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk=
 github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
 github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
 github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
+github.com/microcosm-cc/bluemonday v1.0.6 h1:ZOvqHKtnx0fUpnbQm3m3zKFWE+DRC+XB1onh8JoEObE=
+github.com/microcosm-cc/bluemonday v1.0.6/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8Lp4pvhp+jLS5ihnI=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
 github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
@@ -1321,8 +1319,9 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c h1:KHUzaHIpjWVlVVNh65G3hhuj3KB1HnjY6Cq5cTvRQT8=
 golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
index 19feaa3cce9a590227f66dedc2323f521c116ad9..9f336d8330d094a35683506f4e4efc2751ef95b9 100644 (file)
@@ -46,7 +46,9 @@ func ReplaceSanitizer() {
        sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
 
        // Custom URL-Schemes
-       sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
+       if len(setting.Markdown.CustomURLSchemes) > 0 {
+               sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
+       }
 
        // Allow keyword markup
        sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^` + keywordClass + `$`)).OnElements("span")
index 63b70166d830f4b5d0ff7ebc5013ba94b62e2a04..9e173015d6611c4262047d9d210f3335a9910589 100644 (file)
@@ -6,6 +6,8 @@
 package markup
 
 import (
+       "html/template"
+       "strings"
        "testing"
 
        "github.com/stretchr/testify/assert"
@@ -50,3 +52,13 @@ func Test_Sanitizer(t *testing.T) {
                assert.Equal(t, testCases[i+1], string(SanitizeBytes([]byte(testCases[i]))))
        }
 }
+
+func TestSanitizeNonEscape(t *testing.T) {
+       descStr := "<scrİpt>&lt;script&gt;alert(document.domain)&lt;/script&gt;</scrİpt>"
+
+       output := template.HTML(Sanitize(string(descStr)))
+       if strings.Contains(string(output), "<script>") {
+               t.Errorf("un-escaped <script> in output: %q", output)
+       }
+
+}
diff --git a/vendor/github.com/aymerick/douceur/parser/parser.go b/vendor/github.com/aymerick/douceur/parser/parser.go
new file mode 100644 (file)
index 0000000..6c4917c
--- /dev/null
@@ -0,0 +1,409 @@
+package parser
+
+import (
+       "errors"
+       "fmt"
+       "regexp"
+       "strings"
+
+       "github.com/gorilla/css/scanner"
+
+       "github.com/aymerick/douceur/css"
+)
+
+const (
+       importantSuffixRegexp = `(?i)\s*!important\s*$`
+)
+
+var (
+       importantRegexp *regexp.Regexp
+)
+
+// Parser represents a CSS parser
+type Parser struct {
+       scan *scanner.Scanner // Tokenizer
+
+       // Tokens parsed but not consumed yet
+       tokens []*scanner.Token
+
+       // Rule embedding level
+       embedLevel int
+}
+
+func init() {
+       importantRegexp = regexp.MustCompile(importantSuffixRegexp)
+}
+
+// NewParser instanciates a new parser
+func NewParser(txt string) *Parser {
+       return &Parser{
+               scan: scanner.New(txt),
+       }
+}
+
+// Parse parses a whole stylesheet
+func Parse(text string) (*css.Stylesheet, error) {
+       result, err := NewParser(text).ParseStylesheet()
+       if err != nil {
+               return nil, err
+       }
+
+       return result, nil
+}
+
+// ParseDeclarations parses CSS declarations
+func ParseDeclarations(text string) ([]*css.Declaration, error) {
+       result, err := NewParser(text).ParseDeclarations()
+       if err != nil {
+               return nil, err
+       }
+
+       return result, nil
+}
+
+// ParseStylesheet parses a stylesheet
+func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
+       result := css.NewStylesheet()
+
+       // Parse BOM
+       if _, err := parser.parseBOM(); err != nil {
+               return result, err
+       }
+
+       // Parse list of rules
+       rules, err := parser.ParseRules()
+       if err != nil {
+               return result, err
+       }
+
+       result.Rules = rules
+
+       return result, nil
+}
+
+// ParseRules parses a list of rules
+func (parser *Parser) ParseRules() ([]*css.Rule, error) {
+       result := []*css.Rule{}
+
+       inBlock := false
+       if parser.tokenChar("{") {
+               // parsing a block of rules
+               inBlock = true
+               parser.embedLevel++
+
+               parser.shiftToken()
+       }
+
+       for parser.tokenParsable() {
+               if parser.tokenIgnorable() {
+                       parser.shiftToken()
+               } else if parser.tokenChar("}") {
+                       if !inBlock {
+                               errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
+                               return result, errors.New(errMsg)
+                       }
+
+                       parser.shiftToken()
+                       parser.embedLevel--
+
+                       // finished
+                       break
+               } else {
+                       rule, err := parser.ParseRule()
+                       if err != nil {
+                               return result, err
+                       }
+
+                       rule.EmbedLevel = parser.embedLevel
+                       result = append(result, rule)
+               }
+       }
+
+       return result, parser.err()
+}
+
+// ParseRule parses a rule
+func (parser *Parser) ParseRule() (*css.Rule, error) {
+       if parser.tokenAtKeyword() {
+               return parser.parseAtRule()
+       }
+
+       return parser.parseQualifiedRule()
+}
+
+// ParseDeclarations parses a list of declarations
+func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
+       result := []*css.Declaration{}
+
+       if parser.tokenChar("{") {
+               parser.shiftToken()
+       }
+
+       for parser.tokenParsable() {
+               if parser.tokenIgnorable() {
+                       parser.shiftToken()
+               } else if parser.tokenChar("}") {
+                       // end of block
+                       parser.shiftToken()
+                       break
+               } else {
+                       declaration, err := parser.ParseDeclaration()
+                       if err != nil {
+                               return result, err
+                       }
+
+                       result = append(result, declaration)
+               }
+       }
+
+       return result, parser.err()
+}
+
+// ParseDeclaration parses a declaration
+func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
+       result := css.NewDeclaration()
+       curValue := ""
+
+       for parser.tokenParsable() {
+               if parser.tokenChar(":") {
+                       result.Property = strings.TrimSpace(curValue)
+                       curValue = ""
+
+                       parser.shiftToken()
+               } else if parser.tokenChar(";") || parser.tokenChar("}") {
+                       if result.Property == "" {
+                               errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
+                               return result, errors.New(errMsg)
+                       }
+
+                       if importantRegexp.MatchString(curValue) {
+                               result.Important = true
+                               curValue = importantRegexp.ReplaceAllString(curValue, "")
+                       }
+
+                       result.Value = strings.TrimSpace(curValue)
+
+                       if parser.tokenChar(";") {
+                               parser.shiftToken()
+                       }
+
+                       // finished
+                       break
+               } else {
+                       token := parser.shiftToken()
+                       curValue += token.Value
+               }
+       }
+
+       // log.Printf("[parsed] Declaration: %s", result.String())
+
+       return result, parser.err()
+}
+
+// Parse an At Rule
+func (parser *Parser) parseAtRule() (*css.Rule, error) {
+       // parse rule name (eg: "@import")
+       token := parser.shiftToken()
+
+       result := css.NewRule(css.AtRule)
+       result.Name = token.Value
+
+       for parser.tokenParsable() {
+               if parser.tokenChar(";") {
+                       parser.shiftToken()
+
+                       // finished
+                       break
+               } else if parser.tokenChar("{") {
+                       if result.EmbedsRules() {
+                               // parse rules block
+                               rules, err := parser.ParseRules()
+                               if err != nil {
+                                       return result, err
+                               }
+
+                               result.Rules = rules
+                       } else {
+                               // parse declarations block
+                               declarations, err := parser.ParseDeclarations()
+                               if err != nil {
+                                       return result, err
+                               }
+
+                               result.Declarations = declarations
+                       }
+
+                       // finished
+                       break
+               } else {
+                       // parse prelude
+                       prelude, err := parser.parsePrelude()
+                       if err != nil {
+                               return result, err
+                       }
+
+                       result.Prelude = prelude
+               }
+       }
+
+       // log.Printf("[parsed] Rule: %s", result.String())
+
+       return result, parser.err()
+}
+
+// Parse a Qualified Rule
+func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
+       result := css.NewRule(css.QualifiedRule)
+
+       for parser.tokenParsable() {
+               if parser.tokenChar("{") {
+                       if result.Prelude == "" {
+                               errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
+                               return result, errors.New(errMsg)
+                       }
+
+                       // parse declarations block
+                       declarations, err := parser.ParseDeclarations()
+                       if err != nil {
+                               return result, err
+                       }
+
+                       result.Declarations = declarations
+
+                       // finished
+                       break
+               } else {
+                       // parse prelude
+                       prelude, err := parser.parsePrelude()
+                       if err != nil {
+                               return result, err
+                       }
+
+                       result.Prelude = prelude
+               }
+       }
+
+       result.Selectors = strings.Split(result.Prelude, ",")
+       for i, sel := range result.Selectors {
+               result.Selectors[i] = strings.TrimSpace(sel)
+       }
+
+       // log.Printf("[parsed] Rule: %s", result.String())
+
+       return result, parser.err()
+}
+
+// Parse Rule prelude
+func (parser *Parser) parsePrelude() (string, error) {
+       result := ""
+
+       for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
+               token := parser.shiftToken()
+               result += token.Value
+       }
+
+       result = strings.TrimSpace(result)
+
+       // log.Printf("[parsed] prelude: %s", result)
+
+       return result, parser.err()
+}
+
+// Parse BOM
+func (parser *Parser) parseBOM() (bool, error) {
+       if parser.nextToken().Type == scanner.TokenBOM {
+               parser.shiftToken()
+               return true, nil
+       }
+
+       return false, parser.err()
+}
+
+// Returns next token without removing it from tokens buffer
+func (parser *Parser) nextToken() *scanner.Token {
+       if len(parser.tokens) == 0 {
+               // fetch next token
+               nextToken := parser.scan.Next()
+
+               // log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
+
+               // queue it
+               parser.tokens = append(parser.tokens, nextToken)
+       }
+
+       return parser.tokens[0]
+}
+
+// Returns next token and remove it from the tokens buffer
+func (parser *Parser) shiftToken() *scanner.Token {
+       var result *scanner.Token
+
+       result, parser.tokens = parser.tokens[0], parser.tokens[1:]
+       return result
+}
+
+// Returns tokenizer error, or nil if no error
+func (parser *Parser) err() error {
+       if parser.tokenError() {
+               token := parser.nextToken()
+               return fmt.Errorf("Tokenizer error: %s", token.String())
+       }
+
+       return nil
+}
+
+// Returns true if next token is Error
+func (parser *Parser) tokenError() bool {
+       return parser.nextToken().Type == scanner.TokenError
+}
+
+// Returns true if next token is EOF
+func (parser *Parser) tokenEOF() bool {
+       return parser.nextToken().Type == scanner.TokenEOF
+}
+
+// Returns true if next token is a whitespace
+func (parser *Parser) tokenWS() bool {
+       return parser.nextToken().Type == scanner.TokenS
+}
+
+// Returns true if next token is a comment
+func (parser *Parser) tokenComment() bool {
+       return parser.nextToken().Type == scanner.TokenComment
+}
+
+// Returns true if next token is a CDO or a CDC
+func (parser *Parser) tokenCDOorCDC() bool {
+       switch parser.nextToken().Type {
+       case scanner.TokenCDO, scanner.TokenCDC:
+               return true
+       default:
+               return false
+       }
+}
+
+// Returns true if next token is ignorable
+func (parser *Parser) tokenIgnorable() bool {
+       return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
+}
+
+// Returns true if next token is parsable
+func (parser *Parser) tokenParsable() bool {
+       return !parser.tokenEOF() && !parser.tokenError()
+}
+
+// Returns true if next token is an At Rule keyword
+func (parser *Parser) tokenAtKeyword() bool {
+       return parser.nextToken().Type == scanner.TokenAtKeyword
+}
+
+// Returns true if next token is given character
+func (parser *Parser) tokenChar(value string) bool {
+       token := parser.nextToken()
+       return (token.Type == scanner.TokenChar) && (token.Value == value)
+}
+
+// Returns true if next token marks the end of a prelude
+func (parser *Parser) tokenEndOfPrelude() bool {
+       return parser.tokenChar(";") || parser.tokenChar("{")
+}
diff --git a/vendor/github.com/chris-ramon/douceur/LICENSE b/vendor/github.com/chris-ramon/douceur/LICENSE
deleted file mode 100644 (file)
index 6ce87cd..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 Aymerick JEHANNE
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
diff --git a/vendor/github.com/chris-ramon/douceur/parser/parser.go b/vendor/github.com/chris-ramon/douceur/parser/parser.go
deleted file mode 100644 (file)
index 6c4917c..0000000
+++ /dev/null
@@ -1,409 +0,0 @@
-package parser
-
-import (
-       "errors"
-       "fmt"
-       "regexp"
-       "strings"
-
-       "github.com/gorilla/css/scanner"
-
-       "github.com/aymerick/douceur/css"
-)
-
-const (
-       importantSuffixRegexp = `(?i)\s*!important\s*$`
-)
-
-var (
-       importantRegexp *regexp.Regexp
-)
-
-// Parser represents a CSS parser
-type Parser struct {
-       scan *scanner.Scanner // Tokenizer
-
-       // Tokens parsed but not consumed yet
-       tokens []*scanner.Token
-
-       // Rule embedding level
-       embedLevel int
-}
-
-func init() {
-       importantRegexp = regexp.MustCompile(importantSuffixRegexp)
-}
-
-// NewParser instanciates a new parser
-func NewParser(txt string) *Parser {
-       return &Parser{
-               scan: scanner.New(txt),
-       }
-}
-
-// Parse parses a whole stylesheet
-func Parse(text string) (*css.Stylesheet, error) {
-       result, err := NewParser(text).ParseStylesheet()
-       if err != nil {
-               return nil, err
-       }
-
-       return result, nil
-}
-
-// ParseDeclarations parses CSS declarations
-func ParseDeclarations(text string) ([]*css.Declaration, error) {
-       result, err := NewParser(text).ParseDeclarations()
-       if err != nil {
-               return nil, err
-       }
-
-       return result, nil
-}
-
-// ParseStylesheet parses a stylesheet
-func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
-       result := css.NewStylesheet()
-
-       // Parse BOM
-       if _, err := parser.parseBOM(); err != nil {
-               return result, err
-       }
-
-       // Parse list of rules
-       rules, err := parser.ParseRules()
-       if err != nil {
-               return result, err
-       }
-
-       result.Rules = rules
-
-       return result, nil
-}
-
-// ParseRules parses a list of rules
-func (parser *Parser) ParseRules() ([]*css.Rule, error) {
-       result := []*css.Rule{}
-
-       inBlock := false
-       if parser.tokenChar("{") {
-               // parsing a block of rules
-               inBlock = true
-               parser.embedLevel++
-
-               parser.shiftToken()
-       }
-
-       for parser.tokenParsable() {
-               if parser.tokenIgnorable() {
-                       parser.shiftToken()
-               } else if parser.tokenChar("}") {
-                       if !inBlock {
-                               errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
-                               return result, errors.New(errMsg)
-                       }
-
-                       parser.shiftToken()
-                       parser.embedLevel--
-
-                       // finished
-                       break
-               } else {
-                       rule, err := parser.ParseRule()
-                       if err != nil {
-                               return result, err
-                       }
-
-                       rule.EmbedLevel = parser.embedLevel
-                       result = append(result, rule)
-               }
-       }
-
-       return result, parser.err()
-}
-
-// ParseRule parses a rule
-func (parser *Parser) ParseRule() (*css.Rule, error) {
-       if parser.tokenAtKeyword() {
-               return parser.parseAtRule()
-       }
-
-       return parser.parseQualifiedRule()
-}
-
-// ParseDeclarations parses a list of declarations
-func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
-       result := []*css.Declaration{}
-
-       if parser.tokenChar("{") {
-               parser.shiftToken()
-       }
-
-       for parser.tokenParsable() {
-               if parser.tokenIgnorable() {
-                       parser.shiftToken()
-               } else if parser.tokenChar("}") {
-                       // end of block
-                       parser.shiftToken()
-                       break
-               } else {
-                       declaration, err := parser.ParseDeclaration()
-                       if err != nil {
-                               return result, err
-                       }
-
-                       result = append(result, declaration)
-               }
-       }
-
-       return result, parser.err()
-}
-
-// ParseDeclaration parses a declaration
-func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
-       result := css.NewDeclaration()
-       curValue := ""
-
-       for parser.tokenParsable() {
-               if parser.tokenChar(":") {
-                       result.Property = strings.TrimSpace(curValue)
-                       curValue = ""
-
-                       parser.shiftToken()
-               } else if parser.tokenChar(";") || parser.tokenChar("}") {
-                       if result.Property == "" {
-                               errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
-                               return result, errors.New(errMsg)
-                       }
-
-                       if importantRegexp.MatchString(curValue) {
-                               result.Important = true
-                               curValue = importantRegexp.ReplaceAllString(curValue, "")
-                       }
-
-                       result.Value = strings.TrimSpace(curValue)
-
-                       if parser.tokenChar(";") {
-                               parser.shiftToken()
-                       }
-
-                       // finished
-                       break
-               } else {
-                       token := parser.shiftToken()
-                       curValue += token.Value
-               }
-       }
-
-       // log.Printf("[parsed] Declaration: %s", result.String())
-
-       return result, parser.err()
-}
-
-// Parse an At Rule
-func (parser *Parser) parseAtRule() (*css.Rule, error) {
-       // parse rule name (eg: "@import")
-       token := parser.shiftToken()
-
-       result := css.NewRule(css.AtRule)
-       result.Name = token.Value
-
-       for parser.tokenParsable() {
-               if parser.tokenChar(";") {
-                       parser.shiftToken()
-
-                       // finished
-                       break
-               } else if parser.tokenChar("{") {
-                       if result.EmbedsRules() {
-                               // parse rules block
-                               rules, err := parser.ParseRules()
-                               if err != nil {
-                                       return result, err
-                               }
-
-                               result.Rules = rules
-                       } else {
-                               // parse declarations block
-                               declarations, err := parser.ParseDeclarations()
-                               if err != nil {
-                                       return result, err
-                               }
-
-                               result.Declarations = declarations
-                       }
-
-                       // finished
-                       break
-               } else {
-                       // parse prelude
-                       prelude, err := parser.parsePrelude()
-                       if err != nil {
-                               return result, err
-                       }
-
-                       result.Prelude = prelude
-               }
-       }
-
-       // log.Printf("[parsed] Rule: %s", result.String())
-
-       return result, parser.err()
-}
-
-// Parse a Qualified Rule
-func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
-       result := css.NewRule(css.QualifiedRule)
-
-       for parser.tokenParsable() {
-               if parser.tokenChar("{") {
-                       if result.Prelude == "" {
-                               errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
-                               return result, errors.New(errMsg)
-                       }
-
-                       // parse declarations block
-                       declarations, err := parser.ParseDeclarations()
-                       if err != nil {
-                               return result, err
-                       }
-
-                       result.Declarations = declarations
-
-                       // finished
-                       break
-               } else {
-                       // parse prelude
-                       prelude, err := parser.parsePrelude()
-                       if err != nil {
-                               return result, err
-                       }
-
-                       result.Prelude = prelude
-               }
-       }
-
-       result.Selectors = strings.Split(result.Prelude, ",")
-       for i, sel := range result.Selectors {
-               result.Selectors[i] = strings.TrimSpace(sel)
-       }
-
-       // log.Printf("[parsed] Rule: %s", result.String())
-
-       return result, parser.err()
-}
-
-// Parse Rule prelude
-func (parser *Parser) parsePrelude() (string, error) {
-       result := ""
-
-       for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
-               token := parser.shiftToken()
-               result += token.Value
-       }
-
-       result = strings.TrimSpace(result)
-
-       // log.Printf("[parsed] prelude: %s", result)
-
-       return result, parser.err()
-}
-
-// Parse BOM
-func (parser *Parser) parseBOM() (bool, error) {
-       if parser.nextToken().Type == scanner.TokenBOM {
-               parser.shiftToken()
-               return true, nil
-       }
-
-       return false, parser.err()
-}
-
-// Returns next token without removing it from tokens buffer
-func (parser *Parser) nextToken() *scanner.Token {
-       if len(parser.tokens) == 0 {
-               // fetch next token
-               nextToken := parser.scan.Next()
-
-               // log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
-
-               // queue it
-               parser.tokens = append(parser.tokens, nextToken)
-       }
-
-       return parser.tokens[0]
-}
-
-// Returns next token and remove it from the tokens buffer
-func (parser *Parser) shiftToken() *scanner.Token {
-       var result *scanner.Token
-
-       result, parser.tokens = parser.tokens[0], parser.tokens[1:]
-       return result
-}
-
-// Returns tokenizer error, or nil if no error
-func (parser *Parser) err() error {
-       if parser.tokenError() {
-               token := parser.nextToken()
-               return fmt.Errorf("Tokenizer error: %s", token.String())
-       }
-
-       return nil
-}
-
-// Returns true if next token is Error
-func (parser *Parser) tokenError() bool {
-       return parser.nextToken().Type == scanner.TokenError
-}
-
-// Returns true if next token is EOF
-func (parser *Parser) tokenEOF() bool {
-       return parser.nextToken().Type == scanner.TokenEOF
-}
-
-// Returns true if next token is a whitespace
-func (parser *Parser) tokenWS() bool {
-       return parser.nextToken().Type == scanner.TokenS
-}
-
-// Returns true if next token is a comment
-func (parser *Parser) tokenComment() bool {
-       return parser.nextToken().Type == scanner.TokenComment
-}
-
-// Returns true if next token is a CDO or a CDC
-func (parser *Parser) tokenCDOorCDC() bool {
-       switch parser.nextToken().Type {
-       case scanner.TokenCDO, scanner.TokenCDC:
-               return true
-       default:
-               return false
-       }
-}
-
-// Returns true if next token is ignorable
-func (parser *Parser) tokenIgnorable() bool {
-       return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
-}
-
-// Returns true if next token is parsable
-func (parser *Parser) tokenParsable() bool {
-       return !parser.tokenEOF() && !parser.tokenError()
-}
-
-// Returns true if next token is an At Rule keyword
-func (parser *Parser) tokenAtKeyword() bool {
-       return parser.nextToken().Type == scanner.TokenAtKeyword
-}
-
-// Returns true if next token is given character
-func (parser *Parser) tokenChar(value string) bool {
-       token := parser.nextToken()
-       return (token.Type == scanner.TokenChar) && (token.Value == value)
-}
-
-// Returns true if next token marks the end of a prelude
-func (parser *Parser) tokenEndOfPrelude() bool {
-       return parser.tokenChar(";") || parser.tokenChar("{")
-}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/SECURITY.md b/vendor/github.com/microcosm-cc/bluemonday/SECURITY.md
new file mode 100644 (file)
index 0000000..a344e7c
--- /dev/null
@@ -0,0 +1,15 @@
+# Security Policy
+
+## Supported Versions
+
+Latest tag and tip are supported.
+
+Older tags remain present but changes result in new tags and are not back ported... please verify any issue against the latest tag and tip.
+
+## Reporting a Vulnerability
+
+Email: <bluemonday@buro9.com>
+
+Bluemonday 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.
+
+The maintainer lives in the United Kingdom and whilst the email is monitored expect a reply or ACK when the maintainer is awake.
index 47b521a75bb77f22bd8641c01bb95b56de76eea3..0ff3d77b036f271f7d31710252ae01e4152f0d92 100644 (file)
@@ -1,10 +1,9 @@
 module github.com/microcosm-cc/bluemonday
 
-go 1.9
+go 1.16
 
 require (
-       github.com/aymerick/douceur v0.2.0 // indirect
-       github.com/chris-ramon/douceur v0.2.0
+       github.com/aymerick/douceur v0.2.0
        github.com/gorilla/css v1.0.0 // indirect
-       golang.org/x/net v0.0.0-20181220203305-927f97764cc3
+       golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c
 )
index 8c34e7a4044d8bea84532fbdc26da09168300b40..7955d9eb02111ecefbd5bf098fa75889580ba18d 100644 (file)
@@ -1,8 +1,11 @@
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
-github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
-github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c h1:KHUzaHIpjWVlVVNh65G3hhuj3KB1HnjY6Cq5cTvRQT8=
+golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
index 1ef4c8acd00c312d6ccaa6bd2a66d9192b846cf6..9753d6e95283ac9e5c9c8c619aa5ed4ed6b6f162 100644 (file)
@@ -26,6 +26,7 @@
 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 package bluemonday
 
 import (
index 739d302c3039b3e3d4da22e93b9ebfeff525f2e5..9c7e662fc218e3cf55230d95375c7aaa08ef64a4 100644 (file)
@@ -69,6 +69,9 @@ type Policy struct {
        // Will skip for href="/foo" or href="foo"
        requireNoReferrerFullyQualifiedLinks bool
 
+       // When true, add crossorigin="anonymous" to HTML audio, img, link, script, and video tags
+       requireCrossOriginAnonymous bool
+
        // When true add target="_blank" to fully qualified links
        // Will add for href="http://foo"
        // Will skip for href="/foo" or href="foo"
@@ -433,24 +436,24 @@ func (spb *stylePolicyBuilder) OnElements(elements ...string) *Policy {
 // and return the updated policy
 func (spb *stylePolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
 
-               for _, attr := range spb.propertyNames {
+       for _, attr := range spb.propertyNames {
 
-                       if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok {
-                               spb.p.elsMatchingAndStyles[regex] = make(map[string]stylePolicy)
-                       }
+               if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok {
+                       spb.p.elsMatchingAndStyles[regex] = make(map[string]stylePolicy)
+               }
 
-                       sp := stylePolicy{}
-                       if spb.handler != nil {
-                               sp.handler = spb.handler
-                       } else if len(spb.enum) > 0 {
-                               sp.enum = spb.enum
-                       } else if spb.regexp != nil {
-                               sp.regexp = spb.regexp
-                       } else {
-                               sp.handler = getDefaultHandler(attr)
-                       }
-                       spb.p.elsMatchingAndStyles[regex][attr] = sp
+               sp := stylePolicy{}
+               if spb.handler != nil {
+                       sp.handler = spb.handler
+               } else if len(spb.enum) > 0 {
+                       sp.enum = spb.enum
+               } else if spb.regexp != nil {
+                       sp.regexp = spb.regexp
+               } else {
+                       sp.handler = getDefaultHandler(attr)
                }
+               spb.p.elsMatchingAndStyles[regex][attr] = sp
+       }
 
        return spb.p
 }
@@ -558,6 +561,16 @@ func (p *Policy) RequireNoReferrerOnFullyQualifiedLinks(require bool) *Policy {
        return p
 }
 
+// RequireCrossOriginAnonymous will result in all audio, img, link, script, and
+// video tags having a crossorigin="anonymous" added to them if one does not
+// already exist
+func (p *Policy) RequireCrossOriginAnonymous(require bool) *Policy {
+
+       p.requireCrossOriginAnonymous = require
+
+       return p
+}
+
 // AddTargetBlankToFullyQualifiedLinks will result in all a, area and link tags
 // that point to a non-local destination (i.e. starts with a protocol and has a
 // host) having a target="_blank" added to them if one does not already exist
index a58333aa65e6e865024d0c8617a2f39ee8f811c1..99559bbabe57f8fb5bfd69f0dfc265d2a6816db6 100644 (file)
@@ -39,7 +39,7 @@ import (
 
        "golang.org/x/net/html"
 
-       cssparser "github.com/chris-ramon/douceur/parser"
+       "github.com/aymerick/douceur/parser"
 )
 
 var (
@@ -286,7 +286,7 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
 
                case html.StartTagToken:
 
-                       mostRecentlyStartedToken = strings.ToLower(token.Data)
+                       mostRecentlyStartedToken = normaliseElementName(token.Data)
 
                        aps, ok := p.elsAndAttrs[token.Data]
                        if !ok {
@@ -329,7 +329,7 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
 
                case html.EndTagToken:
 
-                       if mostRecentlyStartedToken == strings.ToLower(token.Data) {
+                       if mostRecentlyStartedToken == normaliseElementName(token.Data) {
                                mostRecentlyStartedToken = ""
                        }
 
@@ -407,11 +407,11 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
 
                        if !skipElementContent {
                                switch mostRecentlyStartedToken {
-                               case "script":
+                               case `script`:
                                        // not encouraged, but if a policy allows JavaScript we
                                        // should not HTML escape it as that would break the output
                                        buff.WriteString(token.Data)
-                               case "style":
+                               case `style`:
                                        // not encouraged, but if a policy allows CSS styles we
                                        // should not HTML escape it as that would break the output
                                        buff.WriteString(token.Data)
@@ -721,6 +721,26 @@ func (p *Policy) sanitizeAttrs(
                }
        }
 
+       if p.requireCrossOriginAnonymous && len(cleanAttrs) > 0 {
+               switch elementName {
+               case "audio", "img", "link", "script", "video":
+                       var crossOriginFound bool
+                       for _, htmlAttr := range cleanAttrs {
+                               if htmlAttr.Key == "crossorigin" {
+                                       crossOriginFound = true
+                                       htmlAttr.Val = "anonymous"
+                               }
+                       }
+
+                       if !crossOriginFound {
+                               crossOrigin := html.Attribute{}
+                               crossOrigin.Key = "crossorigin"
+                               crossOrigin.Val = "anonymous"
+                               cleanAttrs = append(cleanAttrs, crossOrigin)
+                       }
+               }
+       }
+
        return cleanAttrs
 }
 
@@ -744,7 +764,7 @@ func (p *Policy) sanitizeStyles(attr html.Attribute, elementName string) html.At
        if len(attr.Val) > 0 && attr.Val[len(attr.Val)-1] != ';' {
                attr.Val = attr.Val + ";"
        }
-       decs, err := cssparser.ParseDeclarations(attr.Val)
+       decs, err := parser.ParseDeclarations(attr.Val)
        if err != nil {
                attr.Val = ""
                return attr
@@ -944,3 +964,23 @@ func (p *Policy) matchRegex(elementName string) (map[string]attrPolicy, bool) {
        }
        return aps, matched
 }
+
+
+// normaliseElementName takes a HTML element like <script> which is user input
+// and returns a lower case version of it that is immune to UTF-8 to ASCII
+// conversion tricks (like the use of upper case cyrillic i scrİpt which a
+// strings.ToLower would convert to script). Instead this func will preserve
+// all non-ASCII as their escaped equivalent, i.e. \u0130 which reveals the
+// characters when lower cased
+func normaliseElementName(str string) string {
+       // that useful QuoteToASCII put quote marks at the start and end
+       // so those are trimmed off
+       return strings.TrimSuffix(
+               strings.TrimPrefix(
+                       strings.ToLower(
+                               strconv.QuoteToASCII(str),
+                       ),
+                       `"`),
+               `"`,
+       )
+}
\ No newline at end of file
index b07b8900506b34ed3b8311e76941fd62608991f2..87e81071c1049ac637e610b82cef55bfae741b25 100644 (file)
@@ -17,18 +17,45 @@ type Conn struct {
        c       syscall.RawConn
 }
 
+// tcpConn is an interface implemented by net.TCPConn.
+// It can be used for interface assertions to check if a net.Conn is a TCP connection.
+type tcpConn interface {
+       SyscallConn() (syscall.RawConn, error)
+       SetLinger(int) error
+}
+
+var _ tcpConn = (*net.TCPConn)(nil)
+
+// udpConn is an interface implemented by net.UDPConn.
+// It can be used for interface assertions to check if a net.Conn is a UDP connection.
+type udpConn interface {
+       SyscallConn() (syscall.RawConn, error)
+       ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
+}
+
+var _ udpConn = (*net.UDPConn)(nil)
+
+// ipConn is an interface implemented by net.IPConn.
+// It can be used for interface assertions to check if a net.Conn is an IP connection.
+type ipConn interface {
+       SyscallConn() (syscall.RawConn, error)
+       ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *net.IPAddr, err error)
+}
+
+var _ ipConn = (*net.IPConn)(nil)
+
 // NewConn returns a new raw connection.
 func NewConn(c net.Conn) (*Conn, error) {
        var err error
        var cc Conn
        switch c := c.(type) {
-       case *net.TCPConn:
+       case tcpConn:
                cc.network = "tcp"
                cc.c, err = c.SyscallConn()
-       case *net.UDPConn:
+       case udpConn:
                cc.network = "udp"
                cc.c, err = c.SyscallConn()
-       case *net.IPConn:
+       case ipConn:
                cc.network = "ip"
                cc.c, err = c.SyscallConn()
        default:
index a0111a24a0a559d195ec5cf293846b40f5d35685..e0509e0a28b86db1f7f5e22c0d6431780c1d86da 100644 (file)
@@ -92,6 +92,7 @@ github.com/anmitsu/go-shlex
 github.com/asaskevich/govalidator
 # github.com/aymerick/douceur v0.2.0
 github.com/aymerick/douceur/css
+github.com/aymerick/douceur/parser
 # github.com/beorn7/perks v1.0.1
 github.com/beorn7/perks/quantile
 # github.com/blevesearch/bleve/v2 v2.0.2
@@ -178,8 +179,6 @@ github.com/cespare/xxhash/v2
 # github.com/chi-middleware/proxy v1.1.1
 ## explicit
 github.com/chi-middleware/proxy
-# github.com/chris-ramon/douceur v0.2.0
-github.com/chris-ramon/douceur/parser
 # github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448
 ## explicit
 github.com/couchbase/go-couchbase
@@ -597,7 +596,7 @@ github.com/mholt/acmez/acme
 # github.com/mholt/archiver/v3 v3.5.0
 ## explicit
 github.com/mholt/archiver/v3
-# github.com/microcosm-cc/bluemonday v1.0.5 => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8
+# github.com/microcosm-cc/bluemonday v1.0.6
 ## explicit
 github.com/microcosm-cc/bluemonday
 # github.com/miekg/dns v1.1.40
@@ -891,7 +890,7 @@ golang.org/x/crypto/ssh/knownhosts
 # golang.org/x/mod v0.4.1
 golang.org/x/mod/module
 golang.org/x/mod/semver
-# golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c
+# golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
 ## explicit
 golang.org/x/net/bpf
 golang.org/x/net/context
@@ -1065,4 +1064,3 @@ xorm.io/xorm/names
 xorm.io/xorm/schemas
 xorm.io/xorm/tags
 # github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4
-# github.com/microcosm-cc/bluemonday => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8