فهرست منبع

Rename PROXY_* options to MEDIA_PROXY_*

Frédéric Guillot 2 سال پیش
والد
کامیت
c2311e316c

+ 1 - 1
Makefile

@@ -101,7 +101,7 @@ windows-x86:
 	@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@.exe main.go
 
 run:
-	@ LOG_DATE_TIME=1 DEBUG=1 RUN_MIGRATIONS=1 CREATE_ADMIN=1 ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go
+	@ LOG_DATE_TIME=1 LOG_LEVEL=debug RUN_MIGRATIONS=1 CREATE_ADMIN=1 ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go
 
 clean:
 	@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe

+ 6 - 6
internal/api/entry.go

@@ -15,8 +15,8 @@ import (
 	"miniflux.app/v2/internal/http/request"
 	"miniflux.app/v2/internal/http/response/json"
 	"miniflux.app/v2/internal/integration"
+	"miniflux.app/v2/internal/mediaproxy"
 	"miniflux.app/v2/internal/model"
-	"miniflux.app/v2/internal/proxy"
 	"miniflux.app/v2/internal/reader/processor"
 	"miniflux.app/v2/internal/reader/readingtime"
 	"miniflux.app/v2/internal/storage"
@@ -36,14 +36,14 @@ func (h *handler) getEntryFromBuilder(w http.ResponseWriter, r *http.Request, b
 		return
 	}
 
-	entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
-	proxyOption := config.Opts.ProxyOption()
+	entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, r.Host, entry.Content)
+	proxyOption := config.Opts.MediaProxyMode()
 
 	for i := range entry.Enclosures {
 		if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(entry.Enclosures[i].URL) {
-			for _, mediaType := range config.Opts.ProxyMediaTypes() {
+			for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
 				if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
-					entry.Enclosures[i].URL = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
+					entry.Enclosures[i].URL = mediaproxy.ProxifyAbsoluteURL(h.router, r.Host, entry.Enclosures[i].URL)
 					break
 				}
 			}
@@ -164,7 +164,7 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
 	}
 
 	for i := range entries {
-		entries[i].Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entries[i].Content)
+		entries[i].Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, r.Host, entries[i].Content)
 	}
 
 	json.OK(w, r, &entriesResponse{Total: count, Entries: entries})

+ 186 - 43
internal/config/config_test.go

@@ -4,6 +4,7 @@
 package config // import "miniflux.app/v2/internal/config"
 
 import (
+	"bytes"
 	"os"
 	"testing"
 )
@@ -1442,9 +1443,9 @@ func TestPocketConsumerKeyFromUserPrefs(t *testing.T) {
 	}
 }
 
-func TestProxyOption(t *testing.T) {
+func TestMediaProxyMode(t *testing.T) {
 	os.Clearenv()
-	os.Setenv("PROXY_OPTION", "all")
+	os.Setenv("MEDIA_PROXY_MODE", "all")
 
 	parser := NewParser()
 	opts, err := parser.ParseEnvironmentVariables()
@@ -1453,14 +1454,14 @@ func TestProxyOption(t *testing.T) {
 	}
 
 	expected := "all"
-	result := opts.ProxyOption()
+	result := opts.MediaProxyMode()
 
 	if result != expected {
-		t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
+		t.Fatalf(`Unexpected MEDIA_PROXY_MODE value, got %q instead of %q`, result, expected)
 	}
 }
 
-func TestDefaultProxyOptionValue(t *testing.T) {
+func TestDefaultMediaProxyModeValue(t *testing.T) {
 	os.Clearenv()
 
 	parser := NewParser()
@@ -1469,17 +1470,17 @@ func TestDefaultProxyOptionValue(t *testing.T) {
 		t.Fatalf(`Parsing failure: %v`, err)
 	}
 
-	expected := defaultProxyOption
-	result := opts.ProxyOption()
+	expected := defaultMediaProxyMode
+	result := opts.MediaProxyMode()
 
 	if result != expected {
-		t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
+		t.Fatalf(`Unexpected MEDIA_PROXY_MODE value, got %q instead of %q`, result, expected)
 	}
 }
 
-func TestProxyMediaTypes(t *testing.T) {
+func TestMediaProxyResourceTypes(t *testing.T) {
 	os.Clearenv()
-	os.Setenv("PROXY_MEDIA_TYPES", "image,audio")
+	os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image,audio")
 
 	parser := NewParser()
 	opts, err := parser.ParseEnvironmentVariables()
@@ -1489,25 +1490,25 @@ func TestProxyMediaTypes(t *testing.T) {
 
 	expected := []string{"audio", "image"}
 
-	if len(expected) != len(opts.ProxyMediaTypes()) {
-		t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+	if len(expected) != len(opts.MediaProxyResourceTypes()) {
+		t.Fatalf(`Unexpected MEDIA_PROXY_RESOURCE_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 	}
 
 	resultMap := make(map[string]bool)
-	for _, mediaType := range opts.ProxyMediaTypes() {
+	for _, mediaType := range opts.MediaProxyResourceTypes() {
 		resultMap[mediaType] = true
 	}
 
 	for _, mediaType := range expected {
 		if !resultMap[mediaType] {
-			t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+			t.Fatalf(`Unexpected MEDIA_PROXY_RESOURCE_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 		}
 	}
 }
 
-func TestProxyMediaTypesWithDuplicatedValues(t *testing.T) {
+func TestMediaProxyResourceTypesWithDuplicatedValues(t *testing.T) {
 	os.Clearenv()
-	os.Setenv("PROXY_MEDIA_TYPES", "image,audio, image")
+	os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image,audio, image")
 
 	parser := NewParser()
 	opts, err := parser.ParseEnvironmentVariables()
@@ -1516,23 +1517,119 @@ func TestProxyMediaTypesWithDuplicatedValues(t *testing.T) {
 	}
 
 	expected := []string{"audio", "image"}
-	if len(expected) != len(opts.ProxyMediaTypes()) {
-		t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+	if len(expected) != len(opts.MediaProxyResourceTypes()) {
+		t.Fatalf(`Unexpected MEDIA_PROXY_RESOURCE_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 	}
 
 	resultMap := make(map[string]bool)
-	for _, mediaType := range opts.ProxyMediaTypes() {
+	for _, mediaType := range opts.MediaProxyResourceTypes() {
 		resultMap[mediaType] = true
 	}
 
 	for _, mediaType := range expected {
 		if !resultMap[mediaType] {
-			t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+			t.Fatalf(`Unexpected MEDIA_PROXY_RESOURCE_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 		}
 	}
 }
 
-func TestProxyImagesOptionBackwardCompatibility(t *testing.T) {
+func TestDefaultMediaProxyResourceTypes(t *testing.T) {
+	os.Clearenv()
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := []string{"image"}
+
+	if len(expected) != len(opts.MediaProxyResourceTypes()) {
+		t.Fatalf(`Unexpected MEDIA_PROXY_RESOURCE_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
+	}
+
+	resultMap := make(map[string]bool)
+	for _, mediaType := range opts.MediaProxyResourceTypes() {
+		resultMap[mediaType] = true
+	}
+
+	for _, mediaType := range expected {
+		if !resultMap[mediaType] {
+			t.Fatalf(`Unexpected MEDIA_PROXY_RESOURCE_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
+		}
+	}
+}
+
+func TestMediaProxyHTTPClientTimeout(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("MEDIA_PROXY_HTTP_CLIENT_TIMEOUT", "24")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := 24
+	result := opts.MediaProxyHTTPClientTimeout()
+
+	if result != expected {
+		t.Fatalf(`Unexpected MEDIA_PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
+	}
+}
+
+func TestDefaultMediaProxyHTTPClientTimeoutValue(t *testing.T) {
+	os.Clearenv()
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := defaultMediaProxyHTTPClientTimeout
+	result := opts.MediaProxyHTTPClientTimeout()
+
+	if result != expected {
+		t.Fatalf(`Unexpected MEDIA_PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
+	}
+}
+
+func TestMediaProxyCustomURL(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("MEDIA_PROXY_CUSTOM_URL", "http://example.org/proxy")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+	expected := "http://example.org/proxy"
+	result := opts.MediaCustomProxyURL()
+	if result != expected {
+		t.Fatalf(`Unexpected MEDIA_PROXY_CUSTOM_URL value, got %q instead of %q`, result, expected)
+	}
+}
+
+func TestMediaProxyPrivateKey(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "foobar")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := []byte("foobar")
+	result := opts.MediaProxyPrivateKey()
+
+	if !bytes.Equal(result, expected) {
+		t.Fatalf(`Unexpected MEDIA_PROXY_PRIVATE_KEY value, got %q instead of %q`, result, expected)
+	}
+}
+
+func TestProxyImagesOptionForBackwardCompatibility(t *testing.T) {
 	os.Clearenv()
 	os.Setenv("PROXY_IMAGES", "all")
 
@@ -1543,30 +1640,31 @@ func TestProxyImagesOptionBackwardCompatibility(t *testing.T) {
 	}
 
 	expected := []string{"image"}
-	if len(expected) != len(opts.ProxyMediaTypes()) {
-		t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+	if len(expected) != len(opts.MediaProxyResourceTypes()) {
+		t.Fatalf(`Unexpected PROXY_IMAGES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 	}
 
 	resultMap := make(map[string]bool)
-	for _, mediaType := range opts.ProxyMediaTypes() {
+	for _, mediaType := range opts.MediaProxyResourceTypes() {
 		resultMap[mediaType] = true
 	}
 
 	for _, mediaType := range expected {
 		if !resultMap[mediaType] {
-			t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+			t.Fatalf(`Unexpected PROXY_IMAGES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 		}
 	}
 
 	expectedProxyOption := "all"
-	result := opts.ProxyOption()
+	result := opts.MediaProxyMode()
 	if result != expectedProxyOption {
 		t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expectedProxyOption)
 	}
 }
 
-func TestDefaultProxyMediaTypes(t *testing.T) {
+func TestProxyImageURLForBackwardCompatibility(t *testing.T) {
 	os.Clearenv()
+	os.Setenv("PROXY_IMAGE_URL", "http://example.org/proxy")
 
 	parser := NewParser()
 	opts, err := parser.ParseEnvironmentVariables()
@@ -1574,56 +1672,101 @@ func TestDefaultProxyMediaTypes(t *testing.T) {
 		t.Fatalf(`Parsing failure: %v`, err)
 	}
 
-	expected := []string{"image"}
+	expected := "http://example.org/proxy"
+	result := opts.MediaCustomProxyURL()
+	if result != expected {
+		t.Fatalf(`Unexpected PROXY_IMAGE_URL value, got %q instead of %q`, result, expected)
+	}
+}
+
+func TestProxyURLOptionForBackwardCompatibility(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("PROXY_URL", "http://example.org/proxy")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := "http://example.org/proxy"
+	result := opts.MediaCustomProxyURL()
+	if result != expected {
+		t.Fatalf(`Unexpected PROXY_URL value, got %q instead of %q`, result, expected)
+	}
+}
+
+func TestProxyMediaTypesOptionForBackwardCompatibility(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("PROXY_MEDIA_TYPES", "image,audio")
 
-	if len(expected) != len(opts.ProxyMediaTypes()) {
-		t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+	expected := []string{"audio", "image"}
+	if len(expected) != len(opts.MediaProxyResourceTypes()) {
+		t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 	}
 
 	resultMap := make(map[string]bool)
-	for _, mediaType := range opts.ProxyMediaTypes() {
+	for _, mediaType := range opts.MediaProxyResourceTypes() {
 		resultMap[mediaType] = true
 	}
 
 	for _, mediaType := range expected {
 		if !resultMap[mediaType] {
-			t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
+			t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.MediaProxyResourceTypes(), expected)
 		}
 	}
 }
 
-func TestProxyHTTPClientTimeout(t *testing.T) {
+func TestProxyOptionForBackwardCompatibility(t *testing.T) {
 	os.Clearenv()
-	os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24")
+	os.Setenv("PROXY_OPTION", "all")
 
 	parser := NewParser()
 	opts, err := parser.ParseEnvironmentVariables()
 	if err != nil {
 		t.Fatalf(`Parsing failure: %v`, err)
 	}
+	expected := "all"
+	result := opts.MediaProxyMode()
+	if result != expected {
+		t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
+	}
+}
 
-	expected := 24
-	result := opts.ProxyHTTPClientTimeout()
+func TestProxyHTTPClientTimeoutOptionForBackwardCompatibility(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24")
 
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+	expected := 24
+	result := opts.MediaProxyHTTPClientTimeout()
 	if result != expected {
 		t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
 	}
 }
 
-func TestDefaultProxyHTTPClientTimeoutValue(t *testing.T) {
+func TestProxyPrivateKeyOptionForBackwardCompatibility(t *testing.T) {
 	os.Clearenv()
+	os.Setenv("PROXY_PRIVATE_KEY", "foobar")
 
 	parser := NewParser()
 	opts, err := parser.ParseEnvironmentVariables()
 	if err != nil {
 		t.Fatalf(`Parsing failure: %v`, err)
 	}
-
-	expected := defaultProxyHTTPClientTimeout
-	result := opts.ProxyHTTPClientTimeout()
-
-	if result != expected {
-		t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
+	expected := []byte("foobar")
+	result := opts.MediaProxyPrivateKey()
+	if !bytes.Equal(result, expected) {
+		t.Fatalf(`Unexpected PROXY_PRIVATE_KEY value, got %q instead of %q`, result, expected)
 	}
 }
 

+ 36 - 36
internal/config/options.go

@@ -51,10 +51,10 @@ const (
 	defaultCleanupArchiveUnreadDays           = 180
 	defaultCleanupArchiveBatchSize            = 10000
 	defaultCleanupRemoveSessionsDays          = 30
-	defaultProxyHTTPClientTimeout             = 120
-	defaultProxyOption                        = "http-only"
-	defaultProxyMediaTypes                    = "image"
-	defaultProxyUrl                           = ""
+	defaultMediaProxyHTTPClientTimeout        = 120
+	defaultMediaProxyMode                     = "http-only"
+	defaultMediaResourceTypes                 = "image"
+	defaultMediaProxyURL                      = ""
 	defaultFilterEntryMaxAgeDays              = 0
 	defaultFetchOdyseeWatchTime               = false
 	defaultFetchYouTubeWatchTime              = false
@@ -136,10 +136,10 @@ type Options struct {
 	createAdmin                        bool
 	adminUsername                      string
 	adminPassword                      string
-	proxyHTTPClientTimeout             int
-	proxyOption                        string
-	proxyMediaTypes                    []string
-	proxyUrl                           string
+	mediaProxyHTTPClientTimeout        int
+	mediaProxyMode                     string
+	mediaProxyResourceTypes            []string
+	mediaProxyCustomURL                string
 	fetchOdyseeWatchTime               bool
 	fetchYouTubeWatchTime              bool
 	filterEntryMaxAgeDays              int
@@ -167,7 +167,7 @@ type Options struct {
 	metricsPassword                    string
 	watchdog                           bool
 	invidiousInstance                  string
-	proxyPrivateKey                    []byte
+	mediaProxyPrivateKey               []byte
 	webAuthn                           bool
 }
 
@@ -211,10 +211,10 @@ func NewOptions() *Options {
 		pollingParsingErrorLimit:           defaultPollingParsingErrorLimit,
 		workerPoolSize:                     defaultWorkerPoolSize,
 		createAdmin:                        defaultCreateAdmin,
-		proxyHTTPClientTimeout:             defaultProxyHTTPClientTimeout,
-		proxyOption:                        defaultProxyOption,
-		proxyMediaTypes:                    []string{defaultProxyMediaTypes},
-		proxyUrl:                           defaultProxyUrl,
+		mediaProxyHTTPClientTimeout:        defaultMediaProxyHTTPClientTimeout,
+		mediaProxyMode:                     defaultMediaProxyMode,
+		mediaProxyResourceTypes:            []string{defaultMediaResourceTypes},
+		mediaProxyCustomURL:                defaultMediaProxyURL,
 		filterEntryMaxAgeDays:              defaultFilterEntryMaxAgeDays,
 		fetchOdyseeWatchTime:               defaultFetchOdyseeWatchTime,
 		fetchYouTubeWatchTime:              defaultFetchYouTubeWatchTime,
@@ -242,7 +242,7 @@ func NewOptions() *Options {
 		metricsPassword:                    defaultMetricsPassword,
 		watchdog:                           defaultWatchdog,
 		invidiousInstance:                  defaultInvidiousInstance,
-		proxyPrivateKey:                    crypto.GenerateRandomBytes(16),
+		mediaProxyPrivateKey:               crypto.GenerateRandomBytes(16),
 		webAuthn:                           defaultWebAuthn,
 	}
 }
@@ -492,24 +492,29 @@ func (o *Options) FetchOdyseeWatchTime() bool {
 	return o.fetchOdyseeWatchTime
 }
 
-// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
-func (o *Options) ProxyOption() string {
-	return o.proxyOption
+// MediaProxyMode returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
+func (o *Options) MediaProxyMode() string {
+	return o.mediaProxyMode
 }
 
-// ProxyMediaTypes returns a slice of media types to proxy.
-func (o *Options) ProxyMediaTypes() []string {
-	return o.proxyMediaTypes
+// MediaProxyResourceTypes returns a slice of resource types to proxy.
+func (o *Options) MediaProxyResourceTypes() []string {
+	return o.mediaProxyResourceTypes
 }
 
-// ProxyUrl returns a string of a URL to use to proxy image requests
-func (o *Options) ProxyUrl() string {
-	return o.proxyUrl
+// MediaCustomProxyURL returns the custom proxy URL for medias.
+func (o *Options) MediaCustomProxyURL() string {
+	return o.mediaProxyCustomURL
 }
 
-// ProxyHTTPClientTimeout returns the time limit in seconds before the proxy HTTP client cancel the request.
-func (o *Options) ProxyHTTPClientTimeout() int {
-	return o.proxyHTTPClientTimeout
+// MediaProxyHTTPClientTimeout returns the time limit in seconds before the proxy HTTP client cancel the request.
+func (o *Options) MediaProxyHTTPClientTimeout() int {
+	return o.mediaProxyHTTPClientTimeout
+}
+
+// MediaProxyPrivateKey returns the private key used by the media proxy.
+func (o *Options) MediaProxyPrivateKey() []byte {
+	return o.mediaProxyPrivateKey
 }
 
 // HasHTTPService returns true if the HTTP service is enabled.
@@ -605,11 +610,6 @@ func (o *Options) InvidiousInstance() string {
 	return o.invidiousInstance
 }
 
-// ProxyPrivateKey returns the private key used by the media proxy
-func (o *Options) ProxyPrivateKey() []byte {
-	return o.proxyPrivateKey
-}
-
 // WebAuthn returns true if WebAuthn logins are supported
 func (o *Options) WebAuthn() bool {
 	return o.webAuthn
@@ -680,11 +680,11 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
 		"FORCE_REFRESH_INTERVAL":                 o.forceRefreshInterval,
 		"POLLING_PARSING_ERROR_LIMIT":            o.pollingParsingErrorLimit,
 		"POLLING_SCHEDULER":                      o.pollingScheduler,
-		"PROXY_HTTP_CLIENT_TIMEOUT":              o.proxyHTTPClientTimeout,
-		"PROXY_MEDIA_TYPES":                      o.proxyMediaTypes,
-		"PROXY_OPTION":                           o.proxyOption,
-		"PROXY_PRIVATE_KEY":                      redactSecretValue(string(o.proxyPrivateKey), redactSecret),
-		"PROXY_URL":                              o.proxyUrl,
+		"MEDIA_PROXY_HTTP_CLIENT_TIMEOUT":        o.mediaProxyHTTPClientTimeout,
+		"MEDIA_PROXY_RESOURCE_TYPES":             o.mediaProxyResourceTypes,
+		"MEDIA_PROXY_MODE":                       o.mediaProxyMode,
+		"MEDIA_PROXY_PRIVATE_KEY":                redactSecretValue(string(o.mediaProxyPrivateKey), redactSecret),
+		"MEDIA_PROXY_CUSTOM_URL":                 o.mediaProxyCustomURL,
 		"ROOT_URL":                               o.rootURL,
 		"RUN_MIGRATIONS":                         o.runMigrations,
 		"SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL": o.schedulerEntryFrequencyMaxInterval,

+ 31 - 12
internal/config/parser.go

@@ -10,6 +10,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"log/slog"
 	"net/url"
 	"os"
 	"strconv"
@@ -87,6 +88,7 @@ func (p *Parser) parseLines(lines []string) (err error) {
 				p.opts.logFormat = parsedValue
 			}
 		case "DEBUG":
+			slog.Warn("The DEBUG environment variable is deprecated, use LOG_LEVEL instead")
 			parsedValue := parseBool(value, defaultDebug)
 			if parsedValue {
 				p.opts.logLevel = "debug"
@@ -160,20 +162,41 @@ func (p *Parser) parseLines(lines []string) (err error) {
 			p.opts.schedulerRoundRobinMinInterval = parseInt(value, defaultSchedulerRoundRobinMinInterval)
 		case "POLLING_PARSING_ERROR_LIMIT":
 			p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit)
-		// kept for compatibility purpose
 		case "PROXY_IMAGES":
-			p.opts.proxyOption = parseString(value, defaultProxyOption)
+			slog.Warn("The PROXY_IMAGES environment variable is deprecated, use MEDIA_PROXY_MODE instead")
+			p.opts.mediaProxyMode = parseString(value, defaultMediaProxyMode)
 		case "PROXY_HTTP_CLIENT_TIMEOUT":
-			p.opts.proxyHTTPClientTimeout = parseInt(value, defaultProxyHTTPClientTimeout)
+			slog.Warn("The PROXY_HTTP_CLIENT_TIMEOUT environment variable is deprecated, use MEDIA_PROXY_HTTP_CLIENT_TIMEOUT instead")
+			p.opts.mediaProxyHTTPClientTimeout = parseInt(value, defaultMediaProxyHTTPClientTimeout)
+		case "MEDIA_PROXY_HTTP_CLIENT_TIMEOUT":
+			p.opts.mediaProxyHTTPClientTimeout = parseInt(value, defaultMediaProxyHTTPClientTimeout)
 		case "PROXY_OPTION":
-			p.opts.proxyOption = parseString(value, defaultProxyOption)
+			slog.Warn("The PROXY_OPTION environment variable is deprecated, use MEDIA_PROXY_MODE instead")
+			p.opts.mediaProxyMode = parseString(value, defaultMediaProxyMode)
+		case "MEDIA_PROXY_MODE":
+			p.opts.mediaProxyMode = parseString(value, defaultMediaProxyMode)
 		case "PROXY_MEDIA_TYPES":
-			p.opts.proxyMediaTypes = parseStringList(value, []string{defaultProxyMediaTypes})
-		// kept for compatibility purpose
+			slog.Warn("The PROXY_MEDIA_TYPES environment variable is deprecated, use MEDIA_PROXY_RESOURCE_TYPES instead")
+			p.opts.mediaProxyResourceTypes = parseStringList(value, []string{defaultMediaResourceTypes})
+		case "MEDIA_PROXY_RESOURCE_TYPES":
+			p.opts.mediaProxyResourceTypes = parseStringList(value, []string{defaultMediaResourceTypes})
 		case "PROXY_IMAGE_URL":
-			p.opts.proxyUrl = parseString(value, defaultProxyUrl)
+			slog.Warn("The PROXY_IMAGE_URL environment variable is deprecated, use MEDIA_PROXY_CUSTOM_URL instead")
+			p.opts.mediaProxyCustomURL = parseString(value, defaultMediaProxyURL)
 		case "PROXY_URL":
-			p.opts.proxyUrl = parseString(value, defaultProxyUrl)
+			slog.Warn("The PROXY_URL environment variable is deprecated, use MEDIA_PROXY_CUSTOM_URL instead")
+			p.opts.mediaProxyCustomURL = parseString(value, defaultMediaProxyURL)
+		case "PROXY_PRIVATE_KEY":
+			slog.Warn("The PROXY_PRIVATE_KEY environment variable is deprecated, use MEDIA_PROXY_PRIVATE_KEY instead")
+			randomKey := make([]byte, 16)
+			rand.Read(randomKey)
+			p.opts.mediaProxyPrivateKey = parseBytes(value, randomKey)
+		case "MEDIA_PROXY_PRIVATE_KEY":
+			randomKey := make([]byte, 16)
+			rand.Read(randomKey)
+			p.opts.mediaProxyPrivateKey = parseBytes(value, randomKey)
+		case "MEDIA_PROXY_CUSTOM_URL":
+			p.opts.mediaProxyCustomURL = parseString(value, defaultMediaProxyURL)
 		case "CREATE_ADMIN":
 			p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
 		case "ADMIN_USERNAME":
@@ -246,10 +269,6 @@ func (p *Parser) parseLines(lines []string) (err error) {
 			p.opts.watchdog = parseBool(value, defaultWatchdog)
 		case "INVIDIOUS_INSTANCE":
 			p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
-		case "PROXY_PRIVATE_KEY":
-			randomKey := make([]byte, 16)
-			rand.Read(randomKey)
-			p.opts.proxyPrivateKey = parseBytes(value, randomKey)
 		case "WEBAUTHN":
 			p.opts.webAuthn = parseBool(value, defaultWebAuthn)
 		}

+ 2 - 2
internal/fever/handler.go

@@ -13,8 +13,8 @@ import (
 	"miniflux.app/v2/internal/http/request"
 	"miniflux.app/v2/internal/http/response/json"
 	"miniflux.app/v2/internal/integration"
+	"miniflux.app/v2/internal/mediaproxy"
 	"miniflux.app/v2/internal/model"
-	"miniflux.app/v2/internal/proxy"
 	"miniflux.app/v2/internal/storage"
 
 	"github.com/gorilla/mux"
@@ -324,7 +324,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
 			FeedID:    entry.FeedID,
 			Title:     entry.Title,
 			Author:    entry.Author,
-			HTML:      proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content),
+			HTML:      mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, r.Host, entry.Content),
 			URL:       entry.URL,
 			IsSaved:   isSaved,
 			IsRead:    isRead,

+ 5 - 5
internal/googlereader/handler.go

@@ -18,8 +18,8 @@ import (
 	"miniflux.app/v2/internal/http/response/json"
 	"miniflux.app/v2/internal/http/route"
 	"miniflux.app/v2/internal/integration"
+	"miniflux.app/v2/internal/mediaproxy"
 	"miniflux.app/v2/internal/model"
-	"miniflux.app/v2/internal/proxy"
 	"miniflux.app/v2/internal/reader/fetcher"
 	mff "miniflux.app/v2/internal/reader/handler"
 	mfs "miniflux.app/v2/internal/reader/subscription"
@@ -1003,14 +1003,14 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
 			categories = append(categories, userStarred)
 		}
 
-		entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
-		proxyOption := config.Opts.ProxyOption()
+		entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, r.Host, entry.Content)
+		proxyOption := config.Opts.MediaProxyMode()
 
 		for i := range entry.Enclosures {
 			if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(entry.Enclosures[i].URL) {
-				for _, mediaType := range config.Opts.ProxyMediaTypes() {
+				for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
 					if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
-						entry.Enclosures[i].URL = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
+						entry.Enclosures[i].URL = mediaproxy.ProxifyAbsoluteURL(h.router, r.Host, entry.Enclosures[i].URL)
 						break
 					}
 				}

+ 87 - 36
internal/proxy/media_proxy_test.go → internal/mediaproxy/media_proxy_test.go

@@ -1,7 +1,7 @@
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
-package proxy // import "miniflux.app/v2/internal/proxy"
+package mediaproxy // import "miniflux.app/v2/internal/mediaproxy"
 
 import (
 	"net/http"
@@ -29,11 +29,11 @@ func TestProxyFilterWithHttpDefault(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -53,11 +53,11 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -76,11 +76,11 @@ func TestProxyFilterWithHttpNever(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := input
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -99,11 +99,11 @@ func TestProxyFilterWithHttpsNever(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := input
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -124,11 +124,11 @@ func TestProxyFilterWithHttpAlways(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -149,11 +149,11 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="/proxy/LdPNR1GBDigeeNp2ArUQRyZsVqT_PWLfHGjYFrrWWIY=/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -174,11 +174,62 @@ func TestAbsoluteProxyFilterWithHttpsAlways(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := AbsoluteProxyRewriter(r, "localhost", input)
+	output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input)
 	expected := `<p><img src="http://localhost/proxy/LdPNR1GBDigeeNp2ArUQRyZsVqT_PWLfHGjYFrrWWIY=/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
+	}
+}
+
+func TestAbsoluteProxyFilterWithHttpsScheme(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("PROXY_OPTION", "all")
+	os.Setenv("PROXY_MEDIA_TYPES", "image")
+	os.Setenv("PROXY_PRIVATE_KEY", "test")
+	os.Setenv("HTTPS", "1")
+
+	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/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
+	output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input)
+	expected := `<p><img src="https://localhost/proxy/LdPNR1GBDigeeNp2ArUQRyZsVqT_PWLfHGjYFrrWWIY=/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
+
+	if expected != output {
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
+	}
+}
+
+func TestAbsoluteProxyFilterWithHttpsAlwaysAndAudioTag(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("PROXY_OPTION", "all")
+	os.Setenv("PROXY_MEDIA_TYPES", "audio")
+	os.Setenv("PROXY_PRIVATE_KEY", "test")
+
+	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/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
+
+	input := `<audio src="https://website/folder/audio.mp3"></audio>`
+	output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input)
+	expected := `<audio src="http://localhost/proxy/EmBTvmU5B17wGuONkeknkptYopW_Tl6Y6_W8oYbN_Xs=/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9hdWRpby5tcDM="></audio>`
+
+	if expected != output {
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -199,11 +250,11 @@ func TestProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="https://proxy-example/proxy/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -224,19 +275,19 @@ func TestProxyFilterWithHttpsAlwaysAndIncorrectCustomProxyServer(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
 func TestAbsoluteProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
 	os.Clearenv()
-	os.Setenv("PROXY_OPTION", "all")
-	os.Setenv("PROXY_MEDIA_TYPES", "image")
-	os.Setenv("PROXY_URL", "https://proxy-example/proxy")
+	os.Setenv("MEDIA_PROXY_MODE", "all")
+	os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
+	os.Setenv("MEDIA_PROXY_CUSTOM_URL", "https://proxy-example/proxy")
 
 	var err error
 	parser := config.NewParser()
@@ -249,11 +300,11 @@ func TestAbsoluteProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input)
 	expected := `<p><img src="https://proxy-example/proxy/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -273,11 +324,11 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -297,11 +348,11 @@ func TestProxyFilterWithHttpsInvalid(t *testing.T) {
 	r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
 
 	input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 	expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
 
 	if expected != output {
-		t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected)
+		t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
 	}
 }
 
@@ -323,7 +374,7 @@ func TestProxyFilterWithSrcset(t *testing.T) {
 
 	input := `<p><img src="http://website/folder/image.png" srcset="http://website/folder/image2.png 656w, http://website/folder/image3.png 360w" alt="test"></p>`
 	expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" srcset="/proxy/aY5Hb4urDnUCly2vTJ7ExQeeaVS-52O7kjUr2v9VrAs=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMi5wbmc= 656w, /proxy/QgAmrJWiAud_nNAsz3F8OTxaIofwAiO36EDzH_YfMzo=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMy5wbmc= 360w" alt="test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -348,7 +399,7 @@ func TestProxyFilterWithEmptySrcset(t *testing.T) {
 
 	input := `<p><img src="http://website/folder/image.png" srcset="" alt="test"></p>`
 	expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" srcset="" alt="test"/></p>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -373,7 +424,7 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
 
 	input := `<picture><source srcset="http://website/folder/image2.png 656w,   http://website/folder/image3.png 360w, https://website/some,image.png 2x"></picture>`
 	expected := `<picture><source srcset="/proxy/aY5Hb4urDnUCly2vTJ7ExQeeaVS-52O7kjUr2v9VrAs=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMi5wbmc= 656w, /proxy/QgAmrJWiAud_nNAsz3F8OTxaIofwAiO36EDzH_YfMzo=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMy5wbmc= 360w, /proxy/ZIw0hv8WhSTls5aSqhnFaCXlUrKIqTnBRaY0-NaLnds=/aHR0cHM6Ly93ZWJzaXRlL3NvbWUsaW1hZ2UucG5n 2x"/></picture>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -398,7 +449,7 @@ func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
 
 	input := `<picture><source srcset="http://website/folder/image2.png 656w, https://website/some,image.png 2x"></picture>`
 	expected := `<picture><source srcset="/proxy/aY5Hb4urDnUCly2vTJ7ExQeeaVS-52O7kjUr2v9VrAs=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMi5wbmc= 656w, https://website/some,image.png 2x"/></picture>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -422,7 +473,7 @@ func TestProxyWithImageDataURL(t *testing.T) {
 
 	input := `<img src="data:image/gif;base64,test">`
 	expected := `<img src="data:image/gif;base64,test"/>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -446,7 +497,7 @@ func TestProxyWithImageSourceDataURL(t *testing.T) {
 
 	input := `<picture><source srcset="data:image/gif;base64,test"/></picture>`
 	expected := `<picture><source srcset="data:image/gif;base64,test"/></picture>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -471,7 +522,7 @@ func TestProxyFilterWithVideo(t *testing.T) {
 
 	input := `<video poster="https://example.com/img.png" src="https://example.com/video.mp4"></video>`
 	expected := `<video poster="/proxy/aDFfroYL57q5XsojIzATT6OYUCkuVSPXYJQAVrotnLw=/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWcucG5n" src="/proxy/0y3LR8zlx8S8qJkj1qWFOO6x3a-5yf2gLWjGIJV5yyc=/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="></video>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)
@@ -496,7 +547,7 @@ func TestProxyFilterVideoPoster(t *testing.T) {
 
 	input := `<video poster="https://example.com/img.png" src="https://example.com/video.mp4"></video>`
 	expected := `<video poster="/proxy/aDFfroYL57q5XsojIzATT6OYUCkuVSPXYJQAVrotnLw=/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWcucG5n" src="https://example.com/video.mp4"></video>`
-	output := ProxyRewriter(r, input)
+	output := RewriteDocumentWithRelativeProxyURL(r, input)
 
 	if expected != output {
 		t.Errorf(`Not expected output: got %s`, output)

+ 13 - 15
internal/proxy/media_proxy.go → internal/mediaproxy/rewriter.go

@@ -1,7 +1,7 @@
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
-package proxy // import "miniflux.app/v2/internal/proxy"
+package mediaproxy // import "miniflux.app/v2/internal/mediaproxy"
 
 import (
 	"strings"
@@ -16,31 +16,29 @@ import (
 
 type urlProxyRewriter func(router *mux.Router, url string) string
 
-// ProxyRewriter replaces media URLs with internal proxy URLs.
-func ProxyRewriter(router *mux.Router, data string) string {
-	return genericProxyRewriter(router, ProxifyURL, data)
+func RewriteDocumentWithRelativeProxyURL(router *mux.Router, htmlDocument string) string {
+	return genericProxyRewriter(router, ProxifyRelativeURL, htmlDocument)
 }
 
-// AbsoluteProxyRewriter do the same as ProxyRewriter except it uses absolute URLs.
-func AbsoluteProxyRewriter(router *mux.Router, host, data string) string {
+func RewriteDocumentWithAbsoluteProxyURL(router *mux.Router, host, htmlDocument string) string {
 	proxifyFunction := func(router *mux.Router, url string) string {
-		return AbsoluteProxifyURL(router, host, url)
+		return ProxifyAbsoluteURL(router, host, url)
 	}
-	return genericProxyRewriter(router, proxifyFunction, data)
+	return genericProxyRewriter(router, proxifyFunction, htmlDocument)
 }
 
-func genericProxyRewriter(router *mux.Router, proxifyFunction urlProxyRewriter, data string) string {
-	proxyOption := config.Opts.ProxyOption()
+func genericProxyRewriter(router *mux.Router, proxifyFunction urlProxyRewriter, htmlDocument string) string {
+	proxyOption := config.Opts.MediaProxyMode()
 	if proxyOption == "none" {
-		return data
+		return htmlDocument
 	}
 
-	doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlDocument))
 	if err != nil {
-		return data
+		return htmlDocument
 	}
 
-	for _, mediaType := range config.Opts.ProxyMediaTypes() {
+	for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
 		switch mediaType {
 		case "image":
 			doc.Find("img, picture source").Each(func(i int, img *goquery.Selection) {
@@ -91,7 +89,7 @@ func genericProxyRewriter(router *mux.Router, proxifyFunction urlProxyRewriter,
 
 	output, err := doc.Find("body").First().Html()
 	if err != nil {
-		return data
+		return htmlDocument
 	}
 
 	return output

+ 10 - 12
internal/proxy/proxy.go → internal/mediaproxy/url.go

@@ -1,7 +1,7 @@
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
-package proxy // import "miniflux.app/v2/internal/proxy"
+package mediaproxy // import "miniflux.app/v2/internal/mediaproxy"
 
 import (
 	"crypto/hmac"
@@ -18,33 +18,31 @@ import (
 	"miniflux.app/v2/internal/config"
 )
 
-// ProxifyURL generates a relative URL for a proxified resource.
-func ProxifyURL(router *mux.Router, mediaURL string) string {
+func ProxifyRelativeURL(router *mux.Router, mediaURL string) string {
 	if mediaURL == "" {
 		return ""
 	}
 
-	if customProxyURL := config.Opts.ProxyUrl(); customProxyURL != "" {
-		return ProxifyURLWithCustomProxy(mediaURL, customProxyURL)
+	if customProxyURL := config.Opts.MediaCustomProxyURL(); customProxyURL != "" {
+		return proxifyURLWithCustomProxy(mediaURL, customProxyURL)
 	}
 
-	mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey())
+	mac := hmac.New(sha256.New, config.Opts.MediaProxyPrivateKey())
 	mac.Write([]byte(mediaURL))
 	digest := mac.Sum(nil)
 	return route.Path(router, "proxy", "encodedDigest", base64.URLEncoding.EncodeToString(digest), "encodedURL", base64.URLEncoding.EncodeToString([]byte(mediaURL)))
 }
 
-// AbsoluteProxifyURL generates an absolute URL for a proxified resource.
-func AbsoluteProxifyURL(router *mux.Router, host, mediaURL string) string {
+func ProxifyAbsoluteURL(router *mux.Router, host, mediaURL string) string {
 	if mediaURL == "" {
 		return ""
 	}
 
-	if customProxyURL := config.Opts.ProxyUrl(); customProxyURL != "" {
-		return ProxifyURLWithCustomProxy(mediaURL, customProxyURL)
+	if customProxyURL := config.Opts.MediaCustomProxyURL(); customProxyURL != "" {
+		return proxifyURLWithCustomProxy(mediaURL, customProxyURL)
 	}
 
-	proxifiedUrl := ProxifyURL(router, mediaURL)
+	proxifiedUrl := ProxifyRelativeURL(router, mediaURL)
 	scheme := "http"
 	if config.Opts.HTTPS {
 		scheme = "https"
@@ -53,7 +51,7 @@ func AbsoluteProxifyURL(router *mux.Router, host, mediaURL string) string {
 	return scheme + "://" + host + proxifiedUrl
 }
 
-func ProxifyURLWithCustomProxy(mediaURL, customProxyURL string) string {
+func proxifyURLWithCustomProxy(mediaURL, customProxyURL string) string {
 	if customProxyURL == "" {
 		return mediaURL
 	}

+ 6 - 6
internal/template/functions.go

@@ -16,8 +16,8 @@ import (
 	"miniflux.app/v2/internal/crypto"
 	"miniflux.app/v2/internal/http/route"
 	"miniflux.app/v2/internal/locale"
+	"miniflux.app/v2/internal/mediaproxy"
 	"miniflux.app/v2/internal/model"
-	"miniflux.app/v2/internal/proxy"
 	"miniflux.app/v2/internal/timezone"
 	"miniflux.app/v2/internal/urllib"
 
@@ -57,19 +57,19 @@ func (f *funcMap) Map() template.FuncMap {
 			return template.HTML(str)
 		},
 		"proxyFilter": func(data string) string {
-			return proxy.ProxyRewriter(f.router, data)
+			return mediaproxy.RewriteDocumentWithRelativeProxyURL(f.router, data)
 		},
 		"proxyURL": func(link string) string {
-			proxyOption := config.Opts.ProxyOption()
+			mediaProxyMode := config.Opts.MediaProxyMode()
 
-			if proxyOption == "all" || (proxyOption != "none" && !urllib.IsHTTPS(link)) {
-				return proxy.ProxifyURL(f.router, link)
+			if mediaProxyMode == "all" || (mediaProxyMode != "none" && !urllib.IsHTTPS(link)) {
+				return mediaproxy.ProxifyRelativeURL(f.router, link)
 			}
 
 			return link
 		},
 		"mustBeProxyfied": func(mediaType string) bool {
-			return slices.Contains(config.Opts.ProxyMediaTypes(), mediaType)
+			return slices.Contains(config.Opts.MediaProxyResourceTypes(), mediaType)
 		},
 		"domain":    urllib.Domain,
 		"hasPrefix": strings.HasPrefix,

+ 2 - 2
internal/ui/entry_scraper.go

@@ -9,8 +9,8 @@ import (
 	"miniflux.app/v2/internal/http/request"
 	"miniflux.app/v2/internal/http/response/json"
 	"miniflux.app/v2/internal/locale"
+	"miniflux.app/v2/internal/mediaproxy"
 	"miniflux.app/v2/internal/model"
-	"miniflux.app/v2/internal/proxy"
 	"miniflux.app/v2/internal/reader/processor"
 	"miniflux.app/v2/internal/storage"
 )
@@ -65,5 +65,5 @@ func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
 
 	readingTime := locale.NewPrinter(user.Language).Plural("entry.estimated_reading_time", entry.ReadingTime, entry.ReadingTime)
 
-	json.OK(w, r, map[string]string{"content": proxy.ProxyRewriter(h.router, entry.Content), "reading_time": readingTime})
+	json.OK(w, r, map[string]string{"content": mediaproxy.RewriteDocumentWithRelativeProxyURL(h.router, entry.Content), "reading_time": readingTime})
 }

+ 3 - 3
internal/ui/proxy.go

@@ -46,7 +46,7 @@ func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey())
+	mac := hmac.New(sha256.New, config.Opts.MediaProxyPrivateKey())
 	mac.Write(decodedURL)
 	expectedMAC := mac.Sum(nil)
 
@@ -99,9 +99,9 @@ func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
 
 	clt := &http.Client{
 		Transport: &http.Transport{
-			IdleConnTimeout: time.Duration(config.Opts.ProxyHTTPClientTimeout()) * time.Second,
+			IdleConnTimeout: time.Duration(config.Opts.MediaProxyHTTPClientTimeout()) * time.Second,
 		},
-		Timeout: time.Duration(config.Opts.ProxyHTTPClientTimeout()) * time.Second,
+		Timeout: time.Duration(config.Opts.MediaProxyHTTPClientTimeout()) * time.Second,
 	}
 
 	resp, err := clt.Do(req)

+ 25 - 25
miniflux.1

@@ -345,6 +345,31 @@ Set to 1 to enable maintenance mode\&.
 .br
 Disabled by default\&.
 .TP
+.B MEDIA_PROXY_CUSTOM_URL
+Sets an external server to proxy media through\&.
+.br
+Default is empty, Miniflux does the proxying\&.
+.TP
+.B MEDIA_PROXY_HTTP_CLIENT_TIMEOUT
+Time limit in seconds before the media proxy HTTP client cancel the request\&.
+.br
+Default is 120 seconds\&.
+.TP
+.B MEDIA_PROXY_RESOURCE_TYPES
+A comma-separated list of media types to proxify. Supported values are: image, audio, video\&.
+.br
+Default is image\&.
+.TP
+.B MEDIA_PROXY_MODE
+Possible values: http-only, all, or none\&.
+.br
+Default is http-only\&.
+.TP
+.B MEDIA_PROXY_PRIVATE_KEY
+Set a custom custom private key used to sign proxified media URLs\&.
+.br
+By default, a secret is randomly generated during startup\&.
+.TP
 .B METRICS_ALLOWED_NETWORKS
 List of networks allowed to access the metrics endpoint (comma-separated values)\&.
 .br
@@ -458,31 +483,6 @@ Override LISTEN_ADDR to 0.0.0.0:$PORT\&.
 .br
 Default is empty\&.
 .TP
-.B PROXY_HTTP_CLIENT_TIMEOUT
-Time limit in seconds before the proxy HTTP client cancel the request\&.
-.br
-Default is 120 seconds\&.
-.TP
-.B PROXY_MEDIA_TYPES
-A list of media types to proxify (comma-separated values): image, audio, video\&.
-.br
-Default is image only\&.
-.TP
-.B PROXY_OPTION
-Avoids mixed content warnings for external media: http-only, all, or none\&.
-.br
-Default is http-only\&.
-.TP
-.B PROXY_PRIVATE_KEY
-Set a custom custom private key used to sign proxified media URL\&.
-.br
-Default is randomly generated at startup\&.
-.TP
-.B PROXY_URL
-Sets a server to proxy media through\&.
-.br
-Default is empty, miniflux does the proxying\&.
-.TP
 .B RUN_MIGRATIONS
 Set to 1 to run database migrations\&.
 .br