processing_options.go 23 KB

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