Rename PROXY_* options to MEDIA_PROXY_*

This commit is contained in:
Frédéric Guillot 2024-03-20 20:59:09 -07:00
parent ed20771194
commit c2311e316c
14 changed files with 413 additions and 204 deletions

View file

@ -101,7 +101,7 @@ windows-x86:
@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@.exe main.go @ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@.exe main.go
run: 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: clean:
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe @ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe

View file

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

View file

@ -4,6 +4,7 @@
package config // import "miniflux.app/v2/internal/config" package config // import "miniflux.app/v2/internal/config"
import ( import (
"bytes"
"os" "os"
"testing" "testing"
) )
@ -1442,9 +1443,9 @@ func TestPocketConsumerKeyFromUserPrefs(t *testing.T) {
} }
} }
func TestProxyOption(t *testing.T) { func TestMediaProxyMode(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("PROXY_OPTION", "all") os.Setenv("MEDIA_PROXY_MODE", "all")
parser := NewParser() parser := NewParser()
opts, err := parser.ParseEnvironmentVariables() opts, err := parser.ParseEnvironmentVariables()
@ -1453,14 +1454,14 @@ func TestProxyOption(t *testing.T) {
} }
expected := "all" expected := "all"
result := opts.ProxyOption() result := opts.MediaProxyMode()
if result != expected { 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() os.Clearenv()
parser := NewParser() parser := NewParser()
@ -1469,17 +1470,17 @@ func TestDefaultProxyOptionValue(t *testing.T) {
t.Fatalf(`Parsing failure: %v`, err) t.Fatalf(`Parsing failure: %v`, err)
} }
expected := defaultProxyOption expected := defaultMediaProxyMode
result := opts.ProxyOption() result := opts.MediaProxyMode()
if result != expected { 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.Clearenv()
os.Setenv("PROXY_MEDIA_TYPES", "image,audio") os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image,audio")
parser := NewParser() parser := NewParser()
opts, err := parser.ParseEnvironmentVariables() opts, err := parser.ParseEnvironmentVariables()
@ -1489,25 +1490,25 @@ func TestProxyMediaTypes(t *testing.T) {
expected := []string{"audio", "image"} expected := []string{"audio", "image"}
if len(expected) != len(opts.ProxyMediaTypes()) { if len(expected) != len(opts.MediaProxyResourceTypes()) {
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)
} }
resultMap := make(map[string]bool) resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() { for _, mediaType := range opts.MediaProxyResourceTypes() {
resultMap[mediaType] = true resultMap[mediaType] = true
} }
for _, mediaType := range expected { for _, mediaType := range expected {
if !resultMap[mediaType] { 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.Clearenv()
os.Setenv("PROXY_MEDIA_TYPES", "image,audio, image") os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image,audio, image")
parser := NewParser() parser := NewParser()
opts, err := parser.ParseEnvironmentVariables() opts, err := parser.ParseEnvironmentVariables()
@ -1516,23 +1517,119 @@ func TestProxyMediaTypesWithDuplicatedValues(t *testing.T) {
} }
expected := []string{"audio", "image"} expected := []string{"audio", "image"}
if len(expected) != len(opts.ProxyMediaTypes()) { if len(expected) != len(opts.MediaProxyResourceTypes()) {
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)
} }
resultMap := make(map[string]bool) resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() { for _, mediaType := range opts.MediaProxyResourceTypes() {
resultMap[mediaType] = true resultMap[mediaType] = true
} }
for _, mediaType := range expected { for _, mediaType := range expected {
if !resultMap[mediaType] { 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.Clearenv()
os.Setenv("PROXY_IMAGES", "all") os.Setenv("PROXY_IMAGES", "all")
@ -1543,30 +1640,31 @@ func TestProxyImagesOptionBackwardCompatibility(t *testing.T) {
} }
expected := []string{"image"} expected := []string{"image"}
if len(expected) != len(opts.ProxyMediaTypes()) { if len(expected) != len(opts.MediaProxyResourceTypes()) {
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)
} }
resultMap := make(map[string]bool) resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() { for _, mediaType := range opts.MediaProxyResourceTypes() {
resultMap[mediaType] = true resultMap[mediaType] = true
} }
for _, mediaType := range expected { for _, mediaType := range expected {
if !resultMap[mediaType] { 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" expectedProxyOption := "all"
result := opts.ProxyOption() result := opts.MediaProxyMode()
if result != expectedProxyOption { if result != expectedProxyOption {
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, 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.Clearenv()
os.Setenv("PROXY_IMAGE_URL", "http://example.org/proxy")
parser := NewParser() parser := NewParser()
opts, err := parser.ParseEnvironmentVariables() opts, err := parser.ParseEnvironmentVariables()
@ -1574,25 +1672,73 @@ func TestDefaultProxyMediaTypes(t *testing.T) {
t.Fatalf(`Parsing failure: %v`, err) 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()) { func TestProxyURLOptionForBackwardCompatibility(t *testing.T) {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected) 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) resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() { for _, mediaType := range opts.MediaProxyResourceTypes() {
resultMap[mediaType] = true resultMap[mediaType] = true
} }
for _, mediaType := range expected { for _, mediaType := range expected {
if !resultMap[mediaType] { 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.Clearenv()
os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24") os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24")
@ -1601,29 +1747,26 @@ func TestProxyHTTPClientTimeout(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf(`Parsing failure: %v`, err) t.Fatalf(`Parsing failure: %v`, err)
} }
expected := 24 expected := 24
result := opts.ProxyHTTPClientTimeout() result := opts.MediaProxyHTTPClientTimeout()
if result != expected { if result != expected {
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, 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.Clearenv()
os.Setenv("PROXY_PRIVATE_KEY", "foobar")
parser := NewParser() parser := NewParser()
opts, err := parser.ParseEnvironmentVariables() opts, err := parser.ParseEnvironmentVariables()
if err != nil { if err != nil {
t.Fatalf(`Parsing failure: %v`, err) t.Fatalf(`Parsing failure: %v`, err)
} }
expected := []byte("foobar")
expected := defaultProxyHTTPClientTimeout result := opts.MediaProxyPrivateKey()
result := opts.ProxyHTTPClientTimeout() if !bytes.Equal(result, expected) {
t.Fatalf(`Unexpected PROXY_PRIVATE_KEY value, got %q instead of %q`, result, expected)
if result != expected {
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
} }
} }

View file

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

View file

@ -10,6 +10,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log/slog"
"net/url" "net/url"
"os" "os"
"strconv" "strconv"
@ -87,6 +88,7 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.logFormat = parsedValue p.opts.logFormat = parsedValue
} }
case "DEBUG": case "DEBUG":
slog.Warn("The DEBUG environment variable is deprecated, use LOG_LEVEL instead")
parsedValue := parseBool(value, defaultDebug) parsedValue := parseBool(value, defaultDebug)
if parsedValue { if parsedValue {
p.opts.logLevel = "debug" p.opts.logLevel = "debug"
@ -160,20 +162,41 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.schedulerRoundRobinMinInterval = parseInt(value, defaultSchedulerRoundRobinMinInterval) p.opts.schedulerRoundRobinMinInterval = parseInt(value, defaultSchedulerRoundRobinMinInterval)
case "POLLING_PARSING_ERROR_LIMIT": case "POLLING_PARSING_ERROR_LIMIT":
p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit) p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit)
// kept for compatibility purpose
case "PROXY_IMAGES": 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": 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": 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": case "PROXY_MEDIA_TYPES":
p.opts.proxyMediaTypes = parseStringList(value, []string{defaultProxyMediaTypes}) slog.Warn("The PROXY_MEDIA_TYPES environment variable is deprecated, use MEDIA_PROXY_RESOURCE_TYPES instead")
// kept for compatibility purpose p.opts.mediaProxyResourceTypes = parseStringList(value, []string{defaultMediaResourceTypes})
case "MEDIA_PROXY_RESOURCE_TYPES":
p.opts.mediaProxyResourceTypes = parseStringList(value, []string{defaultMediaResourceTypes})
case "PROXY_IMAGE_URL": 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": 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": case "CREATE_ADMIN":
p.opts.createAdmin = parseBool(value, defaultCreateAdmin) p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
case "ADMIN_USERNAME": case "ADMIN_USERNAME":
@ -246,10 +269,6 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.watchdog = parseBool(value, defaultWatchdog) p.opts.watchdog = parseBool(value, defaultWatchdog)
case "INVIDIOUS_INSTANCE": case "INVIDIOUS_INSTANCE":
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance) 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": case "WEBAUTHN":
p.opts.webAuthn = parseBool(value, defaultWebAuthn) p.opts.webAuthn = parseBool(value, defaultWebAuthn)
} }

View file

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

View file

@ -18,8 +18,8 @@ import (
"miniflux.app/v2/internal/http/response/json" "miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/http/route" "miniflux.app/v2/internal/http/route"
"miniflux.app/v2/internal/integration" "miniflux.app/v2/internal/integration"
"miniflux.app/v2/internal/mediaproxy"
"miniflux.app/v2/internal/model" "miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/proxy"
"miniflux.app/v2/internal/reader/fetcher" "miniflux.app/v2/internal/reader/fetcher"
mff "miniflux.app/v2/internal/reader/handler" mff "miniflux.app/v2/internal/reader/handler"
mfs "miniflux.app/v2/internal/reader/subscription" 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) categories = append(categories, userStarred)
} }
entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content) entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, r.Host, entry.Content)
proxyOption := config.Opts.ProxyOption() proxyOption := config.Opts.MediaProxyMode()
for i := range entry.Enclosures { for i := range entry.Enclosures {
if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(entry.Enclosures[i].URL) { 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+"/") { 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 break
} }
} }

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package proxy // import "miniflux.app/v2/internal/proxy" package mediaproxy // import "miniflux.app/v2/internal/mediaproxy"
import ( import (
"net/http" "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") 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>` 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>` expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
if expected != output { 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") 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>` input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
output := ProxyRewriter(r, input) output := RewriteDocumentWithRelativeProxyURL(r, input)
expected := input expected := input
if expected != output { 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") 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>` input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
output := ProxyRewriter(r, input) output := RewriteDocumentWithRelativeProxyURL(r, input)
expected := input expected := input
if expected != output { 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") 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>` 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>` expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="/proxy/LdPNR1GBDigeeNp2ArUQRyZsVqT_PWLfHGjYFrrWWIY=/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="http://localhost/proxy/LdPNR1GBDigeeNp2ArUQRyZsVqT_PWLfHGjYFrrWWIY=/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="https://proxy-example/proxy/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
if expected != output { 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) { func TestAbsoluteProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("PROXY_OPTION", "all") os.Setenv("MEDIA_PROXY_MODE", "all")
os.Setenv("PROXY_MEDIA_TYPES", "image") os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
os.Setenv("PROXY_URL", "https://proxy-example/proxy") os.Setenv("MEDIA_PROXY_CUSTOM_URL", "https://proxy-example/proxy")
var err error var err error
parser := config.NewParser() 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") 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>` 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>` expected := `<p><img src="https://proxy-example/proxy/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
if expected != output { 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") 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>` 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>` expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
if expected != output { 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>` 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>` 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 { if expected != output {
t.Errorf(`Not expected output: got %s`, 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>` input := `<p><img src="http://website/folder/image.png" srcset="" alt="test"></p>`
expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" 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 { if expected != output {
t.Errorf(`Not expected output: got %s`, 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>` 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>` 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 { if expected != output {
t.Errorf(`Not expected output: got %s`, 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>` 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>` 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 { if expected != output {
t.Errorf(`Not expected output: got %s`, 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">` input := `<img src="data:image/gif;base64,test">`
expected := `<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 { if expected != output {
t.Errorf(`Not expected output: got %s`, 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>` input := `<picture><source srcset="data:image/gif;base64,test"/></picture>`
expected := `<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 { if expected != output {
t.Errorf(`Not expected output: got %s`, 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>` 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>` expected := `<video poster="/proxy/aDFfroYL57q5XsojIzATT6OYUCkuVSPXYJQAVrotnLw=/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWcucG5n" src="/proxy/0y3LR8zlx8S8qJkj1qWFOO6x3a-5yf2gLWjGIJV5yyc=/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="></video>`
output := ProxyRewriter(r, input) output := RewriteDocumentWithRelativeProxyURL(r, input)
if expected != output { if expected != output {
t.Errorf(`Not expected output: got %s`, 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>` 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>` expected := `<video poster="/proxy/aDFfroYL57q5XsojIzATT6OYUCkuVSPXYJQAVrotnLw=/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWcucG5n" src="https://example.com/video.mp4"></video>`
output := ProxyRewriter(r, input) output := RewriteDocumentWithRelativeProxyURL(r, input)
if expected != output { if expected != output {
t.Errorf(`Not expected output: got %s`, output) t.Errorf(`Not expected output: got %s`, output)

View file

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

View file

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

View file

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

View file

@ -9,8 +9,8 @@ import (
"miniflux.app/v2/internal/http/request" "miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response/json" "miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/locale" "miniflux.app/v2/internal/locale"
"miniflux.app/v2/internal/mediaproxy"
"miniflux.app/v2/internal/model" "miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/proxy"
"miniflux.app/v2/internal/reader/processor" "miniflux.app/v2/internal/reader/processor"
"miniflux.app/v2/internal/storage" "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) 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})
} }

View file

@ -46,7 +46,7 @@ func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
return return
} }
mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey()) mac := hmac.New(sha256.New, config.Opts.MediaProxyPrivateKey())
mac.Write(decodedURL) mac.Write(decodedURL)
expectedMAC := mac.Sum(nil) expectedMAC := mac.Sum(nil)
@ -99,9 +99,9 @@ func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
clt := &http.Client{ clt := &http.Client{
Transport: &http.Transport{ 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) resp, err := clt.Do(req)

View file

@ -345,6 +345,31 @@ Set to 1 to enable maintenance mode\&.
.br .br
Disabled by default\&. Disabled by default\&.
.TP .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 .B METRICS_ALLOWED_NETWORKS
List of networks allowed to access the metrics endpoint (comma-separated values)\&. List of networks allowed to access the metrics endpoint (comma-separated values)\&.
.br .br
@ -458,31 +483,6 @@ Override LISTEN_ADDR to 0.0.0.0:$PORT\&.
.br .br
Default is empty\&. Default is empty\&.
.TP .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 .B RUN_MIGRATIONS
Set to 1 to run database migrations\&. Set to 1 to run database migrations\&.
.br .br