processing_options.go 15 KB

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