processing_options.go 20 KB

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