processing_options.go 14 KB

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