processing_options.go 22 KB

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