parse.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package optionsparser
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "log/slog"
  6. "maps"
  7. "slices"
  8. "strconv"
  9. "strings"
  10. "github.com/imgproxy/imgproxy/v3/options"
  11. "github.com/imgproxy/imgproxy/v3/options/keys"
  12. "github.com/imgproxy/imgproxy/v3/processing"
  13. "github.com/imgproxy/imgproxy/v3/vips/color"
  14. )
  15. // ensureMaxArgs checks if the number of arguments is as expected
  16. func (p *Parser) ensureMaxArgs(name string, args []string, max int) error {
  17. if len(args) > max {
  18. return newInvalidArgsError(name, args)
  19. }
  20. return nil
  21. }
  22. // parseBool parses a boolean option value and warns if the value is invalid
  23. func (p *Parser) parseBool(o *options.Options, key string, args ...string) error {
  24. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  25. return err
  26. }
  27. b, err := strconv.ParseBool(args[0])
  28. if err != nil {
  29. slog.Warn(fmt.Sprintf("%s `%s` is not a valid boolean value. Treated as false", key, args[0]))
  30. }
  31. o.Set(key, b)
  32. return nil
  33. }
  34. // parseFloat parses a float64 option value
  35. func (p *Parser) parseFloat(o *options.Options, key string, args ...string) error {
  36. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  37. return err
  38. }
  39. f, err := strconv.ParseFloat(args[0], 64)
  40. if err != nil {
  41. return newInvalidArgsError(key, args)
  42. }
  43. o.Set(key, f)
  44. return nil
  45. }
  46. // parsePositiveFloat parses a positive float64 option value
  47. func (p *Parser) parsePositiveFloat(o *options.Options, key string, args ...string) error {
  48. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  49. return err
  50. }
  51. f, err := strconv.ParseFloat(args[0], 64)
  52. if err != nil || f < 0 {
  53. return newInvalidArgumentError(key, args[0], "positive number or 0")
  54. }
  55. o.Set(key, f)
  56. return nil
  57. }
  58. // parsePositiveNonZeroFloat parses a positive non-zero float64 option value
  59. func (p *Parser) parsePositiveNonZeroFloat(o *options.Options, key string, args ...string) error {
  60. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  61. return err
  62. }
  63. f, err := strconv.ParseFloat(args[0], 64)
  64. if err != nil || f <= 0 {
  65. return newInvalidArgumentError(key, args[0], "positive number")
  66. }
  67. o.Set(key, f)
  68. return nil
  69. }
  70. // parseInt parses a positive integer option value
  71. func (p *Parser) parseInt(o *options.Options, key string, args ...string) error {
  72. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  73. return err
  74. }
  75. i, err := strconv.Atoi(args[0])
  76. if err != nil {
  77. return newInvalidArgumentError(key, args[0], "integer number")
  78. }
  79. o.Set(key, i)
  80. return nil
  81. }
  82. // parsePositiveNonZeroInt parses a positive non-zero integer option value
  83. func (p *Parser) parsePositiveNonZeroInt(o *options.Options, key string, args ...string) error {
  84. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  85. return err
  86. }
  87. i, err := strconv.Atoi(args[0])
  88. if err != nil || i <= 0 {
  89. return newInvalidArgumentError(key, args[0], "positive number")
  90. }
  91. o.Set(key, i)
  92. return nil
  93. }
  94. // parsePositiveInt parses a positive integer option value
  95. func (p *Parser) parsePositiveInt(o *options.Options, key string, args ...string) error {
  96. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  97. return err
  98. }
  99. i, err := strconv.Atoi(args[0])
  100. if err != nil || i < 0 {
  101. return newInvalidArgumentError(key, args[0], "positive number or 0")
  102. }
  103. o.Set(key, i)
  104. return nil
  105. }
  106. // parseQualityInt parses a quality integer option value (1-100)
  107. func (p *Parser) parseQualityInt(o *options.Options, key string, args ...string) error {
  108. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  109. return err
  110. }
  111. i, err := strconv.Atoi(args[0])
  112. if err != nil || i < 1 || i > 100 {
  113. return newInvalidArgumentError(key, args[0], "number in range 1-100")
  114. }
  115. o.Set(key, i)
  116. return nil
  117. }
  118. // parseOpacityFloat parses an opacity float option value (0-1)
  119. func (p *Parser) parseOpacityFloat(o *options.Options, key string, args ...string) error {
  120. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  121. return err
  122. }
  123. f, err := strconv.ParseFloat(args[0], 64)
  124. if err != nil || f < 0 || f > 1 {
  125. return newInvalidArgumentError(key, args[0], "number in range 0-1")
  126. }
  127. o.Set(key, f)
  128. return nil
  129. }
  130. // parseResolution parses a resolution option value in megapixels and stores it as pixels
  131. func (p *Parser) parseResolution(o *options.Options, key string, args ...string) error {
  132. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  133. return err
  134. }
  135. f, err := strconv.ParseFloat(args[0], 64)
  136. if err != nil || f < 0 {
  137. return newInvalidArgumentError(key, args[0], "positive number or 0")
  138. }
  139. // Resolution is defined as megapixels but stored as pixels
  140. o.Set(key, int(f*1000000))
  141. return nil
  142. }
  143. // parseBase64String parses a base64-encoded string option value
  144. func (p *Parser) parseBase64String(o *options.Options, key string, args ...string) error {
  145. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  146. return err
  147. }
  148. b, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(args[0], "="))
  149. if err != nil {
  150. return newInvalidArgumentError(key, args[0], "URL-safe base64-encoded string")
  151. }
  152. o.Set(key, string(b))
  153. return nil
  154. }
  155. // parseHexRGBColor parses a hex-encoded RGB color option value
  156. func (p *Parser) parseHexRGBColor(o *options.Options, key string, args ...string) error {
  157. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  158. return err
  159. }
  160. c, err := color.RGBFromHex(args[0])
  161. if err != nil {
  162. return newInvalidArgumentError(key, args[0], "hex-encoded color")
  163. }
  164. o.Set(key, c)
  165. return nil
  166. }
  167. // parseFromMap parses an option value from a map of allowed values
  168. func parseFromMap[T comparable](
  169. p *Parser,
  170. o *options.Options,
  171. key string,
  172. m map[string]T,
  173. args ...string,
  174. ) error {
  175. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  176. return err
  177. }
  178. v, ok := m[args[0]]
  179. if !ok {
  180. return newInvalidArgumentError(key, args[0], slices.Collect(maps.Keys(m))...)
  181. }
  182. o.Set(key, v)
  183. return nil
  184. }
  185. func (p *Parser) parseGravityType(
  186. o *options.Options,
  187. key string,
  188. allowedTypes []processing.GravityType,
  189. args ...string,
  190. ) (processing.GravityType, error) {
  191. if err := p.ensureMaxArgs(key, args, 1); err != nil {
  192. return processing.GravityUnknown, err
  193. }
  194. gType, ok := processing.GravityTypes[args[0]]
  195. if !ok || !slices.Contains(allowedTypes, gType) {
  196. types := make([]string, len(allowedTypes))
  197. for i, at := range allowedTypes {
  198. types[i] = at.String()
  199. }
  200. return processing.GravityUnknown, newInvalidArgumentError(key, args[0], types...)
  201. }
  202. o.Set(key, gType)
  203. return gType, nil
  204. }
  205. func (p *Parser) isGravityOffsetValid(gravity processing.GravityType, offset float64) bool {
  206. return gravity != processing.GravityFocusPoint || (offset >= 0 && offset <= 1)
  207. }
  208. func (p *Parser) parseGravity(
  209. o *options.Options,
  210. key string,
  211. allowedTypes []processing.GravityType,
  212. args ...string,
  213. ) error {
  214. nArgs := len(args)
  215. keyType := key + keys.SuffixType
  216. keyXOffset := key + keys.SuffixXOffset
  217. keyYOffset := key + keys.SuffixYOffset
  218. gType, err := p.parseGravityType(o, keyType, allowedTypes, args[0])
  219. if err != nil {
  220. return err
  221. }
  222. switch gType {
  223. case processing.GravitySmart:
  224. if nArgs > 1 {
  225. return newInvalidArgsError(key, args)
  226. }
  227. o.Delete(keyXOffset)
  228. o.Delete(keyYOffset)
  229. case processing.GravityFocusPoint:
  230. if nArgs != 3 {
  231. return newInvalidArgsError(key, args)
  232. }
  233. fallthrough
  234. default:
  235. if nArgs > 3 {
  236. return newInvalidArgsError(key, args)
  237. }
  238. if nArgs > 1 {
  239. if x, err := strconv.ParseFloat(args[1], 64); err == nil && p.isGravityOffsetValid(gType, x) {
  240. o.Set(keyXOffset, x)
  241. } else {
  242. return newInvalidArgumentError(keyXOffset, args[1])
  243. }
  244. }
  245. if nArgs > 2 {
  246. if y, err := strconv.ParseFloat(args[2], 64); err == nil && p.isGravityOffsetValid(gType, y) {
  247. o.Set(keyYOffset, y)
  248. } else {
  249. return newInvalidArgumentError(keyYOffset, args[2])
  250. }
  251. }
  252. }
  253. return nil
  254. }
  255. func (p *Parser) parseExtend(o *options.Options, key string, args []string) error {
  256. if err := p.ensureMaxArgs(key, args, 4); err != nil {
  257. return err
  258. }
  259. if err := p.parseBool(o, key+keys.SuffixEnabled, args[0]); err != nil {
  260. return err
  261. }
  262. if len(args) > 1 {
  263. return p.parseGravity(o, key+keys.SuffixGravity, processing.ExtendGravityTypes, args[1:]...)
  264. }
  265. return nil
  266. }