From c2311e316c6f7ca109531cb3ad55ada19772800a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Wed, 20 Mar 2024 20:59:09 -0700 Subject: [PATCH] Rename PROXY_* options to MEDIA_PROXY_* --- Makefile | 2 +- internal/api/entry.go | 12 +- internal/config/config_test.go | 229 ++++++++++++++---- internal/config/options.go | 72 +++--- internal/config/parser.go | 43 +++- internal/fever/handler.go | 4 +- internal/googlereader/handler.go | 10 +- .../{proxy => mediaproxy}/media_proxy_test.go | 123 +++++++--- .../media_proxy.go => mediaproxy/rewriter.go} | 28 +-- .../{proxy/proxy.go => mediaproxy/url.go} | 22 +- internal/template/functions.go | 12 +- internal/ui/entry_scraper.go | 4 +- internal/ui/proxy.go | 6 +- miniflux.1 | 50 ++-- 14 files changed, 413 insertions(+), 204 deletions(-) rename internal/{proxy => mediaproxy}/media_proxy_test.go (79%) rename internal/{proxy/media_proxy.go => mediaproxy/rewriter.go} (78%) rename internal/{proxy/proxy.go => mediaproxy/url.go} (60%) diff --git a/Makefile b/Makefile index 2fdb35e6..461d0886 100644 --- a/Makefile +++ b/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 diff --git a/internal/api/entry.go b/internal/api/entry.go index f8d1ce69..6a299a01 100644 --- a/internal/api/entry.go +++ b/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}) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index c907ce34..bcf58da3 100644 --- a/internal/config/config_test.go +++ b/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,25 +1672,73 @@ 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) + } +} - if len(expected) != len(opts.ProxyMediaTypes()) { - t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), 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") + + 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_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) + } +} + +func TestProxyHTTPClientTimeoutOptionForBackwardCompatibility(t *testing.T) { os.Clearenv() os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24") @@ -1601,29 +1747,26 @@ func TestProxyHTTPClientTimeout(t *testing.T) { if err != nil { t.Fatalf(`Parsing failure: %v`, err) } - expected := 24 - result := opts.ProxyHTTPClientTimeout() - + 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) } } diff --git a/internal/config/options.go b/internal/config/options.go index 57696e63..89bff536 100644 --- a/internal/config/options.go +++ b/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, diff --git a/internal/config/parser.go b/internal/config/parser.go index c08abca8..24704710 100644 --- a/internal/config/parser.go +++ b/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) } diff --git a/internal/fever/handler.go b/internal/fever/handler.go index 90a0d5f2..831cbe98 100644 --- a/internal/fever/handler.go +++ b/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, diff --git a/internal/googlereader/handler.go b/internal/googlereader/handler.go index 740b6675..f19ec944 100644 --- a/internal/googlereader/handler.go +++ b/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 } } diff --git a/internal/proxy/media_proxy_test.go b/internal/mediaproxy/media_proxy_test.go similarity index 79% rename from internal/proxy/media_proxy_test.go rename to internal/mediaproxy/media_proxy_test.go index 9b029af4..2006fd6f 100644 --- a/internal/proxy/media_proxy_test.go +++ b/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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - 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 := `

Test

` - 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - output := AbsoluteProxyRewriter(r, "localhost", input) + output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input) expected := `

Test

` 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 := `

Test

` + output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input) + expected := `

Test

` + + 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 := `` + output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input) + expected := `` + + 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithAbsoluteProxyURL(r, "localhost", input) expected := `

Test

` 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

Test

` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) expected := `

Test

` 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 := `

test

` expected := `

test

` - 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 := `

test

` expected := `

test

` - 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 := `` expected := `` - 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 := `` expected := `` - 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 := `` expected := `` - 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 := `` expected := `` - 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 := `` expected := `` - 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 := `` expected := `` - output := ProxyRewriter(r, input) + output := RewriteDocumentWithRelativeProxyURL(r, input) if expected != output { t.Errorf(`Not expected output: got %s`, output) diff --git a/internal/proxy/media_proxy.go b/internal/mediaproxy/rewriter.go similarity index 78% rename from internal/proxy/media_proxy.go rename to internal/mediaproxy/rewriter.go index 33840141..b77be654 100644 --- a/internal/proxy/media_proxy.go +++ b/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 diff --git a/internal/proxy/proxy.go b/internal/mediaproxy/url.go similarity index 60% rename from internal/proxy/proxy.go rename to internal/mediaproxy/url.go index 8bbb59d1..c3a9a953 100644 --- a/internal/proxy/proxy.go +++ b/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 } diff --git a/internal/template/functions.go b/internal/template/functions.go index f8aa2bbd..54e787cf 100644 --- a/internal/template/functions.go +++ b/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, diff --git a/internal/ui/entry_scraper.go b/internal/ui/entry_scraper.go index ad442b16..8eaaffc0 100644 --- a/internal/ui/entry_scraper.go +++ b/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}) } diff --git a/internal/ui/proxy.go b/internal/ui/proxy.go index 110aeb5a..34965275 100644 --- a/internal/ui/proxy.go +++ b/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) diff --git a/miniflux.1 b/miniflux.1 index f211f8d0..0aba7d6b 100644 --- a/miniflux.1 +++ b/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