processing_options.go 26 KB

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