parser.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. // Copyright 2019 Frédéric Guillot. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. package config // import "miniflux.app/config"
  5. import (
  6. "bufio"
  7. "bytes"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. url_parser "net/url"
  13. "os"
  14. "strconv"
  15. "strings"
  16. )
  17. // Parser handles configuration parsing.
  18. type Parser struct {
  19. opts *Options
  20. }
  21. // NewParser returns a new Parser.
  22. func NewParser() *Parser {
  23. return &Parser{
  24. opts: NewOptions(),
  25. }
  26. }
  27. // ParseEnvironmentVariables loads configuration values from environment variables.
  28. func (p *Parser) ParseEnvironmentVariables() (*Options, error) {
  29. err := p.parseLines(os.Environ())
  30. if err != nil {
  31. return nil, err
  32. }
  33. return p.opts, nil
  34. }
  35. // ParseFile loads configuration values from a local file.
  36. func (p *Parser) ParseFile(filename string) (*Options, error) {
  37. fp, err := os.Open(filename)
  38. if err != nil {
  39. return nil, err
  40. }
  41. defer fp.Close()
  42. err = p.parseLines(p.parseFileContent(fp))
  43. if err != nil {
  44. return nil, err
  45. }
  46. return p.opts, nil
  47. }
  48. func (p *Parser) parseFileContent(r io.Reader) (lines []string) {
  49. scanner := bufio.NewScanner(r)
  50. for scanner.Scan() {
  51. line := strings.TrimSpace(scanner.Text())
  52. if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
  53. lines = append(lines, line)
  54. }
  55. }
  56. return lines
  57. }
  58. func (p *Parser) parseLines(lines []string) (err error) {
  59. var port string
  60. for _, line := range lines {
  61. fields := strings.SplitN(line, "=", 2)
  62. key := strings.TrimSpace(fields[0])
  63. value := strings.TrimSpace(fields[1])
  64. switch key {
  65. case "LOG_DATE_TIME":
  66. p.opts.logDateTime = parseBool(value, defaultLogDateTime)
  67. case "DEBUG":
  68. p.opts.debug = parseBool(value, defaultDebug)
  69. case "SERVER_TIMING_HEADER":
  70. p.opts.serverTimingHeader = parseBool(value, defaultTiming)
  71. case "BASE_URL":
  72. p.opts.baseURL, p.opts.rootURL, p.opts.basePath, err = parseBaseURL(value)
  73. if err != nil {
  74. return err
  75. }
  76. case "PORT":
  77. port = value
  78. case "LISTEN_ADDR":
  79. p.opts.listenAddr = parseString(value, defaultListenAddr)
  80. case "DATABASE_URL":
  81. p.opts.databaseURL = parseString(value, defaultDatabaseURL)
  82. case "DATABASE_URL_FILE":
  83. p.opts.databaseURL = readSecretFile(value, defaultDatabaseURL)
  84. case "DATABASE_MAX_CONNS":
  85. p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
  86. case "DATABASE_MIN_CONNS":
  87. p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
  88. case "RUN_MIGRATIONS":
  89. p.opts.runMigrations = parseBool(value, defaultRunMigrations)
  90. case "DISABLE_HSTS":
  91. p.opts.hsts = !parseBool(value, defaultHSTS)
  92. case "HTTPS":
  93. p.opts.HTTPS = parseBool(value, defaultHTTPS)
  94. case "DISABLE_SCHEDULER_SERVICE":
  95. p.opts.schedulerService = !parseBool(value, defaultSchedulerService)
  96. case "DISABLE_HTTP_SERVICE":
  97. p.opts.httpService = !parseBool(value, defaultHTTPService)
  98. case "CERT_FILE":
  99. p.opts.certFile = parseString(value, defaultCertFile)
  100. case "KEY_FILE":
  101. p.opts.certKeyFile = parseString(value, defaultKeyFile)
  102. case "CERT_DOMAIN":
  103. p.opts.certDomain = parseString(value, defaultCertDomain)
  104. case "CERT_CACHE":
  105. p.opts.certCache = parseString(value, defaultCertCache)
  106. case "CLEANUP_FREQUENCY_HOURS":
  107. p.opts.cleanupFrequencyHours = parseInt(value, defaultCleanupFrequencyHours)
  108. case "CLEANUP_ARCHIVE_READ_DAYS":
  109. p.opts.cleanupArchiveReadDays = parseInt(value, defaultCleanupArchiveReadDays)
  110. case "CLEANUP_ARCHIVE_UNREAD_DAYS":
  111. p.opts.cleanupArchiveUnreadDays = parseInt(value, defaultCleanupArchiveUnreadDays)
  112. case "CLEANUP_REMOVE_SESSIONS_DAYS":
  113. p.opts.cleanupRemoveSessionsDays = parseInt(value, defaultCleanupRemoveSessionsDays)
  114. case "WORKER_POOL_SIZE":
  115. p.opts.workerPoolSize = parseInt(value, defaultWorkerPoolSize)
  116. case "POLLING_FREQUENCY":
  117. p.opts.pollingFrequency = parseInt(value, defaultPollingFrequency)
  118. case "BATCH_SIZE":
  119. p.opts.batchSize = parseInt(value, defaultBatchSize)
  120. case "POLLING_SCHEDULER":
  121. p.opts.pollingScheduler = strings.ToLower(parseString(value, defaultPollingScheduler))
  122. case "SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL":
  123. p.opts.schedulerEntryFrequencyMaxInterval = parseInt(value, defaultSchedulerEntryFrequencyMaxInterval)
  124. case "SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL":
  125. p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval)
  126. case "PROXY_IMAGES":
  127. p.opts.proxyImages = parseString(value, defaultProxyImages)
  128. case "CREATE_ADMIN":
  129. p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
  130. case "ADMIN_USERNAME":
  131. p.opts.adminUsername = parseString(value, defaultAdminUsername)
  132. case "ADMIN_USERNAME_FILE":
  133. p.opts.adminUsername = readSecretFile(value, defaultAdminUsername)
  134. case "ADMIN_PASSWORD":
  135. p.opts.adminPassword = parseString(value, defaultAdminPassword)
  136. case "ADMIN_PASSWORD_FILE":
  137. p.opts.adminPassword = readSecretFile(value, defaultAdminPassword)
  138. case "POCKET_CONSUMER_KEY":
  139. p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey)
  140. case "POCKET_CONSUMER_KEY_FILE":
  141. p.opts.pocketConsumerKey = readSecretFile(value, defaultPocketConsumerKey)
  142. case "OAUTH2_USER_CREATION":
  143. p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation)
  144. case "OAUTH2_CLIENT_ID":
  145. p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID)
  146. case "OAUTH2_CLIENT_ID_FILE":
  147. p.opts.oauth2ClientID = readSecretFile(value, defaultOAuth2ClientID)
  148. case "OAUTH2_CLIENT_SECRET":
  149. p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
  150. case "OAUTH2_CLIENT_SECRET_FILE":
  151. p.opts.oauth2ClientSecret = readSecretFile(value, defaultOAuth2ClientSecret)
  152. case "OAUTH2_REDIRECT_URL":
  153. p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
  154. case "OAUTH2_OIDC_DISCOVERY_ENDPOINT":
  155. p.opts.oauth2OidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint)
  156. case "OAUTH2_PROVIDER":
  157. p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
  158. case "HTTP_CLIENT_TIMEOUT":
  159. p.opts.httpClientTimeout = parseInt(value, defaultHTTPClientTimeout)
  160. case "HTTP_CLIENT_MAX_BODY_SIZE":
  161. p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024)
  162. case "HTTP_CLIENT_PROXY":
  163. p.opts.httpClientProxy = parseString(value, defaultHTTPClientProxy)
  164. case "HTTP_CLIENT_USER_AGENT":
  165. p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
  166. case "AUTH_PROXY_HEADER":
  167. p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader)
  168. case "AUTH_PROXY_USER_CREATION":
  169. p.opts.authProxyUserCreation = parseBool(value, defaultAuthProxyUserCreation)
  170. case "MAINTENANCE_MODE":
  171. p.opts.maintenanceMode = parseBool(value, defaultMaintenanceMode)
  172. case "MAINTENANCE_MESSAGE":
  173. p.opts.maintenanceMessage = parseString(value, defaultMaintenanceMessage)
  174. case "METRICS_COLLECTOR":
  175. p.opts.metricsCollector = parseBool(value, defaultMetricsCollector)
  176. case "METRICS_REFRESH_INTERVAL":
  177. p.opts.metricsRefreshInterval = parseInt(value, defaultMetricsRefreshInterval)
  178. case "METRICS_ALLOWED_NETWORKS":
  179. p.opts.metricsAllowedNetworks = parseStringList(value, []string{defaultMetricsAllowedNetworks})
  180. }
  181. }
  182. if port != "" {
  183. p.opts.listenAddr = ":" + port
  184. }
  185. return nil
  186. }
  187. func parseBaseURL(value string) (string, string, string, error) {
  188. if value == "" {
  189. return defaultBaseURL, defaultRootURL, "", nil
  190. }
  191. if value[len(value)-1:] == "/" {
  192. value = value[:len(value)-1]
  193. }
  194. url, err := url_parser.Parse(value)
  195. if err != nil {
  196. return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err)
  197. }
  198. scheme := strings.ToLower(url.Scheme)
  199. if scheme != "https" && scheme != "http" {
  200. return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https")
  201. }
  202. basePath := url.Path
  203. url.Path = ""
  204. return value, url.String(), basePath, nil
  205. }
  206. func parseBool(value string, fallback bool) bool {
  207. if value == "" {
  208. return fallback
  209. }
  210. value = strings.ToLower(value)
  211. if value == "1" || value == "yes" || value == "true" || value == "on" {
  212. return true
  213. }
  214. return false
  215. }
  216. func parseInt(value string, fallback int) int {
  217. if value == "" {
  218. return fallback
  219. }
  220. v, err := strconv.Atoi(value)
  221. if err != nil {
  222. return fallback
  223. }
  224. return v
  225. }
  226. func parseString(value string, fallback string) string {
  227. if value == "" {
  228. return fallback
  229. }
  230. return value
  231. }
  232. func parseStringList(value string, fallback []string) []string {
  233. if value == "" {
  234. return fallback
  235. }
  236. var strList []string
  237. items := strings.Split(value, ",")
  238. for _, item := range items {
  239. strList = append(strList, strings.TrimSpace(item))
  240. }
  241. return strList
  242. }
  243. func readSecretFile(filename, fallback string) string {
  244. data, err := ioutil.ReadFile(filename)
  245. if err != nil {
  246. return fallback
  247. }
  248. value := string(bytes.TrimSpace(data))
  249. if value == "" {
  250. return fallback
  251. }
  252. return value
  253. }