processing_options.go 19 KB

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