1
0

processing_options.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. package main
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. )
  13. type urlOption struct {
  14. Name string
  15. Args []string
  16. }
  17. type urlOptions []urlOption
  18. type processingHeaders struct {
  19. Accept string
  20. Width string
  21. ViewportWidth string
  22. DPR string
  23. }
  24. type gravityType int
  25. const (
  26. gravityUnknown gravityType = iota
  27. gravityCenter
  28. gravityNorth
  29. gravityEast
  30. gravitySouth
  31. gravityWest
  32. gravityNorthWest
  33. gravityNorthEast
  34. gravitySouthWest
  35. gravitySouthEast
  36. gravitySmart
  37. gravityFocusPoint
  38. )
  39. var gravityTypes = map[string]gravityType{
  40. "ce": gravityCenter,
  41. "no": gravityNorth,
  42. "ea": gravityEast,
  43. "so": gravitySouth,
  44. "we": gravityWest,
  45. "nowe": gravityNorthWest,
  46. "noea": gravityNorthEast,
  47. "sowe": gravitySouthWest,
  48. "soea": gravitySouthEast,
  49. "sm": gravitySmart,
  50. "fp": gravityFocusPoint,
  51. }
  52. type resizeType int
  53. const (
  54. resizeFit resizeType = iota
  55. resizeFill
  56. resizeCrop
  57. resizeAuto
  58. )
  59. var resizeTypes = map[string]resizeType{
  60. "fit": resizeFit,
  61. "fill": resizeFill,
  62. "crop": resizeCrop,
  63. "auto": resizeAuto,
  64. }
  65. type rgbColor struct{ R, G, B uint8 }
  66. var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
  67. const (
  68. hexColorLongFormat = "%02x%02x%02x"
  69. hexColorShortFormat = "%1x%1x%1x"
  70. )
  71. type gravityOptions struct {
  72. Type gravityType
  73. X, Y float64
  74. }
  75. type cropOptions struct {
  76. Width int
  77. Height int
  78. Gravity gravityOptions
  79. }
  80. type watermarkOptions struct {
  81. Enabled bool
  82. Opacity float64
  83. Replicate bool
  84. Gravity gravityType
  85. OffsetX int
  86. OffsetY int
  87. Scale float64
  88. }
  89. type processingOptions struct {
  90. Resize resizeType
  91. Width int
  92. Height int
  93. Dpr float64
  94. Gravity gravityOptions
  95. Enlarge bool
  96. Extend bool
  97. Crop cropOptions
  98. Format imageType
  99. Quality int
  100. Flatten bool
  101. Background rgbColor
  102. Blur float32
  103. Sharpen float32
  104. CacheBuster string
  105. Watermark watermarkOptions
  106. PreferWebP bool
  107. EnforceWebP bool
  108. UsedPresets []string
  109. }
  110. const (
  111. imageURLCtxKey = ctxKey("imageUrl")
  112. processingOptionsCtxKey = ctxKey("processingOptions")
  113. urlTokenPlain = "plain"
  114. maxClientHintDPR = 8
  115. msgForbidden = "Forbidden"
  116. msgInvalidURL = "Invalid URL"
  117. )
  118. var (
  119. errInvalidImageURL = errors.New("Invalid image url")
  120. errInvalidURLEncoding = errors.New("Invalid url encoding")
  121. errResultingImageFormatIsNotSupported = errors.New("Resulting image format is not supported")
  122. errInvalidPath = newError(404, "Invalid path", msgInvalidURL)
  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 (po *processingOptions) isPresetUsed(name string) bool {
  141. for _, usedName := range po.UsedPresets {
  142. if usedName == name {
  143. return true
  144. }
  145. }
  146. return false
  147. }
  148. func (po *processingOptions) presetUsed(name string) {
  149. po.UsedPresets = append(po.UsedPresets, name)
  150. }
  151. func colorFromHex(hexcolor string) (rgbColor, error) {
  152. c := rgbColor{}
  153. if !hexColorRegex.MatchString(hexcolor) {
  154. return c, fmt.Errorf("Invalid hex color: %s", hexcolor)
  155. }
  156. if len(hexcolor) == 3 {
  157. fmt.Sscanf(hexcolor, hexColorShortFormat, &c.R, &c.G, &c.B)
  158. c.R *= 17
  159. c.G *= 17
  160. c.B *= 17
  161. } else {
  162. fmt.Sscanf(hexcolor, hexColorLongFormat, &c.R, &c.G, &c.B)
  163. }
  164. return c, nil
  165. }
  166. func decodeBase64URL(parts []string) (string, string, error) {
  167. var format string
  168. urlParts := strings.Split(strings.Join(parts, ""), ".")
  169. if len(urlParts) > 2 {
  170. return "", "", errInvalidURLEncoding
  171. }
  172. if len(urlParts) == 2 && len(urlParts[1]) > 0 {
  173. format = urlParts[1]
  174. }
  175. imageURL, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(urlParts[0], "="))
  176. if err != nil {
  177. return "", "", errInvalidURLEncoding
  178. }
  179. fullURL := fmt.Sprintf("%s%s", conf.BaseURL, string(imageURL))
  180. if _, err := url.ParseRequestURI(fullURL); err != nil {
  181. return "", "", errInvalidImageURL
  182. }
  183. return fullURL, format, nil
  184. }
  185. func decodePlainURL(parts []string) (string, string, error) {
  186. var format string
  187. urlParts := strings.Split(strings.Join(parts, "/"), "@")
  188. if len(urlParts) > 2 {
  189. return "", "", errInvalidURLEncoding
  190. }
  191. if len(urlParts) == 2 && len(urlParts[1]) > 0 {
  192. format = urlParts[1]
  193. }
  194. if unescaped, err := url.PathUnescape(urlParts[0]); err == nil {
  195. fullURL := fmt.Sprintf("%s%s", conf.BaseURL, unescaped)
  196. if _, err := url.ParseRequestURI(fullURL); err == nil {
  197. return fullURL, format, nil
  198. }
  199. }
  200. return "", "", errInvalidImageURL
  201. }
  202. func decodeURL(parts []string) (string, string, error) {
  203. if len(parts) == 0 {
  204. return "", "", errInvalidURLEncoding
  205. }
  206. if parts[0] == urlTokenPlain && len(parts) > 1 {
  207. return decodePlainURL(parts[1:])
  208. }
  209. return decodeBase64URL(parts)
  210. }
  211. func parseDimension(d *int, name, arg string) error {
  212. if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
  213. *d = v
  214. } else {
  215. return fmt.Errorf("Invalid %s: %s", name, arg)
  216. }
  217. return nil
  218. }
  219. func isGravityOffcetValid(gravity gravityType, offset float64) bool {
  220. if gravity == gravityCenter {
  221. return true
  222. }
  223. return offset >= 0 && (gravity != gravityFocusPoint || offset <= 1)
  224. }
  225. func parseGravity(g *gravityOptions, args []string) error {
  226. nArgs := len(args)
  227. if nArgs > 3 {
  228. return fmt.Errorf("Invalid gravity arguments: %v", args)
  229. }
  230. if t, ok := gravityTypes[args[0]]; ok {
  231. g.Type = t
  232. } else {
  233. return fmt.Errorf("Invalid gravity: %s", args[0])
  234. }
  235. if g.Type == gravitySmart && nArgs > 1 {
  236. return fmt.Errorf("Invalid gravity arguments: %v", args)
  237. } else if g.Type == gravityFocusPoint && nArgs != 3 {
  238. return fmt.Errorf("Invalid gravity arguments: %v", args)
  239. }
  240. if nArgs > 1 {
  241. if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
  242. g.X = x
  243. } else {
  244. return fmt.Errorf("Invalid gravity X: %s", args[1])
  245. }
  246. }
  247. if nArgs > 2 {
  248. if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
  249. g.Y = y
  250. } else {
  251. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  252. }
  253. }
  254. return nil
  255. }
  256. func applyWidthOption(po *processingOptions, args []string) error {
  257. if len(args) > 1 {
  258. return fmt.Errorf("Invalid width arguments: %v", args)
  259. }
  260. return parseDimension(&po.Width, "width", args[0])
  261. }
  262. func applyHeightOption(po *processingOptions, args []string) error {
  263. if len(args) > 1 {
  264. return fmt.Errorf("Invalid height arguments: %v", args)
  265. }
  266. return parseDimension(&po.Height, "height", args[0])
  267. }
  268. func applyEnlargeOption(po *processingOptions, args []string) error {
  269. if len(args) > 1 {
  270. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  271. }
  272. po.Enlarge = args[0] != "0"
  273. return nil
  274. }
  275. func applyExtendOption(po *processingOptions, args []string) error {
  276. if len(args) > 1 {
  277. return fmt.Errorf("Invalid extend arguments: %v", args)
  278. }
  279. po.Extend = args[0] != "0"
  280. return nil
  281. }
  282. func applySizeOption(po *processingOptions, args []string) (err error) {
  283. if len(args) > 4 {
  284. return fmt.Errorf("Invalid size arguments: %v", args)
  285. }
  286. if len(args) >= 1 && len(args[0]) > 0 {
  287. if err = applyWidthOption(po, args[0:1]); err != nil {
  288. return
  289. }
  290. }
  291. if len(args) >= 2 && len(args[1]) > 0 {
  292. if err = applyHeightOption(po, args[1:2]); err != nil {
  293. return
  294. }
  295. }
  296. if len(args) >= 3 && len(args[2]) > 0 {
  297. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  298. return
  299. }
  300. }
  301. if len(args) == 4 && len(args[3]) > 0 {
  302. if err = applyExtendOption(po, args[3:4]); err != nil {
  303. return
  304. }
  305. }
  306. return nil
  307. }
  308. func applyResizingTypeOption(po *processingOptions, args []string) error {
  309. if len(args) > 1 {
  310. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  311. }
  312. if r, ok := resizeTypes[args[0]]; ok {
  313. po.Resize = r
  314. } else {
  315. return fmt.Errorf("Invalid resize type: %s", args[0])
  316. }
  317. return nil
  318. }
  319. func applyResizeOption(po *processingOptions, args []string) error {
  320. if len(args) > 5 {
  321. return fmt.Errorf("Invalid resize arguments: %v", args)
  322. }
  323. if len(args[0]) > 0 {
  324. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  325. return err
  326. }
  327. }
  328. if len(args) > 1 {
  329. if err := applySizeOption(po, args[1:]); err != nil {
  330. return err
  331. }
  332. }
  333. return nil
  334. }
  335. func applyDprOption(po *processingOptions, args []string) error {
  336. if len(args) > 1 {
  337. return fmt.Errorf("Invalid dpr arguments: %v", args)
  338. }
  339. if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
  340. po.Dpr = d
  341. } else {
  342. return fmt.Errorf("Invalid dpr: %s", args[0])
  343. }
  344. return nil
  345. }
  346. func applyGravityOption(po *processingOptions, args []string) error {
  347. return parseGravity(&po.Gravity, args)
  348. }
  349. func applyCropOption(po *processingOptions, args []string) error {
  350. if len(args) > 5 {
  351. return fmt.Errorf("Invalid crop arguments: %v", args)
  352. }
  353. if err := parseDimension(&po.Crop.Width, "crop width", args[0]); err != nil {
  354. return err
  355. }
  356. if len(args) > 1 {
  357. if err := parseDimension(&po.Crop.Height, "crop height", args[1]); err != nil {
  358. return err
  359. }
  360. }
  361. if len(args) > 2 {
  362. return parseGravity(&po.Crop.Gravity, args[2:])
  363. }
  364. return nil
  365. }
  366. func applyQualityOption(po *processingOptions, args []string) error {
  367. if len(args) > 1 {
  368. return fmt.Errorf("Invalid quality arguments: %v", args)
  369. }
  370. if q, err := strconv.Atoi(args[0]); err == nil && q > 0 && q <= 100 {
  371. po.Quality = q
  372. } else {
  373. return fmt.Errorf("Invalid quality: %s", args[0])
  374. }
  375. return nil
  376. }
  377. func applyBackgroundOption(po *processingOptions, args []string) error {
  378. switch len(args) {
  379. case 1:
  380. if len(args[0]) == 0 {
  381. po.Flatten = false
  382. } else if c, err := colorFromHex(args[0]); err == nil {
  383. po.Flatten = true
  384. po.Background = c
  385. } else {
  386. return fmt.Errorf("Invalid background argument: %s", err)
  387. }
  388. case 3:
  389. po.Flatten = true
  390. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
  391. po.Background.R = uint8(r)
  392. } else {
  393. return fmt.Errorf("Invalid background red channel: %s", args[0])
  394. }
  395. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
  396. po.Background.G = uint8(g)
  397. } else {
  398. return fmt.Errorf("Invalid background green channel: %s", args[1])
  399. }
  400. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
  401. po.Background.B = uint8(b)
  402. } else {
  403. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  404. }
  405. default:
  406. return fmt.Errorf("Invalid background arguments: %v", args)
  407. }
  408. return nil
  409. }
  410. func applyBlurOption(po *processingOptions, args []string) error {
  411. if len(args) > 1 {
  412. return fmt.Errorf("Invalid blur arguments: %v", args)
  413. }
  414. if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
  415. po.Blur = float32(b)
  416. } else {
  417. return fmt.Errorf("Invalid blur: %s", args[0])
  418. }
  419. return nil
  420. }
  421. func applySharpenOption(po *processingOptions, args []string) error {
  422. if len(args) > 1 {
  423. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  424. }
  425. if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
  426. po.Sharpen = float32(s)
  427. } else {
  428. return fmt.Errorf("Invalid sharpen: %s", args[0])
  429. }
  430. return nil
  431. }
  432. func applyPresetOption(po *processingOptions, args []string) error {
  433. for _, preset := range args {
  434. if p, ok := conf.Presets[preset]; ok {
  435. if po.isPresetUsed(preset) {
  436. logWarning("Recursive preset usage is detected: %s", preset)
  437. continue
  438. }
  439. po.presetUsed(preset)
  440. if err := applyProcessingOptions(po, p); err != nil {
  441. return err
  442. }
  443. } else {
  444. return fmt.Errorf("Unknown asset: %s", preset)
  445. }
  446. }
  447. return nil
  448. }
  449. func applyWatermarkOption(po *processingOptions, args []string) error {
  450. if len(args) > 7 {
  451. return fmt.Errorf("Invalid watermark arguments: %v", args)
  452. }
  453. if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
  454. po.Watermark.Enabled = o > 0
  455. po.Watermark.Opacity = o
  456. } else {
  457. return fmt.Errorf("Invalid watermark opacity: %s", args[0])
  458. }
  459. if len(args) > 1 && len(args[1]) > 0 {
  460. if args[1] == "re" {
  461. po.Watermark.Replicate = true
  462. } else if g, ok := gravityTypes[args[1]]; ok && g != gravityFocusPoint && g != gravitySmart {
  463. po.Watermark.Gravity = g
  464. } else {
  465. return fmt.Errorf("Invalid watermark position: %s", args[1])
  466. }
  467. }
  468. if len(args) > 2 && len(args[2]) > 0 {
  469. if x, err := strconv.Atoi(args[2]); err == nil {
  470. po.Watermark.OffsetX = x
  471. } else {
  472. return fmt.Errorf("Invalid watermark X offset: %s", args[2])
  473. }
  474. }
  475. if len(args) > 3 && len(args[3]) > 0 {
  476. if y, err := strconv.Atoi(args[3]); err == nil {
  477. po.Watermark.OffsetY = y
  478. } else {
  479. return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
  480. }
  481. }
  482. if len(args) > 4 && len(args[4]) > 0 {
  483. if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
  484. po.Watermark.Scale = s
  485. } else {
  486. return fmt.Errorf("Invalid watermark scale: %s", args[4])
  487. }
  488. }
  489. return nil
  490. }
  491. func applyFormatOption(po *processingOptions, args []string) error {
  492. if len(args) > 1 {
  493. return fmt.Errorf("Invalid format arguments: %v", args)
  494. }
  495. if f, ok := imageTypes[args[0]]; ok {
  496. po.Format = f
  497. } else {
  498. return fmt.Errorf("Invalid image format: %s", args[0])
  499. }
  500. if !vipsTypeSupportSave[po.Format] {
  501. return errResultingImageFormatIsNotSupported
  502. }
  503. return nil
  504. }
  505. func applyCacheBusterOption(po *processingOptions, args []string) error {
  506. if len(args) > 1 {
  507. return fmt.Errorf("Invalid cache buster arguments: %v", args)
  508. }
  509. po.CacheBuster = args[0]
  510. return nil
  511. }
  512. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  513. switch name {
  514. case "format", "f", "ext":
  515. if err := applyFormatOption(po, args); err != nil {
  516. return err
  517. }
  518. case "resize", "rs":
  519. if err := applyResizeOption(po, args); err != nil {
  520. return err
  521. }
  522. case "resizing_type", "rt":
  523. if err := applyResizingTypeOption(po, args); err != nil {
  524. return err
  525. }
  526. case "size", "s":
  527. if err := applySizeOption(po, args); err != nil {
  528. return err
  529. }
  530. case "width", "w":
  531. if err := applyWidthOption(po, args); err != nil {
  532. return err
  533. }
  534. case "height", "h":
  535. if err := applyHeightOption(po, args); err != nil {
  536. return err
  537. }
  538. case "enlarge", "el":
  539. if err := applyEnlargeOption(po, args); err != nil {
  540. return err
  541. }
  542. case "extend", "ex":
  543. if err := applyExtendOption(po, args); err != nil {
  544. return err
  545. }
  546. case "dpr":
  547. if err := applyDprOption(po, args); err != nil {
  548. return err
  549. }
  550. case "gravity", "g":
  551. if err := applyGravityOption(po, args); err != nil {
  552. return err
  553. }
  554. case "crop", "c":
  555. if err := applyCropOption(po, args); err != nil {
  556. return err
  557. }
  558. case "quality", "q":
  559. if err := applyQualityOption(po, args); err != nil {
  560. return err
  561. }
  562. case "background", "bg":
  563. if err := applyBackgroundOption(po, args); err != nil {
  564. return err
  565. }
  566. case "blur", "bl":
  567. if err := applyBlurOption(po, args); err != nil {
  568. return err
  569. }
  570. case "sharpen", "sh":
  571. if err := applySharpenOption(po, args); err != nil {
  572. return err
  573. }
  574. case "watermark", "wm":
  575. if err := applyWatermarkOption(po, args); err != nil {
  576. return err
  577. }
  578. case "preset", "pr":
  579. if err := applyPresetOption(po, args); err != nil {
  580. return err
  581. }
  582. case "cachebuster", "cb":
  583. if err := applyCacheBusterOption(po, args); err != nil {
  584. return err
  585. }
  586. default:
  587. return fmt.Errorf("Unknown processing option: %s", name)
  588. }
  589. return nil
  590. }
  591. func applyProcessingOptions(po *processingOptions, options urlOptions) error {
  592. for _, opt := range options {
  593. if err := applyProcessingOption(po, opt.Name, opt.Args); err != nil {
  594. return err
  595. }
  596. }
  597. return nil
  598. }
  599. func parseURLOptions(opts []string) (urlOptions, []string) {
  600. parsed := make(urlOptions, 0, len(opts))
  601. urlStart := len(opts) + 1
  602. for i, opt := range opts {
  603. args := strings.Split(opt, ":")
  604. if len(args) == 1 {
  605. urlStart = i
  606. break
  607. }
  608. parsed = append(parsed, urlOption{Name: args[0], Args: args[1:]})
  609. }
  610. var rest []string
  611. if urlStart < len(opts) {
  612. rest = opts[urlStart:]
  613. } else {
  614. rest = []string{}
  615. }
  616. return parsed, rest
  617. }
  618. func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, error) {
  619. var err error
  620. po := processingOptions{
  621. Resize: resizeFit,
  622. Width: 0,
  623. Height: 0,
  624. Gravity: gravityOptions{Type: gravityCenter},
  625. Enlarge: false,
  626. Quality: conf.Quality,
  627. Format: imageTypeUnknown,
  628. Background: rgbColor{255, 255, 255},
  629. Blur: 0,
  630. Sharpen: 0,
  631. Dpr: 1,
  632. Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityCenter},
  633. UsedPresets: make([]string, 0, len(conf.Presets)),
  634. }
  635. if strings.Contains(headers.Accept, "image/webp") {
  636. po.PreferWebP = conf.EnableWebpDetection || conf.EnforceWebp
  637. po.EnforceWebP = conf.EnforceWebp
  638. }
  639. if conf.EnableClientHints && len(headers.ViewportWidth) > 0 {
  640. if vw, err := strconv.Atoi(headers.ViewportWidth); err == nil {
  641. po.Width = vw
  642. }
  643. }
  644. if conf.EnableClientHints && len(headers.Width) > 0 {
  645. if w, err := strconv.Atoi(headers.Width); err == nil {
  646. po.Width = w
  647. }
  648. }
  649. if conf.EnableClientHints && len(headers.DPR) > 0 {
  650. if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
  651. po.Dpr = dpr
  652. }
  653. }
  654. if _, ok := conf.Presets["default"]; ok {
  655. err = applyPresetOption(&po, []string{"default"})
  656. }
  657. return &po, err
  658. }
  659. func parsePathAdvanced(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  660. po, err := defaultProcessingOptions(headers)
  661. if err != nil {
  662. return "", po, err
  663. }
  664. options, urlParts := parseURLOptions(parts)
  665. if err := applyProcessingOptions(po, options); err != nil {
  666. return "", po, err
  667. }
  668. url, extension, err := decodeURL(urlParts)
  669. if err != nil {
  670. return "", po, err
  671. }
  672. if len(extension) > 0 {
  673. if err := applyFormatOption(po, []string{extension}); err != nil {
  674. return "", po, err
  675. }
  676. }
  677. return url, po, nil
  678. }
  679. func parsePathPresets(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  680. po, err := defaultProcessingOptions(headers)
  681. if err != nil {
  682. return "", po, err
  683. }
  684. presets := strings.Split(parts[0], ":")
  685. urlParts := parts[1:]
  686. if err := applyPresetOption(po, presets); err != nil {
  687. return "", nil, err
  688. }
  689. url, extension, err := decodeURL(urlParts)
  690. if err != nil {
  691. return "", po, err
  692. }
  693. if len(extension) > 0 {
  694. if err := applyFormatOption(po, []string{extension}); err != nil {
  695. return "", po, err
  696. }
  697. }
  698. return url, po, nil
  699. }
  700. func parsePathBasic(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  701. var err error
  702. if len(parts) < 6 {
  703. return "", nil, errInvalidPath
  704. }
  705. po, err := defaultProcessingOptions(headers)
  706. if err != nil {
  707. return "", po, err
  708. }
  709. po.Resize = resizeTypes[parts[0]]
  710. if err = applyWidthOption(po, parts[1:2]); err != nil {
  711. return "", po, err
  712. }
  713. if err = applyHeightOption(po, parts[2:3]); err != nil {
  714. return "", po, err
  715. }
  716. if err = applyGravityOption(po, strings.Split(parts[3], ":")); err != nil {
  717. return "", po, err
  718. }
  719. if err = applyEnlargeOption(po, parts[4:5]); err != nil {
  720. return "", po, err
  721. }
  722. url, extension, err := decodeURL(parts[5:])
  723. if err != nil {
  724. return "", po, err
  725. }
  726. if len(extension) > 0 {
  727. if err := applyFormatOption(po, []string{extension}); err != nil {
  728. return "", po, err
  729. }
  730. }
  731. return url, po, nil
  732. }
  733. func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
  734. path := r.URL.RawPath
  735. if len(path) == 0 {
  736. path = r.URL.Path
  737. }
  738. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  739. if len(parts) < 3 {
  740. return ctx, errInvalidPath
  741. }
  742. if !conf.AllowInsecure {
  743. if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  744. return ctx, newError(403, err.Error(), msgForbidden)
  745. }
  746. }
  747. headers := &processingHeaders{
  748. Accept: r.Header.Get("Accept"),
  749. Width: r.Header.Get("Width"),
  750. ViewportWidth: r.Header.Get("Viewport-Width"),
  751. DPR: r.Header.Get("DPR"),
  752. }
  753. var imageURL string
  754. var po *processingOptions
  755. var err error
  756. if conf.OnlyPresets {
  757. imageURL, po, err = parsePathPresets(parts[1:], headers)
  758. } else if _, ok := resizeTypes[parts[1]]; ok {
  759. imageURL, po, err = parsePathBasic(parts[1:], headers)
  760. } else {
  761. imageURL, po, err = parsePathAdvanced(parts[1:], headers)
  762. }
  763. if err != nil {
  764. return ctx, newError(404, err.Error(), msgInvalidURL)
  765. }
  766. ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
  767. ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
  768. return ctx, nil
  769. }
  770. func getImageURL(ctx context.Context) string {
  771. return ctx.Value(imageURLCtxKey).(string)
  772. }
  773. func getProcessingOptions(ctx context.Context) *processingOptions {
  774. return ctx.Value(processingOptionsCtxKey).(*processingOptions)
  775. }