processing_options.go 19 KB

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