1
0

parse.go 6.9 KB

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