parser.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 "LOG_DATE_TIME":
  64. p.opts.logDateTime = parseBool(value, defaultLogDateTime)
  65. case "DEBUG":
  66. p.opts.debug = parseBool(value, defaultDebug)
  67. case "BASE_URL":
  68. p.opts.baseURL, p.opts.rootURL, p.opts.basePath, err = parseBaseURL(value)
  69. if err != nil {
  70. return err
  71. }
  72. case "PORT":
  73. port = value
  74. case "LISTEN_ADDR":
  75. p.opts.listenAddr = parseString(value, defaultListenAddr)
  76. case "DATABASE_URL":
  77. p.opts.databaseURL = parseString(value, defaultDatabaseURL)
  78. case "DATABASE_MAX_CONNS":
  79. p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
  80. case "DATABASE_MIN_CONNS":
  81. p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
  82. case "RUN_MIGRATIONS":
  83. p.opts.runMigrations = parseBool(value, defaultRunMigrations)
  84. case "DISABLE_HSTS":
  85. p.opts.hsts = !parseBool(value, defaultHSTS)
  86. case "HTTPS":
  87. p.opts.HTTPS = parseBool(value, defaultHTTPS)
  88. case "DISABLE_SCHEDULER_SERVICE":
  89. p.opts.schedulerService = !parseBool(value, defaultSchedulerService)
  90. case "DISABLE_HTTP_SERVICE":
  91. p.opts.httpService = !parseBool(value, defaultHTTPService)
  92. case "CERT_FILE":
  93. p.opts.certFile = parseString(value, defaultCertFile)
  94. case "KEY_FILE":
  95. p.opts.certKeyFile = parseString(value, defaultKeyFile)
  96. case "CERT_DOMAIN":
  97. p.opts.certDomain = parseString(value, defaultCertDomain)
  98. case "CERT_CACHE":
  99. p.opts.certCache = parseString(value, defaultCertCache)
  100. case "CLEANUP_FREQUENCY":
  101. p.opts.cleanupFrequency = parseInt(value, defaultCleanupFrequency)
  102. case "WORKER_POOL_SIZE":
  103. p.opts.workerPoolSize = parseInt(value, defaultWorkerPoolSize)
  104. case "POLLING_FREQUENCY":
  105. p.opts.pollingFrequency = parseInt(value, defaultPollingFrequency)
  106. case "BATCH_SIZE":
  107. p.opts.batchSize = parseInt(value, defaultBatchSize)
  108. case "ARCHIVE_READ_DAYS":
  109. p.opts.archiveReadDays = parseInt(value, defaultArchiveReadDays)
  110. case "PROXY_IMAGES":
  111. p.opts.proxyImages = parseString(value, defaultProxyImages)
  112. case "CREATE_ADMIN":
  113. p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
  114. case "POCKET_CONSUMER_KEY":
  115. p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey)
  116. case "OAUTH2_USER_CREATION":
  117. p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation)
  118. case "OAUTH2_CLIENT_ID":
  119. p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID)
  120. case "OAUTH2_CLIENT_SECRET":
  121. p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
  122. case "OAUTH2_REDIRECT_URL":
  123. p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
  124. case "OAUTH2_PROVIDER":
  125. p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
  126. case "HTTP_CLIENT_TIMEOUT":
  127. p.opts.httpClientTimeout = parseInt(value, defaultHTTPClientTimeout)
  128. case "HTTP_CLIENT_MAX_BODY_SIZE":
  129. p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024)
  130. }
  131. }
  132. if port != "" {
  133. p.opts.listenAddr = ":" + port
  134. }
  135. return nil
  136. }
  137. func parseBaseURL(value string) (string, string, string, error) {
  138. if value == "" {
  139. return defaultBaseURL, defaultRootURL, "", nil
  140. }
  141. if value[len(value)-1:] == "/" {
  142. value = value[:len(value)-1]
  143. }
  144. url, err := url_parser.Parse(value)
  145. if err != nil {
  146. return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err)
  147. }
  148. scheme := strings.ToLower(url.Scheme)
  149. if scheme != "https" && scheme != "http" {
  150. return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https")
  151. }
  152. basePath := url.Path
  153. url.Path = ""
  154. return value, url.String(), basePath, nil
  155. }
  156. func parseBool(value string, fallback bool) bool {
  157. if value == "" {
  158. return fallback
  159. }
  160. value = strings.ToLower(value)
  161. if value == "1" || value == "yes" || value == "true" || value == "on" {
  162. return true
  163. }
  164. return false
  165. }
  166. func parseInt(value string, fallback int) int {
  167. if value == "" {
  168. return fallback
  169. }
  170. v, err := strconv.Atoi(value)
  171. if err != nil {
  172. return fallback
  173. }
  174. return v
  175. }
  176. func parseString(value string, fallback string) string {
  177. if value == "" {
  178. return fallback
  179. }
  180. return value
  181. }