parse.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package options
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "slices"
  6. "strconv"
  7. "github.com/imgproxy/imgproxy/v3/options/keys"
  8. )
  9. // ensureMaxArgs checks if the number of arguments is as expected
  10. func ensureMaxArgs(name string, args []string, max int) error {
  11. if len(args) > max {
  12. return newInvalidArgsError(name, args)
  13. }
  14. return nil
  15. }
  16. // parseBool parses a boolean option value and warns if the value is invalid
  17. func parseBool(o *Options, key string, args ...string) error {
  18. if err := ensureMaxArgs(key, args, 1); err != nil {
  19. return err
  20. }
  21. b, err := strconv.ParseBool(args[0])
  22. if err != nil {
  23. slog.Warn(fmt.Sprintf("%s `%s` is not a valid boolean value. Treated as false", key, args[0]))
  24. }
  25. o.Set(key, b)
  26. return nil
  27. }
  28. // parseFloat parses a float64 option value
  29. func parseFloat(o *Options, key string, args ...string) error {
  30. if err := ensureMaxArgs(key, args, 1); err != nil {
  31. return err
  32. }
  33. f, err := strconv.ParseFloat(args[0], 64)
  34. if err != nil {
  35. return newInvalidArgsError(key, args)
  36. }
  37. o.Set(key, f)
  38. return nil
  39. }
  40. // parsePositiveFloat parses a positive float64 option value
  41. func parsePositiveFloat(o *Options, key string, args ...string) error {
  42. if err := ensureMaxArgs(key, args, 1); err != nil {
  43. return err
  44. }
  45. f, err := strconv.ParseFloat(args[0], 64)
  46. if err != nil || f < 0 {
  47. return newInvalidArgsError(key, args, "positive number or 0")
  48. }
  49. o.Set(key, f)
  50. return nil
  51. }
  52. // parsePositiveNonZeroFloat parses a positive non-zero float64 option value
  53. func parsePositiveNonZeroFloat(o *Options, key string, args ...string) error {
  54. if err := ensureMaxArgs(key, args, 1); err != nil {
  55. return err
  56. }
  57. f, err := strconv.ParseFloat(args[0], 64)
  58. if err != nil || f <= 0 {
  59. return newInvalidArgsError(key, args, "positive number")
  60. }
  61. o.Set(key, f)
  62. return nil
  63. }
  64. // parseInt parses a positive integer option value
  65. func parseInt(o *Options, key string, args ...string) error {
  66. if err := ensureMaxArgs(key, args, 1); err != nil {
  67. return err
  68. }
  69. i, err := strconv.Atoi(args[0])
  70. if err != nil {
  71. return newOptionArgumentError(key, args)
  72. }
  73. o.Set(key, i)
  74. return nil
  75. }
  76. // parsePositiveNonZeroInt parses a positive non-zero integer option value
  77. func parsePositiveNonZeroInt(o *Options, key string, args ...string) error {
  78. if err := ensureMaxArgs(key, args, 1); err != nil {
  79. return err
  80. }
  81. i, err := strconv.Atoi(args[0])
  82. if err != nil || i <= 0 {
  83. return newInvalidArgsError(key, args, "positive number")
  84. }
  85. o.Set(key, i)
  86. return nil
  87. }
  88. // parsePositiveInt parses a positive integer option value
  89. func parsePositiveInt(o *Options, key string, args ...string) error {
  90. if err := ensureMaxArgs(key, args, 1); err != nil {
  91. return err
  92. }
  93. i, err := strconv.Atoi(args[0])
  94. if err != nil || i < 0 {
  95. return newOptionArgumentError("Invalid %s arguments: %s (expected positive number)", key, args)
  96. }
  97. o.Set(key, i)
  98. return nil
  99. }
  100. // parseQualityInt parses a quality integer option value (1-100)
  101. func parseQualityInt(o *Options, key string, args ...string) error {
  102. if err := ensureMaxArgs(key, args, 1); err != nil {
  103. return err
  104. }
  105. i, err := strconv.Atoi(args[0])
  106. if err != nil || i < 1 || i > 100 {
  107. return newInvalidArgsError(key, args, "number in range 1-100")
  108. }
  109. o.Set(key, i)
  110. return nil
  111. }
  112. // parseResolution parses a resolution option value in megapixels and stores it as pixels
  113. func parseResolution(o *Options, key string, args ...string) error {
  114. if err := ensureMaxArgs(key, args, 1); err != nil {
  115. return err
  116. }
  117. f, err := strconv.ParseFloat(args[0], 64)
  118. if err != nil || f < 0 {
  119. return newInvalidArgsError(key, args, "positive number or 0")
  120. }
  121. // Resolution is defined as megapixels but stored as pixels
  122. o.Set(key, int(f*1000000))
  123. return nil
  124. }
  125. func isGravityOffcetValid(gravity GravityType, offset float64) bool {
  126. return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1)
  127. }
  128. func parseGravity(
  129. o *Options,
  130. key string,
  131. args []string,
  132. allowedTypes []GravityType,
  133. ) error {
  134. nArgs := len(args)
  135. keyType := key + keys.SuffixType
  136. keyXOffset := key + keys.SuffixXOffset
  137. keyYOffset := key + keys.SuffixYOffset
  138. gType, ok := gravityTypes[args[0]]
  139. if ok && slices.Contains(allowedTypes, gType) {
  140. o.Set(keyType, gType)
  141. } else {
  142. return newOptionArgumentError("Invalid %s: %s", keyType, args[0])
  143. }
  144. switch gType {
  145. case GravitySmart:
  146. if nArgs > 1 {
  147. return newInvalidArgsError(key, args)
  148. }
  149. o.Delete(keyXOffset)
  150. o.Delete(keyYOffset)
  151. case GravityFocusPoint:
  152. if nArgs != 3 {
  153. return newInvalidArgsError(key, args)
  154. }
  155. fallthrough
  156. default:
  157. if nArgs > 3 {
  158. return newInvalidArgsError(key, args)
  159. }
  160. if nArgs > 1 {
  161. if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(gType, x) {
  162. o.Set(keyXOffset, x)
  163. } else {
  164. return newOptionArgumentError("Invalid %s: %s", keyXOffset, args[1])
  165. }
  166. }
  167. if nArgs > 2 {
  168. if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(gType, y) {
  169. o.Set(keyYOffset, y)
  170. } else {
  171. return newOptionArgumentError("Invalid %s: %s", keyYOffset, args[2])
  172. }
  173. }
  174. }
  175. return nil
  176. }
  177. func parseExtend(o *Options, key string, args []string) error {
  178. if err := ensureMaxArgs(key, args, 4); err != nil {
  179. return err
  180. }
  181. if err := parseBool(o, key+keys.SuffixEnabled, args[0]); err != nil {
  182. return err
  183. }
  184. if len(args) > 1 {
  185. return parseGravity(o, key+keys.SuffixGravity, args[1:], extendGravityTypes)
  186. }
  187. return nil
  188. }