processing_options.go 11 KB

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