processing_options.go 14 KB

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