processing_options.go 28 KB

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