processing_options.go 9.0 KB

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