processing_options.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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 (it imageType) String() string {
  76. for k, v := range imageTypes {
  77. if v == it {
  78. return k
  79. }
  80. }
  81. return ""
  82. }
  83. func (gt gravityType) String() string {
  84. for k, v := range gravityTypes {
  85. if v == gt {
  86. return k
  87. }
  88. }
  89. return ""
  90. }
  91. func (rt resizeType) String() string {
  92. for k, v := range resizeTypes {
  93. if v == rt {
  94. return k
  95. }
  96. }
  97. return ""
  98. }
  99. func decodeURL(parts []string) (string, string, error) {
  100. var extension string
  101. urlParts := strings.Split(strings.Join(parts, ""), ".")
  102. if len(urlParts) > 2 {
  103. return "", "", errors.New("Invalid url encoding")
  104. }
  105. if len(urlParts) == 2 {
  106. extension = urlParts[1]
  107. }
  108. url, err := base64.RawURLEncoding.DecodeString(urlParts[0])
  109. if err != nil {
  110. return "", "", errors.New("Invalid url encoding")
  111. }
  112. return string(url), extension, nil
  113. }
  114. func applyWidthOption(po *processingOptions, args []string) error {
  115. if len(args) > 1 {
  116. return fmt.Errorf("Invalid width arguments: %v", args)
  117. }
  118. if w, err := strconv.Atoi(args[0]); err == nil && w >= 0 {
  119. po.Width = w
  120. } else {
  121. return fmt.Errorf("Invalid width: %s", args[0])
  122. }
  123. return nil
  124. }
  125. func applyHeightOption(po *processingOptions, args []string) error {
  126. if len(args) > 1 {
  127. return fmt.Errorf("Invalid height arguments: %v", args)
  128. }
  129. if h, err := strconv.Atoi(args[0]); err == nil && po.Height >= 0 {
  130. po.Height = h
  131. } else {
  132. return fmt.Errorf("Invalid height: %s", args[0])
  133. }
  134. return nil
  135. }
  136. func applyEnlargeOption(po *processingOptions, args []string) error {
  137. if len(args) > 1 {
  138. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  139. }
  140. po.Enlarge = args[0] != "0"
  141. return nil
  142. }
  143. func applySizeOption(po *processingOptions, args []string) (err error) {
  144. if len(args) > 3 {
  145. return fmt.Errorf("Invalid size arguments: %v", args)
  146. }
  147. if len(args) >= 1 {
  148. if err = applyWidthOption(po, args[0:1]); err != nil {
  149. return
  150. }
  151. }
  152. if len(args) >= 2 {
  153. if err = applyHeightOption(po, args[1:2]); err != nil {
  154. return
  155. }
  156. }
  157. if len(args) == 3 {
  158. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  159. return
  160. }
  161. }
  162. return nil
  163. }
  164. func applyResizeOption(po *processingOptions, args []string) error {
  165. if len(args) > 4 {
  166. return fmt.Errorf("Invalid resize arguments: %v", args)
  167. }
  168. if r, ok := resizeTypes[args[0]]; ok {
  169. po.Resize = r
  170. } else {
  171. return fmt.Errorf("Invalid resize type: %s", args[0])
  172. }
  173. if len(args) > 1 {
  174. if err := applySizeOption(po, args[1:]); err != nil {
  175. return err
  176. }
  177. }
  178. return nil
  179. }
  180. func applyGravityOption(po *processingOptions, args []string) error {
  181. if g, ok := gravityTypes[args[0]]; ok {
  182. po.Gravity.Type = g
  183. } else {
  184. return fmt.Errorf("Invalid gravity: %s", args[0])
  185. }
  186. if po.Gravity.Type == gravityFocusPoint {
  187. if len(args) != 3 {
  188. return fmt.Errorf("Invalid gravity arguments: %v", args)
  189. }
  190. if x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
  191. po.Gravity.X = x
  192. } else {
  193. return fmt.Errorf("Invalid gravity X: %s", args[1])
  194. }
  195. if y, err := strconv.ParseFloat(args[2], 64); err == nil && y >= 0 && y <= 1 {
  196. po.Gravity.Y = y
  197. } else {
  198. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  199. }
  200. } else if len(args) > 1 {
  201. return fmt.Errorf("Invalid gravity arguments: %v", args)
  202. }
  203. return nil
  204. }
  205. func applyBlurOption(po *processingOptions, args []string) error {
  206. if len(args) > 1 {
  207. return fmt.Errorf("Invalid blur arguments: %v", args)
  208. }
  209. if b, err := strconv.ParseFloat(args[0], 32); err == nil || b >= 0 {
  210. po.Blur = float32(b)
  211. } else {
  212. return fmt.Errorf("Invalid blur: %s", args[0])
  213. }
  214. return nil
  215. }
  216. func applySharpenOption(po *processingOptions, args []string) error {
  217. if len(args) > 1 {
  218. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  219. }
  220. if s, err := strconv.ParseFloat(args[0], 32); err == nil || s >= 0 {
  221. po.Sharpen = float32(s)
  222. } else {
  223. return fmt.Errorf("Invalid sharpen: %s", args[0])
  224. }
  225. return nil
  226. }
  227. func applyPresetOption(po *processingOptions, args []string) error {
  228. for _, preset := range args {
  229. if p, ok := conf.Presets[preset]; ok {
  230. if err := applyProcessingOptions(po, p); err != nil {
  231. return err
  232. }
  233. } else {
  234. return fmt.Errorf("Unknown asset: %s", preset)
  235. }
  236. }
  237. return nil
  238. }
  239. func applyFormatOption(po *processingOptions, args []string) error {
  240. if len(args) > 1 {
  241. return fmt.Errorf("Invalid format arguments: %v", args)
  242. }
  243. if conf.EnforceWebp && po.Format == imageTypeWEBP {
  244. // Webp is enforced and already set as format
  245. return nil
  246. }
  247. if f, ok := imageTypes[args[0]]; ok {
  248. po.Format = f
  249. } else {
  250. return fmt.Errorf("Invalid image format: %s", args[0])
  251. }
  252. if !vipsTypeSupportSave[po.Format] {
  253. return errors.New("Resulting image type not supported")
  254. }
  255. return nil
  256. }
  257. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  258. switch name {
  259. case "format":
  260. if err := applyFormatOption(po, args); err != nil {
  261. return err
  262. }
  263. case "resize":
  264. if err := applyResizeOption(po, args); err != nil {
  265. return err
  266. }
  267. case "size":
  268. if err := applySizeOption(po, args); err != nil {
  269. return err
  270. }
  271. case "width":
  272. if err := applyWidthOption(po, args); err != nil {
  273. return err
  274. }
  275. case "height":
  276. if err := applyHeightOption(po, args); err != nil {
  277. return err
  278. }
  279. case "enlarge":
  280. if err := applyEnlargeOption(po, args); err != nil {
  281. return err
  282. }
  283. case "gravity":
  284. if err := applyGravityOption(po, args); err != nil {
  285. return err
  286. }
  287. case "blur":
  288. if err := applyBlurOption(po, args); err != nil {
  289. return err
  290. }
  291. case "sharpen":
  292. if err := applySharpenOption(po, args); err != nil {
  293. return err
  294. }
  295. case "preset":
  296. if err := applyPresetOption(po, args); err != nil {
  297. return err
  298. }
  299. default:
  300. return fmt.Errorf("Unknown processing option: %s", name)
  301. }
  302. return nil
  303. }
  304. func applyProcessingOptions(po *processingOptions, options urlOptions) error {
  305. for name, args := range options {
  306. if err := applyProcessingOption(po, name, args); err != nil {
  307. return err
  308. }
  309. }
  310. return nil
  311. }
  312. func parseURLOptions(opts []string) (urlOptions, []string) {
  313. parsed := make(urlOptions)
  314. urlStart := len(opts) + 1
  315. for i, opt := range opts {
  316. args := strings.Split(opt, ":")
  317. if len(args) == 1 {
  318. urlStart = i
  319. break
  320. }
  321. parsed[args[0]] = args[1:]
  322. }
  323. var rest []string
  324. if urlStart < len(opts) {
  325. rest = opts[urlStart:]
  326. } else {
  327. rest = []string{}
  328. }
  329. return parsed, rest
  330. }
  331. func defaultProcessingOptions(acceptHeader string) (processingOptions, error) {
  332. var err error
  333. po := processingOptions{
  334. Resize: resizeFit,
  335. Width: 0,
  336. Height: 0,
  337. Gravity: gravity{Type: gravityCenter},
  338. Enlarge: false,
  339. Format: imageTypeJPEG,
  340. Blur: 0,
  341. Sharpen: 0,
  342. }
  343. if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(acceptHeader, "image/webp") {
  344. po.Format = imageTypeWEBP
  345. }
  346. if _, ok := conf.Presets["default"]; ok {
  347. err = applyPresetOption(&po, []string{"default"})
  348. }
  349. return po, err
  350. }
  351. func parsePathAdvanced(parts []string, acceptHeader string) (string, processingOptions, error) {
  352. po, err := defaultProcessingOptions(acceptHeader)
  353. if err != nil {
  354. return "", po, err
  355. }
  356. options, urlParts := parseURLOptions(parts)
  357. if err := applyProcessingOptions(&po, options); err != nil {
  358. return "", po, err
  359. }
  360. url, extension, err := decodeURL(urlParts)
  361. if err != nil {
  362. return "", po, err
  363. }
  364. if len(extension) > 0 {
  365. if err := applyFormatOption(&po, []string{extension}); err != nil {
  366. return "", po, errors.New("Resulting image type not supported")
  367. }
  368. }
  369. return string(url), po, nil
  370. }
  371. func parsePathSimple(parts []string, acceptHeader string) (string, processingOptions, error) {
  372. var err error
  373. if len(parts) < 6 {
  374. return "", processingOptions{}, errors.New("Invalid path")
  375. }
  376. po, err := defaultProcessingOptions(acceptHeader)
  377. if err != nil {
  378. return "", po, err
  379. }
  380. po.Resize = resizeTypes[parts[0]]
  381. if err = applyWidthOption(&po, parts[1:2]); err != nil {
  382. return "", po, err
  383. }
  384. if err = applyHeightOption(&po, parts[2:3]); err != nil {
  385. return "", po, err
  386. }
  387. if err = applyGravityOption(&po, parts[3:4]); err != nil {
  388. return "", po, err
  389. }
  390. if err = applyEnlargeOption(&po, parts[4:5]); err != nil {
  391. return "", po, err
  392. }
  393. url, extension, err := decodeURL(parts[5:])
  394. if err != nil {
  395. return "", po, err
  396. }
  397. if len(extension) > 0 {
  398. if err := applyFormatOption(&po, []string{extension}); err != nil {
  399. return "", po, errors.New("Resulting image type not supported")
  400. }
  401. }
  402. return string(url), po, nil
  403. }
  404. func parsePath(r *http.Request) (string, processingOptions, error) {
  405. path := r.URL.Path
  406. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  407. var acceptHeader string
  408. if h, ok := r.Header["Accept"]; ok {
  409. acceptHeader = h[0]
  410. }
  411. if len(parts) < 3 {
  412. return "", processingOptions{}, errors.New("Invalid path")
  413. }
  414. if !conf.AllowInsecure {
  415. if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  416. return "", processingOptions{}, err
  417. }
  418. }
  419. if _, ok := resizeTypes[parts[1]]; ok {
  420. return parsePathSimple(parts[1:], acceptHeader)
  421. } else {
  422. return parsePathAdvanced(parts[1:], acceptHeader)
  423. }
  424. }