parse.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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 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 parseBool(o *options.Options, key string, args ...string) error {
  24. if err := 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 parseFloat(o *options.Options, key string, args ...string) error {
  36. if err := 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 parsePositiveFloat(o *options.Options, key string, args ...string) error {
  48. if err := 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 parsePositiveNonZeroFloat(o *options.Options, key string, args ...string) error {
  60. if err := 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 parseInt(o *options.Options, key string, args ...string) error {
  72. if err := 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 parsePositiveNonZeroInt(o *options.Options, key string, args ...string) error {
  84. if err := 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 parsePositiveInt(o *options.Options, key string, args ...string) error {
  96. if err := 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 parseQualityInt(o *options.Options, key string, args ...string) error {
  108. if err := 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 parseOpacityFloat(o *options.Options, key string, args ...string) error {
  120. if err := 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 parseResolution(o *options.Options, key string, args ...string) error {
  132. if err := 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 parseBase64String(o *options.Options, key string, args ...string) error {
  145. if err := 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 parseHexRGBColor(o *options.Options, key string, args ...string) error {
  157. if err := 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](o *options.Options, key string, m map[string]T, args ...string) error {
  169. if err := ensureMaxArgs(key, args, 1); err != nil {
  170. return err
  171. }
  172. v, ok := m[args[0]]
  173. if !ok {
  174. return newInvalidArgumentError(key, args[0], slices.Collect(maps.Keys(m))...)
  175. }
  176. o.Set(key, v)
  177. return nil
  178. }
  179. func parseGravityType(
  180. o *options.Options,
  181. key string,
  182. allowedTypes []processing.GravityType,
  183. args ...string,
  184. ) (processing.GravityType, error) {
  185. if err := ensureMaxArgs(key, args, 1); err != nil {
  186. return processing.GravityUnknown, err
  187. }
  188. gType, ok := processing.GravityTypes[args[0]]
  189. if !ok || !slices.Contains(allowedTypes, gType) {
  190. types := make([]string, len(allowedTypes))
  191. for i, at := range allowedTypes {
  192. types[i] = at.String()
  193. }
  194. return processing.GravityUnknown, newInvalidArgumentError(key, args[0], types...)
  195. }
  196. o.Set(key, gType)
  197. return gType, nil
  198. }
  199. func isGravityOffsetValid(gravity processing.GravityType, offset float64) bool {
  200. return gravity != processing.GravityFocusPoint || (offset >= 0 && offset <= 1)
  201. }
  202. func parseGravity(
  203. o *options.Options,
  204. key string,
  205. allowedTypes []processing.GravityType,
  206. args ...string,
  207. ) error {
  208. nArgs := len(args)
  209. keyType := key + keys.SuffixType
  210. keyXOffset := key + keys.SuffixXOffset
  211. keyYOffset := key + keys.SuffixYOffset
  212. gType, err := parseGravityType(o, keyType, allowedTypes, args[0])
  213. if err != nil {
  214. return err
  215. }
  216. switch gType {
  217. case processing.GravitySmart:
  218. if nArgs > 1 {
  219. return newInvalidArgsError(key, args)
  220. }
  221. o.Delete(keyXOffset)
  222. o.Delete(keyYOffset)
  223. case processing.GravityFocusPoint:
  224. if nArgs != 3 {
  225. return newInvalidArgsError(key, args)
  226. }
  227. fallthrough
  228. default:
  229. if nArgs > 3 {
  230. return newInvalidArgsError(key, args)
  231. }
  232. if nArgs > 1 {
  233. if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffsetValid(gType, x) {
  234. o.Set(keyXOffset, x)
  235. } else {
  236. return newInvalidArgumentError(keyXOffset, args[1])
  237. }
  238. }
  239. if nArgs > 2 {
  240. if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffsetValid(gType, y) {
  241. o.Set(keyYOffset, y)
  242. } else {
  243. return newInvalidArgumentError(keyYOffset, args[2])
  244. }
  245. }
  246. }
  247. return nil
  248. }
  249. func parseExtend(o *options.Options, key string, args []string) error {
  250. if err := ensureMaxArgs(key, args, 4); err != nil {
  251. return err
  252. }
  253. if err := parseBool(o, key+keys.SuffixEnabled, args[0]); err != nil {
  254. return err
  255. }
  256. if len(args) > 1 {
  257. return parseGravity(o, key+keys.SuffixGravity, processing.ExtendGravityTypes, args[1:]...)
  258. }
  259. return nil
  260. }