processing_options.go 22 KB

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