mirror of
https://github.com/miniflux/v2.git
synced 2025-09-15 18:57:04 +00:00
refactor(config): rewrite config parser
This PR refactors the configuration parser, replacing the old parser implementation with a new, more structured approach that includes validation and improved organization. Key changes: - Complete rewrite of the configuration parser using a map-based structure with built-in validation - Addition of comprehensive validator functions for configuration values - Renamed numerous configuration getter methods for better consistency
This commit is contained in:
parent
502e7108dd
commit
5e607be86a
27 changed files with 3615 additions and 3523 deletions
|
@ -78,7 +78,7 @@ func Parse() {
|
||||||
flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
|
flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
cfg := config.NewParser()
|
cfg := config.NewConfigParser()
|
||||||
|
|
||||||
if flagConfigFile != "" {
|
if flagConfigFile != "" {
|
||||||
config.Opts, err = cfg.ParseFile(flagConfigFile)
|
config.Opts, err = cfg.ParseFile(flagConfigFile)
|
||||||
|
|
11
internal/config/config.go
Normal file
11
internal/config/config.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package config // import "miniflux.app/v2/internal/config"
|
||||||
|
|
||||||
|
import "miniflux.app/v2/internal/version"
|
||||||
|
|
||||||
|
// Opts holds parsed configuration options.
|
||||||
|
var Opts *configOptions
|
||||||
|
|
||||||
|
var defaultHTTPClientUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1688
internal/config/options_parsing_test.go
Normal file
1688
internal/config/options_parsing_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -17,301 +16,205 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parser handles configuration parsing.
|
type configParser struct {
|
||||||
type parser struct {
|
options *configOptions
|
||||||
opts *options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParser returns a new Parser.
|
func NewConfigParser() *configParser {
|
||||||
func NewParser() *parser {
|
return &configParser{
|
||||||
return &parser{
|
options: NewConfigOptions(),
|
||||||
opts: NewOptions(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEnvironmentVariables loads configuration values from environment variables.
|
func (cp *configParser) ParseEnvironmentVariables() (*configOptions, error) {
|
||||||
func (p *parser) ParseEnvironmentVariables() (*options, error) {
|
if err := cp.parseLines(os.Environ()); err != nil {
|
||||||
err := p.parseLines(os.Environ())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return p.opts, nil
|
|
||||||
|
return cp.options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFile loads configuration values from a local file.
|
func (cp *configParser) ParseFile(filename string) (*configOptions, error) {
|
||||||
func (p *parser) ParseFile(filename string) (*options, error) {
|
|
||||||
fp, err := os.Open(filename)
|
fp, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
|
|
||||||
err = p.parseLines(p.parseFileContent(fp))
|
if err := cp.parseLines(parseFileContent(fp)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return p.opts, nil
|
|
||||||
|
return cp.options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseFileContent(r io.Reader) (lines []string) {
|
func (cp *configParser) postParsing() error {
|
||||||
scanner := bufio.NewScanner(r)
|
// Parse basePath and rootURL based on BASE_URL
|
||||||
for scanner.Scan() {
|
baseURL := cp.options.options["BASE_URL"].ParsedStringValue
|
||||||
line := strings.TrimSpace(scanner.Text())
|
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||||
if !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseLines(lines []string) (err error) {
|
parsedURL, err := url.Parse(baseURL)
|
||||||
var port string
|
|
||||||
|
|
||||||
for lineNum, line := range lines {
|
|
||||||
key, value, ok := strings.Cut(line, "=")
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("config: unable to parse configuration, invalid format on line %d", lineNum)
|
|
||||||
}
|
|
||||||
key, value = strings.TrimSpace(key), strings.TrimSpace(value)
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "LOG_FILE":
|
|
||||||
p.opts.logFile = parseString(value, defaultLogFile)
|
|
||||||
case "LOG_DATE_TIME":
|
|
||||||
p.opts.logDateTime = parseBool(value, defaultLogDateTime)
|
|
||||||
case "LOG_LEVEL":
|
|
||||||
parsedValue := parseString(value, defaultLogLevel)
|
|
||||||
if parsedValue == "debug" || parsedValue == "info" || parsedValue == "warning" || parsedValue == "error" {
|
|
||||||
p.opts.logLevel = parsedValue
|
|
||||||
}
|
|
||||||
case "LOG_FORMAT":
|
|
||||||
parsedValue := parseString(value, defaultLogFormat)
|
|
||||||
if parsedValue == "json" || parsedValue == "text" {
|
|
||||||
p.opts.logFormat = parsedValue
|
|
||||||
}
|
|
||||||
case "BASE_URL":
|
|
||||||
p.opts.baseURL, p.opts.rootURL, p.opts.basePath, err = parseBaseURL(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "PORT":
|
|
||||||
port = value
|
|
||||||
case "LISTEN_ADDR":
|
|
||||||
p.opts.listenAddr = parseStringList(value, []string{defaultListenAddr})
|
|
||||||
case "DATABASE_URL":
|
|
||||||
p.opts.databaseURL = parseString(value, defaultDatabaseURL)
|
|
||||||
case "DATABASE_URL_FILE":
|
|
||||||
p.opts.databaseURL = readSecretFile(value, defaultDatabaseURL)
|
|
||||||
case "DATABASE_MAX_CONNS":
|
|
||||||
p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
|
|
||||||
case "DATABASE_MIN_CONNS":
|
|
||||||
p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
|
|
||||||
case "DATABASE_CONNECTION_LIFETIME":
|
|
||||||
p.opts.databaseConnectionLifetime = parseInt(value, defaultDatabaseConnectionLifetime)
|
|
||||||
case "FILTER_ENTRY_MAX_AGE_DAYS":
|
|
||||||
p.opts.filterEntryMaxAgeDays = parseInt(value, defaultFilterEntryMaxAgeDays)
|
|
||||||
case "RUN_MIGRATIONS":
|
|
||||||
p.opts.runMigrations = parseBool(value, defaultRunMigrations)
|
|
||||||
case "DISABLE_HSTS":
|
|
||||||
p.opts.hsts = !parseBool(value, defaultHSTS)
|
|
||||||
case "HTTPS":
|
|
||||||
p.opts.HTTPS = parseBool(value, defaultHTTPS)
|
|
||||||
case "DISABLE_SCHEDULER_SERVICE":
|
|
||||||
p.opts.schedulerService = !parseBool(value, defaultSchedulerService)
|
|
||||||
case "DISABLE_HTTP_SERVICE":
|
|
||||||
p.opts.httpService = !parseBool(value, defaultHTTPService)
|
|
||||||
case "CERT_FILE":
|
|
||||||
p.opts.certFile = parseString(value, defaultCertFile)
|
|
||||||
case "KEY_FILE":
|
|
||||||
p.opts.certKeyFile = parseString(value, defaultKeyFile)
|
|
||||||
case "CERT_DOMAIN":
|
|
||||||
p.opts.certDomain = parseString(value, defaultCertDomain)
|
|
||||||
case "CLEANUP_FREQUENCY_HOURS":
|
|
||||||
p.opts.cleanupFrequencyInterval = parseInterval(value, time.Hour, defaultCleanupFrequency)
|
|
||||||
case "CLEANUP_ARCHIVE_READ_DAYS":
|
|
||||||
p.opts.cleanupArchiveReadInterval = parseInterval(value, 24*time.Hour, defaultCleanupArchiveReadInterval)
|
|
||||||
case "CLEANUP_ARCHIVE_UNREAD_DAYS":
|
|
||||||
p.opts.cleanupArchiveUnreadInterval = parseInterval(value, 24*time.Hour, defaultCleanupArchiveUnreadInterval)
|
|
||||||
case "CLEANUP_ARCHIVE_BATCH_SIZE":
|
|
||||||
p.opts.cleanupArchiveBatchSize = parseInt(value, defaultCleanupArchiveBatchSize)
|
|
||||||
case "CLEANUP_REMOVE_SESSIONS_DAYS":
|
|
||||||
p.opts.cleanupRemoveSessionsInterval = parseInterval(value, 24*time.Hour, defaultCleanupRemoveSessionsInterval)
|
|
||||||
case "WORKER_POOL_SIZE":
|
|
||||||
p.opts.workerPoolSize = parseInt(value, defaultWorkerPoolSize)
|
|
||||||
case "FORCE_REFRESH_INTERVAL":
|
|
||||||
p.opts.forceRefreshInterval = parseInterval(value, time.Minute, defaultForceRefreshInterval)
|
|
||||||
case "BATCH_SIZE":
|
|
||||||
p.opts.batchSize = parseInt(value, defaultBatchSize)
|
|
||||||
case "POLLING_FREQUENCY":
|
|
||||||
p.opts.pollingFrequency = parseInterval(value, time.Minute, defaultPollingFrequency)
|
|
||||||
case "POLLING_LIMIT_PER_HOST":
|
|
||||||
p.opts.pollingLimitPerHost = parseInt(value, 0)
|
|
||||||
case "POLLING_PARSING_ERROR_LIMIT":
|
|
||||||
p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit)
|
|
||||||
case "POLLING_SCHEDULER":
|
|
||||||
p.opts.pollingScheduler = strings.ToLower(parseString(value, defaultPollingScheduler))
|
|
||||||
case "SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL":
|
|
||||||
p.opts.schedulerEntryFrequencyMaxInterval = parseInterval(value, time.Minute, defaultSchedulerEntryFrequencyMaxInterval)
|
|
||||||
case "SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL":
|
|
||||||
p.opts.schedulerEntryFrequencyMinInterval = parseInterval(value, time.Minute, defaultSchedulerEntryFrequencyMinInterval)
|
|
||||||
case "SCHEDULER_ENTRY_FREQUENCY_FACTOR":
|
|
||||||
p.opts.schedulerEntryFrequencyFactor = parseInt(value, defaultSchedulerEntryFrequencyFactor)
|
|
||||||
case "SCHEDULER_ROUND_ROBIN_MIN_INTERVAL":
|
|
||||||
p.opts.schedulerRoundRobinMinInterval = parseInterval(value, time.Minute, defaultSchedulerRoundRobinMinInterval)
|
|
||||||
case "SCHEDULER_ROUND_ROBIN_MAX_INTERVAL":
|
|
||||||
p.opts.schedulerRoundRobinMaxInterval = parseInterval(value, time.Minute, defaultSchedulerRoundRobinMaxInterval)
|
|
||||||
case "MEDIA_PROXY_HTTP_CLIENT_TIMEOUT":
|
|
||||||
p.opts.mediaProxyHTTPClientTimeout = parseInterval(value, time.Second, defaultMediaProxyHTTPClientTimeout)
|
|
||||||
case "MEDIA_PROXY_MODE":
|
|
||||||
p.opts.mediaProxyMode = parseString(value, defaultMediaProxyMode)
|
|
||||||
case "MEDIA_PROXY_RESOURCE_TYPES":
|
|
||||||
p.opts.mediaProxyResourceTypes = parseStringList(value, []string{defaultMediaResourceTypes})
|
|
||||||
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, err = url.Parse(parseString(value, defaultMediaProxyURL))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("config: invalid MEDIA_PROXY_CUSTOM_URL value: %w", err)
|
|
||||||
}
|
|
||||||
case "CREATE_ADMIN":
|
|
||||||
p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
|
|
||||||
case "ADMIN_USERNAME":
|
|
||||||
p.opts.adminUsername = parseString(value, defaultAdminUsername)
|
|
||||||
case "ADMIN_USERNAME_FILE":
|
|
||||||
p.opts.adminUsername = readSecretFile(value, defaultAdminUsername)
|
|
||||||
case "ADMIN_PASSWORD":
|
|
||||||
p.opts.adminPassword = parseString(value, defaultAdminPassword)
|
|
||||||
case "ADMIN_PASSWORD_FILE":
|
|
||||||
p.opts.adminPassword = readSecretFile(value, defaultAdminPassword)
|
|
||||||
case "OAUTH2_USER_CREATION":
|
|
||||||
p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation)
|
|
||||||
case "OAUTH2_CLIENT_ID":
|
|
||||||
p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID)
|
|
||||||
case "OAUTH2_CLIENT_ID_FILE":
|
|
||||||
p.opts.oauth2ClientID = readSecretFile(value, defaultOAuth2ClientID)
|
|
||||||
case "OAUTH2_CLIENT_SECRET":
|
|
||||||
p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
|
|
||||||
case "OAUTH2_CLIENT_SECRET_FILE":
|
|
||||||
p.opts.oauth2ClientSecret = readSecretFile(value, defaultOAuth2ClientSecret)
|
|
||||||
case "OAUTH2_REDIRECT_URL":
|
|
||||||
p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
|
|
||||||
case "OAUTH2_OIDC_DISCOVERY_ENDPOINT":
|
|
||||||
p.opts.oidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint)
|
|
||||||
case "OAUTH2_OIDC_PROVIDER_NAME":
|
|
||||||
p.opts.oidcProviderName = parseString(value, defaultOauth2OidcProviderName)
|
|
||||||
case "OAUTH2_PROVIDER":
|
|
||||||
p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
|
|
||||||
case "DISABLE_LOCAL_AUTH":
|
|
||||||
p.opts.disableLocalAuth = parseBool(value, defaultDisableLocalAuth)
|
|
||||||
case "HTTP_CLIENT_TIMEOUT":
|
|
||||||
p.opts.httpClientTimeout = parseInterval(value, time.Second, defaultHTTPClientTimeout)
|
|
||||||
case "HTTP_CLIENT_MAX_BODY_SIZE":
|
|
||||||
p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024)
|
|
||||||
case "HTTP_CLIENT_PROXY":
|
|
||||||
p.opts.httpClientProxyURL, err = url.Parse(parseString(value, defaultHTTPClientProxy))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("config: invalid HTTP_CLIENT_PROXY value: %w", err)
|
|
||||||
}
|
|
||||||
case "HTTP_CLIENT_PROXIES":
|
|
||||||
p.opts.httpClientProxies = parseStringList(value, []string{})
|
|
||||||
case "HTTP_CLIENT_USER_AGENT":
|
|
||||||
p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
|
|
||||||
case "HTTP_SERVER_TIMEOUT":
|
|
||||||
p.opts.httpServerTimeout = parseInterval(value, time.Second, defaultHTTPServerTimeout)
|
|
||||||
case "AUTH_PROXY_HEADER":
|
|
||||||
p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader)
|
|
||||||
case "AUTH_PROXY_USER_CREATION":
|
|
||||||
p.opts.authProxyUserCreation = parseBool(value, defaultAuthProxyUserCreation)
|
|
||||||
case "MAINTENANCE_MODE":
|
|
||||||
p.opts.maintenanceMode = parseBool(value, defaultMaintenanceMode)
|
|
||||||
case "MAINTENANCE_MESSAGE":
|
|
||||||
p.opts.maintenanceMessage = parseString(value, defaultMaintenanceMessage)
|
|
||||||
case "METRICS_COLLECTOR":
|
|
||||||
p.opts.metricsCollector = parseBool(value, defaultMetricsCollector)
|
|
||||||
case "METRICS_REFRESH_INTERVAL":
|
|
||||||
p.opts.metricsRefreshInterval = parseInterval(value, time.Second, defaultMetricsRefreshInterval)
|
|
||||||
case "METRICS_ALLOWED_NETWORKS":
|
|
||||||
p.opts.metricsAllowedNetworks = parseStringList(value, []string{defaultMetricsAllowedNetworks})
|
|
||||||
case "METRICS_USERNAME":
|
|
||||||
p.opts.metricsUsername = parseString(value, defaultMetricsUsername)
|
|
||||||
case "METRICS_USERNAME_FILE":
|
|
||||||
p.opts.metricsUsername = readSecretFile(value, defaultMetricsUsername)
|
|
||||||
case "METRICS_PASSWORD":
|
|
||||||
p.opts.metricsPassword = parseString(value, defaultMetricsPassword)
|
|
||||||
case "METRICS_PASSWORD_FILE":
|
|
||||||
p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
|
|
||||||
case "FETCH_BILIBILI_WATCH_TIME":
|
|
||||||
p.opts.fetchBilibiliWatchTime = parseBool(value, defaultFetchBilibiliWatchTime)
|
|
||||||
case "FETCH_NEBULA_WATCH_TIME":
|
|
||||||
p.opts.fetchNebulaWatchTime = parseBool(value, defaultFetchNebulaWatchTime)
|
|
||||||
case "FETCH_ODYSEE_WATCH_TIME":
|
|
||||||
p.opts.fetchOdyseeWatchTime = parseBool(value, defaultFetchOdyseeWatchTime)
|
|
||||||
case "FETCH_YOUTUBE_WATCH_TIME":
|
|
||||||
p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
|
|
||||||
case "YOUTUBE_API_KEY":
|
|
||||||
p.opts.youTubeApiKey = parseString(value, defaultYouTubeApiKey)
|
|
||||||
case "YOUTUBE_EMBED_URL_OVERRIDE":
|
|
||||||
p.opts.youTubeEmbedUrlOverride = parseString(value, defaultYouTubeEmbedUrlOverride)
|
|
||||||
case "WATCHDOG":
|
|
||||||
p.opts.watchdog = parseBool(value, defaultWatchdog)
|
|
||||||
case "INVIDIOUS_INSTANCE":
|
|
||||||
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
|
|
||||||
case "WEBAUTHN":
|
|
||||||
p.opts.webAuthn = parseBool(value, defaultWebAuthn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if port != "" {
|
|
||||||
p.opts.listenAddr = []string{":" + port}
|
|
||||||
}
|
|
||||||
|
|
||||||
youtubeEmbedURL, err := url.Parse(p.opts.youTubeEmbedUrlOverride)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config: invalid YOUTUBE_EMBED_URL_OVERRIDE value: %w", err)
|
return fmt.Errorf("invalid BASE_URL: %v", err)
|
||||||
}
|
|
||||||
p.opts.youTubeEmbedDomain = youtubeEmbedURL.Hostname()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBaseURL(value string) (string, string, string, error) {
|
|
||||||
if value == "" {
|
|
||||||
return defaultBaseURL, defaultRootURL, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
value = strings.TrimSuffix(value, "/")
|
|
||||||
|
|
||||||
parsedURL, err := url.Parse(value)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", fmt.Errorf("config: invalid BASE_URL: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := strings.ToLower(parsedURL.Scheme)
|
scheme := strings.ToLower(parsedURL.Scheme)
|
||||||
if scheme != "https" && scheme != "http" {
|
if scheme != "https" && scheme != "http" {
|
||||||
return "", "", "", errors.New("config: invalid BASE_URL: scheme must be http or https")
|
return fmt.Errorf("BASE_URL scheme must be http or https")
|
||||||
}
|
}
|
||||||
|
|
||||||
basePath := parsedURL.Path
|
cp.options.options["BASE_URL"].ParsedStringValue = baseURL
|
||||||
|
cp.options.basePath = parsedURL.Path
|
||||||
|
|
||||||
parsedURL.Path = ""
|
parsedURL.Path = ""
|
||||||
return value, parsedURL.String(), basePath, nil
|
cp.options.rootURL = parsedURL.String()
|
||||||
|
|
||||||
|
// Parse YouTube embed domain based on YOUTUBE_EMBED_URL_OVERRIDE
|
||||||
|
youTubeEmbedURLOverride := cp.options.options["YOUTUBE_EMBED_URL_OVERRIDE"].ParsedStringValue
|
||||||
|
if youTubeEmbedURLOverride != "" {
|
||||||
|
parsedYouTubeEmbedURL, err := url.Parse(youTubeEmbedURLOverride)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid YOUTUBE_EMBED_URL_OVERRIDE: %v", err)
|
||||||
|
}
|
||||||
|
cp.options.youTubeEmbedDomain = parsedYouTubeEmbedURL.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a media proxy private key if not set
|
||||||
|
if len(cp.options.options["MEDIA_PROXY_PRIVATE_KEY"].ParsedBytesValue) == 0 {
|
||||||
|
randomKey := make([]byte, 16)
|
||||||
|
rand.Read(randomKey)
|
||||||
|
cp.options.options["MEDIA_PROXY_PRIVATE_KEY"].ParsedBytesValue = randomKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override LISTEN_ADDR with PORT if set (for compatibility reasons)
|
||||||
|
if cp.options.Port() != "" {
|
||||||
|
cp.options.options["LISTEN_ADDR"].ParsedStringList = []string{":" + cp.options.Port()}
|
||||||
|
cp.options.options["LISTEN_ADDR"].RawValue = ":" + cp.options.Port()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBool(value string, fallback bool) bool {
|
func (cp *configParser) parseLines(lines []string) error {
|
||||||
|
for lineNum, line := range lines {
|
||||||
|
key, value, ok := strings.Cut(line, "=")
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unable to parse configuration, invalid format on line %d", lineNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, value = strings.TrimSpace(key), strings.TrimSpace(value)
|
||||||
|
if err := cp.parseLine(key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cp.postParsing(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *configParser) parseLine(key, value string) error {
|
||||||
|
field, exists := cp.options.options[key]
|
||||||
|
if !exists {
|
||||||
|
// Ignore unknown configuration keys to avoid parsing unrelated environment variables.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the option if a validator is provided
|
||||||
|
if field.Validator != nil {
|
||||||
|
if err := field.Validator(value); err != nil {
|
||||||
|
return fmt.Errorf("invalid value for key %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the raw value based on its type
|
||||||
|
switch field.ValueType {
|
||||||
|
case stringType:
|
||||||
|
field.ParsedStringValue = parseStringValue(value, field.ParsedStringValue)
|
||||||
|
field.RawValue = value
|
||||||
|
case stringListType:
|
||||||
|
field.ParsedStringList = parseStringListValue(value, field.ParsedStringList)
|
||||||
|
field.RawValue = value
|
||||||
|
case boolType:
|
||||||
|
parsedValue, err := parseBoolValue(value, field.ParsedBoolValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid boolean value for key %s: %v", key, err)
|
||||||
|
}
|
||||||
|
field.ParsedBoolValue = parsedValue
|
||||||
|
field.RawValue = value
|
||||||
|
case intType:
|
||||||
|
field.ParsedIntValue = parseIntValue(value, field.ParsedIntValue)
|
||||||
|
field.RawValue = value
|
||||||
|
case int64Type:
|
||||||
|
field.ParsedInt64Value = ParsedInt64Value(value, field.ParsedInt64Value)
|
||||||
|
field.RawValue = value
|
||||||
|
case secondType:
|
||||||
|
field.ParsedDuration = parseDurationValue(value, time.Second, field.ParsedDuration)
|
||||||
|
field.RawValue = value
|
||||||
|
case minuteType:
|
||||||
|
field.ParsedDuration = parseDurationValue(value, time.Minute, field.ParsedDuration)
|
||||||
|
field.RawValue = value
|
||||||
|
case hourType:
|
||||||
|
field.ParsedDuration = parseDurationValue(value, time.Hour, field.ParsedDuration)
|
||||||
|
field.RawValue = value
|
||||||
|
case dayType:
|
||||||
|
field.ParsedDuration = parseDurationValue(value, time.Hour*24, field.ParsedDuration)
|
||||||
|
field.RawValue = value
|
||||||
|
case urlType:
|
||||||
|
parsedURL, err := parseURLValue(value, field.ParsedURLValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid URL for key %s: %v", key, err)
|
||||||
|
}
|
||||||
|
field.ParsedURLValue = parsedURL
|
||||||
|
field.RawValue = value
|
||||||
|
case secretFileType:
|
||||||
|
secretValue, err := readSecretFileValue(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading secret file for key %s: %v", key, err)
|
||||||
|
}
|
||||||
|
if field.TargetKey != "" {
|
||||||
|
if targetField, ok := cp.options.options[field.TargetKey]; ok {
|
||||||
|
targetField.ParsedStringValue = secretValue
|
||||||
|
targetField.RawValue = secretValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.RawValue = value
|
||||||
|
case bytesType:
|
||||||
|
if value != "" {
|
||||||
|
field.ParsedBytesValue = []byte(value)
|
||||||
|
field.RawValue = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStringValue(value string, fallback string) string {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBoolValue(value string, fallback bool) (bool, error) {
|
||||||
|
if value == "" {
|
||||||
|
return fallback, nil
|
||||||
|
}
|
||||||
|
|
||||||
value = strings.ToLower(value)
|
value = strings.ToLower(value)
|
||||||
if value == "1" || value == "yes" || value == "true" || value == "on" {
|
if value == "1" || value == "yes" || value == "true" || value == "on" {
|
||||||
return true
|
return true, nil
|
||||||
|
}
|
||||||
|
if value == "0" || value == "no" || value == "false" || value == "off" {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false, fmt.Errorf("invalid boolean value: %q", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInt(value string, fallback int) int {
|
func parseIntValue(value string, fallback int) int {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
@ -324,14 +227,20 @@ func parseInt(value string, fallback int) int {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseString(value string, fallback string) string {
|
func ParsedInt64Value(value string, fallback int64) int64 {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
return value
|
|
||||||
|
v, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStringList(value string, fallback []string) []string {
|
func parseStringListValue(value string, fallback []string) []string {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
@ -351,16 +260,7 @@ func parseStringList(value string, fallback []string) []string {
|
||||||
return strList
|
return strList
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBytes(value string, fallback []byte) []byte {
|
func parseDurationValue(value string, unit time.Duration, fallback time.Duration) time.Duration {
|
||||||
if value == "" {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseInterval converts an integer "value" to [time.Duration] using "unit" as multiplier.
|
|
||||||
func parseInterval(value string, unit time.Duration, fallback time.Duration) time.Duration {
|
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
@ -373,16 +273,40 @@ func parseInterval(value string, unit time.Duration, fallback time.Duration) tim
|
||||||
return time.Duration(v) * unit
|
return time.Duration(v) * unit
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSecretFile(filename, fallback string) string {
|
func parseURLValue(value string, fallback *url.URL) (*url.URL, error) {
|
||||||
|
if value == "" {
|
||||||
|
return fallback, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(value)
|
||||||
|
if err != nil {
|
||||||
|
return fallback, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSecretFileValue(filename string) (string, error) {
|
||||||
data, err := os.ReadFile(filename)
|
data, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fallback
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
value := string(bytes.TrimSpace(data))
|
value := string(bytes.TrimSpace(data))
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return "", fmt.Errorf("secret file is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFileContent(r io.Reader) (lines []string) {
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,163 +4,432 @@
|
||||||
package config // import "miniflux.app/v2/internal/config"
|
package config // import "miniflux.app/v2/internal/config"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseBoolValue(t *testing.T) {
|
func TestParseStringValue(t *testing.T) {
|
||||||
scenarios := map[string]bool{
|
// Test with non-empty value
|
||||||
"": true,
|
result := parseStringValue("test", "fallback")
|
||||||
"1": true,
|
if result != "test" {
|
||||||
"Yes": true,
|
t.Errorf("Expected 'test', got '%s'", result)
|
||||||
"yes": true,
|
|
||||||
"True": true,
|
|
||||||
"true": true,
|
|
||||||
"on": true,
|
|
||||||
"false": false,
|
|
||||||
"off": false,
|
|
||||||
"invalid": false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for input, expected := range scenarios {
|
// Test with empty value
|
||||||
result := parseBool(input, true)
|
result = parseStringValue("", "fallback")
|
||||||
if result != expected {
|
if result != "fallback" {
|
||||||
t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected)
|
t.Errorf("Expected 'fallback', got '%s'", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with empty value and empty fallback
|
||||||
|
result = parseStringValue("", "")
|
||||||
|
if result != "" {
|
||||||
|
t.Errorf("Expected empty string, got '%s'", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBoolValue(t *testing.T) {
|
||||||
|
// Test with empty value - should return fallback
|
||||||
|
result, err := parseBoolValue("", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result != true {
|
||||||
|
t.Errorf("Expected true, got %v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test true values
|
||||||
|
trueValues := []string{"1", "yes", "true", "on", "YES", "TRUE", "ON"}
|
||||||
|
for _, value := range trueValues {
|
||||||
|
result, err := parseBoolValue(value, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for value '%s': %v", value, err)
|
||||||
|
}
|
||||||
|
if result != true {
|
||||||
|
t.Errorf("Expected true for '%s', got %v", value, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStringValueWithUnsetVariable(t *testing.T) {
|
// Test false values
|
||||||
if parseString("", "defaultValue") != "defaultValue" {
|
falseValues := []string{"0", "no", "false", "off", "NO", "FALSE", "OFF"}
|
||||||
t.Errorf(`Unset variables should returns the default value`)
|
for _, value := range falseValues {
|
||||||
|
result, err := parseBoolValue(value, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error for value '%s': %v", value, err)
|
||||||
|
}
|
||||||
|
if result != false {
|
||||||
|
t.Errorf("Expected false for '%s', got %v", value, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStringValue(t *testing.T) {
|
// Test invalid value - should return error
|
||||||
if parseString("test", "defaultValue") != "test" {
|
_, err = parseBoolValue("invalid", false)
|
||||||
t.Errorf(`Defined variables should returns the specified value`)
|
if err == nil {
|
||||||
}
|
t.Error("Expected error for invalid boolean value")
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseIntValueWithUnsetVariable(t *testing.T) {
|
|
||||||
if parseInt("", 42) != 42 {
|
|
||||||
t.Errorf(`Unset variables should returns the default value`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseIntValueWithInvalidInput(t *testing.T) {
|
|
||||||
if parseInt("invalid integer", 42) != 42 {
|
|
||||||
t.Errorf(`Invalid integer should returns the default value`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseIntValue(t *testing.T) {
|
func TestParseIntValue(t *testing.T) {
|
||||||
if parseInt("2018", 42) != 2018 {
|
// Test with empty value - should return fallback
|
||||||
t.Errorf(`Defined variables should returns the specified value`)
|
result := parseIntValue("", 42)
|
||||||
|
if result != 42 {
|
||||||
|
t.Errorf("Expected 42, got %d", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with valid integer
|
||||||
|
result = parseIntValue("123", 42)
|
||||||
|
if result != 123 {
|
||||||
|
t.Errorf("Expected 123, got %d", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with invalid integer - should return fallback
|
||||||
|
result = parseIntValue("invalid", 42)
|
||||||
|
if result != 42 {
|
||||||
|
t.Errorf("Expected 42, got %d", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with zero
|
||||||
|
result = parseIntValue("0", 42)
|
||||||
|
if result != 0 {
|
||||||
|
t.Errorf("Expected 0, got %d", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseListenAddr(t *testing.T) {
|
func TestParsedInt64Value(t *testing.T) {
|
||||||
defaultExpected := []string{defaultListenAddr}
|
// Test with empty value - should return fallback
|
||||||
|
result := ParsedInt64Value("", 42)
|
||||||
tests := []struct {
|
if result != 42 {
|
||||||
name string
|
t.Errorf("Expected 42, got %d", result)
|
||||||
listenAddr string
|
|
||||||
port string
|
|
||||||
expected []string
|
|
||||||
lines []string // Used for direct lines parsing instead of individual env vars
|
|
||||||
isLineOriented bool // Flag to indicate if we use lines
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Single LISTEN_ADDR",
|
|
||||||
listenAddr: "127.0.0.1:8080",
|
|
||||||
expected: []string{"127.0.0.1:8080"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Multiple LISTEN_ADDR comma-separated",
|
|
||||||
listenAddr: "127.0.0.1:8080,:8081,/tmp/miniflux.sock",
|
|
||||||
expected: []string{"127.0.0.1:8080", ":8081", "/tmp/miniflux.sock"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Multiple LISTEN_ADDR with spaces around commas",
|
|
||||||
listenAddr: "127.0.0.1:8080 , :8081",
|
|
||||||
expected: []string{"127.0.0.1:8080", ":8081"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty LISTEN_ADDR",
|
|
||||||
listenAddr: "",
|
|
||||||
expected: defaultExpected,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PORT overrides LISTEN_ADDR",
|
|
||||||
listenAddr: "127.0.0.1:8000",
|
|
||||||
port: "8082",
|
|
||||||
expected: []string{":8082"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PORT overrides empty LISTEN_ADDR",
|
|
||||||
listenAddr: "",
|
|
||||||
port: "8083",
|
|
||||||
expected: []string{":8083"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LISTEN_ADDR with empty segment (comma)",
|
|
||||||
listenAddr: "127.0.0.1:8080,,:8081",
|
|
||||||
expected: []string{"127.0.0.1:8080", ":8081"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PORT override with lines parsing",
|
|
||||||
isLineOriented: true,
|
|
||||||
lines: []string{"LISTEN_ADDR=127.0.0.1:8000", "PORT=8082"},
|
|
||||||
expected: []string{":8082"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LISTEN_ADDR only with lines parsing (comma)",
|
|
||||||
isLineOriented: true,
|
|
||||||
lines: []string{"LISTEN_ADDR=10.0.0.1:9090,10.0.0.2:9091"},
|
|
||||||
expected: []string{"10.0.0.1:9090", "10.0.0.2:9091"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty LISTEN_ADDR with lines parsing (default)",
|
|
||||||
isLineOriented: true,
|
|
||||||
lines: []string{"LISTEN_ADDR="},
|
|
||||||
expected: defaultExpected,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
// Test with valid int64
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
result = ParsedInt64Value("9223372036854775807", 42)
|
||||||
parser := NewParser()
|
if result != 9223372036854775807 {
|
||||||
var err error
|
t.Errorf("Expected 9223372036854775807, got %d", result)
|
||||||
|
}
|
||||||
|
|
||||||
if tt.isLineOriented {
|
// Test with invalid int64 - should return fallback
|
||||||
err = parser.parseLines(tt.lines)
|
result = ParsedInt64Value("invalid", 42)
|
||||||
} else {
|
if result != 42 {
|
||||||
// Simulate os.Environ() behaviour for individual var testing
|
t.Errorf("Expected 42, got %d", result)
|
||||||
var envLines []string
|
}
|
||||||
if tt.listenAddr != "" {
|
}
|
||||||
envLines = append(envLines, "LISTEN_ADDR="+tt.listenAddr)
|
|
||||||
}
|
func TestParseStringListValue(t *testing.T) {
|
||||||
if tt.port != "" {
|
// Test with empty value - should return fallback
|
||||||
envLines = append(envLines, "PORT="+tt.port)
|
fallback := []string{"a", "b"}
|
||||||
}
|
result := parseStringListValue("", fallback)
|
||||||
// Add a dummy var if both are empty to avoid empty lines slice if not intended
|
if !reflect.DeepEqual(result, fallback) {
|
||||||
if tt.listenAddr == "" && tt.port == "" && tt.name == "Empty LISTEN_ADDR" {
|
t.Errorf("Expected %v, got %v", fallback, result)
|
||||||
// This case specifically tests empty LISTEN_ADDR resulting in default
|
}
|
||||||
// So, we pass LISTEN_ADDR=
|
|
||||||
envLines = append(envLines, "LISTEN_ADDR=")
|
// Test with single value
|
||||||
}
|
result = parseStringListValue("item1", nil)
|
||||||
err = parser.parseLines(envLines)
|
expected := []string{"item1"}
|
||||||
}
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, result)
|
||||||
if err != nil {
|
}
|
||||||
t.Fatalf("parseLines() error = %v", err)
|
|
||||||
}
|
// Test with multiple values
|
||||||
|
result = parseStringListValue("item1,item2,item3", nil)
|
||||||
opts := parser.opts
|
expected = []string{"item1", "item2", "item3"}
|
||||||
if !reflect.DeepEqual(opts.ListenAddr(), tt.expected) {
|
if !reflect.DeepEqual(result, expected) {
|
||||||
t.Errorf("ListenAddr() got = %v, want %v", opts.ListenAddr(), tt.expected)
|
t.Errorf("Expected %v, got %v", expected, result)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
// Test with duplicates - should remove duplicates
|
||||||
|
result = parseStringListValue("item1,item2,item1", nil)
|
||||||
|
expected = []string{"item1", "item2"}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with spaces
|
||||||
|
result = parseStringListValue(" item1 , item2 , item3 ", nil)
|
||||||
|
expected = []string{"item1", "item2", "item3"}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDurationValue(t *testing.T) {
|
||||||
|
// Test with empty value - should return fallback
|
||||||
|
fallback := 5 * time.Second
|
||||||
|
result := parseDurationValue("", time.Second, fallback)
|
||||||
|
if result != fallback {
|
||||||
|
t.Errorf("Expected %v, got %v", fallback, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with valid duration
|
||||||
|
result = parseDurationValue("30", time.Second, fallback)
|
||||||
|
expected := 30 * time.Second
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with minutes
|
||||||
|
result = parseDurationValue("5", time.Minute, fallback)
|
||||||
|
expected = 5 * time.Minute
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with invalid value - should return fallback
|
||||||
|
result = parseDurationValue("invalid", time.Second, fallback)
|
||||||
|
if result != fallback {
|
||||||
|
t.Errorf("Expected %v, got %v", fallback, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseURLValue(t *testing.T) {
|
||||||
|
// Test with empty value - should return fallback
|
||||||
|
fallbackURL, _ := url.Parse("https://fallback.com")
|
||||||
|
result, err := parseURLValue("", fallbackURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result != fallbackURL {
|
||||||
|
t.Errorf("Expected %v, got %v", fallbackURL, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with valid URL
|
||||||
|
result, err = parseURLValue("https://example.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result.String() != "https://example.com" {
|
||||||
|
t.Errorf("Expected https://example.com, got %s", result.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with invalid URL - should return fallback and error
|
||||||
|
result, err = parseURLValue("://invalid", fallbackURL)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for invalid URL")
|
||||||
|
}
|
||||||
|
if result != fallbackURL {
|
||||||
|
t.Errorf("Expected fallback URL, got %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFileParsing(t *testing.T) {
|
||||||
|
fileContent := `
|
||||||
|
# This is a comment
|
||||||
|
LOG_FILE=miniflux.log
|
||||||
|
LOG_DATE_TIME=1
|
||||||
|
LOG_FORMAT=json
|
||||||
|
LISTEN_ADDR=:8080,:8443
|
||||||
|
`
|
||||||
|
|
||||||
|
// Write a temporary config file and parse it
|
||||||
|
tmpFile, err := os.CreateTemp("", "miniflux-*.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temporary file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
filename := tmpFile.Name()
|
||||||
|
if _, err := tmpFile.WriteString(fileContent); err != nil {
|
||||||
|
t.Fatalf("Failed to write to temporary file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogFile() != "miniflux.log" {
|
||||||
|
t.Fatalf("Unexpected log file, got %q", configOptions.LogFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogDateTime() != true {
|
||||||
|
t.Fatalf("Unexpected log datetime, got %v", configOptions.LogDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogFormat() != "json" {
|
||||||
|
t.Fatalf("Unexpected log format, got %q", configOptions.LogFormat())
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogLevel() != "info" {
|
||||||
|
t.Fatalf("Unexpected log level, got %q", configOptions.LogLevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(configOptions.ListenAddr()) != 2 || configOptions.ListenAddr()[0] != ":8080" || configOptions.ListenAddr()[1] != ":8443" {
|
||||||
|
t.Fatalf("Unexpected listen addresses, got %v", configOptions.ListenAddr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFileParsingWithIncorrectKeyValuePair(t *testing.T) {
|
||||||
|
fileContent := `
|
||||||
|
LOG_FILE=miniflux.log
|
||||||
|
INVALID_LINE
|
||||||
|
`
|
||||||
|
|
||||||
|
// Write a temporary config file and parse it
|
||||||
|
tmpFile, err := os.CreateTemp("", "miniflux-*.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temporary file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
filename := tmpFile.Name()
|
||||||
|
if _, err := tmpFile.WriteString(fileContent); err != nil {
|
||||||
|
t.Fatalf("Failed to write to temporary file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
_, err = configParser.ParseFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Invalid lines should be ignored, but got error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAdminPasswordFileOption(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp("", "password-*.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temporary file: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
password := "supersecret"
|
||||||
|
if _, err := tmpFile.WriteString(password); err != nil {
|
||||||
|
t.Fatalf("Failed to write to temporary file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("ADMIN_PASSWORD_FILE", tmpFile.Name())
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.AdminPassword() != password {
|
||||||
|
t.Fatalf("Unexpected admin password, got %q", configOptions.AdminPassword())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAdminPasswordFileOptionWithEmptyFile(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp("", "empty-password-*.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temporary file: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("ADMIN_PASSWORD_FILE", tmpFile.Name())
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
_, err = configParser.ParseEnvironmentVariables()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error due to empty password file, but got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogFileOptionDefaultValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogFile() != "stderr" {
|
||||||
|
t.Fatalf("Unexpected default log file, got %q", configOptions.LogFile())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogFileOptionWithCustomFilename(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("LOG_FILE", "miniflux.log")
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogFile() != "miniflux.log" {
|
||||||
|
t.Fatalf("Unexpected log file, got %q", configOptions.LogFile())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogFileOptionWithEmptyValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("LOG_FILE", "")
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogFile() != "stderr" {
|
||||||
|
t.Fatalf("Unexpected log file, got %q", configOptions.LogFile())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogDateTimeOptionDefaultValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogDateTime() != false {
|
||||||
|
t.Fatalf("Unexpected default log datetime, got %v", configOptions.LogDateTime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogDateTimeOptionWithCustomValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("LOG_DATE_TIME", "true")
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogDateTime() != true {
|
||||||
|
t.Fatalf("Unexpected log datetime, got %v", configOptions.LogDateTime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogDateTimeOptionWithEmptyValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("LOG_DATE_TIME", "")
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parsing error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configOptions.LogDateTime() != false {
|
||||||
|
t.Fatalf("Unexpected log datetime, got %v", configOptions.LogDateTime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogDateTimeOptionWithIncorrectValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("LOG_DATE_TIME", "invalid")
|
||||||
|
|
||||||
|
configParser := NewConfigParser()
|
||||||
|
if _, err := configParser.ParseEnvironmentVariables(); err == nil {
|
||||||
|
t.Fatal("Expected parsing error, got nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
internal/config/validators.go
Normal file
60
internal/config/validators.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package config // import "miniflux.app/v2/internal/config"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateChoices(rawValue string, choices []string) error {
|
||||||
|
if !slices.Contains(choices, rawValue) {
|
||||||
|
return fmt.Errorf("value must be one of: %v", strings.Join(choices, ", "))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateListChoices(inputValues, choices []string) error {
|
||||||
|
for _, value := range inputValues {
|
||||||
|
if !slices.Contains(choices, value) {
|
||||||
|
return fmt.Errorf("value must be one of: %v", strings.Join(choices, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateGreaterThan(rawValue string, min int) error {
|
||||||
|
intValue, err := strconv.Atoi(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("value must be an integer")
|
||||||
|
}
|
||||||
|
if intValue > min {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("value must be at least %d", min)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateGreaterOrEqualThan(rawValue string, min int) error {
|
||||||
|
intValue, err := strconv.Atoi(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("value must be an integer")
|
||||||
|
}
|
||||||
|
if intValue >= min {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("value must be greater or equal than %d", min)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRange(rawValue string, min, max int) error {
|
||||||
|
intValue, err := strconv.Atoi(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("value must be an integer")
|
||||||
|
}
|
||||||
|
if intValue < min || intValue > max {
|
||||||
|
return fmt.Errorf("value must be between %d and %d", min, max)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
372
internal/config/validators_test.go
Normal file
372
internal/config/validators_test.go
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package config // import "miniflux.app/v2/internal/config"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateChoices(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rawValue string
|
||||||
|
choices []string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid choice",
|
||||||
|
rawValue: "option1",
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid choice from middle",
|
||||||
|
rawValue: "option2",
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid choice from end",
|
||||||
|
rawValue: "option3",
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid choice",
|
||||||
|
rawValue: "invalid",
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty value with non-empty choices",
|
||||||
|
rawValue: "",
|
||||||
|
choices: []string{"option1", "option2"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case sensitive - different case",
|
||||||
|
rawValue: "OPTION1",
|
||||||
|
choices: []string{"option1", "option2"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single choice valid",
|
||||||
|
rawValue: "only",
|
||||||
|
choices: []string{"only"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty choices list",
|
||||||
|
rawValue: "anything",
|
||||||
|
choices: []string{},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateChoices(tt.rawValue, tt.choices)
|
||||||
|
if tt.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error but got none")
|
||||||
|
} else {
|
||||||
|
// Verify error message format
|
||||||
|
expectedPrefix := "value must be one of:"
|
||||||
|
if !strings.Contains(err.Error(), expectedPrefix) {
|
||||||
|
t.Errorf("error message should contain '%s', got: %s", expectedPrefix, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateListChoices(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inputValues []string
|
||||||
|
choices []string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all valid choices",
|
||||||
|
inputValues: []string{"option1", "option2"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single valid choice",
|
||||||
|
inputValues: []string{"option1"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty input list",
|
||||||
|
inputValues: []string{},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all choices from available list",
|
||||||
|
inputValues: []string{"option1", "option2", "option3"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate valid choices",
|
||||||
|
inputValues: []string{"option1", "option1", "option2"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one invalid choice",
|
||||||
|
inputValues: []string{"option1", "invalid"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all invalid choices",
|
||||||
|
inputValues: []string{"invalid1", "invalid2"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case sensitive - different case",
|
||||||
|
inputValues: []string{"OPTION1"},
|
||||||
|
choices: []string{"option1", "option2"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string in input",
|
||||||
|
inputValues: []string{""},
|
||||||
|
choices: []string{"option1", "option2"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty choices list with non-empty input",
|
||||||
|
inputValues: []string{"anything"},
|
||||||
|
choices: []string{},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed valid and invalid choices",
|
||||||
|
inputValues: []string{"option1", "invalid", "option2"},
|
||||||
|
choices: []string{"option1", "option2", "option3"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateListChoices(tt.inputValues, tt.choices)
|
||||||
|
if tt.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error but got none")
|
||||||
|
} else {
|
||||||
|
// Verify error message format
|
||||||
|
expectedPrefix := "value must be one of:"
|
||||||
|
if !strings.Contains(err.Error(), expectedPrefix) {
|
||||||
|
t.Errorf("error message should contain '%s', got: %s", expectedPrefix, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateGreaterThan(t *testing.T) {
|
||||||
|
if err := validateGreaterThan("10", 5); err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateGreaterThan("5", 5); err == nil {
|
||||||
|
t.Errorf("expected error, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateGreaterThan("abc", 5); err == nil {
|
||||||
|
t.Errorf("expected error for non-integer input, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateGreaterThan("-1", 0); err == nil {
|
||||||
|
t.Errorf("expected error for value below minimum, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateGreaterOrEqualThan(t *testing.T) {
|
||||||
|
if err := validateGreaterOrEqualThan("10", 5); err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateGreaterOrEqualThan("5", 5); err != nil {
|
||||||
|
t.Errorf("expected no error for equal value, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateGreaterOrEqualThan("abc", 5); err == nil {
|
||||||
|
t.Errorf("expected error for non-integer input, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateGreaterOrEqualThan("-1", 0); err == nil {
|
||||||
|
t.Errorf("expected error for value below minimum, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateRange(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rawValue string
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
expectError bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid integer within range",
|
||||||
|
rawValue: "5",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid integer at minimum",
|
||||||
|
rawValue: "1",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid integer at maximum",
|
||||||
|
rawValue: "10",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid zero in range",
|
||||||
|
rawValue: "0",
|
||||||
|
min: -5,
|
||||||
|
max: 5,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid negative in range",
|
||||||
|
rawValue: "-3",
|
||||||
|
min: -5,
|
||||||
|
max: 5,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "integer below minimum",
|
||||||
|
rawValue: "0",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be between 1 and 10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "integer above maximum",
|
||||||
|
rawValue: "11",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be between 1 and 10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "integer far below minimum",
|
||||||
|
rawValue: "-100",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be between 1 and 10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "integer far above maximum",
|
||||||
|
rawValue: "100",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be between 1 and 10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-integer string",
|
||||||
|
rawValue: "abc",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be an integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
rawValue: "",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be an integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float string",
|
||||||
|
rawValue: "5.5",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be an integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string with spaces",
|
||||||
|
rawValue: " 5 ",
|
||||||
|
min: 1,
|
||||||
|
max: 10,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be an integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single value range",
|
||||||
|
rawValue: "5",
|
||||||
|
min: 5,
|
||||||
|
max: 5,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single value range - below",
|
||||||
|
rawValue: "4",
|
||||||
|
min: 5,
|
||||||
|
max: 5,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be between 5 and 5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single value range - above",
|
||||||
|
rawValue: "6",
|
||||||
|
min: 5,
|
||||||
|
max: 5,
|
||||||
|
expectError: true,
|
||||||
|
errorMsg: "value must be between 5 and 5",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateRange(tt.rawValue, tt.min, tt.max)
|
||||||
|
if tt.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error but got none")
|
||||||
|
} else if tt.errorMsg != "" && err.Error() != tt.errorMsg {
|
||||||
|
t.Errorf("expected error message '%s', got '%s'", tt.errorMsg, err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ func StartWebServer(store *storage.Storage, pool *worker.Pool) []*http.Server {
|
||||||
slog.Error("ACME HTTP challenge server failed", slog.Any("error", err))
|
slog.Error("ACME HTTP challenge server failed", slog.Any("error", err))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
config.Opts.HTTPS = true
|
config.Opts.SetHTTPSValue(true)
|
||||||
httpServers = append(httpServers, challengeServer)
|
httpServers = append(httpServers, challengeServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func StartWebServer(store *storage.Storage, pool *worker.Pool) []*http.Server {
|
||||||
case certFile != "" && keyFile != "":
|
case certFile != "" && keyFile != "":
|
||||||
server.Addr = listenAddr
|
server.Addr = listenAddr
|
||||||
startTLSServer(server, certFile, keyFile)
|
startTLSServer(server, certFile, keyFile)
|
||||||
config.Opts.HTTPS = true
|
config.Opts.SetHTTPSValue(true)
|
||||||
default:
|
default:
|
||||||
server.Addr = listenAddr
|
server.Addr = listenAddr
|
||||||
startHTTPServer(server)
|
startHTTPServer(server)
|
||||||
|
@ -148,7 +148,7 @@ func startUnixSocketServer(server *http.Server, socketFile string) {
|
||||||
slog.String("key_file", keyFile),
|
slog.String("key_file", keyFile),
|
||||||
)
|
)
|
||||||
// Ensure HTTPS is marked as true if any listener uses TLS
|
// Ensure HTTPS is marked as true if any listener uses TLS
|
||||||
config.Opts.HTTPS = true
|
config.Opts.SetHTTPSValue(true)
|
||||||
if err := server.ServeTLS(listener, certFile, keyFile); err != http.ErrServerClosed {
|
if err := server.ServeTLS(listener, certFile, keyFile); err != http.ErrServerClosed {
|
||||||
printErrorAndExit("TLS Unix socket server failed to start on %s: %v", socketFile, err)
|
printErrorAndExit("TLS Unix socket server failed to start on %s: %v", socketFile, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func middleware(next http.Handler) http.Handler {
|
||||||
ctx = context.WithValue(ctx, request.ClientIPContextKey, clientIP)
|
ctx = context.WithValue(ctx, request.ClientIPContextKey, clientIP)
|
||||||
|
|
||||||
if r.Header.Get("X-Forwarded-Proto") == "https" {
|
if r.Header.Get("X-Forwarded-Proto") == "https" {
|
||||||
config.Opts.HTTPS = true
|
config.Opts.SetHTTPSValue(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
t1 := time.Now()
|
t1 := time.Now()
|
||||||
|
@ -36,7 +36,7 @@ func middleware(next http.Handler) http.Handler {
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if config.Opts.HTTPS && config.Opts.HasHSTS() {
|
if config.Opts.HTTPS() && config.Opts.HasHSTS() {
|
||||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestProxyFilterWithHttpDefault(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -43,7 +43,7 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -66,7 +66,7 @@ func TestProxyFilterWithHttpNever(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_MODE", "none")
|
os.Setenv("MEDIA_PROXY_MODE", "none")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -89,7 +89,7 @@ func TestProxyFilterWithHttpsNever(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_MODE", "none")
|
os.Setenv("MEDIA_PROXY_MODE", "none")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -114,7 +114,7 @@ func TestProxyFilterWithHttpAlways(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -139,7 +139,7 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -164,7 +164,7 @@ func TestAbsoluteProxyFilterWithHttpsAlways(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -188,7 +188,7 @@ func TestAbsoluteProxyFilterWithCustomPortAndSubfolderInBaseURL(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -226,7 +226,7 @@ func TestAbsoluteProxyFilterWithHttpsAlwaysAndAudioTag(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -251,7 +251,7 @@ func TestProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_CUSTOM_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.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -276,7 +276,7 @@ func TestProxyFilterWithHttpsAlwaysAndIncorrectCustomProxyServer(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_CUSTOM_URL", "http://:8080example.com")
|
os.Setenv("MEDIA_PROXY_CUSTOM_URL", "http://:8080example.com")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf(`Incorrect proxy URL silently accepted (MEDIA_PROXY_CUSTOM_URL=%q): %q`, os.Getenv("MEDIA_PROXY_CUSTOM_URL"), config.Opts.MediaCustomProxyURL())
|
t.Fatalf(`Incorrect proxy URL silently accepted (MEDIA_PROXY_CUSTOM_URL=%q): %q`, os.Getenv("MEDIA_PROXY_CUSTOM_URL"), config.Opts.MediaCustomProxyURL())
|
||||||
|
@ -290,7 +290,7 @@ func TestAbsoluteProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_CUSTOM_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.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -313,46 +313,8 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_MODE", "invalid")
|
os.Setenv("MEDIA_PROXY_MODE", "invalid")
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
if _, err := config.NewConfigParser().ParseEnvironmentVariables(); err == nil {
|
||||||
parser := config.NewParser()
|
t.Fatalf(`Parsing should have failed (MEDIA_PROXY_MODE=%q): %q`, os.Getenv("MEDIA_PROXY_MODE"), config.Opts.MediaProxyMode())
|
||||||
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="http://website/folder/image.png" alt="Test"/></p>`
|
|
||||||
output := RewriteDocumentWithRelativeProxyURL(r, input)
|
|
||||||
expected := `<p><img src="/proxy/okK5PsdNY8F082UMQEAbLPeUFfbe2WnNfInNmR9T4WA=/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
|
|
||||||
|
|
||||||
if expected != output {
|
|
||||||
t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyFilterWithHttpsInvalid(t *testing.T) {
|
|
||||||
os.Clearenv()
|
|
||||||
os.Setenv("MEDIA_PROXY_MODE", "invalid")
|
|
||||||
os.Setenv("MEDIA_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 := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
|
|
||||||
output := RewriteDocumentWithRelativeProxyURL(r, input)
|
|
||||||
expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
|
|
||||||
|
|
||||||
if expected != output {
|
|
||||||
t.Errorf(`Not expected output: got %q instead of %q`, output, expected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +325,7 @@ func TestProxyFilterWithSrcset(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -388,7 +350,7 @@ func TestProxyFilterWithEmptySrcset(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -413,7 +375,7 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -433,12 +395,12 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
|
||||||
|
|
||||||
func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
|
func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("MEDIA_PROXY_MODE", "https")
|
os.Setenv("MEDIA_PROXY_MODE", "http-only")
|
||||||
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -462,7 +424,7 @@ func TestProxyWithImageDataURL(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -486,7 +448,7 @@ func TestProxyWithImageSourceDataURL(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -511,7 +473,7 @@ func TestProxyFilterWithVideo(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -536,7 +498,7 @@ func TestProxyFilterVideoPoster(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -561,7 +523,7 @@ func TestProxyFilterVideoPosterOnce(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
|
|
@ -290,7 +290,7 @@ func TestEnclosure_ProxifyEnclosureURL(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Config parsing failure: %v`, err)
|
t.Fatalf(`Config parsing failure: %v`, err)
|
||||||
|
@ -420,7 +420,7 @@ func TestEnclosureList_ProxifyEnclosureURL(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Config parsing failure: %v`, err)
|
t.Fatalf(`Config parsing failure: %v`, err)
|
||||||
|
@ -535,7 +535,7 @@ func TestEnclosure_ProxifyEnclosureURL_EdgeCases(t *testing.T) {
|
||||||
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
|
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Config parsing failure: %v`, err)
|
t.Fatalf(`Config parsing failure: %v`, err)
|
||||||
|
|
|
@ -80,7 +80,7 @@ func TestFeedScheduleNextCheckRoundRobinDefault(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -102,7 +102,7 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayAboveMinInterval(t *test
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -125,7 +125,7 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayBelowMinInterval(t *test
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -148,7 +148,7 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayAboveMaxInterval(t *test
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -174,7 +174,7 @@ func TestFeedScheduleNextCheckRoundRobinMinInterval(t *testing.T) {
|
||||||
os.Setenv("SCHEDULER_ROUND_ROBIN_MIN_INTERVAL", strconv.Itoa(minInterval))
|
os.Setenv("SCHEDULER_ROUND_ROBIN_MIN_INTERVAL", strconv.Itoa(minInterval))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -201,7 +201,7 @@ func TestFeedScheduleNextCheckEntryFrequencyMaxInterval(t *testing.T) {
|
||||||
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -230,7 +230,7 @@ func TestFeedScheduleNextCheckEntryFrequencyMaxIntervalZeroWeeklyCount(t *testin
|
||||||
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -259,7 +259,7 @@ func TestFeedScheduleNextCheckEntryFrequencyMinInterval(t *testing.T) {
|
||||||
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -286,7 +286,7 @@ func TestFeedScheduleNextCheckEntryFrequencyFactor(t *testing.T) {
|
||||||
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_FACTOR", strconv.Itoa(factor))
|
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_FACTOR", strconv.Itoa(factor))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -315,7 +315,7 @@ func TestFeedScheduleNextCheckEntryFrequencySmallNewTTL(t *testing.T) {
|
||||||
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
@ -351,7 +351,7 @@ func TestFeedScheduleNextCheckEntryFrequencyLargeNewTTL(t *testing.T) {
|
||||||
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
|
|
@ -414,7 +414,7 @@ func TestKeeplistRulesBehavior(t *testing.T) {
|
||||||
// Tests for isBlockedGlobally function
|
// Tests for isBlockedGlobally function
|
||||||
func TestIsBlockedGlobally(t *testing.T) {
|
func TestIsBlockedGlobally(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
config.Opts, err = config.NewParser().ParseEnvironmentVariables()
|
config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
}
|
}
|
||||||
|
@ -429,7 +429,7 @@ func TestIsBlockedGlobally(t *testing.T) {
|
||||||
os.Setenv("FILTER_ENTRY_MAX_AGE_DAYS", "30")
|
os.Setenv("FILTER_ENTRY_MAX_AGE_DAYS", "30")
|
||||||
defer os.Clearenv()
|
defer os.Clearenv()
|
||||||
|
|
||||||
config.Opts, err = config.NewParser().ParseEnvironmentVariables()
|
config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,11 @@ func getVideoIDFromYouTubeURL(websiteURL string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldFetchYouTubeWatchTimeForSingleEntry(entry *model.Entry) bool {
|
func shouldFetchYouTubeWatchTimeForSingleEntry(entry *model.Entry) bool {
|
||||||
return config.Opts.FetchYouTubeWatchTime() && config.Opts.YouTubeApiKey() == "" && isYouTubeVideoURL(entry.URL)
|
return config.Opts.FetchYouTubeWatchTime() && config.Opts.YouTubeAPIKey() == "" && isYouTubeVideoURL(entry.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldFetchYouTubeWatchTimeInBulk() bool {
|
func shouldFetchYouTubeWatchTimeInBulk() bool {
|
||||||
return config.Opts.FetchYouTubeWatchTime() && config.Opts.YouTubeApiKey() != ""
|
return config.Opts.FetchYouTubeWatchTime() && config.Opts.YouTubeAPIKey() != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchYouTubeWatchTimeForSingleEntry(websiteURL string) (int, error) {
|
func fetchYouTubeWatchTimeForSingleEntry(websiteURL string) (int, error) {
|
||||||
|
@ -82,7 +82,7 @@ func fetchYouTubeWatchTimeFromApiInBulk(videoIDs []string) (map[string]time.Dura
|
||||||
|
|
||||||
apiQuery := url.Values{}
|
apiQuery := url.Values{}
|
||||||
apiQuery.Set("id", strings.Join(videoIDs, ","))
|
apiQuery.Set("id", strings.Join(videoIDs, ","))
|
||||||
apiQuery.Set("key", config.Opts.YouTubeApiKey())
|
apiQuery.Set("key", config.Opts.YouTubeAPIKey())
|
||||||
apiQuery.Set("part", "contentDetails")
|
apiQuery.Set("part", "contentDetails")
|
||||||
|
|
||||||
apiURL := url.URL{
|
apiURL := url.URL{
|
||||||
|
|
|
@ -67,7 +67,7 @@ func TestRewriteWithNoMatchingRule(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteYoutubeVideoLink(t *testing.T) {
|
func TestRewriteYoutubeVideoLink(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
URL: "https://www.youtube.com/watch?v=1234",
|
URL: "https://www.youtube.com/watch?v=1234",
|
||||||
|
@ -87,7 +87,7 @@ func TestRewriteYoutubeVideoLink(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteYoutubeShortLink(t *testing.T) {
|
func TestRewriteYoutubeShortLink(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
|
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
|
||||||
|
@ -107,7 +107,7 @@ func TestRewriteYoutubeShortLink(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteIncorrectYoutubeLink(t *testing.T) {
|
func TestRewriteIncorrectYoutubeLink(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
URL: "https://www.youtube.com/some-page",
|
URL: "https://www.youtube.com/some-page",
|
||||||
|
@ -131,7 +131,7 @@ func TestRewriteYoutubeLinkAndCustomEmbedURL(t *testing.T) {
|
||||||
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -156,7 +156,7 @@ func TestRewriteYoutubeLinkAndCustomEmbedURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
|
func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
URL: "https://www.youtube.com/watch?v=1234",
|
URL: "https://www.youtube.com/watch?v=1234",
|
||||||
Title: `A title`,
|
Title: `A title`,
|
||||||
|
@ -176,7 +176,7 @@ func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
|
func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
|
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
|
||||||
Title: `A title`,
|
Title: `A title`,
|
||||||
|
@ -196,7 +196,7 @@ func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddYoutubeVideoFromId(t *testing.T) {
|
func TestAddYoutubeVideoFromId(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
scenarios := map[string]string{
|
scenarios := map[string]string{
|
||||||
// Test with single YouTube ID
|
// Test with single YouTube ID
|
||||||
|
@ -239,7 +239,7 @@ func TestAddYoutubeVideoFromIdWithCustomEmbedURL(t *testing.T) {
|
||||||
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := config.NewParser()
|
parser := config.NewConfigParser()
|
||||||
config.Opts, err = parser.ParseEnvironmentVariables()
|
config.Opts, err = parser.ParseEnvironmentVariables()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -392,7 +392,7 @@ func TestInvalidNestedTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidIFrame(t *testing.T) {
|
func TestInvalidIFrame(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
input := `<iframe src="http://example.org/"></iframe>`
|
input := `<iframe src="http://example.org/"></iframe>`
|
||||||
expected := ``
|
expected := ``
|
||||||
|
@ -404,7 +404,7 @@ func TestInvalidIFrame(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSameDomainIFrame(t *testing.T) {
|
func TestSameDomainIFrame(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
input := `<iframe src="http://example.com/test"></iframe>`
|
input := `<iframe src="http://example.com/test"></iframe>`
|
||||||
expected := ``
|
expected := ``
|
||||||
|
@ -416,7 +416,7 @@ func TestSameDomainIFrame(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvidiousIFrame(t *testing.T) {
|
func TestInvidiousIFrame(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
input := `<iframe src="https://yewtu.be/watch?v=video_id"></iframe>`
|
input := `<iframe src="https://yewtu.be/watch?v=video_id"></iframe>`
|
||||||
expected := `<iframe src="https://yewtu.be/watch?v=video_id" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
expected := `<iframe src="https://yewtu.be/watch?v=video_id" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||||
|
@ -432,7 +432,7 @@ func TestCustomYoutubeEmbedURL(t *testing.T) {
|
||||||
|
|
||||||
defer os.Clearenv()
|
defer os.Clearenv()
|
||||||
var err error
|
var err error
|
||||||
if config.Opts, err = config.NewParser().ParseEnvironmentVariables(); err != nil {
|
if config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables(); err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +446,7 @@ func TestCustomYoutubeEmbedURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIFrameWithChildElements(t *testing.T) {
|
func TestIFrameWithChildElements(t *testing.T) {
|
||||||
config.Opts = config.NewOptions()
|
config.Opts = config.NewConfigOptions()
|
||||||
|
|
||||||
input := `<iframe src="https://www.youtube.com/"><p>test</p></iframe>`
|
input := `<iframe src="https://www.youtube.com/"><p>test</p></iframe>`
|
||||||
expected := `<iframe src="https://www.youtube.com/" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
expected := `<iframe src="https://www.youtube.com/" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||||
|
@ -850,7 +850,7 @@ func TestReplaceYoutubeURLWithCustomURL(t *testing.T) {
|
||||||
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
config.Opts, err = config.NewParser().ParseEnvironmentVariables()
|
config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Parsing failure: %v`, err)
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (f *funcMap) Map() template.FuncMap {
|
||||||
"baseURL": config.Opts.BaseURL,
|
"baseURL": config.Opts.BaseURL,
|
||||||
"rootURL": config.Opts.RootURL,
|
"rootURL": config.Opts.RootURL,
|
||||||
"disableLocalAuth": config.Opts.DisableLocalAuth,
|
"disableLocalAuth": config.Opts.DisableLocalAuth,
|
||||||
"oidcProviderName": config.Opts.OIDCProviderName,
|
"oidcProviderName": config.Opts.OAuth2OIDCProviderName,
|
||||||
"hasOAuth2Provider": func(provider string) bool {
|
"hasOAuth2Provider": func(provider string) bool {
|
||||||
return config.Opts.OAuth2Provider() == provider
|
return config.Opts.OAuth2Provider() == provider
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (h *handler) showAboutPage(w http.ResponseWriter, r *http.Request) {
|
||||||
view.Set("user", user)
|
view.Set("user", user)
|
||||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||||
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
|
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
|
||||||
view.Set("globalConfigOptions", config.Opts.SortedOptions(true))
|
view.Set("globalConfigOptions", config.Opts.ConfigMap(true))
|
||||||
view.Set("postgres_version", h.store.DatabaseVersion())
|
view.Set("postgres_version", h.store.DatabaseVersion())
|
||||||
view.Set("go_version", runtime.Version())
|
view.Set("go_version", runtime.Version())
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
http.SetCookie(w, cookie.New(
|
http.SetCookie(w, cookie.New(
|
||||||
cookie.CookieUserSessionID,
|
cookie.CookieUserSessionID,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
config.Opts.HTTPS,
|
config.Opts.HTTPS(),
|
||||||
config.Opts.BasePath(),
|
config.Opts.BasePath(),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (h *handler) logout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
http.SetCookie(w, cookie.Expired(
|
http.SetCookie(w, cookie.Expired(
|
||||||
cookie.CookieUserSessionID,
|
cookie.CookieUserSessionID,
|
||||||
config.Opts.HTTPS,
|
config.Opts.HTTPS(),
|
||||||
config.Opts.BasePath(),
|
config.Opts.BasePath(),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ func (m *middleware) handleAppSession(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, config.Opts.HTTPS, config.Opts.BasePath()))
|
http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, config.Opts.HTTPS(), config.Opts.BasePath()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
|
@ -261,7 +261,7 @@ func (m *middleware) handleAuthProxy(next http.Handler) http.Handler {
|
||||||
http.SetCookie(w, cookie.New(
|
http.SetCookie(w, cookie.New(
|
||||||
cookie.CookieUserSessionID,
|
cookie.CookieUserSessionID,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
config.Opts.HTTPS,
|
config.Opts.HTTPS(),
|
||||||
config.Opts.BasePath(),
|
config.Opts.BasePath(),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,6 @@ func getOAuth2Manager(ctx context.Context) *oauth2.Manager {
|
||||||
config.Opts.OAuth2ClientID(),
|
config.Opts.OAuth2ClientID(),
|
||||||
config.Opts.OAuth2ClientSecret(),
|
config.Opts.OAuth2ClientSecret(),
|
||||||
config.Opts.OAuth2RedirectURL(),
|
config.Opts.OAuth2RedirectURL(),
|
||||||
config.Opts.OIDCDiscoveryEndpoint(),
|
config.Opts.OAuth2OIDCDiscoveryEndpoint(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
http.SetCookie(w, cookie.New(
|
http.SetCookie(w, cookie.New(
|
||||||
cookie.CookieUserSessionID,
|
cookie.CookieUserSessionID,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
config.Opts.HTTPS,
|
config.Opts.HTTPS(),
|
||||||
config.Opts.BasePath(),
|
config.Opts.BasePath(),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -331,7 +331,7 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
http.SetCookie(w, cookie.New(
|
http.SetCookie(w, cookie.New(
|
||||||
cookie.CookieUserSessionID,
|
cookie.CookieUserSessionID,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
config.Opts.HTTPS,
|
config.Opts.HTTPS(),
|
||||||
config.Opts.BasePath(),
|
config.Opts.BasePath(),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -329,7 +329,7 @@ Default is 300 seconds\&.
|
||||||
.B HTTPS
|
.B HTTPS
|
||||||
Forces cookies to use secure flag and send HSTS header\&.
|
Forces cookies to use secure flag and send HSTS header\&.
|
||||||
.br
|
.br
|
||||||
Default is empty\&.
|
Default is disabled\&.
|
||||||
.TP
|
.TP
|
||||||
.B INVIDIOUS_INSTANCE
|
.B INVIDIOUS_INSTANCE
|
||||||
Set a custom invidious instance to use\&.
|
Set a custom invidious instance to use\&.
|
||||||
|
@ -466,7 +466,7 @@ Default is empty\&.
|
||||||
.B OAUTH2_OIDC_PROVIDER_NAME
|
.B OAUTH2_OIDC_PROVIDER_NAME
|
||||||
Name to display for the OIDC provider\&.
|
Name to display for the OIDC provider\&.
|
||||||
.br
|
.br
|
||||||
Default is OpenID Connect\&.
|
Default is "OpenID Connect"\&.
|
||||||
.TP
|
.TP
|
||||||
.B OAUTH2_PROVIDER
|
.B OAUTH2_PROVIDER
|
||||||
Possible values are "google" or "oidc"\&.
|
Possible values are "google" or "oidc"\&.
|
||||||
|
@ -537,7 +537,7 @@ Default is 1\&.
|
||||||
.B SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL
|
.B SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL
|
||||||
Maximum interval in minutes for the entry frequency scheduler\&.
|
Maximum interval in minutes for the entry frequency scheduler\&.
|
||||||
.br
|
.br
|
||||||
Default is 24 hours\&.
|
Default is 1440 minutes (24 hours)\&.
|
||||||
.TP
|
.TP
|
||||||
.B SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL
|
.B SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL
|
||||||
Minimum interval in minutes for the entry frequency scheduler\&.
|
Minimum interval in minutes for the entry frequency scheduler\&.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue