processing_options.go 19 KB

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