Repository: microcosm-cc/bluemonday (go)

Issue:
Is there a way to allow all URL schemes?
Basically something like `AllowURLSchemeWithCustomPolicy` but for all schemes.

References:
- https://github.com/go-gitea/gitea/issues/21146
- https://github.com/go-gitea/gitea/issues/16721

Pull request description:
Allow custom URL schemes by matching regex
This allows a bit more flexibility when we want to allow an unknown number of URL schemes (usually all parsable ones).

- Closes #174

Patch:
diff --git a/policy.go b/policy.go
index c446fad..995f46c 100644
--- a/policy.go
+++ b/policy.go
@@ -117,6 +117,10 @@ type Policy struct {
 	// returning true are allowed.
 	allowURLSchemes map[string][]urlPolicy
 
+	// These regexps are used to match allowed URL schemes, for example
+	// if one would want to allow all URL schemes, they would add `.+`
+	allowURLSchemeRegexps []*regexp.Regexp
+
 	// If an element has had all attributes removed as a result of a policy
 	// being applied, then the element would be removed from the output.
 	//
@@ -221,6 +225,7 @@ func (p *Policy) init() {
 		p.elsMatchingAndStyles = make(map[*regexp.Regexp]map[string][]stylePolicy)
 		p.globalStyles = make(map[string][]stylePolicy)
 		p.allowURLSchemes = make(map[string][]urlPolicy)
+		p.allowURLSchemeRegexps = make([]*regexp.Regexp, 0)
 		p.setOfElementsAllowedWithoutAttrs = make(map[string]struct{})
 		p.setOfElementsToSkipContent = make(map[string]struct{})
 		p.initialized = true
@@ -563,6 +568,13 @@ func (p *Policy) AllowElementsMatching(regex *regexp.Regexp) *Policy {
 	return p
 }
 
+// AllowURLSchemesMatching will append URL schemes to the allowlist if they
+// match a regexp.
+func (p *Policy) AllowURLSchemesMatching(r *regexp.Regexp) *Policy {
+	p.allowURLSchemeRegexps = append(p.allowURLSchemeRegexps, r)
+	return p
+}
+
 // RequireNoFollowOnLinks will result in all a, area, link tags having a
 // rel="nofollow"added to them if one does not already exist
 //
diff --git a/sanitize.go b/sanitize.go
index ff29d2c..9121aef 100644
--- a/sanitize.go
+++ b/sanitize.go
@@ -970,6 +970,11 @@ func (p *Policy) validURL(rawurl string) (string, bool) {
 		}
 
 		if u.Scheme != "" {
+			for _, r := range p.allowURLSchemeRegexps {
+				if r.MatchString(u.Scheme) {
+					return u.String(), true
+				}
+			}
 
 			urlPolicies, ok := p.allowURLSchemes[u.Scheme]
 			if !ok {


Test patch:
diff --git a/sanitize_test.go b/sanitize_test.go
index 4db6b9c..4e3a08f 100644
--- a/sanitize_test.go
+++ b/sanitize_test.go
@@ -3985,3 +3985,26 @@ func TestIssue171(t *testing.T) {
 			expected)
 	}
 }
+
+func TestIssue174(t *testing.T) {
+	// https://github.com/microcosm-cc/bluemonday/issues/174
+	//
+	// Allow all URL schemes
+	p := UGCPolicy()
+	p.AllowURLSchemesMatching(regexp.MustCompile(`.+`))
+
+	input := `<a href="cbthunderlink://somebase64string"></a>
+<a href="matrix:roomid/psumPMeAfzgAeQpXMG:feneas.org?action=join"></a>
+<a href="https://github.com"></a>`
+	out := p.Sanitize(input)
+	expected := `<a href="cbthunderlink://somebase64string" rel="nofollow"></a>
+<a href="matrix:roomid/psumPMeAfzgAeQpXMG:feneas.org?action=join" rel="nofollow"></a>
+<a href="https://github.com" rel="nofollow"></a>`
+	if out != expected {
+		t.Errorf(
+			"test failed;\ninput   : %s\noutput  : %s\nexpected: %s",
+			input,
+			out,
+			expected)
+	}
+}