processing_options.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  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 gravityOptions struct {
  49. Type gravityType
  50. X, Y float64
  51. }
  52. type resizeType int
  53. const (
  54. resizeFit resizeType = iota
  55. resizeFill
  56. resizeCrop
  57. )
  58. var resizeTypes = map[string]resizeType{
  59. "fit": resizeFit,
  60. "fill": resizeFill,
  61. "crop": resizeCrop,
  62. }
  63. type rgbColor struct{ R, G, B uint8 }
  64. var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
  65. const (
  66. hexColorLongFormat = "%02x%02x%02x"
  67. hexColorShortFormat = "%1x%1x%1x"
  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 parseGravity(g *gravityOptions, args []string) error {
  212. if t, ok := gravityTypes[args[0]]; ok {
  213. g.Type = t
  214. } else {
  215. return fmt.Errorf("Invalid gravity: %s", args[0])
  216. }
  217. if g.Type == gravityFocusPoint {
  218. if len(args) != 3 {
  219. return fmt.Errorf("Invalid gravity arguments: %v", args)
  220. }
  221. if x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
  222. g.X = x
  223. } else {
  224. return fmt.Errorf("Invalid gravity X: %s", args[1])
  225. }
  226. if y, err := strconv.ParseFloat(args[2], 64); err == nil && y >= 0 && y <= 1 {
  227. g.Y = y
  228. } else {
  229. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  230. }
  231. } else if len(args) > 1 {
  232. return fmt.Errorf("Invalid gravity arguments: %v", args)
  233. }
  234. return nil
  235. }
  236. func applyWidthOption(po *processingOptions, args []string) error {
  237. if len(args) > 1 {
  238. return fmt.Errorf("Invalid width arguments: %v", args)
  239. }
  240. return parseDimension(&po.Width, "width", args[0])
  241. }
  242. func applyHeightOption(po *processingOptions, args []string) error {
  243. if len(args) > 1 {
  244. return fmt.Errorf("Invalid height arguments: %v", args)
  245. }
  246. return parseDimension(&po.Height, "height", args[0])
  247. }
  248. func applyEnlargeOption(po *processingOptions, args []string) error {
  249. if len(args) > 1 {
  250. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  251. }
  252. po.Enlarge = args[0] != "0"
  253. return nil
  254. }
  255. func applyExtendOption(po *processingOptions, args []string) error {
  256. if len(args) > 1 {
  257. return fmt.Errorf("Invalid extend arguments: %v", args)
  258. }
  259. po.Extend = args[0] != "0"
  260. return nil
  261. }
  262. func applySizeOption(po *processingOptions, args []string) (err error) {
  263. if len(args) > 4 {
  264. return fmt.Errorf("Invalid size arguments: %v", args)
  265. }
  266. if len(args) >= 1 && len(args[0]) > 0 {
  267. if err = applyWidthOption(po, args[0:1]); err != nil {
  268. return
  269. }
  270. }
  271. if len(args) >= 2 && len(args[1]) > 0 {
  272. if err = applyHeightOption(po, args[1:2]); err != nil {
  273. return
  274. }
  275. }
  276. if len(args) >= 3 && len(args[2]) > 0 {
  277. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  278. return
  279. }
  280. }
  281. if len(args) == 4 && len(args[3]) > 0 {
  282. if err = applyExtendOption(po, args[3:4]); err != nil {
  283. return
  284. }
  285. }
  286. return nil
  287. }
  288. func applyResizingTypeOption(po *processingOptions, args []string) error {
  289. if len(args) > 1 {
  290. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  291. }
  292. if r, ok := resizeTypes[args[0]]; ok {
  293. po.Resize = r
  294. } else {
  295. return fmt.Errorf("Invalid resize type: %s", args[0])
  296. }
  297. return nil
  298. }
  299. func applyResizeOption(po *processingOptions, args []string) error {
  300. if len(args) > 5 {
  301. return fmt.Errorf("Invalid resize arguments: %v", args)
  302. }
  303. if len(args[0]) > 0 {
  304. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  305. return err
  306. }
  307. }
  308. if len(args) > 1 {
  309. if err := applySizeOption(po, args[1:]); err != nil {
  310. return err
  311. }
  312. }
  313. return nil
  314. }
  315. func applyDprOption(po *processingOptions, args []string) error {
  316. if len(args) > 1 {
  317. return fmt.Errorf("Invalid dpr arguments: %v", args)
  318. }
  319. if d, err := strconv.ParseFloat(args[0], 64); err == nil && (d > 0 && d != 1) {
  320. po.Dpr = d
  321. } else {
  322. return fmt.Errorf("Invalid dpr: %s", args[0])
  323. }
  324. return nil
  325. }
  326. func applyGravityOption(po *processingOptions, args []string) error {
  327. return parseGravity(&po.Gravity, args)
  328. }
  329. func applyCropOption(po *processingOptions, args []string) error {
  330. if len(args) > 5 {
  331. return fmt.Errorf("Invalid crop arguments: %v", args)
  332. }
  333. if err := parseDimension(&po.Crop.Width, "crop width", args[0]); err != nil {
  334. return err
  335. }
  336. if len(args) > 1 {
  337. if err := parseDimension(&po.Crop.Height, "crop height", args[1]); err != nil {
  338. return err
  339. }
  340. }
  341. if len(args) > 2 {
  342. return parseGravity(&po.Crop.Gravity, args[2:])
  343. }
  344. return nil
  345. }
  346. func applyQualityOption(po *processingOptions, args []string) error {
  347. if len(args) > 1 {
  348. return fmt.Errorf("Invalid quality arguments: %v", args)
  349. }
  350. if q, err := strconv.Atoi(args[0]); err == nil && q > 0 && q <= 100 {
  351. po.Quality = q
  352. } else {
  353. return fmt.Errorf("Invalid quality: %s", args[0])
  354. }
  355. return nil
  356. }
  357. func applyBackgroundOption(po *processingOptions, args []string) error {
  358. switch len(args) {
  359. case 1:
  360. if len(args[0]) == 0 {
  361. po.Flatten = false
  362. } else if c, err := colorFromHex(args[0]); err == nil {
  363. po.Flatten = true
  364. po.Background = c
  365. } else {
  366. return fmt.Errorf("Invalid background argument: %s", err)
  367. }
  368. case 3:
  369. po.Flatten = true
  370. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
  371. po.Background.R = uint8(r)
  372. } else {
  373. return fmt.Errorf("Invalid background red channel: %s", args[0])
  374. }
  375. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
  376. po.Background.G = uint8(g)
  377. } else {
  378. return fmt.Errorf("Invalid background green channel: %s", args[1])
  379. }
  380. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
  381. po.Background.B = uint8(b)
  382. } else {
  383. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  384. }
  385. default:
  386. return fmt.Errorf("Invalid background arguments: %v", args)
  387. }
  388. return nil
  389. }
  390. func applyBlurOption(po *processingOptions, args []string) error {
  391. if len(args) > 1 {
  392. return fmt.Errorf("Invalid blur arguments: %v", args)
  393. }
  394. if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
  395. po.Blur = float32(b)
  396. } else {
  397. return fmt.Errorf("Invalid blur: %s", args[0])
  398. }
  399. return nil
  400. }
  401. func applySharpenOption(po *processingOptions, args []string) error {
  402. if len(args) > 1 {
  403. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  404. }
  405. if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
  406. po.Sharpen = float32(s)
  407. } else {
  408. return fmt.Errorf("Invalid sharpen: %s", args[0])
  409. }
  410. return nil
  411. }
  412. func applyPresetOption(po *processingOptions, args []string) error {
  413. for _, preset := range args {
  414. if p, ok := conf.Presets[preset]; ok {
  415. if po.isPresetUsed(preset) {
  416. logWarning("Recursive preset usage is detected: %s", preset)
  417. continue
  418. }
  419. po.presetUsed(preset)
  420. if err := applyProcessingOptions(po, p); err != nil {
  421. return err
  422. }
  423. } else {
  424. return fmt.Errorf("Unknown asset: %s", preset)
  425. }
  426. }
  427. return nil
  428. }
  429. func applyWatermarkOption(po *processingOptions, args []string) error {
  430. if len(args) > 7 {
  431. return fmt.Errorf("Invalid watermark arguments: %v", args)
  432. }
  433. if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
  434. po.Watermark.Enabled = o > 0
  435. po.Watermark.Opacity = o
  436. } else {
  437. return fmt.Errorf("Invalid watermark opacity: %s", args[0])
  438. }
  439. if len(args) > 1 && len(args[1]) > 0 {
  440. if args[1] == "re" {
  441. po.Watermark.Replicate = true
  442. } else if g, ok := gravityTypes[args[1]]; ok && g != gravityFocusPoint && g != gravitySmart {
  443. po.Watermark.Gravity = g
  444. } else {
  445. return fmt.Errorf("Invalid watermark position: %s", args[1])
  446. }
  447. }
  448. if len(args) > 2 && len(args[2]) > 0 {
  449. if x, err := strconv.Atoi(args[2]); err == nil {
  450. po.Watermark.OffsetX = x
  451. } else {
  452. return fmt.Errorf("Invalid watermark X offset: %s", args[2])
  453. }
  454. }
  455. if len(args) > 3 && len(args[3]) > 0 {
  456. if y, err := strconv.Atoi(args[3]); err == nil {
  457. po.Watermark.OffsetY = y
  458. } else {
  459. return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
  460. }
  461. }
  462. if len(args) > 4 && len(args[4]) > 0 {
  463. if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
  464. po.Watermark.Scale = s
  465. } else {
  466. return fmt.Errorf("Invalid watermark scale: %s", args[4])
  467. }
  468. }
  469. return nil
  470. }
  471. func applyFormatOption(po *processingOptions, args []string) error {
  472. if len(args) > 1 {
  473. return fmt.Errorf("Invalid format arguments: %v", args)
  474. }
  475. if conf.EnforceWebp && po.Format == imageTypeWEBP {
  476. // Webp is enforced and already set as format
  477. return nil
  478. }
  479. if f, ok := imageTypes[args[0]]; ok {
  480. po.Format = f
  481. } else {
  482. return fmt.Errorf("Invalid image format: %s", args[0])
  483. }
  484. if !vipsTypeSupportSave[po.Format] {
  485. return errResultingImageFormatIsNotSupported
  486. }
  487. return nil
  488. }
  489. func applyCacheBusterOption(po *processingOptions, args []string) error {
  490. if len(args) > 1 {
  491. return fmt.Errorf("Invalid cache buster arguments: %v", args)
  492. }
  493. po.CacheBuster = args[0]
  494. return nil
  495. }
  496. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  497. switch name {
  498. case "format", "f", "ext":
  499. if err := applyFormatOption(po, args); err != nil {
  500. return err
  501. }
  502. case "resize", "rs":
  503. if err := applyResizeOption(po, args); err != nil {
  504. return err
  505. }
  506. case "resizing_type", "rt":
  507. if err := applyResizingTypeOption(po, args); err != nil {
  508. return err
  509. }
  510. case "size", "s":
  511. if err := applySizeOption(po, args); err != nil {
  512. return err
  513. }
  514. case "width", "w":
  515. if err := applyWidthOption(po, args); err != nil {
  516. return err
  517. }
  518. case "height", "h":
  519. if err := applyHeightOption(po, args); err != nil {
  520. return err
  521. }
  522. case "enlarge", "el":
  523. if err := applyEnlargeOption(po, args); err != nil {
  524. return err
  525. }
  526. case "extend", "ex":
  527. if err := applyExtendOption(po, args); err != nil {
  528. return err
  529. }
  530. case "dpr":
  531. if err := applyDprOption(po, args); err != nil {
  532. return err
  533. }
  534. case "gravity", "g":
  535. if err := applyGravityOption(po, args); err != nil {
  536. return err
  537. }
  538. case "crop", "c":
  539. if err := applyCropOption(po, args); err != nil {
  540. return err
  541. }
  542. case "quality", "q":
  543. if err := applyQualityOption(po, args); err != nil {
  544. return err
  545. }
  546. case "background", "bg":
  547. if err := applyBackgroundOption(po, args); err != nil {
  548. return err
  549. }
  550. case "blur", "bl":
  551. if err := applyBlurOption(po, args); err != nil {
  552. return err
  553. }
  554. case "sharpen", "sh":
  555. if err := applySharpenOption(po, args); err != nil {
  556. return err
  557. }
  558. case "watermark", "wm":
  559. if err := applyWatermarkOption(po, args); err != nil {
  560. return err
  561. }
  562. case "preset", "pr":
  563. if err := applyPresetOption(po, args); err != nil {
  564. return err
  565. }
  566. case "cachebuster", "cb":
  567. if err := applyCacheBusterOption(po, args); err != nil {
  568. return err
  569. }
  570. default:
  571. return fmt.Errorf("Unknown processing option: %s", name)
  572. }
  573. return nil
  574. }
  575. func applyProcessingOptions(po *processingOptions, options urlOptions) error {
  576. for name, args := range options {
  577. if err := applyProcessingOption(po, name, args); err != nil {
  578. return err
  579. }
  580. }
  581. return nil
  582. }
  583. func parseURLOptions(opts []string) (urlOptions, []string) {
  584. parsed := make(urlOptions)
  585. urlStart := len(opts) + 1
  586. for i, opt := range opts {
  587. args := strings.Split(opt, ":")
  588. if len(args) == 1 {
  589. urlStart = i
  590. break
  591. }
  592. parsed[args[0]] = args[1:]
  593. }
  594. var rest []string
  595. if urlStart < len(opts) {
  596. rest = opts[urlStart:]
  597. } else {
  598. rest = []string{}
  599. }
  600. return parsed, rest
  601. }
  602. func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, error) {
  603. var err error
  604. po := processingOptions{
  605. Resize: resizeFit,
  606. Width: 0,
  607. Height: 0,
  608. Gravity: gravityOptions{Type: gravityCenter},
  609. Enlarge: false,
  610. Quality: conf.Quality,
  611. Format: imageTypeUnknown,
  612. Background: rgbColor{255, 255, 255},
  613. Blur: 0,
  614. Sharpen: 0,
  615. Dpr: 1,
  616. Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityCenter},
  617. UsedPresets: make([]string, 0, len(conf.Presets)),
  618. }
  619. if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(headers.Accept, "image/webp") {
  620. po.Format = imageTypeWEBP
  621. }
  622. if conf.EnableClientHints && len(headers.ViewportWidth) > 0 {
  623. if vw, err := strconv.Atoi(headers.ViewportWidth); err == nil {
  624. po.Width = vw
  625. }
  626. }
  627. if conf.EnableClientHints && len(headers.Width) > 0 {
  628. if w, err := strconv.Atoi(headers.Width); err == nil {
  629. po.Width = w
  630. }
  631. }
  632. if conf.EnableClientHints && len(headers.DPR) > 0 {
  633. if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
  634. po.Dpr = dpr
  635. }
  636. }
  637. if _, ok := conf.Presets["default"]; ok {
  638. err = applyPresetOption(&po, []string{"default"})
  639. }
  640. return &po, err
  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. var err error
  685. if len(parts) < 6 {
  686. return "", nil, errInvalidPath
  687. }
  688. po, err := defaultProcessingOptions(headers)
  689. if err != nil {
  690. return "", po, err
  691. }
  692. po.Resize = resizeTypes[parts[0]]
  693. if err = applyWidthOption(po, parts[1:2]); err != nil {
  694. return "", po, err
  695. }
  696. if err = applyHeightOption(po, parts[2:3]); err != nil {
  697. return "", po, err
  698. }
  699. if err = applyGravityOption(po, strings.Split(parts[3], ":")); err != nil {
  700. return "", po, err
  701. }
  702. if err = applyEnlargeOption(po, parts[4:5]); err != nil {
  703. return "", po, err
  704. }
  705. url, extension, err := decodeURL(parts[5:])
  706. if err != nil {
  707. return "", po, err
  708. }
  709. if len(extension) > 0 {
  710. if err := applyFormatOption(po, []string{extension}); err != nil {
  711. return "", po, err
  712. }
  713. }
  714. return url, po, nil
  715. }
  716. func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
  717. path := r.URL.RawPath
  718. if len(path) == 0 {
  719. path = r.URL.Path
  720. }
  721. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  722. if len(parts) < 3 {
  723. return ctx, errInvalidPath
  724. }
  725. if !conf.AllowInsecure {
  726. if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  727. return ctx, newError(403, err.Error(), msgForbidden)
  728. }
  729. }
  730. headers := &processingHeaders{
  731. Accept: r.Header.Get("Accept"),
  732. Width: r.Header.Get("Width"),
  733. ViewportWidth: r.Header.Get("Viewport-Width"),
  734. DPR: r.Header.Get("DPR"),
  735. }
  736. var imageURL string
  737. var po *processingOptions
  738. var err error
  739. if conf.OnlyPresets {
  740. imageURL, po, err = parsePathPresets(parts[1:], headers)
  741. } else if _, ok := resizeTypes[parts[1]]; ok {
  742. imageURL, po, err = parsePathBasic(parts[1:], headers)
  743. } else {
  744. imageURL, po, err = parsePathAdvanced(parts[1:], headers)
  745. }
  746. if err != nil {
  747. return ctx, newError(404, err.Error(), msgInvalidURL)
  748. }
  749. ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
  750. ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
  751. return ctx, nil
  752. }
  753. func getImageURL(ctx context.Context) string {
  754. return ctx.Value(imageURLCtxKey).(string)
  755. }
  756. func getProcessingOptions(ctx context.Context) *processingOptions {
  757. return ctx.Value(processingOptionsCtxKey).(*processingOptions)
  758. }