processing_options.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  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. )
  54. var resizeTypes = map[string]resizeType{
  55. "fit": resizeFit,
  56. "fill": resizeFill,
  57. "crop": resizeCrop,
  58. }
  59. type rgbColor struct{ R, G, B uint8 }
  60. var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
  61. const (
  62. hexColorLongFormat = "%02x%02x%02x"
  63. hexColorShortFormat = "%1x%1x%1x"
  64. )
  65. type gravityOptions struct {
  66. Type gravityType
  67. X, Y float64
  68. }
  69. type cropOptions struct {
  70. Width int
  71. Height int
  72. Gravity gravityOptions
  73. }
  74. type watermarkOptions struct {
  75. Enabled bool
  76. Opacity float64
  77. Replicate bool
  78. Gravity gravityType
  79. OffsetX int
  80. OffsetY int
  81. Scale float64
  82. }
  83. type processingOptions struct {
  84. Resize resizeType
  85. Width int
  86. Height int
  87. Dpr float64
  88. Gravity gravityOptions
  89. Enlarge bool
  90. Extend bool
  91. Crop cropOptions
  92. Format imageType
  93. Quality int
  94. Flatten bool
  95. Background rgbColor
  96. Blur float32
  97. Sharpen float32
  98. CacheBuster string
  99. Watermark watermarkOptions
  100. UsedPresets []string
  101. }
  102. const (
  103. imageURLCtxKey = ctxKey("imageUrl")
  104. processingOptionsCtxKey = ctxKey("processingOptions")
  105. urlTokenPlain = "plain"
  106. maxClientHintDPR = 8
  107. msgForbidden = "Forbidden"
  108. msgInvalidURL = "Invalid URL"
  109. )
  110. var (
  111. errInvalidImageURL = errors.New("Invalid image url")
  112. errInvalidURLEncoding = errors.New("Invalid url encoding")
  113. errResultingImageFormatIsNotSupported = errors.New("Resulting image format is not supported")
  114. errInvalidPath = newError(404, "Invalid path", msgInvalidURL)
  115. )
  116. func (gt gravityType) String() string {
  117. for k, v := range gravityTypes {
  118. if v == gt {
  119. return k
  120. }
  121. }
  122. return ""
  123. }
  124. func (rt resizeType) String() string {
  125. for k, v := range resizeTypes {
  126. if v == rt {
  127. return k
  128. }
  129. }
  130. return ""
  131. }
  132. func (po *processingOptions) isPresetUsed(name string) bool {
  133. for _, usedName := range po.UsedPresets {
  134. if usedName == name {
  135. return true
  136. }
  137. }
  138. return false
  139. }
  140. func (po *processingOptions) presetUsed(name string) {
  141. po.UsedPresets = append(po.UsedPresets, name)
  142. }
  143. func colorFromHex(hexcolor string) (rgbColor, error) {
  144. c := rgbColor{}
  145. if !hexColorRegex.MatchString(hexcolor) {
  146. return c, fmt.Errorf("Invalid hex color: %s", hexcolor)
  147. }
  148. if len(hexcolor) == 3 {
  149. fmt.Sscanf(hexcolor, hexColorShortFormat, &c.R, &c.G, &c.B)
  150. c.R *= 17
  151. c.G *= 17
  152. c.B *= 17
  153. } else {
  154. fmt.Sscanf(hexcolor, hexColorLongFormat, &c.R, &c.G, &c.B)
  155. }
  156. return c, nil
  157. }
  158. func decodeBase64URL(parts []string) (string, string, error) {
  159. var format string
  160. urlParts := strings.Split(strings.Join(parts, ""), ".")
  161. if len(urlParts) > 2 {
  162. return "", "", errInvalidURLEncoding
  163. }
  164. if len(urlParts) == 2 && len(urlParts[1]) > 0 {
  165. format = urlParts[1]
  166. }
  167. imageURL, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(urlParts[0], "="))
  168. if err != nil {
  169. return "", "", errInvalidURLEncoding
  170. }
  171. fullURL := fmt.Sprintf("%s%s", conf.BaseURL, string(imageURL))
  172. if _, err := url.ParseRequestURI(fullURL); err != nil {
  173. return "", "", errInvalidImageURL
  174. }
  175. return fullURL, format, nil
  176. }
  177. func decodePlainURL(parts []string) (string, string, error) {
  178. var format string
  179. urlParts := strings.Split(strings.Join(parts, "/"), "@")
  180. if len(urlParts) > 2 {
  181. return "", "", errInvalidURLEncoding
  182. }
  183. if len(urlParts) == 2 && len(urlParts[1]) > 0 {
  184. format = urlParts[1]
  185. }
  186. if unescaped, err := url.PathUnescape(urlParts[0]); err == nil {
  187. fullURL := fmt.Sprintf("%s%s", conf.BaseURL, unescaped)
  188. if _, err := url.ParseRequestURI(fullURL); err == nil {
  189. return fullURL, format, nil
  190. }
  191. }
  192. return "", "", errInvalidImageURL
  193. }
  194. func decodeURL(parts []string) (string, string, error) {
  195. if len(parts) == 0 {
  196. return "", "", errInvalidURLEncoding
  197. }
  198. if parts[0] == urlTokenPlain && len(parts) > 1 {
  199. return decodePlainURL(parts[1:])
  200. }
  201. return decodeBase64URL(parts)
  202. }
  203. func parseDimension(d *int, name, arg string) error {
  204. if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
  205. *d = v
  206. } else {
  207. return fmt.Errorf("Invalid %s: %s", name, arg)
  208. }
  209. return nil
  210. }
  211. func isGravityOffcetValid(gravity gravityType, offset float64) bool {
  212. if gravity == gravityCenter {
  213. return true
  214. }
  215. return offset >= 0 && (gravity != gravityFocusPoint || offset <= 1)
  216. }
  217. func parseGravity(g *gravityOptions, args []string) error {
  218. nArgs := len(args)
  219. if nArgs > 3 {
  220. return fmt.Errorf("Invalid gravity arguments: %v", args)
  221. }
  222. if t, ok := gravityTypes[args[0]]; ok {
  223. g.Type = t
  224. } else {
  225. return fmt.Errorf("Invalid gravity: %s", args[0])
  226. }
  227. if g.Type == gravitySmart && nArgs > 1 {
  228. return fmt.Errorf("Invalid gravity arguments: %v", args)
  229. } else if g.Type == gravityFocusPoint && nArgs != 3 {
  230. return fmt.Errorf("Invalid gravity arguments: %v", args)
  231. }
  232. if nArgs > 1 {
  233. if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
  234. g.X = x
  235. } else {
  236. return fmt.Errorf("Invalid gravity X: %s", args[1])
  237. }
  238. }
  239. if nArgs > 2 {
  240. if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
  241. g.Y = y
  242. } else {
  243. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  244. }
  245. }
  246. return nil
  247. }
  248. func applyWidthOption(po *processingOptions, args []string) error {
  249. if len(args) > 1 {
  250. return fmt.Errorf("Invalid width arguments: %v", args)
  251. }
  252. return parseDimension(&po.Width, "width", args[0])
  253. }
  254. func applyHeightOption(po *processingOptions, args []string) error {
  255. if len(args) > 1 {
  256. return fmt.Errorf("Invalid height arguments: %v", args)
  257. }
  258. return parseDimension(&po.Height, "height", args[0])
  259. }
  260. func applyEnlargeOption(po *processingOptions, args []string) error {
  261. if len(args) > 1 {
  262. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  263. }
  264. po.Enlarge = args[0] != "0"
  265. return nil
  266. }
  267. func applyExtendOption(po *processingOptions, args []string) error {
  268. if len(args) > 1 {
  269. return fmt.Errorf("Invalid extend arguments: %v", args)
  270. }
  271. po.Extend = args[0] != "0"
  272. return nil
  273. }
  274. func applySizeOption(po *processingOptions, args []string) (err error) {
  275. if len(args) > 4 {
  276. return fmt.Errorf("Invalid size arguments: %v", args)
  277. }
  278. if len(args) >= 1 && len(args[0]) > 0 {
  279. if err = applyWidthOption(po, args[0:1]); err != nil {
  280. return
  281. }
  282. }
  283. if len(args) >= 2 && len(args[1]) > 0 {
  284. if err = applyHeightOption(po, args[1:2]); err != nil {
  285. return
  286. }
  287. }
  288. if len(args) >= 3 && len(args[2]) > 0 {
  289. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  290. return
  291. }
  292. }
  293. if len(args) == 4 && len(args[3]) > 0 {
  294. if err = applyExtendOption(po, args[3:4]); err != nil {
  295. return
  296. }
  297. }
  298. return nil
  299. }
  300. func applyResizingTypeOption(po *processingOptions, args []string) error {
  301. if len(args) > 1 {
  302. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  303. }
  304. if r, ok := resizeTypes[args[0]]; ok {
  305. po.Resize = r
  306. } else {
  307. return fmt.Errorf("Invalid resize type: %s", args[0])
  308. }
  309. return nil
  310. }
  311. func applyResizeOption(po *processingOptions, args []string) error {
  312. if len(args) > 5 {
  313. return fmt.Errorf("Invalid resize arguments: %v", args)
  314. }
  315. if len(args[0]) > 0 {
  316. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  317. return err
  318. }
  319. }
  320. if len(args) > 1 {
  321. if err := applySizeOption(po, args[1:]); err != nil {
  322. return err
  323. }
  324. }
  325. return nil
  326. }
  327. func applyDprOption(po *processingOptions, args []string) error {
  328. if len(args) > 1 {
  329. return fmt.Errorf("Invalid dpr arguments: %v", args)
  330. }
  331. if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
  332. po.Dpr = d
  333. } else {
  334. return fmt.Errorf("Invalid dpr: %s", args[0])
  335. }
  336. return nil
  337. }
  338. func applyGravityOption(po *processingOptions, args []string) error {
  339. return parseGravity(&po.Gravity, args)
  340. }
  341. func applyCropOption(po *processingOptions, args []string) error {
  342. if len(args) > 5 {
  343. return fmt.Errorf("Invalid crop arguments: %v", args)
  344. }
  345. if err := parseDimension(&po.Crop.Width, "crop width", args[0]); err != nil {
  346. return err
  347. }
  348. if len(args) > 1 {
  349. if err := parseDimension(&po.Crop.Height, "crop height", args[1]); err != nil {
  350. return err
  351. }
  352. }
  353. if len(args) > 2 {
  354. return parseGravity(&po.Crop.Gravity, args[2:])
  355. }
  356. return nil
  357. }
  358. func applyQualityOption(po *processingOptions, args []string) error {
  359. if len(args) > 1 {
  360. return fmt.Errorf("Invalid quality arguments: %v", args)
  361. }
  362. if q, err := strconv.Atoi(args[0]); err == nil && q > 0 && q <= 100 {
  363. po.Quality = q
  364. } else {
  365. return fmt.Errorf("Invalid quality: %s", args[0])
  366. }
  367. return nil
  368. }
  369. func applyBackgroundOption(po *processingOptions, args []string) error {
  370. switch len(args) {
  371. case 1:
  372. if len(args[0]) == 0 {
  373. po.Flatten = false
  374. } else if c, err := colorFromHex(args[0]); err == nil {
  375. po.Flatten = true
  376. po.Background = c
  377. } else {
  378. return fmt.Errorf("Invalid background argument: %s", err)
  379. }
  380. case 3:
  381. po.Flatten = true
  382. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
  383. po.Background.R = uint8(r)
  384. } else {
  385. return fmt.Errorf("Invalid background red channel: %s", args[0])
  386. }
  387. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
  388. po.Background.G = uint8(g)
  389. } else {
  390. return fmt.Errorf("Invalid background green channel: %s", args[1])
  391. }
  392. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
  393. po.Background.B = uint8(b)
  394. } else {
  395. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  396. }
  397. default:
  398. return fmt.Errorf("Invalid background arguments: %v", args)
  399. }
  400. return nil
  401. }
  402. func applyBlurOption(po *processingOptions, args []string) error {
  403. if len(args) > 1 {
  404. return fmt.Errorf("Invalid blur arguments: %v", args)
  405. }
  406. if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
  407. po.Blur = float32(b)
  408. } else {
  409. return fmt.Errorf("Invalid blur: %s", args[0])
  410. }
  411. return nil
  412. }
  413. func applySharpenOption(po *processingOptions, args []string) error {
  414. if len(args) > 1 {
  415. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  416. }
  417. if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
  418. po.Sharpen = float32(s)
  419. } else {
  420. return fmt.Errorf("Invalid sharpen: %s", args[0])
  421. }
  422. return nil
  423. }
  424. func applyPresetOption(po *processingOptions, args []string) error {
  425. for _, preset := range args {
  426. if p, ok := conf.Presets[preset]; ok {
  427. if po.isPresetUsed(preset) {
  428. logWarning("Recursive preset usage is detected: %s", preset)
  429. continue
  430. }
  431. po.presetUsed(preset)
  432. if err := applyProcessingOptions(po, p); err != nil {
  433. return err
  434. }
  435. } else {
  436. return fmt.Errorf("Unknown asset: %s", preset)
  437. }
  438. }
  439. return nil
  440. }
  441. func applyWatermarkOption(po *processingOptions, args []string) error {
  442. if len(args) > 7 {
  443. return fmt.Errorf("Invalid watermark arguments: %v", args)
  444. }
  445. if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
  446. po.Watermark.Enabled = o > 0
  447. po.Watermark.Opacity = o
  448. } else {
  449. return fmt.Errorf("Invalid watermark opacity: %s", args[0])
  450. }
  451. if len(args) > 1 && len(args[1]) > 0 {
  452. if args[1] == "re" {
  453. po.Watermark.Replicate = true
  454. } else if g, ok := gravityTypes[args[1]]; ok && g != gravityFocusPoint && g != gravitySmart {
  455. po.Watermark.Gravity = g
  456. } else {
  457. return fmt.Errorf("Invalid watermark position: %s", args[1])
  458. }
  459. }
  460. if len(args) > 2 && len(args[2]) > 0 {
  461. if x, err := strconv.Atoi(args[2]); err == nil {
  462. po.Watermark.OffsetX = x
  463. } else {
  464. return fmt.Errorf("Invalid watermark X offset: %s", args[2])
  465. }
  466. }
  467. if len(args) > 3 && len(args[3]) > 0 {
  468. if y, err := strconv.Atoi(args[3]); err == nil {
  469. po.Watermark.OffsetY = y
  470. } else {
  471. return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
  472. }
  473. }
  474. if len(args) > 4 && len(args[4]) > 0 {
  475. if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
  476. po.Watermark.Scale = s
  477. } else {
  478. return fmt.Errorf("Invalid watermark scale: %s", args[4])
  479. }
  480. }
  481. return nil
  482. }
  483. func applyFormatOption(po *processingOptions, args []string) error {
  484. if len(args) > 1 {
  485. return fmt.Errorf("Invalid format arguments: %v", args)
  486. }
  487. if conf.EnforceWebp && po.Format == imageTypeWEBP {
  488. // Webp is enforced and already set as format
  489. return nil
  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 (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(headers.Accept, "image/webp") {
  632. po.Format = imageTypeWEBP
  633. }
  634. if conf.EnableClientHints && len(headers.ViewportWidth) > 0 {
  635. if vw, err := strconv.Atoi(headers.ViewportWidth); err == nil {
  636. po.Width = vw
  637. }
  638. }
  639. if conf.EnableClientHints && len(headers.Width) > 0 {
  640. if w, err := strconv.Atoi(headers.Width); err == nil {
  641. po.Width = w
  642. }
  643. }
  644. if conf.EnableClientHints && len(headers.DPR) > 0 {
  645. if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
  646. po.Dpr = dpr
  647. }
  648. }
  649. if _, ok := conf.Presets["default"]; ok {
  650. err = applyPresetOption(&po, []string{"default"})
  651. }
  652. return &po, err
  653. }
  654. func parsePathAdvanced(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  655. po, err := defaultProcessingOptions(headers)
  656. if err != nil {
  657. return "", po, err
  658. }
  659. options, urlParts := parseURLOptions(parts)
  660. if err := applyProcessingOptions(po, options); err != nil {
  661. return "", po, err
  662. }
  663. url, extension, err := decodeURL(urlParts)
  664. if err != nil {
  665. return "", po, err
  666. }
  667. if len(extension) > 0 {
  668. if err := applyFormatOption(po, []string{extension}); err != nil {
  669. return "", po, err
  670. }
  671. }
  672. return url, po, nil
  673. }
  674. func parsePathPresets(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  675. po, err := defaultProcessingOptions(headers)
  676. if err != nil {
  677. return "", po, err
  678. }
  679. presets := strings.Split(parts[0], ":")
  680. urlParts := parts[1:]
  681. if err := applyPresetOption(po, presets); err != nil {
  682. return "", nil, err
  683. }
  684. url, extension, err := decodeURL(urlParts)
  685. if err != nil {
  686. return "", po, err
  687. }
  688. if len(extension) > 0 {
  689. if err := applyFormatOption(po, []string{extension}); err != nil {
  690. return "", po, err
  691. }
  692. }
  693. return url, po, nil
  694. }
  695. func parsePathBasic(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  696. var err error
  697. if len(parts) < 6 {
  698. return "", nil, errInvalidPath
  699. }
  700. po, err := defaultProcessingOptions(headers)
  701. if err != nil {
  702. return "", po, err
  703. }
  704. po.Resize = resizeTypes[parts[0]]
  705. if err = applyWidthOption(po, parts[1:2]); err != nil {
  706. return "", po, err
  707. }
  708. if err = applyHeightOption(po, parts[2:3]); err != nil {
  709. return "", po, err
  710. }
  711. if err = applyGravityOption(po, strings.Split(parts[3], ":")); err != nil {
  712. return "", po, err
  713. }
  714. if err = applyEnlargeOption(po, parts[4:5]); err != nil {
  715. return "", po, err
  716. }
  717. url, extension, err := decodeURL(parts[5:])
  718. if err != nil {
  719. return "", po, err
  720. }
  721. if len(extension) > 0 {
  722. if err := applyFormatOption(po, []string{extension}); err != nil {
  723. return "", po, err
  724. }
  725. }
  726. return url, po, nil
  727. }
  728. func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
  729. path := r.URL.RawPath
  730. if len(path) == 0 {
  731. path = r.URL.Path
  732. }
  733. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  734. if len(parts) < 3 {
  735. return ctx, errInvalidPath
  736. }
  737. if !conf.AllowInsecure {
  738. if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  739. return ctx, newError(403, err.Error(), msgForbidden)
  740. }
  741. }
  742. headers := &processingHeaders{
  743. Accept: r.Header.Get("Accept"),
  744. Width: r.Header.Get("Width"),
  745. ViewportWidth: r.Header.Get("Viewport-Width"),
  746. DPR: r.Header.Get("DPR"),
  747. }
  748. var imageURL string
  749. var po *processingOptions
  750. var err error
  751. if conf.OnlyPresets {
  752. imageURL, po, err = parsePathPresets(parts[1:], headers)
  753. } else if _, ok := resizeTypes[parts[1]]; ok {
  754. imageURL, po, err = parsePathBasic(parts[1:], headers)
  755. } else {
  756. imageURL, po, err = parsePathAdvanced(parts[1:], headers)
  757. }
  758. if err != nil {
  759. return ctx, newError(404, err.Error(), msgInvalidURL)
  760. }
  761. ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
  762. ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
  763. return ctx, nil
  764. }
  765. func getImageURL(ctx context.Context) string {
  766. return ctx.Value(imageURLCtxKey).(string)
  767. }
  768. func getProcessingOptions(ctx context.Context) *processingOptions {
  769. return ctx.Value(processingOptionsCtxKey).(*processingOptions)
  770. }