processing_options.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. package main
  2. /*
  3. #cgo LDFLAGS: -s -w
  4. #include "image_types.h"
  5. */
  6. import "C"
  7. import (
  8. "encoding/base64"
  9. "errors"
  10. "fmt"
  11. "net/http"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. )
  16. type urlOptions map[string][]string
  17. type imageType int
  18. const (
  19. imageTypeUnknown = C.UNKNOWN
  20. imageTypeJPEG = C.JPEG
  21. imageTypePNG = C.PNG
  22. imageTypeWEBP = C.WEBP
  23. imageTypeGIF = C.GIF
  24. )
  25. var imageTypes = map[string]imageType{
  26. "jpeg": imageTypeJPEG,
  27. "jpg": imageTypeJPEG,
  28. "png": imageTypePNG,
  29. "webp": imageTypeWEBP,
  30. "gif": imageTypeGIF,
  31. }
  32. type gravityType int
  33. const (
  34. gravityCenter gravityType = iota
  35. gravityNorth
  36. gravityEast
  37. gravitySouth
  38. gravityWest
  39. gravitySmart
  40. gravityFocusPoint
  41. )
  42. var gravityTypes = map[string]gravityType{
  43. "ce": gravityCenter,
  44. "no": gravityNorth,
  45. "ea": gravityEast,
  46. "so": gravitySouth,
  47. "we": gravityWest,
  48. "sm": gravitySmart,
  49. "fp": gravityFocusPoint,
  50. }
  51. type gravity struct {
  52. Type gravityType
  53. X, Y float64
  54. }
  55. type resizeType int
  56. const (
  57. resizeFit resizeType = iota
  58. resizeFill
  59. resizeCrop
  60. )
  61. var resizeTypes = map[string]resizeType{
  62. "fit": resizeFit,
  63. "fill": resizeFill,
  64. "crop": resizeCrop,
  65. }
  66. type color struct{ R, G, B uint8 }
  67. var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
  68. const (
  69. hexColorLongFormat = "%02x%02x%02x"
  70. hexColorShortFormat = "%1x%1x%1x"
  71. )
  72. type processingOptions struct {
  73. Resize resizeType
  74. Width int
  75. Height int
  76. Gravity gravity
  77. Enlarge bool
  78. Format imageType
  79. Flatten bool
  80. Background color
  81. Blur float32
  82. Sharpen float32
  83. UsedPresets []string
  84. }
  85. func (it imageType) String() string {
  86. for k, v := range imageTypes {
  87. if v == it {
  88. return k
  89. }
  90. }
  91. return ""
  92. }
  93. func (gt gravityType) String() string {
  94. for k, v := range gravityTypes {
  95. if v == gt {
  96. return k
  97. }
  98. }
  99. return ""
  100. }
  101. func (rt resizeType) String() string {
  102. for k, v := range resizeTypes {
  103. if v == rt {
  104. return k
  105. }
  106. }
  107. return ""
  108. }
  109. func colorFromHex(hexcolor string) (color, error) {
  110. c := color{}
  111. if !hexColorRegex.MatchString(hexcolor) {
  112. return c, fmt.Errorf("Invalid hex color: %s", hexcolor)
  113. }
  114. if len(hexcolor) == 3 {
  115. fmt.Sscanf(hexcolor, hexColorShortFormat, &c.R, &c.G, &c.B)
  116. c.R *= 17
  117. c.G *= 17
  118. c.B *= 17
  119. } else {
  120. fmt.Sscanf(hexcolor, hexColorLongFormat, &c.R, &c.G, &c.B)
  121. }
  122. return c, nil
  123. }
  124. func (po *processingOptions) isPresetUsed(name string) bool {
  125. for _, usedName := range po.UsedPresets {
  126. if usedName == name {
  127. return true
  128. }
  129. }
  130. return false
  131. }
  132. func (po *processingOptions) presetUsed(name string) {
  133. po.UsedPresets = append(po.UsedPresets, name)
  134. }
  135. func decodeURL(parts []string) (string, string, error) {
  136. var extension string
  137. urlParts := strings.Split(strings.Join(parts, ""), ".")
  138. if len(urlParts) > 2 {
  139. return "", "", errors.New("Invalid url encoding")
  140. }
  141. if len(urlParts) == 2 {
  142. extension = urlParts[1]
  143. }
  144. url, err := base64.RawURLEncoding.DecodeString(urlParts[0])
  145. if err != nil {
  146. return "", "", errors.New("Invalid url encoding")
  147. }
  148. return string(url), extension, nil
  149. }
  150. func applyWidthOption(po *processingOptions, args []string) error {
  151. if len(args) > 1 {
  152. return fmt.Errorf("Invalid width arguments: %v", args)
  153. }
  154. if w, err := strconv.Atoi(args[0]); err == nil && w >= 0 {
  155. po.Width = w
  156. } else {
  157. return fmt.Errorf("Invalid width: %s", args[0])
  158. }
  159. return nil
  160. }
  161. func applyHeightOption(po *processingOptions, args []string) error {
  162. if len(args) > 1 {
  163. return fmt.Errorf("Invalid height arguments: %v", args)
  164. }
  165. if h, err := strconv.Atoi(args[0]); err == nil && po.Height >= 0 {
  166. po.Height = h
  167. } else {
  168. return fmt.Errorf("Invalid height: %s", args[0])
  169. }
  170. return nil
  171. }
  172. func applyEnlargeOption(po *processingOptions, args []string) error {
  173. if len(args) > 1 {
  174. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  175. }
  176. po.Enlarge = args[0] != "0"
  177. return nil
  178. }
  179. func applySizeOption(po *processingOptions, args []string) (err error) {
  180. if len(args) > 3 {
  181. return fmt.Errorf("Invalid size arguments: %v", args)
  182. }
  183. if len(args) >= 1 && len(args[0]) > 0 {
  184. if err = applyWidthOption(po, args[0:1]); err != nil {
  185. return
  186. }
  187. }
  188. if len(args) >= 2 && len(args[1]) > 0 {
  189. if err = applyHeightOption(po, args[1:2]); err != nil {
  190. return
  191. }
  192. }
  193. if len(args) == 3 && len(args[2]) > 0 {
  194. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  195. return
  196. }
  197. }
  198. return nil
  199. }
  200. func applyResizingTypeOption(po *processingOptions, args []string) error {
  201. if len(args) > 1 {
  202. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  203. }
  204. if r, ok := resizeTypes[args[0]]; ok {
  205. po.Resize = r
  206. } else {
  207. return fmt.Errorf("Invalid resize type: %s", args[0])
  208. }
  209. return nil
  210. }
  211. func applyResizeOption(po *processingOptions, args []string) error {
  212. if len(args) > 4 {
  213. return fmt.Errorf("Invalid resize arguments: %v", args)
  214. }
  215. if len(args[0]) > 0 {
  216. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  217. return err
  218. }
  219. }
  220. if len(args) > 1 {
  221. if err := applySizeOption(po, args[1:]); err != nil {
  222. return err
  223. }
  224. }
  225. return nil
  226. }
  227. func applyGravityOption(po *processingOptions, args []string) error {
  228. if g, ok := gravityTypes[args[0]]; ok {
  229. po.Gravity.Type = g
  230. } else {
  231. return fmt.Errorf("Invalid gravity: %s", args[0])
  232. }
  233. if po.Gravity.Type == gravityFocusPoint {
  234. if len(args) != 3 {
  235. return fmt.Errorf("Invalid gravity arguments: %v", args)
  236. }
  237. if x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
  238. po.Gravity.X = x
  239. } else {
  240. return fmt.Errorf("Invalid gravity X: %s", args[1])
  241. }
  242. if y, err := strconv.ParseFloat(args[2], 64); err == nil && y >= 0 && y <= 1 {
  243. po.Gravity.Y = y
  244. } else {
  245. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  246. }
  247. } else if len(args) > 1 {
  248. return fmt.Errorf("Invalid gravity arguments: %v", args)
  249. }
  250. return nil
  251. }
  252. func applyBackgroundOption(po *processingOptions, args []string) error {
  253. switch len(args) {
  254. case 1:
  255. if len(args[0]) == 0 {
  256. po.Flatten = false
  257. } else if c, err := colorFromHex(args[0]); err == nil {
  258. po.Flatten = true
  259. po.Background = c
  260. } else {
  261. return fmt.Errorf("Invalid background argument: %s", err)
  262. }
  263. case 3:
  264. po.Flatten = true
  265. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r >= 0 && r <= 255 {
  266. po.Background.R = uint8(r)
  267. } else {
  268. return fmt.Errorf("Invalid background red channel: %s", args[0])
  269. }
  270. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g >= 0 && g <= 255 {
  271. po.Background.G = uint8(g)
  272. } else {
  273. return fmt.Errorf("Invalid background green channel: %s", args[1])
  274. }
  275. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b >= 0 && b <= 255 {
  276. po.Background.B = uint8(b)
  277. } else {
  278. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  279. }
  280. default:
  281. return fmt.Errorf("Invalid background arguments: %v", args)
  282. }
  283. return nil
  284. }
  285. func applyBlurOption(po *processingOptions, args []string) error {
  286. if len(args) > 1 {
  287. return fmt.Errorf("Invalid blur arguments: %v", args)
  288. }
  289. if b, err := strconv.ParseFloat(args[0], 32); err == nil || b >= 0 {
  290. po.Blur = float32(b)
  291. } else {
  292. return fmt.Errorf("Invalid blur: %s", args[0])
  293. }
  294. return nil
  295. }
  296. func applySharpenOption(po *processingOptions, args []string) error {
  297. if len(args) > 1 {
  298. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  299. }
  300. if s, err := strconv.ParseFloat(args[0], 32); err == nil || s >= 0 {
  301. po.Sharpen = float32(s)
  302. } else {
  303. return fmt.Errorf("Invalid sharpen: %s", args[0])
  304. }
  305. return nil
  306. }
  307. func applyPresetOption(po *processingOptions, args []string) error {
  308. for _, preset := range args {
  309. if p, ok := conf.Presets[preset]; ok {
  310. if po.isPresetUsed(preset) {
  311. return fmt.Errorf("Recursive preset usage is detected: %s", preset)
  312. }
  313. po.presetUsed(preset)
  314. if err := applyProcessingOptions(po, p); err != nil {
  315. return err
  316. }
  317. } else {
  318. return fmt.Errorf("Unknown asset: %s", preset)
  319. }
  320. }
  321. return nil
  322. }
  323. func applyFormatOption(po *processingOptions, args []string) error {
  324. if len(args) > 1 {
  325. return fmt.Errorf("Invalid format arguments: %v", args)
  326. }
  327. if conf.EnforceWebp && po.Format == imageTypeWEBP {
  328. // Webp is enforced and already set as format
  329. return nil
  330. }
  331. if f, ok := imageTypes[args[0]]; ok {
  332. po.Format = f
  333. } else {
  334. return fmt.Errorf("Invalid image format: %s", args[0])
  335. }
  336. if !vipsTypeSupportSave[po.Format] {
  337. return errors.New("Resulting image type not supported")
  338. }
  339. return nil
  340. }
  341. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  342. switch name {
  343. case "format", "f", "ext":
  344. if err := applyFormatOption(po, args); err != nil {
  345. return err
  346. }
  347. case "resize", "rs":
  348. if err := applyResizeOption(po, args); err != nil {
  349. return err
  350. }
  351. case "resizing_type", "rt":
  352. if err := applyResizingTypeOption(po, args); err != nil {
  353. return err
  354. }
  355. case "size", "s":
  356. if err := applySizeOption(po, args); err != nil {
  357. return err
  358. }
  359. case "width", "w":
  360. if err := applyWidthOption(po, args); err != nil {
  361. return err
  362. }
  363. case "height", "h":
  364. if err := applyHeightOption(po, args); err != nil {
  365. return err
  366. }
  367. case "enlarge", "el":
  368. if err := applyEnlargeOption(po, args); err != nil {
  369. return err
  370. }
  371. case "gravity", "g":
  372. if err := applyGravityOption(po, args); err != nil {
  373. return err
  374. }
  375. case "background", "bg":
  376. if err := applyBackgroundOption(po, args); err != nil {
  377. return err
  378. }
  379. case "blur", "bl":
  380. if err := applyBlurOption(po, args); err != nil {
  381. return err
  382. }
  383. case "sharpen", "sh":
  384. if err := applySharpenOption(po, args); err != nil {
  385. return err
  386. }
  387. case "preset", "pr":
  388. if err := applyPresetOption(po, args); err != nil {
  389. return err
  390. }
  391. default:
  392. return fmt.Errorf("Unknown processing option: %s", name)
  393. }
  394. return nil
  395. }
  396. func applyProcessingOptions(po *processingOptions, options urlOptions) error {
  397. for name, args := range options {
  398. if err := applyProcessingOption(po, name, args); err != nil {
  399. return err
  400. }
  401. }
  402. return nil
  403. }
  404. func parseURLOptions(opts []string) (urlOptions, []string) {
  405. parsed := make(urlOptions)
  406. urlStart := len(opts) + 1
  407. for i, opt := range opts {
  408. args := strings.Split(opt, ":")
  409. if len(args) == 1 {
  410. urlStart = i
  411. break
  412. }
  413. parsed[args[0]] = args[1:]
  414. }
  415. var rest []string
  416. if urlStart < len(opts) {
  417. rest = opts[urlStart:]
  418. } else {
  419. rest = []string{}
  420. }
  421. return parsed, rest
  422. }
  423. func defaultProcessingOptions(acceptHeader string) (processingOptions, error) {
  424. var err error
  425. po := processingOptions{
  426. Resize: resizeFit,
  427. Width: 0,
  428. Height: 0,
  429. Gravity: gravity{Type: gravityCenter},
  430. Enlarge: false,
  431. Format: imageTypeJPEG,
  432. Blur: 0,
  433. Sharpen: 0,
  434. UsedPresets: make([]string, 0),
  435. }
  436. if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(acceptHeader, "image/webp") {
  437. po.Format = imageTypeWEBP
  438. }
  439. if _, ok := conf.Presets["default"]; ok {
  440. err = applyPresetOption(&po, []string{"default"})
  441. }
  442. return po, err
  443. }
  444. func parsePathAdvanced(parts []string, acceptHeader string) (string, processingOptions, error) {
  445. po, err := defaultProcessingOptions(acceptHeader)
  446. if err != nil {
  447. return "", po, err
  448. }
  449. options, urlParts := parseURLOptions(parts)
  450. if err := applyProcessingOptions(&po, options); err != nil {
  451. return "", po, err
  452. }
  453. url, extension, err := decodeURL(urlParts)
  454. if err != nil {
  455. return "", po, err
  456. }
  457. if len(extension) > 0 {
  458. if err := applyFormatOption(&po, []string{extension}); err != nil {
  459. return "", po, errors.New("Resulting image type not supported")
  460. }
  461. }
  462. return string(url), po, nil
  463. }
  464. func parsePathSimple(parts []string, acceptHeader string) (string, processingOptions, error) {
  465. var err error
  466. if len(parts) < 6 {
  467. return "", processingOptions{}, errors.New("Invalid path")
  468. }
  469. po, err := defaultProcessingOptions(acceptHeader)
  470. if err != nil {
  471. return "", po, err
  472. }
  473. po.Resize = resizeTypes[parts[0]]
  474. if err = applyWidthOption(&po, parts[1:2]); err != nil {
  475. return "", po, err
  476. }
  477. if err = applyHeightOption(&po, parts[2:3]); err != nil {
  478. return "", po, err
  479. }
  480. if err = applyGravityOption(&po, strings.Split(parts[3], ":")); err != nil {
  481. return "", po, err
  482. }
  483. if err = applyEnlargeOption(&po, parts[4:5]); err != nil {
  484. return "", po, err
  485. }
  486. url, extension, err := decodeURL(parts[5:])
  487. if err != nil {
  488. return "", po, err
  489. }
  490. if len(extension) > 0 {
  491. if err := applyFormatOption(&po, []string{extension}); err != nil {
  492. return "", po, errors.New("Resulting image type not supported")
  493. }
  494. }
  495. return string(url), po, nil
  496. }
  497. func parsePath(r *http.Request) (string, processingOptions, error) {
  498. path := r.URL.Path
  499. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  500. var acceptHeader string
  501. if h, ok := r.Header["Accept"]; ok {
  502. acceptHeader = h[0]
  503. }
  504. if len(parts) < 3 {
  505. return "", processingOptions{}, errors.New("Invalid path")
  506. }
  507. if !conf.AllowInsecure {
  508. if err := validatePath(parts[0], strings.TrimPrefix(path, fmt.Sprintf("/%s", parts[0]))); err != nil {
  509. return "", processingOptions{}, err
  510. }
  511. }
  512. if _, ok := resizeTypes[parts[1]]; ok {
  513. return parsePathSimple(parts[1:], acceptHeader)
  514. }
  515. return parsePathAdvanced(parts[1:], acceptHeader)
  516. }