parent
8f9ccc6540
commit
2c2700a31d
20 changed files with 534 additions and 200 deletions
13
api/entry.go
13
api/entry.go
|
@ -35,12 +35,17 @@ func (h *handler) getEntryFromBuilder(w http.ResponseWriter, r *http.Request, b
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Content = proxy.AbsoluteImageProxyRewriter(h.router, r.Host, entry.Content)
|
entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
|
||||||
proxyImage := config.Opts.ProxyImages()
|
proxyOption := config.Opts.ProxyOption()
|
||||||
|
|
||||||
for i := range entry.Enclosures {
|
for i := range entry.Enclosures {
|
||||||
if strings.HasPrefix(entry.Enclosures[i].MimeType, "image/") && (proxyImage == "all" || proxyImage != "none" && !url.IsHTTPS(entry.Enclosures[i].URL)) {
|
if proxyOption == "all" || proxyOption != "none" && !url.IsHTTPS(entry.Enclosures[i].URL) {
|
||||||
|
for _, mediaType := range config.Opts.ProxyMediaTypes() {
|
||||||
|
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 = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +163,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.AbsoluteImageProxyRewriter(h.router, r.Host, entries[i].Content)
|
entries[i].Content = proxy.AbsoluteProxyRewriter(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})
|
||||||
|
|
|
@ -1163,9 +1163,9 @@ func TestPocketConsumerKeyFromUserPrefs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyImages(t *testing.T) {
|
func TestProxyOption(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewParser()
|
||||||
opts, err := parser.ParseEnvironmentVariables()
|
opts, err := parser.ParseEnvironmentVariables()
|
||||||
|
@ -1174,14 +1174,14 @@ func TestProxyImages(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := "all"
|
expected := "all"
|
||||||
result := opts.ProxyImages()
|
result := opts.ProxyOption()
|
||||||
|
|
||||||
if result != expected {
|
if result != expected {
|
||||||
t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected)
|
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultProxyImagesValue(t *testing.T) {
|
func TestDefaultProxyOptionValue(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewParser()
|
||||||
|
@ -1190,11 +1190,101 @@ func TestDefaultProxyImagesValue(t *testing.T) {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := defaultProxyImages
|
expected := defaultProxyOption
|
||||||
result := opts.ProxyImages()
|
result := opts.ProxyOption()
|
||||||
|
|
||||||
if result != expected {
|
if result != expected {
|
||||||
t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected)
|
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyMediaTypes(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.ProxyMediaTypes()) {
|
||||||
|
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMap := make(map[string]bool)
|
||||||
|
for _, mediaType := range opts.ProxyMediaTypes() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultProxyMediaTypes(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.ProxyMediaTypes()) {
|
||||||
|
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMap := make(map[string]bool)
|
||||||
|
for _, mediaType := range opts.ProxyMediaTypes() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyHTTPClientTimeout(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24")
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
opts, err := parser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := 24
|
||||||
|
result := opts.ProxyHTTPClientTimeout()
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultProxyHTTPClientTimeoutValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1297,6 +1387,41 @@ func TestDefaultHTTPClientMaxBodySizeValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPServerTimeout(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("HTTP_SERVER_TIMEOUT", "342")
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
opts, err := parser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := 342
|
||||||
|
result := opts.HTTPServerTimeout()
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf(`Unexpected HTTP_SERVER_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultHTTPServerTimeoutValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
opts, err := parser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := defaultHTTPServerTimeout
|
||||||
|
result := opts.HTTPServerTimeout()
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf(`Unexpected HTTP_SERVER_TIMEOUT value, got %d instead of %d`, result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseConfigFile(t *testing.T) {
|
func TestParseConfigFile(t *testing.T) {
|
||||||
content := []byte(`
|
content := []byte(`
|
||||||
# This is a comment
|
# This is a comment
|
||||||
|
|
|
@ -46,8 +46,10 @@ const (
|
||||||
defaultCleanupArchiveUnreadDays = 180
|
defaultCleanupArchiveUnreadDays = 180
|
||||||
defaultCleanupArchiveBatchSize = 10000
|
defaultCleanupArchiveBatchSize = 10000
|
||||||
defaultCleanupRemoveSessionsDays = 30
|
defaultCleanupRemoveSessionsDays = 30
|
||||||
defaultProxyImages = "http-only"
|
defaultProxyHTTPClientTimeout = 120
|
||||||
defaultProxyImageUrl = ""
|
defaultProxyOption = "http-only"
|
||||||
|
defaultProxyMediaTypes = "image"
|
||||||
|
defaultProxyUrl = ""
|
||||||
defaultFetchYouTubeWatchTime = false
|
defaultFetchYouTubeWatchTime = false
|
||||||
defaultCreateAdmin = false
|
defaultCreateAdmin = false
|
||||||
defaultAdminUsername = ""
|
defaultAdminUsername = ""
|
||||||
|
@ -62,6 +64,7 @@ const (
|
||||||
defaultHTTPClientTimeout = 20
|
defaultHTTPClientTimeout = 20
|
||||||
defaultHTTPClientMaxBodySize = 15
|
defaultHTTPClientMaxBodySize = 15
|
||||||
defaultHTTPClientProxy = ""
|
defaultHTTPClientProxy = ""
|
||||||
|
defaultHTTPServerTimeout = 300
|
||||||
defaultAuthProxyHeader = ""
|
defaultAuthProxyHeader = ""
|
||||||
defaultAuthProxyUserCreation = false
|
defaultAuthProxyUserCreation = false
|
||||||
defaultMaintenanceMode = false
|
defaultMaintenanceMode = false
|
||||||
|
@ -117,8 +120,10 @@ type Options struct {
|
||||||
createAdmin bool
|
createAdmin bool
|
||||||
adminUsername string
|
adminUsername string
|
||||||
adminPassword string
|
adminPassword string
|
||||||
proxyImages string
|
proxyHTTPClientTimeout int
|
||||||
proxyImageUrl string
|
proxyOption string
|
||||||
|
proxyMediaTypes []string
|
||||||
|
proxyUrl string
|
||||||
fetchYouTubeWatchTime bool
|
fetchYouTubeWatchTime bool
|
||||||
oauth2UserCreationAllowed bool
|
oauth2UserCreationAllowed bool
|
||||||
oauth2ClientID string
|
oauth2ClientID string
|
||||||
|
@ -131,6 +136,7 @@ type Options struct {
|
||||||
httpClientMaxBodySize int64
|
httpClientMaxBodySize int64
|
||||||
httpClientProxy string
|
httpClientProxy string
|
||||||
httpClientUserAgent string
|
httpClientUserAgent string
|
||||||
|
httpServerTimeout int
|
||||||
authProxyHeader string
|
authProxyHeader string
|
||||||
authProxyUserCreation bool
|
authProxyUserCreation bool
|
||||||
maintenanceMode bool
|
maintenanceMode bool
|
||||||
|
@ -181,8 +187,10 @@ func NewOptions() *Options {
|
||||||
pollingParsingErrorLimit: defaultPollingParsingErrorLimit,
|
pollingParsingErrorLimit: defaultPollingParsingErrorLimit,
|
||||||
workerPoolSize: defaultWorkerPoolSize,
|
workerPoolSize: defaultWorkerPoolSize,
|
||||||
createAdmin: defaultCreateAdmin,
|
createAdmin: defaultCreateAdmin,
|
||||||
proxyImages: defaultProxyImages,
|
proxyHTTPClientTimeout: defaultProxyHTTPClientTimeout,
|
||||||
proxyImageUrl: defaultProxyImageUrl,
|
proxyOption: defaultProxyOption,
|
||||||
|
proxyMediaTypes: []string{defaultProxyMediaTypes},
|
||||||
|
proxyUrl: defaultProxyUrl,
|
||||||
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
|
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
|
||||||
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
|
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
|
||||||
oauth2ClientID: defaultOAuth2ClientID,
|
oauth2ClientID: defaultOAuth2ClientID,
|
||||||
|
@ -195,6 +203,7 @@ func NewOptions() *Options {
|
||||||
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
|
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
|
||||||
httpClientProxy: defaultHTTPClientProxy,
|
httpClientProxy: defaultHTTPClientProxy,
|
||||||
httpClientUserAgent: defaultHTTPClientUserAgent,
|
httpClientUserAgent: defaultHTTPClientUserAgent,
|
||||||
|
httpServerTimeout: defaultHTTPServerTimeout,
|
||||||
authProxyHeader: defaultAuthProxyHeader,
|
authProxyHeader: defaultAuthProxyHeader,
|
||||||
authProxyUserCreation: defaultAuthProxyUserCreation,
|
authProxyUserCreation: defaultAuthProxyUserCreation,
|
||||||
maintenanceMode: defaultMaintenanceMode,
|
maintenanceMode: defaultMaintenanceMode,
|
||||||
|
@ -414,14 +423,24 @@ func (o *Options) FetchYouTubeWatchTime() bool {
|
||||||
return o.fetchYouTubeWatchTime
|
return o.fetchYouTubeWatchTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
|
// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
|
||||||
func (o *Options) ProxyImages() string {
|
func (o *Options) ProxyOption() string {
|
||||||
return o.proxyImages
|
return o.proxyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyImageUrl returns a string of a URL to use to proxy image requests
|
// ProxyMediaTypes returns a slice of media types to proxy.
|
||||||
func (o *Options) ProxyImageUrl() string {
|
func (o *Options) ProxyMediaTypes() []string {
|
||||||
return o.proxyImageUrl
|
return o.proxyMediaTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyUrl returns a string of a URL to use to proxy image requests
|
||||||
|
func (o *Options) ProxyUrl() string {
|
||||||
|
return o.proxyUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyHTTPClientTimeout returns the time limit in seconds before the proxy HTTP client cancel the request.
|
||||||
|
func (o *Options) ProxyHTTPClientTimeout() int {
|
||||||
|
return o.proxyHTTPClientTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasHTTPService returns true if the HTTP service is enabled.
|
// HasHTTPService returns true if the HTTP service is enabled.
|
||||||
|
@ -457,6 +476,11 @@ func (o *Options) HTTPClientProxy() string {
|
||||||
return o.httpClientProxy
|
return o.httpClientProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPServerTimeout returns the time limit in seconds before the HTTP server cancel the request.
|
||||||
|
func (o *Options) HTTPServerTimeout() int {
|
||||||
|
return o.httpServerTimeout
|
||||||
|
}
|
||||||
|
|
||||||
// HasHTTPClientProxyConfigured returns true if the HTTP proxy is configured.
|
// HasHTTPClientProxyConfigured returns true if the HTTP proxy is configured.
|
||||||
func (o *Options) HasHTTPClientProxyConfigured() bool {
|
func (o *Options) HasHTTPClientProxyConfigured() bool {
|
||||||
return o.httpClientProxy != ""
|
return o.httpClientProxy != ""
|
||||||
|
@ -541,6 +565,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"HTTP_CLIENT_PROXY": o.httpClientProxy,
|
"HTTP_CLIENT_PROXY": o.httpClientProxy,
|
||||||
"HTTP_CLIENT_TIMEOUT": o.httpClientTimeout,
|
"HTTP_CLIENT_TIMEOUT": o.httpClientTimeout,
|
||||||
"HTTP_CLIENT_USER_AGENT": o.httpClientUserAgent,
|
"HTTP_CLIENT_USER_AGENT": o.httpClientUserAgent,
|
||||||
|
"HTTP_SERVER_TIMEOUT": o.httpServerTimeout,
|
||||||
"HTTP_SERVICE": o.httpService,
|
"HTTP_SERVICE": o.httpService,
|
||||||
"KEY_FILE": o.certKeyFile,
|
"KEY_FILE": o.certKeyFile,
|
||||||
"INVIDIOUS_INSTANCE": o.invidiousInstance,
|
"INVIDIOUS_INSTANCE": o.invidiousInstance,
|
||||||
|
@ -561,9 +586,11 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"POLLING_FREQUENCY": o.pollingFrequency,
|
"POLLING_FREQUENCY": o.pollingFrequency,
|
||||||
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
|
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
|
||||||
"POLLING_SCHEDULER": o.pollingScheduler,
|
"POLLING_SCHEDULER": o.pollingScheduler,
|
||||||
"PROXY_IMAGES": o.proxyImages,
|
"PROXY_HTTP_CLIENT_TIMEOUT": o.proxyHTTPClientTimeout,
|
||||||
"PROXY_IMAGE_URL": o.proxyImageUrl,
|
|
||||||
"PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
|
"PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
|
||||||
|
"PROXY_MEDIA_TYPES": o.proxyMediaTypes,
|
||||||
|
"PROXY_OPTION": o.proxyOption,
|
||||||
|
"PROXY_URL": o.proxyUrl,
|
||||||
"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,
|
||||||
|
|
|
@ -138,10 +138,21 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||||
p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval)
|
p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval)
|
||||||
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.proxyImages = parseString(value, defaultProxyImages)
|
p.opts.proxyOption = parseString(value, defaultProxyOption)
|
||||||
|
p.opts.proxyMediaTypes = append(p.opts.proxyMediaTypes, "image")
|
||||||
|
case "PROXY_HTTP_CLIENT_TIMEOUT":
|
||||||
|
p.opts.proxyHTTPClientTimeout = parseInt(value, defaultProxyHTTPClientTimeout)
|
||||||
|
case "PROXY_OPTION":
|
||||||
|
p.opts.proxyOption = parseString(value, defaultProxyOption)
|
||||||
|
case "PROXY_MEDIA_TYPES":
|
||||||
|
p.opts.proxyMediaTypes = parseStringList(value, []string{defaultProxyMediaTypes})
|
||||||
|
// kept for compatibility purpose
|
||||||
case "PROXY_IMAGE_URL":
|
case "PROXY_IMAGE_URL":
|
||||||
p.opts.proxyImageUrl = parseString(value, defaultProxyImageUrl)
|
p.opts.proxyUrl = parseString(value, defaultProxyUrl)
|
||||||
|
case "PROXY_URL":
|
||||||
|
p.opts.proxyUrl = parseString(value, defaultProxyUrl)
|
||||||
case "CREATE_ADMIN":
|
case "CREATE_ADMIN":
|
||||||
p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
|
p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
|
||||||
case "ADMIN_USERNAME":
|
case "ADMIN_USERNAME":
|
||||||
|
@ -180,6 +191,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||||
p.opts.httpClientProxy = parseString(value, defaultHTTPClientProxy)
|
p.opts.httpClientProxy = parseString(value, defaultHTTPClientProxy)
|
||||||
case "HTTP_CLIENT_USER_AGENT":
|
case "HTTP_CLIENT_USER_AGENT":
|
||||||
p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
|
p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
|
||||||
|
case "HTTP_SERVER_TIMEOUT":
|
||||||
|
p.opts.httpServerTimeout = parseInt(value, defaultHTTPServerTimeout)
|
||||||
case "AUTH_PROXY_HEADER":
|
case "AUTH_PROXY_HEADER":
|
||||||
p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader)
|
p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader)
|
||||||
case "AUTH_PROXY_USER_CREATION":
|
case "AUTH_PROXY_USER_CREATION":
|
||||||
|
|
|
@ -310,7 +310,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.AbsoluteImageProxyRewriter(h.router, r.Host, entry.Content),
|
HTML: proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content),
|
||||||
URL: entry.URL,
|
URL: entry.URL,
|
||||||
IsSaved: isSaved,
|
IsSaved: isSaved,
|
||||||
IsRead: isRead,
|
IsRead: isRead,
|
||||||
|
|
|
@ -841,12 +841,17 @@ func (h *handler) streamItemContents(w http.ResponseWriter, r *http.Request) {
|
||||||
categories = append(categories, userStarred)
|
categories = append(categories, userStarred)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Content = proxy.AbsoluteImageProxyRewriter(h.router, r.Host, entry.Content)
|
entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
|
||||||
proxyImage := config.Opts.ProxyImages()
|
proxyOption := config.Opts.ProxyOption()
|
||||||
|
|
||||||
for i := range entry.Enclosures {
|
for i := range entry.Enclosures {
|
||||||
if strings.HasPrefix(entry.Enclosures[i].MimeType, "image/") && (proxyImage == "all" || proxyImage != "none" && !url.IsHTTPS(entry.Enclosures[i].URL)) {
|
if proxyOption == "all" || proxyOption != "none" && !url.IsHTTPS(entry.Enclosures[i].URL) {
|
||||||
|
for _, mediaType := range config.Opts.ProxyMediaTypes() {
|
||||||
|
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 = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"miniflux.app/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const compressionThreshold = 1024
|
const compressionThreshold = 1024
|
||||||
|
@ -88,7 +90,10 @@ func (b *Builder) Write() {
|
||||||
case io.Reader:
|
case io.Reader:
|
||||||
// Compression not implemented in this case
|
// Compression not implemented in this case
|
||||||
b.writeHeaders()
|
b.writeHeaders()
|
||||||
io.Copy(b.w, v)
|
_, err := io.Copy(b.w, v)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,3 +72,16 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||||
func Redirect(w http.ResponseWriter, r *http.Request, uri string) {
|
func Redirect(w http.ResponseWriter, r *http.Request, uri string) {
|
||||||
http.Redirect(w, r, uri, http.StatusFound)
|
http.Redirect(w, r, uri, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestedRangeNotSatisfiable sends a range not satisfiable error to the client.
|
||||||
|
func RequestedRangeNotSatisfiable(w http.ResponseWriter, r *http.Request, contentRange string) {
|
||||||
|
logger.Error("[HTTP:Range Not Satisfiable] %s", r.URL)
|
||||||
|
|
||||||
|
builder := response.New(w, r)
|
||||||
|
builder.WithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||||
|
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
|
||||||
|
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
|
||||||
|
builder.WithHeader("Content-Range", contentRange)
|
||||||
|
builder.WithBody("Range Not Satisfiable")
|
||||||
|
builder.Write()
|
||||||
|
}
|
||||||
|
|
|
@ -210,3 +210,32 @@ func TestRedirectResponse(t *testing.T) {
|
||||||
t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, expectedResult)
|
t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, expectedResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequestedRangeNotSatisfiable(t *testing.T) {
|
||||||
|
r, err := http.NewRequest("GET", "/", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
RequestedRangeNotSatisfiable(w, r, "bytes */12777")
|
||||||
|
})
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
expectedStatusCode := http.StatusRequestedRangeNotSatisfiable
|
||||||
|
if resp.StatusCode != expectedStatusCode {
|
||||||
|
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedContentRangeHeader := "bytes */12777"
|
||||||
|
actualContentRangeHeader := resp.Header.Get("Content-Range")
|
||||||
|
if actualContentRangeHeader != expectedContentRangeHeader {
|
||||||
|
t.Fatalf(`Unexpected content range header, got %q instead of %q`, actualContentRangeHeader, expectedContentRangeHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
23
miniflux.1
23
miniflux.1
|
@ -365,13 +365,23 @@ Path to a secret key exposed as a file, it should contain $POCKET_CONSUMER_KEY v
|
||||||
.br
|
.br
|
||||||
Default is empty\&.
|
Default is empty\&.
|
||||||
.TP
|
.TP
|
||||||
.B PROXY_IMAGES
|
.B PROXY_OPTION
|
||||||
Avoids mixed content warnings for external images: http-only, all, or none\&.
|
Avoids mixed content warnings for external media: http-only, all, or none\&.
|
||||||
.br
|
.br
|
||||||
Default is http-only\&.
|
Default is http-only\&.
|
||||||
.TP
|
.TP
|
||||||
.B PROXY_IMAGE_URL
|
.B PROXY_MEDIA_TYPES
|
||||||
Sets a server to proxy images through\&.
|
A list of media types to proxify (comma-separated values): image, audio, video\&.
|
||||||
|
.br
|
||||||
|
Default is image only\&.
|
||||||
|
.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_URL
|
||||||
|
Sets a server to proxy media through\&.
|
||||||
.br
|
.br
|
||||||
Default is empty, miniflux does the proxying\&.
|
Default is empty, miniflux does the proxying\&.
|
||||||
.TP
|
.TP
|
||||||
|
@ -397,6 +407,11 @@ When empty, Miniflux uses a default User-Agent that includes the Miniflux versio
|
||||||
.br
|
.br
|
||||||
Default is empty.
|
Default is empty.
|
||||||
.TP
|
.TP
|
||||||
|
.B HTTP_SERVER_TIMEOUT
|
||||||
|
Time limit in seconds before the HTTP client cancel the request\&.
|
||||||
|
.br
|
||||||
|
Default is 300 seconds\&.
|
||||||
|
.TP
|
||||||
.B AUTH_PROXY_HEADER
|
.B AUTH_PROXY_HEADER
|
||||||
Proxy authentication HTTP header\&.
|
Proxy authentication HTTP header\&.
|
||||||
.br
|
.br
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
// Copyright 2020 Frédéric Guillot. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package proxy // import "miniflux.app/proxy"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"miniflux.app/config"
|
|
||||||
"miniflux.app/reader/sanitizer"
|
|
||||||
"miniflux.app/url"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type urlProxyRewriter func(router *mux.Router, url string) string
|
|
||||||
|
|
||||||
// ImageProxyRewriter replaces image URLs with internal proxy URLs.
|
|
||||||
func ImageProxyRewriter(router *mux.Router, data string) string {
|
|
||||||
return genericImageProxyRewriter(router, ProxifyURL, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AbsoluteImageProxyRewriter do the same as ImageProxyRewriter except it uses absolute URLs.
|
|
||||||
func AbsoluteImageProxyRewriter(router *mux.Router, host, data string) string {
|
|
||||||
proxifyFunction := func(router *mux.Router, url string) string {
|
|
||||||
return AbsoluteProxifyURL(router, host, url)
|
|
||||||
}
|
|
||||||
return genericImageProxyRewriter(router, proxifyFunction, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func genericImageProxyRewriter(router *mux.Router, proxifyFunction urlProxyRewriter, data string) string {
|
|
||||||
proxyImages := config.Opts.ProxyImages()
|
|
||||||
if proxyImages == "none" {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
|
|
||||||
if err != nil {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.Find("img").Each(func(i int, img *goquery.Selection) {
|
|
||||||
if srcAttrValue, ok := img.Attr("src"); ok {
|
|
||||||
if !isDataURL(srcAttrValue) && (proxyImages == "all" || !url.IsHTTPS(srcAttrValue)) {
|
|
||||||
img.SetAttr("src", proxifyFunction(router, srcAttrValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if srcsetAttrValue, ok := img.Attr("srcset"); ok {
|
|
||||||
proxifySourceSet(img, router, proxifyFunction, proxyImages, srcsetAttrValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
doc.Find("picture source").Each(func(i int, sourceElement *goquery.Selection) {
|
|
||||||
if srcsetAttrValue, ok := sourceElement.Attr("srcset"); ok {
|
|
||||||
proxifySourceSet(sourceElement, router, proxifyFunction, proxyImages, srcsetAttrValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
output, err := doc.Find("body").First().Html()
|
|
||||||
if err != nil {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func proxifySourceSet(element *goquery.Selection, router *mux.Router, proxifyFunction urlProxyRewriter, proxyImages, srcsetAttrValue string) {
|
|
||||||
imageCandidates := sanitizer.ParseSrcSetAttribute(srcsetAttrValue)
|
|
||||||
|
|
||||||
for _, imageCandidate := range imageCandidates {
|
|
||||||
if !isDataURL(imageCandidate.ImageURL) && (proxyImages == "all" || !url.IsHTTPS(imageCandidate.ImageURL)) {
|
|
||||||
imageCandidate.ImageURL = proxifyFunction(router, imageCandidate.ImageURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
element.SetAttr("srcset", imageCandidates.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDataURL(s string) bool {
|
|
||||||
return strings.HasPrefix(s, "data:")
|
|
||||||
}
|
|
123
proxy/media_proxy.go
Normal file
123
proxy/media_proxy.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright 2020 Frédéric Guillot. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proxy // import "miniflux.app/proxy"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"miniflux.app/config"
|
||||||
|
"miniflux.app/reader/sanitizer"
|
||||||
|
"miniflux.app/url"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsoluteProxyRewriter do the same as ProxyRewriter except it uses absolute URLs.
|
||||||
|
func AbsoluteProxyRewriter(router *mux.Router, host, data string) string {
|
||||||
|
proxifyFunction := func(router *mux.Router, url string) string {
|
||||||
|
return AbsoluteProxifyURL(router, host, url)
|
||||||
|
}
|
||||||
|
return genericProxyRewriter(router, proxifyFunction, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func genericProxyRewriter(router *mux.Router, proxifyFunction urlProxyRewriter, data string) string {
|
||||||
|
proxyOption := config.Opts.ProxyOption()
|
||||||
|
if proxyOption == "none" {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mediaType := range config.Opts.ProxyMediaTypes() {
|
||||||
|
switch mediaType {
|
||||||
|
case "image":
|
||||||
|
doc.Find("img").Each(func(i int, img *goquery.Selection) {
|
||||||
|
if srcAttrValue, ok := img.Attr("src"); ok {
|
||||||
|
if !isDataURL(srcAttrValue) && (proxyOption == "all" || !url.IsHTTPS(srcAttrValue)) {
|
||||||
|
img.SetAttr("src", proxifyFunction(router, srcAttrValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcsetAttrValue, ok := img.Attr("srcset"); ok {
|
||||||
|
proxifySourceSet(img, router, proxifyFunction, proxyOption, srcsetAttrValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
doc.Find("picture source").Each(func(i int, sourceElement *goquery.Selection) {
|
||||||
|
if srcsetAttrValue, ok := sourceElement.Attr("srcset"); ok {
|
||||||
|
proxifySourceSet(sourceElement, router, proxifyFunction, proxyOption, srcsetAttrValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
case "audio":
|
||||||
|
doc.Find("audio").Each(func(i int, audio *goquery.Selection) {
|
||||||
|
if srcAttrValue, ok := audio.Attr("src"); ok {
|
||||||
|
if !isDataURL(srcAttrValue) && (proxyOption == "all" || !url.IsHTTPS(srcAttrValue)) {
|
||||||
|
audio.SetAttr("src", proxifyFunction(router, srcAttrValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
doc.Find("audio source").Each(func(i int, sourceElement *goquery.Selection) {
|
||||||
|
if srcAttrValue, ok := sourceElement.Attr("src"); ok {
|
||||||
|
if !isDataURL(srcAttrValue) && (proxyOption == "all" || !url.IsHTTPS(srcAttrValue)) {
|
||||||
|
sourceElement.SetAttr("src", proxifyFunction(router, srcAttrValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
case "video":
|
||||||
|
doc.Find("video").Each(func(i int, video *goquery.Selection) {
|
||||||
|
if srcAttrValue, ok := video.Attr("src"); ok {
|
||||||
|
if !isDataURL(srcAttrValue) && (proxyOption == "all" || !url.IsHTTPS(srcAttrValue)) {
|
||||||
|
video.SetAttr("src", proxifyFunction(router, srcAttrValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
doc.Find("video source").Each(func(i int, sourceElement *goquery.Selection) {
|
||||||
|
if srcAttrValue, ok := sourceElement.Attr("src"); ok {
|
||||||
|
if !isDataURL(srcAttrValue) && (proxyOption == "all" || !url.IsHTTPS(srcAttrValue)) {
|
||||||
|
sourceElement.SetAttr("src", proxifyFunction(router, srcAttrValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := doc.Find("body").First().Html()
|
||||||
|
if err != nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxifySourceSet(element *goquery.Selection, router *mux.Router, proxifyFunction urlProxyRewriter, proxyOption, srcsetAttrValue string) {
|
||||||
|
imageCandidates := sanitizer.ParseSrcSetAttribute(srcsetAttrValue)
|
||||||
|
|
||||||
|
for _, imageCandidate := range imageCandidates {
|
||||||
|
if !isDataURL(imageCandidate.ImageURL) && (proxyOption == "all" || !url.IsHTTPS(imageCandidate.ImageURL)) {
|
||||||
|
imageCandidate.ImageURL = proxifyFunction(router, imageCandidate.ImageURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.SetAttr("srcset", imageCandidates.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDataURL(s string) bool {
|
||||||
|
return strings.HasPrefix(s, "data:")
|
||||||
|
}
|
|
@ -15,7 +15,9 @@ import (
|
||||||
|
|
||||||
func TestProxyFilterWithHttpDefault(t *testing.T) {
|
func TestProxyFilterWithHttpDefault(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "http-only")
|
os.Setenv("PROXY_OPTION", "http-only")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -25,11 +27,11 @@ func TestProxyFilterWithHttpDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
expected := `<p><img src="/proxy/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 "%s" instead of "%s"`, output, expected)
|
||||||
|
@ -38,7 +40,8 @@ func TestProxyFilterWithHttpDefault(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpsDefault(t *testing.T) {
|
func TestProxyFilterWithHttpsDefault(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "http-only")
|
os.Setenv("PROXY_OPTION", "http-only")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -48,10 +51,10 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(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 {
|
||||||
|
@ -61,7 +64,7 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpNever(t *testing.T) {
|
func TestProxyFilterWithHttpNever(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "none")
|
os.Setenv("PROXY_OPTION", "none")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -71,10 +74,10 @@ func TestProxyFilterWithHttpNever(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
expected := input
|
expected := input
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
|
@ -84,7 +87,7 @@ func TestProxyFilterWithHttpNever(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpsNever(t *testing.T) {
|
func TestProxyFilterWithHttpsNever(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "none")
|
os.Setenv("PROXY_OPTION", "none")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -94,10 +97,10 @@ func TestProxyFilterWithHttpsNever(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
expected := input
|
expected := input
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
|
@ -107,7 +110,9 @@ func TestProxyFilterWithHttpsNever(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpAlways(t *testing.T) {
|
func TestProxyFilterWithHttpAlways(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -117,11 +122,11 @@ func TestProxyFilterWithHttpAlways(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
expected := `<p><img src="/proxy/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 "%s" instead of "%s"`, output, expected)
|
||||||
|
@ -130,7 +135,9 @@ func TestProxyFilterWithHttpAlways(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpsAlways(t *testing.T) {
|
func TestProxyFilterWithHttpsAlways(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -140,11 +147,11 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
expected := `<p><img src="/proxy/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 "%s" instead of "%s"`, output, expected)
|
||||||
|
@ -153,8 +160,9 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
|
func TestProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
os.Setenv("PROXY_IMAGE_URL", "https://proxy-example/proxy")
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_URL", "https://proxy-example/proxy")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -164,10 +172,10 @@ func TestProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(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 {
|
||||||
|
@ -177,7 +185,8 @@ func TestProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpInvalid(t *testing.T) {
|
func TestProxyFilterWithHttpInvalid(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "invalid")
|
os.Setenv("PROXY_OPTION", "invalid")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -187,11 +196,11 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
expected := `<p><img src="/proxy/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 "%s" instead of "%s"`, output, expected)
|
||||||
|
@ -200,7 +209,8 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithHttpsInvalid(t *testing.T) {
|
func TestProxyFilterWithHttpsInvalid(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "invalid")
|
os.Setenv("PROXY_OPTION", "invalid")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -210,10 +220,10 @@ func TestProxyFilterWithHttpsInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(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 {
|
||||||
|
@ -223,7 +233,9 @@ func TestProxyFilterWithHttpsInvalid(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithSrcset(t *testing.T) {
|
func TestProxyFilterWithSrcset(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -233,11 +245,11 @@ func TestProxyFilterWithSrcset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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" 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/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" srcset="/proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMi5wbmc= 656w, /proxy/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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
t.Errorf(`Not expected output: got %s`, output)
|
t.Errorf(`Not expected output: got %s`, output)
|
||||||
|
@ -246,7 +258,9 @@ func TestProxyFilterWithSrcset(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithEmptySrcset(t *testing.T) {
|
func TestProxyFilterWithEmptySrcset(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -256,11 +270,11 @@ func TestProxyFilterWithEmptySrcset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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" srcset="" alt="test"></p>`
|
input := `<p><img src="http://website/folder/image.png" srcset="" alt="test"></p>`
|
||||||
expected := `<p><img src="/proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" srcset="" alt="test"/></p>`
|
expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" srcset="" alt="test"/></p>`
|
||||||
output := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
t.Errorf(`Not expected output: got %s`, output)
|
t.Errorf(`Not expected output: got %s`, output)
|
||||||
|
@ -269,7 +283,9 @@ func TestProxyFilterWithEmptySrcset(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterWithPictureSource(t *testing.T) {
|
func TestProxyFilterWithPictureSource(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -279,11 +295,11 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := `<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/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMi5wbmc= 656w, /proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlMy5wbmc= 360w, /proxy/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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
t.Errorf(`Not expected output: got %s`, output)
|
t.Errorf(`Not expected output: got %s`, output)
|
||||||
|
@ -292,7 +308,9 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
|
func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "https")
|
os.Setenv("PROXY_OPTION", "https")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
os.Setenv("PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -302,20 +320,21 @@ func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := `<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/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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
t.Errorf(`Not expected output: got %s`, output)
|
t.Errorf(`Not expected output: got %s`, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImageProxyWithImageDataURL(t *testing.T) {
|
func TestProxyWithImageDataURL(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -325,20 +344,21 @@ func TestImageProxyWithImageDataURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := `<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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
t.Errorf(`Not expected output: got %s`, output)
|
t.Errorf(`Not expected output: got %s`, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImageProxyWithImageSourceDataURL(t *testing.T) {
|
func TestProxyWithImageSourceDataURL(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("PROXY_IMAGES", "all")
|
os.Setenv("PROXY_OPTION", "all")
|
||||||
|
os.Setenv("PROXY_MEDIA_TYPES", "image")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewParser()
|
||||||
|
@ -348,11 +368,11 @@ func TestImageProxyWithImageSourceDataURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/proxy/{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 := `<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 := ImageProxyRewriter(r, input)
|
output := ProxyRewriter(r, input)
|
||||||
|
|
||||||
if expected != output {
|
if expected != output {
|
||||||
t.Errorf(`Not expected output: got %s`, output)
|
t.Errorf(`Not expected output: got %s`, output)
|
|
@ -21,7 +21,7 @@ import (
|
||||||
// ProxifyURL generates a relative URL for a proxified resource.
|
// ProxifyURL generates a relative URL for a proxified resource.
|
||||||
func ProxifyURL(router *mux.Router, link string) string {
|
func ProxifyURL(router *mux.Router, link string) string {
|
||||||
if link != "" {
|
if link != "" {
|
||||||
proxyImageUrl := config.Opts.ProxyImageUrl()
|
proxyImageUrl := config.Opts.ProxyUrl()
|
||||||
|
|
||||||
if proxyImageUrl == "" {
|
if proxyImageUrl == "" {
|
||||||
mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey())
|
mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey())
|
||||||
|
@ -44,7 +44,7 @@ func ProxifyURL(router *mux.Router, link string) string {
|
||||||
// AbsoluteProxifyURL generates an absolute URL for a proxified resource.
|
// AbsoluteProxifyURL generates an absolute URL for a proxified resource.
|
||||||
func AbsoluteProxifyURL(router *mux.Router, host, link string) string {
|
func AbsoluteProxifyURL(router *mux.Router, host, link string) string {
|
||||||
if link != "" {
|
if link != "" {
|
||||||
proxyImageUrl := config.Opts.ProxyImageUrl()
|
proxyImageUrl := config.Opts.ProxyUrl()
|
||||||
|
|
||||||
if proxyImageUrl == "" {
|
if proxyImageUrl == "" {
|
||||||
mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey())
|
mac := hmac.New(sha256.New, config.Opts.ProxyPrivateKey())
|
||||||
|
|
|
@ -37,9 +37,9 @@ func Serve(store *storage.Storage, pool *worker.Pool) *http.Server {
|
||||||
certDomain := config.Opts.CertDomain()
|
certDomain := config.Opts.CertDomain()
|
||||||
listenAddr := config.Opts.ListenAddr()
|
listenAddr := config.Opts.ListenAddr()
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
ReadTimeout: 300 * time.Second,
|
ReadTimeout: time.Duration(config.Opts.HTTPServerTimeout()) * time.Second,
|
||||||
WriteTimeout: 300 * time.Second,
|
WriteTimeout: time.Duration(config.Opts.HTTPServerTimeout()) * time.Second,
|
||||||
IdleTimeout: 300 * time.Second,
|
IdleTimeout: time.Duration(config.Opts.HTTPServerTimeout()) * time.Second,
|
||||||
Handler: setupHandler(store, pool),
|
Handler: setupHandler(store, pool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,17 +61,25 @@ 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.ImageProxyRewriter(f.router, data)
|
return proxy.ProxyRewriter(f.router, data)
|
||||||
},
|
},
|
||||||
"proxyURL": func(link string) string {
|
"proxyURL": func(link string) string {
|
||||||
proxyImages := config.Opts.ProxyImages()
|
proxyOption := config.Opts.ProxyOption()
|
||||||
|
|
||||||
if proxyImages == "all" || (proxyImages != "none" && !url.IsHTTPS(link)) {
|
if proxyOption == "all" || (proxyOption != "none" && !url.IsHTTPS(link)) {
|
||||||
return proxy.ProxifyURL(f.router, link)
|
return proxy.ProxifyURL(f.router, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
return link
|
return link
|
||||||
},
|
},
|
||||||
|
"mustBeProxyfied": func(mediaType string) bool {
|
||||||
|
for _, t := range config.Opts.ProxyMediaTypes() {
|
||||||
|
if t == mediaType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
"domain": func(websiteURL string) string {
|
"domain": func(websiteURL string) string {
|
||||||
return url.Domain(websiteURL)
|
return url.Domain(websiteURL)
|
||||||
},
|
},
|
||||||
|
|
|
@ -159,18 +159,26 @@
|
||||||
{{ if hasPrefix .MimeType "audio/" }}
|
{{ if hasPrefix .MimeType "audio/" }}
|
||||||
<div class="enclosure-audio">
|
<div class="enclosure-audio">
|
||||||
<audio controls preload="metadata">
|
<audio controls preload="metadata">
|
||||||
|
{{ if (and $.user (mustBeProxyfied "audio")) }}
|
||||||
|
<source src="{{ proxyURL .URL }}" type="{{ .MimeType }}">
|
||||||
|
{{ else }}
|
||||||
<source src="{{ .URL | safeURL }}" type="{{ .MimeType }}">
|
<source src="{{ .URL | safeURL }}" type="{{ .MimeType }}">
|
||||||
|
{{ end }}
|
||||||
</audio>
|
</audio>
|
||||||
</div>
|
</div>
|
||||||
{{ else if hasPrefix .MimeType "video/" }}
|
{{ else if hasPrefix .MimeType "video/" }}
|
||||||
<div class="enclosure-video">
|
<div class="enclosure-video">
|
||||||
<video controls preload="metadata">
|
<video controls preload="metadata">
|
||||||
|
{{ if (and $.user (mustBeProxyfied "video")) }}
|
||||||
|
<source src="{{ proxyURL .URL }}" type="{{ .MimeType }}">
|
||||||
|
{{ else }}
|
||||||
<source src="{{ .URL | safeURL }}" type="{{ .MimeType }}">
|
<source src="{{ .URL | safeURL }}" type="{{ .MimeType }}">
|
||||||
|
{{ end }}
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
{{ else if hasPrefix .MimeType "image/" }}
|
{{ else if hasPrefix .MimeType "image/" }}
|
||||||
<div class="enclosure-image">
|
<div class="enclosure-image">
|
||||||
{{ if $.user }}
|
{{ if (and $.user (mustBeProxyfied "image")) }}
|
||||||
<img src="{{ proxyURL .URL }}" title="{{ .URL }} ({{ .MimeType }})" loading="lazy" alt="{{ .URL }} ({{ .MimeType }})">
|
<img src="{{ proxyURL .URL }}" title="{{ .URL }} ({{ .MimeType }})" loading="lazy" alt="{{ .URL }} ({{ .MimeType }})">
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<img src="{{ .URL | safeURL }}" title="{{ .URL }} ({{ .MimeType }})" loading="lazy" alt="{{ .URL }} ({{ .MimeType }})">
|
<img src="{{ .URL | safeURL }}" title="{{ .URL }} ({{ .MimeType }})" loading="lazy" alt="{{ .URL }} ({{ .MimeType }})">
|
||||||
|
|
|
@ -67,5 +67,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.ImageProxyRewriter(h.router, entry.Content), "reading_time": readingTime})
|
json.OK(w, r, map[string]string{"content": proxy.ProxyRewriter(h.router, entry.Content), "reading_time": readingTime})
|
||||||
}
|
}
|
||||||
|
|
38
ui/proxy.go
38
ui/proxy.go
|
@ -20,8 +20,8 @@ import (
|
||||||
"miniflux.app/logger"
|
"miniflux.app/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) imageProxy(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
// If we receive a "If-None-Match" header, we assume the image is already stored in browser cache.
|
// If we receive a "If-None-Match" header, we assume the media is already stored in browser cache.
|
||||||
if r.Header.Get("If-None-Match") != "" {
|
if r.Header.Get("If-None-Match") != "" {
|
||||||
w.WriteHeader(http.StatusNotModified)
|
w.WriteHeader(http.StatusNotModified)
|
||||||
return
|
return
|
||||||
|
@ -55,10 +55,10 @@ func (h *handler) imageProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imageURL := string(decodedURL)
|
mediaURL := string(decodedURL)
|
||||||
logger.Debug(`[Proxy] Fetching %q`, imageURL)
|
logger.Debug(`[Proxy] Fetching %q`, mediaURL)
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", imageURL, nil)
|
req, err := http.NewRequest("GET", mediaURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
html.ServerError(w, r, err)
|
html.ServerError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -67,8 +67,18 @@ func (h *handler) imageProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
// Note: User-Agent HTTP header is omitted to avoid being blocked by bot protection mechanisms.
|
// Note: User-Agent HTTP header is omitted to avoid being blocked by bot protection mechanisms.
|
||||||
req.Header.Add("Connection", "close")
|
req.Header.Add("Connection", "close")
|
||||||
|
|
||||||
|
forwardedRequestHeader := []string{"Range", "Accept", "Accept-Encoding"}
|
||||||
|
for _, requestHeaderName := range forwardedRequestHeader {
|
||||||
|
if r.Header.Get(requestHeaderName) != "" {
|
||||||
|
req.Header.Add(requestHeaderName, r.Header.Get(requestHeaderName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clt := &http.Client{
|
clt := &http.Client{
|
||||||
Timeout: time.Duration(config.Opts.HTTPClientTimeout()) * time.Second,
|
Transport: &http.Transport{
|
||||||
|
IdleConnTimeout: time.Duration(config.Opts.ProxyHTTPClientTimeout()) * time.Second,
|
||||||
|
},
|
||||||
|
Timeout: time.Duration(config.Opts.ProxyHTTPClientTimeout()) * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := clt.Do(req)
|
resp, err := clt.Do(req)
|
||||||
|
@ -78,8 +88,13 @@ func (h *handler) imageProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
|
||||||
logger.Error(`[Proxy] Status Code is %d for URL %q`, resp.StatusCode, imageURL)
|
logger.Error(`[Proxy] Status Code is %d for URL %q`, resp.StatusCode, mediaURL)
|
||||||
|
html.RequestedRangeNotSatisfiable(w, r, resp.Header.Get("Content-Range"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
|
||||||
|
logger.Error(`[Proxy] Status Code is %d for URL %q`, resp.StatusCode, mediaURL)
|
||||||
html.NotFound(w, r)
|
html.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -87,8 +102,15 @@ func (h *handler) imageProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
etag := crypto.HashFromBytes(decodedURL)
|
etag := crypto.HashFromBytes(decodedURL)
|
||||||
|
|
||||||
response.New(w, r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) {
|
response.New(w, r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) {
|
||||||
|
b.WithStatus(resp.StatusCode)
|
||||||
b.WithHeader("Content-Security-Policy", `default-src 'self'`)
|
b.WithHeader("Content-Security-Policy", `default-src 'self'`)
|
||||||
b.WithHeader("Content-Type", resp.Header.Get("Content-Type"))
|
b.WithHeader("Content-Type", resp.Header.Get("Content-Type"))
|
||||||
|
forwardedResponseHeader := []string{"Content-Encoding", "Content-Type", "Content-Length", "Accept-Ranges", "Content-Range"}
|
||||||
|
for _, responseHeaderName := range forwardedResponseHeader {
|
||||||
|
if resp.Header.Get(responseHeaderName) != "" {
|
||||||
|
b.WithHeader(responseHeaderName, resp.Header.Get(responseHeaderName))
|
||||||
|
}
|
||||||
|
}
|
||||||
b.WithBody(resp.Body)
|
b.WithBody(resp.Body)
|
||||||
b.WithoutCompression()
|
b.WithoutCompression()
|
||||||
b.Write()
|
b.Write()
|
||||||
|
|
2
ui/ui.go
2
ui/ui.go
|
@ -96,7 +96,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
|
||||||
uiRouter.HandleFunc("/entry/status", handler.updateEntriesStatus).Name("updateEntriesStatus").Methods(http.MethodPost)
|
uiRouter.HandleFunc("/entry/status", handler.updateEntriesStatus).Name("updateEntriesStatus").Methods(http.MethodPost)
|
||||||
uiRouter.HandleFunc("/entry/save/{entryID}", handler.saveEntry).Name("saveEntry").Methods(http.MethodPost)
|
uiRouter.HandleFunc("/entry/save/{entryID}", handler.saveEntry).Name("saveEntry").Methods(http.MethodPost)
|
||||||
uiRouter.HandleFunc("/entry/download/{entryID}", handler.fetchContent).Name("fetchContent").Methods(http.MethodPost)
|
uiRouter.HandleFunc("/entry/download/{entryID}", handler.fetchContent).Name("fetchContent").Methods(http.MethodPost)
|
||||||
uiRouter.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", handler.imageProxy).Name("proxy").Methods(http.MethodGet)
|
uiRouter.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", handler.mediaProxy).Name("proxy").Methods(http.MethodGet)
|
||||||
uiRouter.HandleFunc("/entry/bookmark/{entryID}", handler.toggleBookmark).Name("toggleBookmark").Methods(http.MethodPost)
|
uiRouter.HandleFunc("/entry/bookmark/{entryID}", handler.toggleBookmark).Name("toggleBookmark").Methods(http.MethodPost)
|
||||||
|
|
||||||
// Share pages.
|
// Share pages.
|
||||||
|
|
Loading…
Add table
Reference in a new issue