processing_options.go 27 KB

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