processing_options.go 24 KB

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