processing_options.go 20 KB

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