processing_options.go 16 KB

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