processing_options.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. package main
  2. /*
  3. #cgo LDFLAGS: -s -w
  4. #include <image_types.h>
  5. */
  6. import "C"
  7. import (
  8. "encoding/base64"
  9. "errors"
  10. "fmt"
  11. "net/http"
  12. "strconv"
  13. "strings"
  14. )
  15. type imageType int
  16. const (
  17. imageTypeUnknown = C.UNKNOWN
  18. imageTypeJPEG = C.JPEG
  19. imageTypePNG = C.PNG
  20. imageTypeWEBP = C.WEBP
  21. imageTypeGIF = C.GIF
  22. )
  23. var imageTypes = map[string]imageType{
  24. "jpeg": imageTypeJPEG,
  25. "jpg": imageTypeJPEG,
  26. "png": imageTypePNG,
  27. "webp": imageTypeWEBP,
  28. "gif": imageTypeGIF,
  29. }
  30. type gravityType int
  31. const (
  32. gravityCenter gravityType = iota
  33. gravityNorth
  34. gravityEast
  35. gravitySouth
  36. gravityWest
  37. gravitySmart
  38. )
  39. var gravityTypes = map[string]gravityType{
  40. "ce": gravityCenter,
  41. "no": gravityNorth,
  42. "ea": gravityEast,
  43. "so": gravitySouth,
  44. "we": gravityWest,
  45. "sm": gravitySmart,
  46. }
  47. type resizeType int
  48. const (
  49. resizeFit resizeType = iota
  50. resizeFill
  51. resizeCrop
  52. )
  53. var resizeTypes = map[string]resizeType{
  54. "fit": resizeFit,
  55. "fill": resizeFill,
  56. "crop": resizeCrop,
  57. }
  58. type processingOptions struct {
  59. Resize resizeType
  60. Width int
  61. Height int
  62. Gravity gravityType
  63. Enlarge bool
  64. Format imageType
  65. Blur float32
  66. Sharpen float32
  67. }
  68. func defaultProcessingOptions() processingOptions {
  69. return processingOptions{
  70. Resize: resizeFit,
  71. Width: 0,
  72. Height: 0,
  73. Gravity: gravityCenter,
  74. Enlarge: false,
  75. Format: imageTypeJPEG,
  76. Blur: 0,
  77. Sharpen: 0,
  78. }
  79. }
  80. func decodeURL(parts []string) (string, imageType, error) {
  81. var imgType imageType = imageTypeJPEG
  82. urlParts := strings.Split(strings.Join(parts, ""), ".")
  83. if len(urlParts) > 2 {
  84. return "", 0, errors.New("Invalid url encoding")
  85. }
  86. if len(urlParts) == 2 {
  87. if f, ok := imageTypes[urlParts[1]]; ok {
  88. imgType = f
  89. } else {
  90. return "", 0, fmt.Errorf("Invalid image format: %s", urlParts[1])
  91. }
  92. }
  93. url, err := base64.RawURLEncoding.DecodeString(urlParts[0])
  94. if err != nil {
  95. return "", 0, errors.New("Invalid url encoding")
  96. }
  97. return string(url), imgType, nil
  98. }
  99. func applyWidthOption(po *processingOptions, args []string) error {
  100. if len(args) > 1 {
  101. return fmt.Errorf("Invalid width arguments: %v", args)
  102. }
  103. if w, err := strconv.Atoi(args[0]); err == nil || w >= 0 {
  104. po.Width = w
  105. } else {
  106. return fmt.Errorf("Invalid width: %s", args[0])
  107. }
  108. return nil
  109. }
  110. func applyHeightOption(po *processingOptions, args []string) error {
  111. if len(args) > 1 {
  112. return fmt.Errorf("Invalid height arguments: %v", args)
  113. }
  114. if h, err := strconv.Atoi(args[0]); err == nil || po.Height >= 0 {
  115. po.Height = h
  116. } else {
  117. return fmt.Errorf("Invalid height: %s", args[0])
  118. }
  119. return nil
  120. }
  121. func applyEnlargeOption(po *processingOptions, args []string) error {
  122. if len(args) > 1 {
  123. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  124. }
  125. po.Enlarge = args[0] != "0"
  126. return nil
  127. }
  128. func applySizeOption(po *processingOptions, args []string) (err error) {
  129. if len(args) > 3 {
  130. return fmt.Errorf("Invalid size arguments: %v", args)
  131. }
  132. if len(args) >= 1 {
  133. if err = applyWidthOption(po, args[0:1]); err != nil {
  134. return
  135. }
  136. }
  137. if len(args) >= 2 {
  138. if err = applyHeightOption(po, args[1:2]); err != nil {
  139. return
  140. }
  141. }
  142. if len(args) == 3 {
  143. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  144. return
  145. }
  146. }
  147. return nil
  148. }
  149. func applyResizeOption(po *processingOptions, args []string) error {
  150. if len(args) > 4 {
  151. return fmt.Errorf("Invalid resize arguments: %v", args)
  152. }
  153. if r, ok := resizeTypes[args[0]]; ok {
  154. po.Resize = r
  155. } else {
  156. return fmt.Errorf("Invalid resize type: %s", args[0])
  157. }
  158. if len(args) > 1 {
  159. if err := applySizeOption(po, args[1:]); err != nil {
  160. return err
  161. }
  162. }
  163. return nil
  164. }
  165. func applyGravityOption(po *processingOptions, args []string) error {
  166. if len(args) > 1 {
  167. return fmt.Errorf("Invalid resize arguments: %v", args)
  168. }
  169. if g, ok := gravityTypes[args[0]]; ok {
  170. po.Gravity = g
  171. } else {
  172. return fmt.Errorf("Invalid gravity: %s", args[0])
  173. }
  174. return nil
  175. }
  176. func applyBlurOption(po *processingOptions, args []string) error {
  177. if len(args) > 1 {
  178. return fmt.Errorf("Invalid blur arguments: %v", args)
  179. }
  180. if b, err := strconv.ParseFloat(args[0], 32); err == nil || b >= 0 {
  181. po.Blur = float32(b)
  182. } else {
  183. return fmt.Errorf("Invalid blur: %s", args[0])
  184. }
  185. return nil
  186. }
  187. func applySharpenOption(po *processingOptions, args []string) error {
  188. if len(args) > 1 {
  189. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  190. }
  191. if s, err := strconv.ParseFloat(args[0], 32); err == nil || s >= 0 {
  192. po.Sharpen = float32(s)
  193. } else {
  194. return fmt.Errorf("Invalid sharpen: %s", args[0])
  195. }
  196. return nil
  197. }
  198. func applyFormatOption(po *processingOptions, imgType imageType) error {
  199. if !vipsTypeSupportSave[imgType] {
  200. return errors.New("Resulting image type not supported")
  201. }
  202. po.Format = imgType
  203. return nil
  204. }
  205. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  206. switch name {
  207. case "resize":
  208. if err := applyResizeOption(po, args); err != nil {
  209. return err
  210. }
  211. case "size":
  212. if err := applySizeOption(po, args); err != nil {
  213. return err
  214. }
  215. case "width":
  216. if err := applyWidthOption(po, args); err != nil {
  217. return err
  218. }
  219. case "height":
  220. if err := applyHeightOption(po, args); err != nil {
  221. return err
  222. }
  223. case "enlarge":
  224. if err := applyEnlargeOption(po, args); err != nil {
  225. return err
  226. }
  227. case "gravity":
  228. if err := applyGravityOption(po, args); err != nil {
  229. return err
  230. }
  231. case "blur":
  232. if err := applyBlurOption(po, args); err != nil {
  233. return err
  234. }
  235. case "sharpen":
  236. if err := applySharpenOption(po, args); err != nil {
  237. return err
  238. }
  239. }
  240. return nil
  241. }
  242. func parsePathAdvanced(parts []string) (string, processingOptions, error) {
  243. var urlStart int
  244. po := defaultProcessingOptions()
  245. for i, part := range parts {
  246. args := strings.Split(part, ":")
  247. if len(args) == 1 {
  248. urlStart = i
  249. break
  250. }
  251. if err := applyProcessingOption(&po, args[0], args[1:]); err != nil {
  252. return "", po, err
  253. }
  254. }
  255. url, imgType, err := decodeURL(parts[urlStart:])
  256. if err != nil {
  257. return "", po, err
  258. }
  259. if err := applyFormatOption(&po, imgType); err != nil {
  260. return "", po, errors.New("Resulting image type not supported")
  261. }
  262. return string(url), po, nil
  263. }
  264. func parsePathSimple(parts []string) (string, processingOptions, error) {
  265. var po processingOptions
  266. var err error
  267. if len(parts) < 6 {
  268. return "", po, errors.New("Invalid path")
  269. }
  270. po.Resize = resizeTypes[parts[0]]
  271. if err = applyWidthOption(&po, parts[1:2]); err != nil {
  272. return "", po, err
  273. }
  274. if err = applyHeightOption(&po, parts[2:3]); err != nil {
  275. return "", po, err
  276. }
  277. if err = applyGravityOption(&po, parts[3:4]); err != nil {
  278. return "", po, err
  279. }
  280. if err = applyEnlargeOption(&po, parts[4:5]); err != nil {
  281. return "", po, err
  282. }
  283. url, imgType, err := decodeURL(parts[5:])
  284. if err != nil {
  285. return "", po, err
  286. }
  287. if err := applyFormatOption(&po, imgType); err != nil {
  288. return "", po, errors.New("Resulting image type not supported")
  289. }
  290. return string(url), po, nil
  291. }
  292. func parsePath(r *http.Request) (string, processingOptions, error) {
  293. path := r.URL.Path
  294. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  295. if len(parts) < 3 {
  296. return "", processingOptions{}, errors.New("Invalid path")
  297. }
  298. // if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  299. // return "", processingOptions{}, err
  300. // }
  301. if _, ok := resizeTypes[parts[1]]; ok {
  302. return parsePathSimple(parts[1:])
  303. } else {
  304. return parsePathAdvanced(parts[1:])
  305. }
  306. }