processing_options.go 16 KB

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