parsers.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package env
  2. import (
  3. "bufio"
  4. "encoding/hex"
  5. "fmt"
  6. "os"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "github.com/imgproxy/imgproxy/v3/imagetype"
  12. )
  13. // Int parses an integer from the environment variable
  14. func Int(i *int, desc Desc) error {
  15. env, ok := desc.Get()
  16. if !ok {
  17. return nil
  18. }
  19. value, err := strconv.Atoi(env)
  20. if err != nil {
  21. return desc.ErrorParse(err)
  22. }
  23. *i = value
  24. return nil
  25. }
  26. // Float parses a float64 value from the environment variable
  27. func Float(i *float64, desc Desc) error {
  28. env, ok := desc.Get()
  29. if !ok {
  30. return nil
  31. }
  32. value, err := strconv.ParseFloat(env, 64)
  33. if err != nil {
  34. return desc.ErrorParse(err)
  35. }
  36. *i = value
  37. return nil
  38. }
  39. // MegaInt parses a "megascale" integer from the environment variable
  40. func MegaInt(f *int, desc Desc) error {
  41. env, ok := desc.Get()
  42. if !ok {
  43. return nil
  44. }
  45. value, err := strconv.ParseFloat(env, 64)
  46. if err != nil {
  47. return desc.ErrorParse(err)
  48. }
  49. *f = int(value) * 1_000_000
  50. return nil
  51. }
  52. // duration parses a duration (in resolution) from the environment variable
  53. func duration(d *time.Duration, desc Desc, resolution time.Duration) error {
  54. env, ok := desc.Get()
  55. if !ok {
  56. return nil
  57. }
  58. value, err := strconv.Atoi(env)
  59. if err != nil {
  60. return desc.ErrorParse(err)
  61. }
  62. *d = time.Duration(value) * time.Second
  63. return nil
  64. }
  65. // Duration parses a duration (in seconds) from the environment variable
  66. func Duration(d *time.Duration, desc Desc) error {
  67. return duration(d, desc, time.Second)
  68. }
  69. // DurationMils parses a duration (in milliseconds) from the environment variable
  70. func DurationMils(d *time.Duration, desc Desc) error {
  71. return duration(d, desc, time.Millisecond)
  72. }
  73. // String sets the string from the environment variable. Empty value is allowed.
  74. func String(s *string, desc Desc) error {
  75. if env, ok := desc.Get(); ok {
  76. *s = env
  77. }
  78. return nil
  79. }
  80. // Bool parses a boolean from the environment variable
  81. func Bool(b *bool, desc Desc) error {
  82. env, ok := desc.Get()
  83. if !ok {
  84. return nil
  85. }
  86. value, err := strconv.ParseBool(env)
  87. if err != nil {
  88. return desc.ErrorParse(err)
  89. }
  90. *b = value
  91. return nil
  92. }
  93. // StringSliceSep parses a string slice from the environment variable, using the given separator
  94. func StringSliceSep(s *[]string, desc Desc, sep string) error {
  95. env, ok := desc.Get()
  96. if !ok {
  97. return nil
  98. }
  99. parts := strings.Split(env, sep)
  100. for i, p := range parts {
  101. parts[i] = strings.TrimSpace(p)
  102. }
  103. *s = parts
  104. return nil
  105. }
  106. // StringSliceFile parses a string slice from a file, one entry per line
  107. func StringSliceFile(s *[]string, desc Desc, path string) error {
  108. if len(path) == 0 {
  109. return nil
  110. }
  111. f, err := os.Open(path)
  112. if err != nil {
  113. return desc.Errorf("can't open file %s", path)
  114. }
  115. defer f.Close()
  116. scanner := bufio.NewScanner(f)
  117. for scanner.Scan() {
  118. str := strings.TrimSpace(scanner.Text())
  119. if len(str) == 0 || strings.HasPrefix(str, "#") {
  120. continue
  121. }
  122. *s = append(*s, str)
  123. }
  124. if err := scanner.Err(); err != nil {
  125. return desc.Errorf("failed to read presets file: %s", err)
  126. }
  127. return nil
  128. }
  129. // StringSlice parses a string slice from the environment variable, using comma as a separator
  130. func StringSlice(s *[]string, desc Desc) error {
  131. StringSliceSep(s, desc, ",")
  132. return nil
  133. }
  134. // URLPath parses and normalizes a URL path from the environment variable
  135. func URLPath(s *string, desc Desc) error {
  136. env, ok := desc.Get()
  137. if !ok {
  138. return nil
  139. }
  140. if i := strings.IndexByte(env, '?'); i >= 0 {
  141. env = env[:i]
  142. }
  143. if i := strings.IndexByte(env, '#'); i >= 0 {
  144. env = env[:i]
  145. }
  146. if len(env) > 0 && env[len(env)-1] == '/' {
  147. env = env[:len(env)-1]
  148. }
  149. if len(env) > 0 && env[0] != '/' {
  150. env = "/" + env
  151. }
  152. *s = env
  153. return nil
  154. }
  155. // ImageTypes parses a slice of image types from the environment variable
  156. func ImageTypes(it *[]imagetype.Type, desc Desc) error {
  157. // Get image types from environment variable
  158. env, ok := desc.Get()
  159. if !ok {
  160. return nil
  161. }
  162. parts := strings.Split(env, ",")
  163. *it = make([]imagetype.Type, 0, len(parts))
  164. for _, p := range parts {
  165. part := strings.TrimSpace(p)
  166. // For every part passed through the environment variable,
  167. // check if it matches any of the image types defined in
  168. // the imagetype package or return error.
  169. t, ok := imagetype.GetTypeByName(part)
  170. if !ok {
  171. return desc.Errorf("unknown image format: %s", part)
  172. }
  173. *it = append(*it, t)
  174. }
  175. return nil
  176. }
  177. // ImageTypesQuality parses a string of format=queality pairs
  178. func ImageTypesQuality(m map[imagetype.Type]int, desc Desc) error {
  179. env, ok := desc.Get()
  180. if !ok {
  181. return nil
  182. }
  183. parts := strings.SplitSeq(env, ",")
  184. for p := range parts {
  185. i := strings.Index(p, "=")
  186. if i < 0 {
  187. return desc.Errorf("invalid format quality string: %s", p)
  188. }
  189. // Split the string into image type and quality
  190. imgtypeStr, qStr := strings.TrimSpace(p[:i]), strings.TrimSpace(p[i+1:])
  191. // Check if quality is a valid integer
  192. q, err := strconv.Atoi(qStr)
  193. if err != nil || q <= 0 || q > 100 {
  194. return desc.Errorf("invalid quality: %s", p)
  195. }
  196. t, ok := imagetype.GetTypeByName(imgtypeStr)
  197. if !ok {
  198. return desc.Errorf("unknown image format: %s", imgtypeStr)
  199. }
  200. m[t] = q
  201. }
  202. return nil
  203. }
  204. // Patterns parses a slice of regexps from the environment variable
  205. func Patterns(s *[]*regexp.Regexp, desc Desc) error {
  206. env, ok := desc.Get()
  207. if !ok {
  208. return nil
  209. }
  210. parts := strings.Split(env, ",")
  211. result := make([]*regexp.Regexp, len(parts))
  212. for i, p := range parts {
  213. result[i] = RegexpFromPattern(strings.TrimSpace(p))
  214. }
  215. *s = result
  216. return nil
  217. }
  218. // RegexpFromPattern creates a regexp from a wildcard pattern
  219. func RegexpFromPattern(pattern string) *regexp.Regexp {
  220. var result strings.Builder
  221. // Perform prefix matching
  222. result.WriteString("^")
  223. for i, part := range strings.Split(pattern, "*") {
  224. // Add a regexp match all without slashes for each wildcard character
  225. if i > 0 {
  226. result.WriteString("([^/]*)")
  227. }
  228. // Quote other parts of the pattern
  229. result.WriteString(regexp.QuoteMeta(part))
  230. }
  231. // It is safe to use regexp.MustCompile since the expression is always valid
  232. return regexp.MustCompile(result.String())
  233. }
  234. // HexSlice parses a slice of hex-encoded byte slices from the environment variable
  235. func HexSlice(b *[][]byte, desc Desc) error {
  236. var err error
  237. env, ok := desc.Get()
  238. if !ok {
  239. return nil
  240. }
  241. parts := strings.Split(env, ",")
  242. keys := make([][]byte, len(parts))
  243. for i, part := range parts {
  244. if keys[i], err = hex.DecodeString(part); err != nil {
  245. return desc.Errorf("%s expected to be hex-encoded string", part)
  246. }
  247. }
  248. *b = keys
  249. return nil
  250. }
  251. // FromMap sets a value from a enum map based on the environment variable
  252. func FromMap[T any](v *T, m map[string]T, desc Desc) error {
  253. env, ok := desc.Get()
  254. if !ok {
  255. return nil
  256. }
  257. if val, ok := m[env]; ok {
  258. *v = val
  259. } else {
  260. return desc.Errorf("%s", env)
  261. }
  262. return nil
  263. }
  264. // StringMap parses a map of string key-value pairs from the environment variable
  265. func StringMap(m *map[string]string, desc Desc) error {
  266. env, ok := desc.Get()
  267. if !ok {
  268. return nil
  269. }
  270. mm := make(map[string]string)
  271. keyvalues := strings.SplitSeq(env, ";")
  272. for keyvalue := range keyvalues {
  273. parts := strings.SplitN(keyvalue, "=", 2)
  274. if len(parts) != 2 {
  275. return fmt.Errorf("invalid key/value: %s", keyvalue)
  276. }
  277. mm[parts[0]] = parts[1]
  278. }
  279. *m = mm
  280. return nil
  281. }