Repository: microcosm-cc/bluemonday
Pull request #179 (2023-07-12 07:52:45)

Added src rewriter
Allows for people to rewrite src tags.
Useful to proxify request and anonymize a client's data for example. (How gmail rewrites them)


Patch:
diff --git a/go.mod b/go.mod
index 77565ff..42abd60 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/aymerick/douceur v0.2.0
-	golang.org/x/net v0.12.0
+	golang.org/x/net v0.10.0
 )
 
 require github.com/gorilla/css v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 7aceeab..d459821 100644
--- a/go.sum
+++ b/go.sum
@@ -2,5 +2,5 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 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.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
diff --git a/policy.go b/policy.go
index 995f46c..931ca82 100644
--- a/policy.go
+++ b/policy.go
@@ -121,6 +121,11 @@ type Policy struct {
 	// if one would want to allow all URL schemes, they would add `.+`
 	allowURLSchemeRegexps []*regexp.Regexp
 
+	// If srcRewriter is not nil, it is used to rewrite the src attribute
+	// of tags that download resources, such as <img> and <script>.
+	// It requires that the URL is parsable by "net/url" url.Parse().
+	srcRewriter urlRewriter
+
 	// 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.
 	//
@@ -196,6 +201,8 @@ type stylePolicyBuilder struct {
 
 type urlPolicy func(url *url.URL) (allowUrl bool)
 
+type urlRewriter func(*url.URL)
+
 type SandboxValue int64
 
 const (
@@ -575,6 +582,17 @@ func (p *Policy) AllowURLSchemesMatching(r *regexp.Regexp) *Policy {
 	return p
 }
 
+// RewriteSrc will rewrite the src attribute of a resource downloading tag
+// (e.g. <img>, <script>, <iframe>) using the provided function.
+//
+// For a mail provider, this can be used to proxify requests and prevent
+// the sender from learning the IP address of the recipient and / or
+// whether the recipient has opened the email (and when / how often).
+func (p *Policy) RewriteSrc(fn urlRewriter) *Policy {
+	p.srcRewriter = fn
+	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 9121aef..2b4485e 100644
--- a/sanitize.go
+++ b/sanitize.go
@@ -612,6 +612,14 @@ attrsLoop:
 				case "audio", "embed", "iframe", "img", "script", "source", "track", "video":
 					if htmlAttr.Key == "src" {
 						if u, ok := p.validURL(htmlAttr.Val); ok {
+							if p.srcRewriter != nil {
+								parsedURL, err := url.Parse(u)
+								if err != nil {
+									fmt.Println(err)
+								}
+								p.srcRewriter(parsedURL)
+								u = parsedURL.String()
+							}
 							htmlAttr.Val = u
 							tmpAttrs = append(tmpAttrs, htmlAttr)
 						}


Test patch:
diff --git a/sanitize_test.go b/sanitize_test.go
index 4e3a08f..eb7f094 100644
--- a/sanitize_test.go
+++ b/sanitize_test.go
@@ -150,16 +150,27 @@ func TestLinks(t *testing.T) {
 		},
 		{
 			in:       `<img src="giraffe.gif" />`,
-			expected: `<img src="giraffe.gif"/>`,
+			expected: `<img src="https://proxy.example.com/?u=giraffe.gif"/>`,
 		},
 		{
 			in:       `<img src="giraffe.gif?height=500&amp;width=500&amp;flag" />`,
-			expected: `<img src="giraffe.gif?height=500&amp;width=500&amp;flag"/>`,
+			expected: `<img src="https://proxy.example.com/?u=giraffe.gif?height=500&amp;width=500&amp;flag"/>`,
 		},
 	}
 
 	p := UGCPolicy()
 	p.RequireParseableURLs(true)
+	p.RewriteSrc(func(u *url.URL) {
+		// Proxify all requests to "https://proxy.example.com/?u=http://example.com/"
+		// This is a contrived example, but it shows how to rewrite URLs
+		// to proxy all requests through a single URL.
+
+		url := u.String()
+		u.Scheme = "https"
+		u.Host = "proxy.example.com"
+		u.Path = "/"
+		u.RawQuery = "u=" + url
+	})
 
 	// These tests are run concurrently to enable the race detector to pick up
 	// potential issues