1
0
Fork 0
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:
Frédéric Guillot 2025-09-14 10:51:04 -07:00 committed by GitHub
parent 502e7108dd
commit 5e607be86a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 3615 additions and 3523 deletions

View file

@ -78,7 +78,7 @@ func Parse() {
flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
flag.Parse()
cfg := config.NewParser()
cfg := config.NewConfigParser()
if flagConfigFile != "" {
config.Opts, err = cfg.ParseFile(flagConfigFile)

11
internal/config/config.go Normal file
View 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

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,6 @@ import (
"bufio"
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
"net/url"
@ -17,301 +16,205 @@ import (
"time"
)
// parser handles configuration parsing.
type parser struct {
opts *options
type configParser struct {
options *configOptions
}
// NewParser returns a new Parser.
func NewParser() *parser {
return &parser{
opts: NewOptions(),
func NewConfigParser() *configParser {
return &configParser{
options: NewConfigOptions(),
}
}
// ParseEnvironmentVariables loads configuration values from environment variables.
func (p *parser) ParseEnvironmentVariables() (*options, error) {
err := p.parseLines(os.Environ())
if err != nil {
func (cp *configParser) ParseEnvironmentVariables() (*configOptions, error) {
if err := cp.parseLines(os.Environ()); err != nil {
return nil, err
}
return p.opts, nil
return cp.options, nil
}
// ParseFile loads configuration values from a local file.
func (p *parser) ParseFile(filename string) (*options, error) {
func (cp *configParser) ParseFile(filename string) (*configOptions, error) {
fp, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fp.Close()
err = p.parseLines(p.parseFileContent(fp))
if err != nil {
if err := cp.parseLines(parseFileContent(fp)); err != nil {
return nil, err
}
return p.opts, nil
return cp.options, nil
}
func (p *parser) 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
}
func (cp *configParser) postParsing() error {
// Parse basePath and rootURL based on BASE_URL
baseURL := cp.options.options["BASE_URL"].ParsedStringValue
baseURL = strings.TrimSuffix(baseURL, "/")
func (p *parser) parseLines(lines []string) (err error) {
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)
parsedURL, err := url.Parse(baseURL)
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 {
return fmt.Errorf("config: invalid YOUTUBE_EMBED_URL_OVERRIDE value: %w", 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)
return fmt.Errorf("invalid BASE_URL: %v", err)
}
scheme := strings.ToLower(parsedURL.Scheme)
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 = ""
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 == "" {
return fallback
}
return value
}
func parseBoolValue(value string, fallback bool) (bool, error) {
if value == "" {
return fallback, nil
}
value = strings.ToLower(value)
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 == "" {
return fallback
}
@ -324,14 +227,20 @@ func parseInt(value string, fallback int) int {
return v
}
func parseString(value string, fallback string) string {
func ParsedInt64Value(value string, fallback int64) int64 {
if value == "" {
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 == "" {
return fallback
}
@ -351,16 +260,7 @@ func parseStringList(value string, fallback []string) []string {
return strList
}
func parseBytes(value string, fallback []byte) []byte {
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 {
func parseDurationValue(value string, unit time.Duration, fallback time.Duration) time.Duration {
if value == "" {
return fallback
}
@ -373,16 +273,40 @@ func parseInterval(value string, unit time.Duration, fallback time.Duration) tim
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)
if err != nil {
return fallback
return "", err
}
value := string(bytes.TrimSpace(data))
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
}

View file

@ -4,163 +4,432 @@
package config // import "miniflux.app/v2/internal/config"
import (
"net/url"
"os"
"reflect"
"testing"
"time"
)
func TestParseBoolValue(t *testing.T) {
scenarios := map[string]bool{
"": true,
"1": true,
"Yes": true,
"yes": true,
"True": true,
"true": true,
"on": true,
"false": false,
"off": false,
"invalid": false,
}
for input, expected := range scenarios {
result := parseBool(input, true)
if result != expected {
t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected)
}
}
}
func TestParseStringValueWithUnsetVariable(t *testing.T) {
if parseString("", "defaultValue") != "defaultValue" {
t.Errorf(`Unset variables should returns the default value`)
}
}
func TestParseStringValue(t *testing.T) {
if parseString("test", "defaultValue") != "test" {
t.Errorf(`Defined variables should returns the specified value`)
// Test with non-empty value
result := parseStringValue("test", "fallback")
if result != "test" {
t.Errorf("Expected 'test', got '%s'", result)
}
// Test with empty value
result = parseStringValue("", "fallback")
if result != "fallback" {
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 TestParseIntValueWithUnsetVariable(t *testing.T) {
if parseInt("", 42) != 42 {
t.Errorf(`Unset variables should returns the default value`)
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)
}
}
func TestParseIntValueWithInvalidInput(t *testing.T) {
if parseInt("invalid integer", 42) != 42 {
t.Errorf(`Invalid integer should returns the default value`)
// 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)
}
}
// Test false values
falseValues := []string{"0", "no", "false", "off", "NO", "FALSE", "OFF"}
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)
}
}
// Test invalid value - should return error
_, err = parseBoolValue("invalid", false)
if err == nil {
t.Error("Expected error for invalid boolean value")
}
}
func TestParseIntValue(t *testing.T) {
if parseInt("2018", 42) != 2018 {
t.Errorf(`Defined variables should returns the specified value`)
// Test with empty value - should return fallback
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) {
defaultExpected := []string{defaultListenAddr}
tests := []struct {
name string
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,
},
func TestParsedInt64Value(t *testing.T) {
// Test with empty value - should return fallback
result := ParsedInt64Value("", 42)
if result != 42 {
t.Errorf("Expected 42, got %d", result)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := NewParser()
var err error
if tt.isLineOriented {
err = parser.parseLines(tt.lines)
} else {
// Simulate os.Environ() behaviour for individual var testing
var envLines []string
if tt.listenAddr != "" {
envLines = append(envLines, "LISTEN_ADDR="+tt.listenAddr)
}
if tt.port != "" {
envLines = append(envLines, "PORT="+tt.port)
}
// Add a dummy var if both are empty to avoid empty lines slice if not intended
if tt.listenAddr == "" && tt.port == "" && tt.name == "Empty LISTEN_ADDR" {
// This case specifically tests empty LISTEN_ADDR resulting in default
// So, we pass LISTEN_ADDR=
envLines = append(envLines, "LISTEN_ADDR=")
}
err = parser.parseLines(envLines)
// Test with valid int64
result = ParsedInt64Value("9223372036854775807", 42)
if result != 9223372036854775807 {
t.Errorf("Expected 9223372036854775807, got %d", result)
}
// Test with invalid int64 - should return fallback
result = ParsedInt64Value("invalid", 42)
if result != 42 {
t.Errorf("Expected 42, got %d", result)
}
}
func TestParseStringListValue(t *testing.T) {
// Test with empty value - should return fallback
fallback := []string{"a", "b"}
result := parseStringListValue("", fallback)
if !reflect.DeepEqual(result, fallback) {
t.Errorf("Expected %v, got %v", fallback, result)
}
// Test with single value
result = parseStringListValue("item1", nil)
expected := []string{"item1"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
// Test with multiple values
result = parseStringListValue("item1,item2,item3", nil)
expected = []string{"item1", "item2", "item3"}
if !reflect.DeepEqual(result, 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.Fatalf("parseLines() error = %v", err)
t.Errorf("Unexpected error: %v", err)
}
if result != fallbackURL {
t.Errorf("Expected %v, got %v", fallbackURL, result)
}
opts := parser.opts
if !reflect.DeepEqual(opts.ListenAddr(), tt.expected) {
t.Errorf("ListenAddr() got = %v, want %v", opts.ListenAddr(), tt.expected)
// 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")
}
}

View 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
}

View 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)
}
}
})
}
}

View file

@ -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))
}
}()
config.Opts.HTTPS = true
config.Opts.SetHTTPSValue(true)
httpServers = append(httpServers, challengeServer)
}
@ -95,7 +95,7 @@ func StartWebServer(store *storage.Storage, pool *worker.Pool) []*http.Server {
case certFile != "" && keyFile != "":
server.Addr = listenAddr
startTLSServer(server, certFile, keyFile)
config.Opts.HTTPS = true
config.Opts.SetHTTPSValue(true)
default:
server.Addr = listenAddr
startHTTPServer(server)
@ -148,7 +148,7 @@ func startUnixSocketServer(server *http.Server, socketFile string) {
slog.String("key_file", keyFile),
)
// 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 {
printErrorAndExit("TLS Unix socket server failed to start on %s: %v", socketFile, err)
}

View file

@ -20,7 +20,7 @@ func middleware(next http.Handler) http.Handler {
ctx = context.WithValue(ctx, request.ClientIPContextKey, clientIP)
if r.Header.Get("X-Forwarded-Proto") == "https" {
config.Opts.HTTPS = true
config.Opts.SetHTTPSValue(true)
}
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")
}

View file

@ -19,7 +19,7 @@ func TestProxyFilterWithHttpDefault(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -43,7 +43,7 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) {
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -66,7 +66,7 @@ func TestProxyFilterWithHttpNever(t *testing.T) {
os.Setenv("MEDIA_PROXY_MODE", "none")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -89,7 +89,7 @@ func TestProxyFilterWithHttpsNever(t *testing.T) {
os.Setenv("MEDIA_PROXY_MODE", "none")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -114,7 +114,7 @@ func TestProxyFilterWithHttpAlways(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -139,7 +139,7 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -164,7 +164,7 @@ func TestAbsoluteProxyFilterWithHttpsAlways(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -188,7 +188,7 @@ func TestAbsoluteProxyFilterWithCustomPortAndSubfolderInBaseURL(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -226,7 +226,7 @@ func TestAbsoluteProxyFilterWithHttpsAlwaysAndAudioTag(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -276,7 +276,7 @@ func TestProxyFilterWithHttpsAlwaysAndIncorrectCustomProxyServer(t *testing.T) {
os.Setenv("MEDIA_PROXY_CUSTOM_URL", "http://:8080example.com")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
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())
@ -290,7 +290,7 @@ func TestAbsoluteProxyFilterWithHttpsAlwaysAndCustomProxyServer(t *testing.T) {
os.Setenv("MEDIA_PROXY_CUSTOM_URL", "https://proxy-example/proxy")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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_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="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)
if _, err := config.NewConfigParser().ParseEnvironmentVariables(); err == nil {
t.Fatalf(`Parsing should have failed (MEDIA_PROXY_MODE=%q): %q`, os.Getenv("MEDIA_PROXY_MODE"), config.Opts.MediaProxyMode())
}
}
@ -363,7 +325,7 @@ func TestProxyFilterWithSrcset(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -388,7 +350,7 @@ func TestProxyFilterWithEmptySrcset(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -413,7 +375,7 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -433,12 +395,12 @@ func TestProxyFilterWithPictureSource(t *testing.T) {
func TestProxyFilterOnlyNonHTTPWithPictureSource(t *testing.T) {
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_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -462,7 +424,7 @@ func TestProxyWithImageDataURL(t *testing.T) {
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -486,7 +448,7 @@ func TestProxyWithImageSourceDataURL(t *testing.T) {
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -511,7 +473,7 @@ func TestProxyFilterWithVideo(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -536,7 +498,7 @@ func TestProxyFilterVideoPoster(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -561,7 +523,7 @@ func TestProxyFilterVideoPosterOnce(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)

View file

@ -290,7 +290,7 @@ func TestEnclosure_ProxifyEnclosureURL(t *testing.T) {
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Config parsing failure: %v`, err)

View file

@ -80,7 +80,7 @@ func TestFeedScheduleNextCheckRoundRobinDefault(t *testing.T) {
os.Clearenv()
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -102,7 +102,7 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayAboveMinInterval(t *test
os.Clearenv()
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -125,7 +125,7 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayBelowMinInterval(t *test
os.Clearenv()
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -148,7 +148,7 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayAboveMaxInterval(t *test
os.Clearenv()
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -230,7 +230,7 @@ func TestFeedScheduleNextCheckEntryFrequencyMaxIntervalZeroWeeklyCount(t *testin
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", strconv.Itoa(minInterval))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
@ -286,7 +286,7 @@ func TestFeedScheduleNextCheckEntryFrequencyFactor(t *testing.T) {
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_FACTOR", strconv.Itoa(factor))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
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))
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)

View file

@ -414,7 +414,7 @@ func TestKeeplistRulesBehavior(t *testing.T) {
// Tests for isBlockedGlobally function
func TestIsBlockedGlobally(t *testing.T) {
var err error
config.Opts, err = config.NewParser().ParseEnvironmentVariables()
config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
@ -429,7 +429,7 @@ func TestIsBlockedGlobally(t *testing.T) {
os.Setenv("FILTER_ENTRY_MAX_AGE_DAYS", "30")
defer os.Clearenv()
config.Opts, err = config.NewParser().ParseEnvironmentVariables()
config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}

View file

@ -31,11 +31,11 @@ func getVideoIDFromYouTubeURL(websiteURL string) string {
}
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 {
return config.Opts.FetchYouTubeWatchTime() && config.Opts.YouTubeApiKey() != ""
return config.Opts.FetchYouTubeWatchTime() && config.Opts.YouTubeAPIKey() != ""
}
func fetchYouTubeWatchTimeForSingleEntry(websiteURL string) (int, error) {
@ -82,7 +82,7 @@ func fetchYouTubeWatchTimeFromApiInBulk(videoIDs []string) (map[string]time.Dura
apiQuery := url.Values{}
apiQuery.Set("id", strings.Join(videoIDs, ","))
apiQuery.Set("key", config.Opts.YouTubeApiKey())
apiQuery.Set("key", config.Opts.YouTubeAPIKey())
apiQuery.Set("part", "contentDetails")
apiURL := url.URL{

View file

@ -67,7 +67,7 @@ func TestRewriteWithNoMatchingRule(t *testing.T) {
}
func TestRewriteYoutubeVideoLink(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
controlEntry := &model.Entry{
URL: "https://www.youtube.com/watch?v=1234",
@ -87,7 +87,7 @@ func TestRewriteYoutubeVideoLink(t *testing.T) {
}
func TestRewriteYoutubeShortLink(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
controlEntry := &model.Entry{
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
@ -107,7 +107,7 @@ func TestRewriteYoutubeShortLink(t *testing.T) {
}
func TestRewriteIncorrectYoutubeLink(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
controlEntry := &model.Entry{
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/")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
@ -156,7 +156,7 @@ func TestRewriteYoutubeLinkAndCustomEmbedURL(t *testing.T) {
}
func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
controlEntry := &model.Entry{
URL: "https://www.youtube.com/watch?v=1234",
Title: `A title`,
@ -176,7 +176,7 @@ func TestRewriteYoutubeVideoLinkUsingInvidious(t *testing.T) {
}
func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
controlEntry := &model.Entry{
URL: "https://www.youtube.com/shorts/1LUWKWZkPjo",
Title: `A title`,
@ -196,7 +196,7 @@ func TestRewriteYoutubeShortLinkUsingInvidious(t *testing.T) {
}
func TestAddYoutubeVideoFromId(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
scenarios := map[string]string{
// Test with single YouTube ID
@ -239,7 +239,7 @@ func TestAddYoutubeVideoFromIdWithCustomEmbedURL(t *testing.T) {
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
var err error
parser := config.NewParser()
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {

View file

@ -392,7 +392,7 @@ func TestInvalidNestedTag(t *testing.T) {
}
func TestInvalidIFrame(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
input := `<iframe src="http://example.org/"></iframe>`
expected := ``
@ -404,7 +404,7 @@ func TestInvalidIFrame(t *testing.T) {
}
func TestSameDomainIFrame(t *testing.T) {
config.Opts = config.NewOptions()
config.Opts = config.NewConfigOptions()
input := `<iframe src="http://example.com/test"></iframe>`
expected := ``
@ -416,7 +416,7 @@ func TestSameDomainIFrame(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>`
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()
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)
}
@ -446,7 +446,7 @@ func TestCustomYoutubeEmbedURL(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>`
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/")
var err error
config.Opts, err = config.NewParser().ParseEnvironmentVariables()
config.Opts, err = config.NewConfigParser().ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}

View file

@ -41,7 +41,7 @@ func (f *funcMap) Map() template.FuncMap {
"baseURL": config.Opts.BaseURL,
"rootURL": config.Opts.RootURL,
"disableLocalAuth": config.Opts.DisableLocalAuth,
"oidcProviderName": config.Opts.OIDCProviderName,
"oidcProviderName": config.Opts.OAuth2OIDCProviderName,
"hasOAuth2Provider": func(provider string) bool {
return config.Opts.OAuth2Provider() == provider
},

View file

@ -33,7 +33,7 @@ func (h *handler) showAboutPage(w http.ResponseWriter, r *http.Request) {
view.Set("user", user)
view.Set("countUnread", h.store.CountUnreadEntries(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("go_version", runtime.Version())

View file

@ -89,7 +89,7 @@ func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
config.Opts.HTTPS,
config.Opts.HTTPS(),
config.Opts.BasePath(),
))

View file

@ -32,7 +32,7 @@ func (h *handler) logout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.Expired(
cookie.CookieUserSessionID,
config.Opts.HTTPS,
config.Opts.HTTPS(),
config.Opts.BasePath(),
))

View file

@ -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 {
@ -261,7 +261,7 @@ func (m *middleware) handleAuthProxy(next http.Handler) http.Handler {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
config.Opts.HTTPS,
config.Opts.HTTPS(),
config.Opts.BasePath(),
))

View file

@ -16,6 +16,6 @@ func getOAuth2Manager(ctx context.Context) *oauth2.Manager {
config.Opts.OAuth2ClientID(),
config.Opts.OAuth2ClientSecret(),
config.Opts.OAuth2RedirectURL(),
config.Opts.OIDCDiscoveryEndpoint(),
config.Opts.OAuth2OIDCDiscoveryEndpoint(),
)
}

View file

@ -145,7 +145,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
config.Opts.HTTPS,
config.Opts.HTTPS(),
config.Opts.BasePath(),
))

View file

@ -331,7 +331,7 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
config.Opts.HTTPS,
config.Opts.HTTPS(),
config.Opts.BasePath(),
))

View file

@ -329,7 +329,7 @@ Default is 300 seconds\&.
.B HTTPS
Forces cookies to use secure flag and send HSTS header\&.
.br
Default is empty\&.
Default is disabled\&.
.TP
.B INVIDIOUS_INSTANCE
Set a custom invidious instance to use\&.
@ -466,7 +466,7 @@ Default is empty\&.
.B OAUTH2_OIDC_PROVIDER_NAME
Name to display for the OIDC provider\&.
.br
Default is OpenID Connect\&.
Default is "OpenID Connect"\&.
.TP
.B OAUTH2_PROVIDER
Possible values are "google" or "oidc"\&.
@ -537,7 +537,7 @@ Default is 1\&.
.B SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL
Maximum interval in minutes for the entry frequency scheduler\&.
.br
Default is 24 hours\&.
Default is 1440 minutes (24 hours)\&.
.TP
.B SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL
Minimum interval in minutes for the entry frequency scheduler\&.