processing_options.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. package main
  2. /*
  3. #cgo LDFLAGS: -s -w
  4. #include "image_types.h"
  5. */
  6. import "C"
  7. import (
  8. "context"
  9. "encoding/base64"
  10. "errors"
  11. "fmt"
  12. "net/http"
  13. "regexp"
  14. "strconv"
  15. "strings"
  16. )
  17. type urlOptions map[string][]string
  18. type imageType int
  19. const (
  20. imageTypeUnknown = C.UNKNOWN
  21. imageTypeJPEG = C.JPEG
  22. imageTypePNG = C.PNG
  23. imageTypeWEBP = C.WEBP
  24. imageTypeGIF = C.GIF
  25. )
  26. var imageTypes = map[string]imageType{
  27. "jpeg": imageTypeJPEG,
  28. "jpg": imageTypeJPEG,
  29. "png": imageTypePNG,
  30. "webp": imageTypeWEBP,
  31. "gif": imageTypeGIF,
  32. }
  33. type gravityType int
  34. const (
  35. gravityCenter gravityType = iota
  36. gravityNorth
  37. gravityEast
  38. gravitySouth
  39. gravityWest
  40. gravityNorthWest
  41. gravityNorthEast
  42. gravitySouthWest
  43. gravitySouthEast
  44. gravitySmart
  45. gravityFocusPoint
  46. )
  47. var gravityTypes = map[string]gravityType{
  48. "ce": gravityCenter,
  49. "no": gravityNorth,
  50. "ea": gravityEast,
  51. "so": gravitySouth,
  52. "we": gravityWest,
  53. "nowe": gravityNorthWest,
  54. "noea": gravityNorthEast,
  55. "sowe": gravitySouthWest,
  56. "soea": gravitySouthEast,
  57. "sm": gravitySmart,
  58. "fp": gravityFocusPoint,
  59. }
  60. type gravityOptions struct {
  61. Type gravityType
  62. X, Y float64
  63. }
  64. type resizeType int
  65. const (
  66. resizeFit resizeType = iota
  67. resizeFill
  68. resizeCrop
  69. )
  70. var resizeTypes = map[string]resizeType{
  71. "fit": resizeFit,
  72. "fill": resizeFill,
  73. "crop": resizeCrop,
  74. }
  75. type color struct{ R, G, B uint8 }
  76. var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
  77. const (
  78. hexColorLongFormat = "%02x%02x%02x"
  79. hexColorShortFormat = "%1x%1x%1x"
  80. )
  81. type watermarkOptions struct {
  82. Enabled bool
  83. Opacity float64
  84. Replicate bool
  85. Gravity gravityType
  86. OffsetX int
  87. OffsetY int
  88. Scale float64
  89. }
  90. type processingOptions struct {
  91. Resize resizeType
  92. Width int
  93. Height int
  94. Gravity gravityOptions
  95. Enlarge bool
  96. Format imageType
  97. Quality int
  98. Flatten bool
  99. Background color
  100. Blur float32
  101. Sharpen float32
  102. CacheBuster string
  103. Watermark watermarkOptions
  104. UsedPresets []string
  105. }
  106. const (
  107. imageURLCtxKey = ctxKey("imageUrl")
  108. processingOptionsCtxKey = ctxKey("processingOptions")
  109. )
  110. var (
  111. errInvalidURLEncoding = errors.New("Invalid url encoding")
  112. errInvalidPath = errors.New("Invalid path")
  113. errResultingImageFormatIsNotSupported = errors.New("Resulting image format is not supported")
  114. )
  115. func (it imageType) String() string {
  116. for k, v := range imageTypes {
  117. if v == it {
  118. return k
  119. }
  120. }
  121. return ""
  122. }
  123. func (gt gravityType) String() string {
  124. for k, v := range gravityTypes {
  125. if v == gt {
  126. return k
  127. }
  128. }
  129. return ""
  130. }
  131. func (rt resizeType) String() string {
  132. for k, v := range resizeTypes {
  133. if v == rt {
  134. return k
  135. }
  136. }
  137. return ""
  138. }
  139. func colorFromHex(hexcolor string) (color, error) {
  140. c := color{}
  141. if !hexColorRegex.MatchString(hexcolor) {
  142. return c, fmt.Errorf("Invalid hex color: %s", hexcolor)
  143. }
  144. if len(hexcolor) == 3 {
  145. fmt.Sscanf(hexcolor, hexColorShortFormat, &c.R, &c.G, &c.B)
  146. c.R *= 17
  147. c.G *= 17
  148. c.B *= 17
  149. } else {
  150. fmt.Sscanf(hexcolor, hexColorLongFormat, &c.R, &c.G, &c.B)
  151. }
  152. return c, nil
  153. }
  154. func (po *processingOptions) isPresetUsed(name string) bool {
  155. for _, usedName := range po.UsedPresets {
  156. if usedName == name {
  157. return true
  158. }
  159. }
  160. return false
  161. }
  162. func (po *processingOptions) presetUsed(name string) {
  163. po.UsedPresets = append(po.UsedPresets, name)
  164. }
  165. func decodeURL(parts []string) (string, string, error) {
  166. var extension string
  167. urlParts := strings.Split(strings.Join(parts, ""), ".")
  168. if len(urlParts) > 2 {
  169. return "", "", errInvalidURLEncoding
  170. }
  171. if len(urlParts) == 2 {
  172. extension = urlParts[1]
  173. }
  174. url, err := base64.RawURLEncoding.DecodeString(urlParts[0])
  175. if err != nil {
  176. return "", "", errInvalidURLEncoding
  177. }
  178. return string(url), extension, nil
  179. }
  180. func applyWidthOption(po *processingOptions, args []string) error {
  181. if len(args) > 1 {
  182. return fmt.Errorf("Invalid width arguments: %v", args)
  183. }
  184. if w, err := strconv.Atoi(args[0]); err == nil && w >= 0 {
  185. po.Width = w
  186. } else {
  187. return fmt.Errorf("Invalid width: %s", args[0])
  188. }
  189. return nil
  190. }
  191. func applyHeightOption(po *processingOptions, args []string) error {
  192. if len(args) > 1 {
  193. return fmt.Errorf("Invalid height arguments: %v", args)
  194. }
  195. if h, err := strconv.Atoi(args[0]); err == nil && po.Height >= 0 {
  196. po.Height = h
  197. } else {
  198. return fmt.Errorf("Invalid height: %s", args[0])
  199. }
  200. return nil
  201. }
  202. func applyEnlargeOption(po *processingOptions, args []string) error {
  203. if len(args) > 1 {
  204. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  205. }
  206. po.Enlarge = args[0] != "0"
  207. return nil
  208. }
  209. func applySizeOption(po *processingOptions, args []string) (err error) {
  210. if len(args) > 3 {
  211. return fmt.Errorf("Invalid size arguments: %v", args)
  212. }
  213. if len(args) >= 1 && len(args[0]) > 0 {
  214. if err = applyWidthOption(po, args[0:1]); err != nil {
  215. return
  216. }
  217. }
  218. if len(args) >= 2 && len(args[1]) > 0 {
  219. if err = applyHeightOption(po, args[1:2]); err != nil {
  220. return
  221. }
  222. }
  223. if len(args) == 3 && len(args[2]) > 0 {
  224. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  225. return
  226. }
  227. }
  228. return nil
  229. }
  230. func applyResizingTypeOption(po *processingOptions, args []string) error {
  231. if len(args) > 1 {
  232. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  233. }
  234. if r, ok := resizeTypes[args[0]]; ok {
  235. po.Resize = r
  236. } else {
  237. return fmt.Errorf("Invalid resize type: %s", args[0])
  238. }
  239. return nil
  240. }
  241. func applyResizeOption(po *processingOptions, args []string) error {
  242. if len(args) > 4 {
  243. return fmt.Errorf("Invalid resize arguments: %v", args)
  244. }
  245. if len(args[0]) > 0 {
  246. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  247. return err
  248. }
  249. }
  250. if len(args) > 1 {
  251. if err := applySizeOption(po, args[1:]); err != nil {
  252. return err
  253. }
  254. }
  255. return nil
  256. }
  257. func applyGravityOption(po *processingOptions, args []string) error {
  258. if g, ok := gravityTypes[args[0]]; ok {
  259. po.Gravity.Type = g
  260. } else {
  261. return fmt.Errorf("Invalid gravity: %s", args[0])
  262. }
  263. if po.Gravity.Type == gravityFocusPoint {
  264. if len(args) != 3 {
  265. return fmt.Errorf("Invalid gravity arguments: %v", args)
  266. }
  267. if x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
  268. po.Gravity.X = x
  269. } else {
  270. return fmt.Errorf("Invalid gravity X: %s", args[1])
  271. }
  272. if y, err := strconv.ParseFloat(args[2], 64); err == nil && y >= 0 && y <= 1 {
  273. po.Gravity.Y = y
  274. } else {
  275. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  276. }
  277. } else if len(args) > 1 {
  278. return fmt.Errorf("Invalid gravity arguments: %v", args)
  279. }
  280. return nil
  281. }
  282. func applyQualityOption(po *processingOptions, args []string) error {
  283. if len(args) > 1 {
  284. return fmt.Errorf("Invalid quality arguments: %v", args)
  285. }
  286. if q, err := strconv.Atoi(args[0]); err == nil && q > 0 && q <= 100 {
  287. po.Quality = q
  288. } else {
  289. return fmt.Errorf("Invalid quality: %s", args[0])
  290. }
  291. return nil
  292. }
  293. func applyBackgroundOption(po *processingOptions, args []string) error {
  294. switch len(args) {
  295. case 1:
  296. if len(args[0]) == 0 {
  297. po.Flatten = false
  298. } else if c, err := colorFromHex(args[0]); err == nil {
  299. po.Flatten = true
  300. po.Background = c
  301. } else {
  302. return fmt.Errorf("Invalid background argument: %s", err)
  303. }
  304. case 3:
  305. po.Flatten = true
  306. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r >= 0 && r <= 255 {
  307. po.Background.R = uint8(r)
  308. } else {
  309. return fmt.Errorf("Invalid background red channel: %s", args[0])
  310. }
  311. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g >= 0 && g <= 255 {
  312. po.Background.G = uint8(g)
  313. } else {
  314. return fmt.Errorf("Invalid background green channel: %s", args[1])
  315. }
  316. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b >= 0 && b <= 255 {
  317. po.Background.B = uint8(b)
  318. } else {
  319. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  320. }
  321. default:
  322. return fmt.Errorf("Invalid background arguments: %v", args)
  323. }
  324. return nil
  325. }
  326. func applyBlurOption(po *processingOptions, args []string) error {
  327. if len(args) > 1 {
  328. return fmt.Errorf("Invalid blur arguments: %v", args)
  329. }
  330. if b, err := strconv.ParseFloat(args[0], 32); err == nil || b >= 0 {
  331. po.Blur = float32(b)
  332. } else {
  333. return fmt.Errorf("Invalid blur: %s", args[0])
  334. }
  335. return nil
  336. }
  337. func applySharpenOption(po *processingOptions, args []string) error {
  338. if len(args) > 1 {
  339. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  340. }
  341. if s, err := strconv.ParseFloat(args[0], 32); err == nil || s >= 0 {
  342. po.Sharpen = float32(s)
  343. } else {
  344. return fmt.Errorf("Invalid sharpen: %s", args[0])
  345. }
  346. return nil
  347. }
  348. func applyPresetOption(po *processingOptions, args []string) error {
  349. for _, preset := range args {
  350. if p, ok := conf.Presets[preset]; ok {
  351. if po.isPresetUsed(preset) {
  352. return fmt.Errorf("Recursive preset usage is detected: %s", preset)
  353. }
  354. po.presetUsed(preset)
  355. if err := applyProcessingOptions(po, p); err != nil {
  356. return err
  357. }
  358. } else {
  359. return fmt.Errorf("Unknown asset: %s", preset)
  360. }
  361. }
  362. return nil
  363. }
  364. func applyWatermarkOption(po *processingOptions, args []string) error {
  365. if len(args) > 7 {
  366. return fmt.Errorf("Invalid watermark arguments: %v", args)
  367. }
  368. if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
  369. po.Watermark.Enabled = o > 0
  370. po.Watermark.Opacity = o
  371. } else {
  372. return fmt.Errorf("Invalid watermark opacity: %s", args[0])
  373. }
  374. if len(args) > 1 && len(args[1]) > 0 {
  375. if args[1] == "re" {
  376. po.Watermark.Replicate = true
  377. } else if g, ok := gravityTypes[args[1]]; ok && g != gravityFocusPoint && g != gravitySmart {
  378. po.Watermark.Gravity = g
  379. } else {
  380. return fmt.Errorf("Invalid watermark position: %s", args[1])
  381. }
  382. }
  383. if len(args) > 2 && len(args[2]) > 0 {
  384. if x, err := strconv.Atoi(args[2]); err == nil {
  385. po.Watermark.OffsetX = x
  386. } else {
  387. return fmt.Errorf("Invalid watermark X offset: %s", args[2])
  388. }
  389. }
  390. if len(args) > 3 && len(args[3]) > 0 {
  391. if y, err := strconv.Atoi(args[3]); err == nil {
  392. po.Watermark.OffsetY = y
  393. } else {
  394. return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
  395. }
  396. }
  397. if len(args) > 4 && len(args[4]) > 0 {
  398. if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
  399. po.Watermark.Scale = s
  400. } else {
  401. return fmt.Errorf("Invalid watermark scale: %s", args[4])
  402. }
  403. }
  404. return nil
  405. }
  406. func applyFormatOption(po *processingOptions, args []string) error {
  407. if len(args) > 1 {
  408. return fmt.Errorf("Invalid format arguments: %v", args)
  409. }
  410. if conf.EnforceWebp && po.Format == imageTypeWEBP {
  411. // Webp is enforced and already set as format
  412. return nil
  413. }
  414. if f, ok := imageTypes[args[0]]; ok {
  415. po.Format = f
  416. } else {
  417. return fmt.Errorf("Invalid image format: %s", args[0])
  418. }
  419. if !vipsTypeSupportSave[po.Format] {
  420. return errResultingImageFormatIsNotSupported
  421. }
  422. return nil
  423. }
  424. func applyCacheBusterOption(po *processingOptions, args []string) error {
  425. if len(args) > 1 {
  426. return fmt.Errorf("Invalid cache buster arguments: %v", args)
  427. }
  428. po.CacheBuster = args[0]
  429. return nil
  430. }
  431. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  432. switch name {
  433. case "format", "f", "ext":
  434. if err := applyFormatOption(po, args); err != nil {
  435. return err
  436. }
  437. case "resize", "rs":
  438. if err := applyResizeOption(po, args); err != nil {
  439. return err
  440. }
  441. case "resizing_type", "rt":
  442. if err := applyResizingTypeOption(po, args); err != nil {
  443. return err
  444. }
  445. case "size", "s":
  446. if err := applySizeOption(po, args); err != nil {
  447. return err
  448. }
  449. case "width", "w":
  450. if err := applyWidthOption(po, args); err != nil {
  451. return err
  452. }
  453. case "height", "h":
  454. if err := applyHeightOption(po, args); err != nil {
  455. return err
  456. }
  457. case "enlarge", "el":
  458. if err := applyEnlargeOption(po, args); err != nil {
  459. return err
  460. }
  461. case "gravity", "g":
  462. if err := applyGravityOption(po, args); err != nil {
  463. return err
  464. }
  465. case "quality", "q":
  466. if err := applyQualityOption(po, args); err != nil {
  467. return err
  468. }
  469. case "background", "bg":
  470. if err := applyBackgroundOption(po, args); err != nil {
  471. return err
  472. }
  473. case "blur", "bl":
  474. if err := applyBlurOption(po, args); err != nil {
  475. return err
  476. }
  477. case "sharpen", "sh":
  478. if err := applySharpenOption(po, args); err != nil {
  479. return err
  480. }
  481. case "watermark", "wm":
  482. if err := applyWatermarkOption(po, args); err != nil {
  483. return err
  484. }
  485. case "preset", "pr":
  486. if err := applyPresetOption(po, args); err != nil {
  487. return err
  488. }
  489. case "cachebuster", "cb":
  490. if err := applyCacheBusterOption(po, args); err != nil {
  491. return err
  492. }
  493. default:
  494. return fmt.Errorf("Unknown processing option: %s", name)
  495. }
  496. return nil
  497. }
  498. func applyProcessingOptions(po *processingOptions, options urlOptions) error {
  499. for name, args := range options {
  500. if err := applyProcessingOption(po, name, args); err != nil {
  501. return err
  502. }
  503. }
  504. return nil
  505. }
  506. func parseURLOptions(opts []string) (urlOptions, []string) {
  507. parsed := make(urlOptions)
  508. urlStart := len(opts) + 1
  509. for i, opt := range opts {
  510. args := strings.Split(opt, ":")
  511. if len(args) == 1 {
  512. urlStart = i
  513. break
  514. }
  515. parsed[args[0]] = args[1:]
  516. }
  517. var rest []string
  518. if urlStart < len(opts) {
  519. rest = opts[urlStart:]
  520. } else {
  521. rest = []string{}
  522. }
  523. return parsed, rest
  524. }
  525. func defaultProcessingOptions(acceptHeader string) (*processingOptions, error) {
  526. var err error
  527. po := processingOptions{
  528. Resize: resizeFit,
  529. Width: 0,
  530. Height: 0,
  531. Gravity: gravityOptions{Type: gravityCenter},
  532. Enlarge: false,
  533. Quality: conf.Quality,
  534. Format: imageTypeJPEG,
  535. Blur: 0,
  536. Sharpen: 0,
  537. Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityCenter},
  538. UsedPresets: make([]string, 0, len(conf.Presets)),
  539. }
  540. if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(acceptHeader, "image/webp") {
  541. po.Format = imageTypeWEBP
  542. }
  543. if _, ok := conf.Presets["default"]; ok {
  544. err = applyPresetOption(&po, []string{"default"})
  545. }
  546. return &po, err
  547. }
  548. func parsePathAdvanced(parts []string, acceptHeader string) (string, *processingOptions, error) {
  549. po, err := defaultProcessingOptions(acceptHeader)
  550. if err != nil {
  551. return "", po, err
  552. }
  553. options, urlParts := parseURLOptions(parts)
  554. if err := applyProcessingOptions(po, options); err != nil {
  555. return "", po, err
  556. }
  557. url, extension, err := decodeURL(urlParts)
  558. if err != nil {
  559. return "", po, err
  560. }
  561. if len(extension) > 0 {
  562. if err := applyFormatOption(po, []string{extension}); err != nil {
  563. return "", po, err
  564. }
  565. }
  566. return string(url), po, nil
  567. }
  568. func parsePathSimple(parts []string, acceptHeader string) (string, *processingOptions, error) {
  569. var err error
  570. if len(parts) < 6 {
  571. return "", nil, errInvalidPath
  572. }
  573. po, err := defaultProcessingOptions(acceptHeader)
  574. if err != nil {
  575. return "", po, err
  576. }
  577. po.Resize = resizeTypes[parts[0]]
  578. if err = applyWidthOption(po, parts[1:2]); err != nil {
  579. return "", po, err
  580. }
  581. if err = applyHeightOption(po, parts[2:3]); err != nil {
  582. return "", po, err
  583. }
  584. if err = applyGravityOption(po, strings.Split(parts[3], ":")); err != nil {
  585. return "", po, err
  586. }
  587. if err = applyEnlargeOption(po, parts[4:5]); err != nil {
  588. return "", po, err
  589. }
  590. url, extension, err := decodeURL(parts[5:])
  591. if err != nil {
  592. return "", po, err
  593. }
  594. if len(extension) > 0 {
  595. if err := applyFormatOption(po, []string{extension}); err != nil {
  596. return "", po, err
  597. }
  598. }
  599. return string(url), po, nil
  600. }
  601. func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
  602. path := r.URL.Path
  603. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  604. var acceptHeader string
  605. if h, ok := r.Header["Accept"]; ok {
  606. acceptHeader = h[0]
  607. }
  608. if len(parts) < 3 {
  609. return ctx, errInvalidPath
  610. }
  611. if !conf.AllowInsecure {
  612. if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  613. return ctx, err
  614. }
  615. }
  616. var imageURL string
  617. var po *processingOptions
  618. var err error
  619. if _, ok := resizeTypes[parts[1]]; ok {
  620. imageURL, po, err = parsePathSimple(parts[1:], acceptHeader)
  621. } else {
  622. imageURL, po, err = parsePathAdvanced(parts[1:], acceptHeader)
  623. }
  624. if err != nil {
  625. return ctx, err
  626. }
  627. ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
  628. ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
  629. return ctx, err
  630. }
  631. func getImageURL(ctx context.Context) string {
  632. return ctx.Value(imageURLCtxKey).(string)
  633. }
  634. func getProcessingOptions(ctx context.Context) *processingOptions {
  635. return ctx.Value(processingOptionsCtxKey).(*processingOptions)
  636. }