processing_options.go 26 KB

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