processing_options.go 20 KB

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