parser.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. "errors"
  8. "fmt"
  9. "io"
  10. url_parser "net/url"
  11. "os"
  12. "strconv"
  13. "strings"
  14. )
  15. // Parser handles configuration parsing.
  16. type Parser struct {
  17. opts *Options
  18. }
  19. // NewParser returns a new Parser.
  20. func NewParser() *Parser {
  21. return &Parser{
  22. opts: NewOptions(),
  23. }
  24. }
  25. // ParseEnvironmentVariables loads configuration values from environment variables.
  26. func (p *Parser) ParseEnvironmentVariables() (*Options, error) {
  27. err := p.parseLines(os.Environ())
  28. if err != nil {
  29. return nil, err
  30. }
  31. return p.opts, nil
  32. }
  33. // ParseFile loads configuration values from a local file.
  34. func (p *Parser) ParseFile(filename string) (*Options, error) {
  35. fp, err := os.Open(filename)
  36. if err != nil {
  37. return nil, err
  38. }
  39. defer fp.Close()
  40. err = p.parseLines(p.parseFileContent(fp))
  41. if err != nil {
  42. return nil, err
  43. }
  44. return p.opts, nil
  45. }
  46. func (p *Parser) parseFileContent(r io.Reader) (lines []string) {
  47. scanner := bufio.NewScanner(r)
  48. for scanner.Scan() {
  49. line := strings.TrimSpace(scanner.Text())
  50. if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
  51. lines = append(lines, line)
  52. }
  53. }
  54. return lines
  55. }
  56. func (p *Parser) parseLines(lines []string) (err error) {
  57. var port string
  58. for _, line := range lines {
  59. fields := strings.SplitN(line, "=", 2)
  60. key := strings.TrimSpace(fields[0])
  61. value := strings.TrimSpace(fields[1])
  62. switch key {
  63. case "DEBUG":
  64. p.opts.debug = parseBool(value, defaultDebug)
  65. case "BASE_URL":
  66. p.opts.baseURL, p.opts.rootURL, p.opts.basePath, err = parseBaseURL(value)
  67. if err != nil {
  68. return err
  69. }
  70. case "PORT":
  71. port = value
  72. case "LISTEN_ADDR":
  73. p.opts.listenAddr = parseString(value, defaultListenAddr)
  74. case "DATABASE_URL":
  75. p.opts.databaseURL = parseString(value, defaultDatabaseURL)
  76. case "DATABASE_MAX_CONNS":
  77. p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
  78. case "DATABASE_MIN_CONNS":
  79. p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
  80. case "RUN_MIGRATIONS":
  81. p.opts.runMigrations = parseBool(value, defaultRunMigrations)
  82. case "DISABLE_HSTS":
  83. p.opts.hsts = !parseBool(value, defaultHSTS)
  84. case "HTTPS":
  85. p.opts.HTTPS = parseBool(value, defaultHTTPS)
  86. case "DISABLE_SCHEDULER_SERVICE":
  87. p.opts.schedulerService = !parseBool(value, defaultSchedulerService)
  88. case "DISABLE_HTTP_SERVICE":
  89. p.opts.httpService = !parseBool(value, defaultHTTPService)
  90. case "CERT_FILE":
  91. p.opts.certFile = parseString(value, defaultCertFile)
  92. case "KEY_FILE":
  93. p.opts.certKeyFile = parseString(value, defaultKeyFile)
  94. case "CERT_DOMAIN":
  95. p.opts.certDomain = parseString(value, defaultCertDomain)
  96. case "CERT_CACHE":
  97. p.opts.certCache = parseString(value, defaultCertCache)
  98. case "CLEANUP_FREQUENCY":
  99. p.opts.cleanupFrequency = parseInt(value, defaultCleanupFrequency)
  100. case "WORKER_POOL_SIZE":
  101. p.opts.workerPoolSize = parseInt(value, defaultWorkerPoolSize)
  102. case "POLLING_FREQUENCY":
  103. p.opts.pollingFrequency = parseInt(value, defaultPollingFrequency)
  104. case "BATCH_SIZE":
  105. p.opts.batchSize = parseInt(value, defaultBatchSize)
  106. case "ARCHIVE_READ_DAYS":
  107. p.opts.archiveReadDays = parseInt(value, defaultArchiveReadDays)
  108. case "PROXY_IMAGES":
  109. p.opts.proxyImages = parseString(value, defaultProxyImages)
  110. case "CREATE_ADMIN":
  111. p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
  112. case "POCKET_CONSUMER_KEY":
  113. p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey)
  114. case "OAUTH2_USER_CREATION":
  115. p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation)
  116. case "OAUTH2_CLIENT_ID":
  117. p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID)
  118. case "OAUTH2_CLIENT_SECRET":
  119. p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
  120. case "OAUTH2_REDIRECT_URL":
  121. p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
  122. case "OAUTH2_PROVIDER":
  123. p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
  124. case "HTTP_CLIENT_TIMEOUT":
  125. p.opts.httpClientTimeout = parseInt(value, defaultHTTPClientTimeout)
  126. case "HTTP_CLIENT_MAX_BODY_SIZE":
  127. p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024)
  128. }
  129. }
  130. if port != "" {
  131. p.opts.listenAddr = ":" + port
  132. }
  133. return nil
  134. }
  135. func parseBaseURL(value string) (string, string, string, error) {
  136. if value == "" {
  137. return defaultBaseURL, defaultRootURL, "", nil
  138. }
  139. if value[len(value)-1:] == "/" {
  140. value = value[:len(value)-1]
  141. }
  142. url, err := url_parser.Parse(value)
  143. if err != nil {
  144. return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err)
  145. }
  146. scheme := strings.ToLower(url.Scheme)
  147. if scheme != "https" && scheme != "http" {
  148. return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https")
  149. }
  150. basePath := url.Path
  151. url.Path = ""
  152. return value, url.String(), basePath, nil
  153. }
  154. func parseBool(value string, fallback bool) bool {
  155. if value == "" {
  156. return fallback
  157. }
  158. value = strings.ToLower(value)
  159. if value == "1" || value == "yes" || value == "true" || value == "on" {
  160. return true
  161. }
  162. return false
  163. }
  164. func parseInt(value string, fallback int) int {
  165. if value == "" {
  166. return fallback
  167. }
  168. v, err := strconv.Atoi(value)
  169. if err != nil {
  170. return fallback
  171. }
  172. return v
  173. }
  174. func parseString(value string, fallback string) string {
  175. if value == "" {
  176. return fallback
  177. }
  178. return value
  179. }