processing_options.go 27 KB

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