diff --git a/proxy/image_proxy.go b/proxy/image_proxy.go
new file mode 100644
index 00000000..56109599
--- /dev/null
+++ b/proxy/image_proxy.go
@@ -0,0 +1,80 @@
+// Copyright 2020 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package proxy // import "miniflux.app/proxy"
+
+import (
+ "strings"
+
+ "miniflux.app/config"
+ "miniflux.app/url"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/gorilla/mux"
+)
+
+// ImageProxyRewriter replaces image URLs with internal proxy URLs.
+func ImageProxyRewriter(router *mux.Router, data string) string {
+ proxyImages := config.Opts.ProxyImages()
+ if proxyImages == "none" {
+ return data
+ }
+
+ doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
+ if err != nil {
+ return data
+ }
+
+ doc.Find("img").Each(func(i int, img *goquery.Selection) {
+ if srcAttr, ok := img.Attr("src"); ok {
+ if proxyImages == "all" || !url.IsHTTPS(srcAttr) {
+ img.SetAttr("src", ProxifyURL(router, srcAttr))
+ }
+ }
+
+ if srcsetAttr, ok := img.Attr("srcset"); ok {
+ if proxyImages == "all" || !url.IsHTTPS(srcsetAttr) {
+ proxifySourceSet(img, router, srcsetAttr)
+ }
+ }
+ })
+
+ doc.Find("picture source").Each(func(i int, sourceElement *goquery.Selection) {
+ if srcsetAttr, ok := sourceElement.Attr("srcset"); ok {
+ if proxyImages == "all" || !url.IsHTTPS(srcsetAttr) {
+ proxifySourceSet(sourceElement, router, srcsetAttr)
+ }
+ }
+ })
+
+ output, err := doc.Find("body").First().Html()
+ if err != nil {
+ return data
+ }
+
+ return output
+}
+
+func proxifySourceSet(element *goquery.Selection, router *mux.Router, attributeValue string) {
+ var proxifiedSources []string
+
+ for _, source := range strings.Split(attributeValue, ",") {
+ parts := strings.Split(strings.TrimSpace(source), " ")
+ nbParts := len(parts)
+
+ if nbParts > 0 {
+ source = ProxifyURL(router, parts[0])
+
+ if nbParts > 1 {
+ source += " " + parts[1]
+ }
+
+ proxifiedSources = append(proxifiedSources, source)
+ }
+ }
+
+ if len(proxifiedSources) > 0 {
+ element.SetAttr("srcset", strings.Join(proxifiedSources, ", "))
+ }
+}
diff --git a/proxy/image_proxy_test.go b/proxy/image_proxy_test.go
new file mode 100644
index 00000000..d336a7e5
--- /dev/null
+++ b/proxy/image_proxy_test.go
@@ -0,0 +1,244 @@
+// Copyright 2020 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package proxy // import "miniflux.app/proxy"
+
+import (
+ "net/http"
+ "os"
+ "testing"
+
+ "github.com/gorilla/mux"
+ "miniflux.app/config"
+)
+
+func TestProxyFilterWithHttpDefault(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "http-only")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `

`
+ output := ImageProxyRewriter(r, input)
+ expected := `
`
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpsDefault(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "http-only")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := `
`
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpNever(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "none")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := input
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpsNever(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "none")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := input
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpAlways(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "all")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := `
`
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpsAlways(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "all")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := `
`
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpInvalid(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "invalid")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := `
`
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithHttpsInvalid(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "invalid")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ output := ImageProxyRewriter(r, input)
+ expected := `
`
+
+ if expected != output {
+ t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+ }
+}
+
+func TestProxyFilterWithSrcset(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "all")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := `
`
+ expected := `
`
+ output := ImageProxyRewriter(r, input)
+
+ if expected != output {
+ t.Errorf(`Not expected output: got %s`, output)
+ }
+}
+
+func TestProxyFilterWithPictureSource(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("PROXY_IMAGES", "all")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ r := mux.NewRouter()
+ r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+ input := ``
+ expected := ``
+ output := ImageProxyRewriter(r, input)
+
+ if expected != output {
+ t.Errorf(`Not expected output: got %s`, output)
+ }
+}
diff --git a/proxy/proxy.go b/proxy/proxy.go
new file mode 100644
index 00000000..a6705638
--- /dev/null
+++ b/proxy/proxy.go
@@ -0,0 +1,18 @@
+// Copyright 2020 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package proxy // import "miniflux.app/proxy"
+
+import (
+ "encoding/base64"
+
+ "miniflux.app/http/route"
+
+ "github.com/gorilla/mux"
+)
+
+// ProxifyURL generates an URL for a proxified resource.
+func ProxifyURL(router *mux.Router, link string) string {
+ return route.Path(router, "proxy", "encodedURL", base64.URLEncoding.EncodeToString([]byte(link)))
+}
diff --git a/template/functions.go b/template/functions.go
index 6914d096..fc03e122 100644
--- a/template/functions.go
+++ b/template/functions.go
@@ -5,7 +5,6 @@
package template // import "miniflux.app/template"
import (
- "encoding/base64"
"fmt"
"html/template"
"math"
@@ -18,11 +17,11 @@ import (
"miniflux.app/http/route"
"miniflux.app/locale"
"miniflux.app/model"
+ "miniflux.app/proxy"
"miniflux.app/reader/sanitizer"
"miniflux.app/timezone"
"miniflux.app/url"
- "github.com/PuerkitoBio/goquery"
"github.com/gorilla/mux"
"github.com/rylans/getlang"
)
@@ -58,13 +57,13 @@ func (f *funcMap) Map() template.FuncMap {
return template.HTML(str)
},
"proxyFilter": func(data string) string {
- return imageProxyFilter(f.router, data)
+ return proxy.ImageProxyRewriter(f.router, data)
},
"proxyURL": func(link string) string {
proxyImages := config.Opts.ProxyImages()
if proxyImages == "all" || (proxyImages != "none" && !url.IsHTTPS(link)) {
- return proxify(f.router, link)
+ return proxy.ProxifyURL(f.router, link)
}
return link
@@ -183,71 +182,6 @@ func elapsedTime(printer *locale.Printer, tz string, t time.Time) string {
}
}
-func imageProxyFilter(router *mux.Router, data string) string {
- proxyImages := config.Opts.ProxyImages()
- if proxyImages == "none" {
- return data
- }
-
- doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
- if err != nil {
- return data
- }
-
- doc.Find("img").Each(func(i int, img *goquery.Selection) {
- if srcAttr, ok := img.Attr("src"); ok {
- if proxyImages == "all" || !url.IsHTTPS(srcAttr) {
- img.SetAttr("src", proxify(router, srcAttr))
- }
- }
-
- if srcsetAttr, ok := img.Attr("srcset"); ok {
- if proxyImages == "all" || !url.IsHTTPS(srcsetAttr) {
- proxifySourceSet(img, router, srcsetAttr)
- }
- }
- })
-
- doc.Find("picture source").Each(func(i int, sourceElement *goquery.Selection) {
- if srcsetAttr, ok := sourceElement.Attr("srcset"); ok {
- if proxyImages == "all" || !url.IsHTTPS(srcsetAttr) {
- proxifySourceSet(sourceElement, router, srcsetAttr)
- }
- }
- })
-
- output, _ := doc.Find("body").First().Html()
- return output
-}
-
-func proxifySourceSet(element *goquery.Selection, router *mux.Router, attributeValue string) {
- var proxifiedSources []string
-
- for _, source := range strings.Split(attributeValue, ",") {
- parts := strings.Split(strings.TrimSpace(source), " ")
- nbParts := len(parts)
-
- if nbParts > 0 {
- source = proxify(router, parts[0])
-
- if nbParts > 1 {
- source += " " + parts[1]
- }
-
- proxifiedSources = append(proxifiedSources, source)
- }
- }
-
- if len(proxifiedSources) > 0 {
- element.SetAttr("srcset", strings.Join(proxifiedSources, ", "))
- }
-}
-
-func proxify(router *mux.Router, link string) string {
- // We use base64 url encoding to avoid slash in the URL.
- return route.Path(router, "proxy", "encodedURL", base64.URLEncoding.EncodeToString([]byte(link)))
-}
-
func formatFileSize(b int64) string {
const unit = 1024
if b < unit {
diff --git a/template/functions_test.go b/template/functions_test.go
index 1b327bea..3df35bfb 100644
--- a/template/functions_test.go
+++ b/template/functions_test.go
@@ -5,15 +5,10 @@
package template // import "miniflux.app/template"
import (
- "net/http"
- "os"
"testing"
"time"
- "miniflux.app/config"
"miniflux.app/locale"
-
- "github.com/gorilla/mux"
)
func TestDict(t *testing.T) {
@@ -131,236 +126,6 @@ func TestElapsedTime(t *testing.T) {
}
}
-func TestProxyFilterWithHttpDefault(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "http-only")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := `
`
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpsDefault(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "http-only")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := `
`
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpNever(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "none")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := input
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpsNever(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "none")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := input
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpAlways(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "all")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := `
`
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpsAlways(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "all")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := `
`
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpInvalid(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "invalid")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := `
`
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithHttpsInvalid(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "invalid")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- output := imageProxyFilter(r, input)
- expected := `
`
-
- if expected != output {
- t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
- }
-}
-
-func TestProxyFilterWithSrcset(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "all")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := `
`
- expected := `
`
- output := imageProxyFilter(r, input)
-
- if expected != output {
- t.Errorf(`Not expected output: got %s`, output)
- }
-}
-
-func TestProxyFilterWithPictureSource(t *testing.T) {
- os.Clearenv()
- os.Setenv("PROXY_IMAGES", "all")
-
- var err error
- parser := config.NewParser()
- config.Opts, err = parser.ParseEnvironmentVariables()
- if err != nil {
- t.Fatalf(`Parsing failure: %v`, err)
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
-
- input := ``
- expected := ``
- output := imageProxyFilter(r, input)
-
- if expected != output {
- t.Errorf(`Not expected output: got %s`, output)
- }
-}
-
func TestFormatFileSize(t *testing.T) {
scenarios := []struct {
input int64
diff --git a/ui/entry_scraper.go b/ui/entry_scraper.go
index 0e7904a2..4ece7e82 100644
--- a/ui/entry_scraper.go
+++ b/ui/entry_scraper.go
@@ -10,6 +10,7 @@ import (
"miniflux.app/http/request"
"miniflux.app/http/response/json"
"miniflux.app/model"
+ "miniflux.app/proxy"
"miniflux.app/reader/processor"
)
@@ -37,5 +38,5 @@ func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
h.store.UpdateEntryContent(entry)
- json.OK(w, r, map[string]string{"content": entry.Content})
+ json.OK(w, r, map[string]string{"content": proxy.ImageProxyRewriter(h.router, entry.Content)})
}