processing_options_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. package optionsparser
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "regexp"
  8. "strings"
  9. "testing"
  10. "time"
  11. "github.com/imgproxy/imgproxy/v3/imagetype"
  12. "github.com/imgproxy/imgproxy/v3/options"
  13. "github.com/imgproxy/imgproxy/v3/options/keys"
  14. "github.com/imgproxy/imgproxy/v3/processing"
  15. "github.com/imgproxy/imgproxy/v3/testutil"
  16. "github.com/imgproxy/imgproxy/v3/vips/color"
  17. "github.com/stretchr/testify/suite"
  18. )
  19. type ProcessingOptionsTestSuite struct {
  20. testutil.LazySuite
  21. config testutil.LazyObj[*Config]
  22. parser testutil.LazyObj[*Parser]
  23. }
  24. func (s *ProcessingOptionsTestSuite) SetupSuite() {
  25. s.config, _ = testutil.NewLazySuiteObj(
  26. s,
  27. func() (*Config, error) {
  28. c := NewDefaultConfig()
  29. return &c, nil
  30. },
  31. )
  32. s.parser, _ = testutil.NewLazySuiteObj(
  33. s,
  34. func() (*Parser, error) {
  35. return New(s.config())
  36. },
  37. )
  38. }
  39. func (s *ProcessingOptionsTestSuite) SetupSubTest() {
  40. s.ResetLazyObjects()
  41. }
  42. func (s *ProcessingOptionsTestSuite) TestParseBase64URL() {
  43. originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
  44. path := fmt.Sprintf("/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  45. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  46. s.Require().NoError(err)
  47. s.Require().Equal(originURL, imageURL)
  48. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  49. }
  50. func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithFilename() {
  51. s.config().Base64URLIncludesFilename = true
  52. originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
  53. path := fmt.Sprintf("/size:100:100/%s.png/puppy.jpg", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  54. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  55. s.Require().NoError(err)
  56. s.Require().Equal(originURL, imageURL)
  57. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  58. }
  59. func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithoutExtension() {
  60. originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
  61. path := fmt.Sprintf("/size:100:100/%s", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  62. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  63. s.Require().NoError(err)
  64. s.Require().Equal(originURL, imageURL)
  65. s.Require().Equal(imagetype.Unknown, options.Get(o, keys.Format, imagetype.Unknown))
  66. }
  67. func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithBase() {
  68. s.config().BaseURL = "http://images.dev/"
  69. originURL := "lorem/ipsum.jpg?param=value"
  70. path := fmt.Sprintf("/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  71. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  72. s.Require().NoError(err)
  73. s.Require().Equal(fmt.Sprintf("%s%s", s.config().BaseURL, originURL), imageURL)
  74. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  75. }
  76. func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithReplacement() {
  77. s.config().URLReplacements = []URLReplacement{
  78. {Regexp: regexp.MustCompile("^test://([^/]*)/"), Replacement: "test2://images.dev/${1}/dolor/"},
  79. {Regexp: regexp.MustCompile("^test2://"), Replacement: "http://"},
  80. }
  81. originURL := "test://lorem/ipsum.jpg?param=value"
  82. path := fmt.Sprintf("/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  83. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  84. s.Require().NoError(err)
  85. s.Require().Equal("http://images.dev/lorem/dolor/ipsum.jpg?param=value", imageURL)
  86. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  87. }
  88. func (s *ProcessingOptionsTestSuite) TestParsePlainURL() {
  89. originURL := "http://images.dev/lorem/ipsum.jpg"
  90. path := fmt.Sprintf("/size:100:100/plain/%s@png", originURL)
  91. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  92. s.Require().NoError(err)
  93. s.Require().Equal(originURL, imageURL)
  94. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  95. }
  96. func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithoutExtension() {
  97. originURL := "http://images.dev/lorem/ipsum.jpg"
  98. path := fmt.Sprintf("/size:100:100/plain/%s", originURL)
  99. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  100. s.Require().NoError(err)
  101. s.Require().Equal(originURL, imageURL)
  102. s.Require().Equal(imagetype.Unknown, options.Get(o, keys.Format, imagetype.Unknown))
  103. }
  104. func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscaped() {
  105. originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
  106. path := fmt.Sprintf("/size:100:100/plain/%s@png", url.PathEscape(originURL))
  107. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  108. s.Require().NoError(err)
  109. s.Require().Equal(originURL, imageURL)
  110. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  111. }
  112. func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithBase() {
  113. s.config().BaseURL = "http://images.dev/"
  114. originURL := "lorem/ipsum.jpg"
  115. path := fmt.Sprintf("/size:100:100/plain/%s@png", originURL)
  116. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  117. s.Require().NoError(err)
  118. s.Require().Equal(fmt.Sprintf("%s%s", s.config().BaseURL, originURL), imageURL)
  119. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  120. }
  121. func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithReplacement() {
  122. s.config().URLReplacements = []URLReplacement{
  123. {Regexp: regexp.MustCompile("^test://([^/]*)/"), Replacement: "test2://images.dev/${1}/dolor/"},
  124. {Regexp: regexp.MustCompile("^test2://"), Replacement: "http://"},
  125. }
  126. originURL := "test://lorem/ipsum.jpg"
  127. path := fmt.Sprintf("/size:100:100/plain/%s@png", originURL)
  128. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  129. s.Require().NoError(err)
  130. s.Require().Equal("http://images.dev/lorem/dolor/ipsum.jpg", imageURL)
  131. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  132. }
  133. func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedWithBase() {
  134. s.config().BaseURL = "http://images.dev/"
  135. originURL := "lorem/ipsum.jpg?param=value"
  136. path := fmt.Sprintf("/size:100:100/plain/%s@png", url.PathEscape(originURL))
  137. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  138. s.Require().NoError(err)
  139. s.Require().Equal(fmt.Sprintf("%s%s", s.config().BaseURL, originURL), imageURL)
  140. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  141. }
  142. func (s *ProcessingOptionsTestSuite) TestParseWithArgumentsSeparator() {
  143. s.config().ArgumentsSeparator = ","
  144. path := "/size,100,100,1/plain/http://images.dev/lorem/ipsum.jpg"
  145. o, _, err := s.parser().ParsePath(path, make(http.Header))
  146. s.Require().NoError(err)
  147. s.Require().Equal(100, o.GetInt(keys.Width, 0))
  148. s.Require().Equal(100, o.GetInt(keys.Height, 0))
  149. s.Require().True(o.GetBool(keys.Enlarge, false))
  150. }
  151. func (s *ProcessingOptionsTestSuite) TestParsePathFormat() {
  152. path := "/format:webp/plain/http://images.dev/lorem/ipsum.jpg"
  153. o, _, err := s.parser().ParsePath(path, make(http.Header))
  154. s.Require().NoError(err)
  155. s.Require().Equal(imagetype.WEBP, options.Get(o, keys.Format, imagetype.Unknown))
  156. }
  157. func (s *ProcessingOptionsTestSuite) TestParsePathResize() {
  158. path := "/resize:fill:100:200:1/plain/http://images.dev/lorem/ipsum.jpg"
  159. o, _, err := s.parser().ParsePath(path, make(http.Header))
  160. s.Require().NoError(err)
  161. s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
  162. s.Require().Equal(100, o.GetInt(keys.Width, 0))
  163. s.Require().Equal(200, o.GetInt(keys.Height, 0))
  164. s.Require().True(o.GetBool(keys.Enlarge, false))
  165. }
  166. func (s *ProcessingOptionsTestSuite) TestParsePathResizingType() {
  167. path := "/resizing_type:fill/plain/http://images.dev/lorem/ipsum.jpg"
  168. o, _, err := s.parser().ParsePath(path, make(http.Header))
  169. s.Require().NoError(err)
  170. s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
  171. }
  172. func (s *ProcessingOptionsTestSuite) TestParsePathSize() {
  173. path := "/size:100:200:1/plain/http://images.dev/lorem/ipsum.jpg"
  174. o, _, err := s.parser().ParsePath(path, make(http.Header))
  175. s.Require().NoError(err)
  176. s.Require().Equal(100, o.GetInt(keys.Width, 0))
  177. s.Require().Equal(200, o.GetInt(keys.Height, 0))
  178. s.Require().True(o.GetBool(keys.Enlarge, false))
  179. }
  180. func (s *ProcessingOptionsTestSuite) TestParsePathWidth() {
  181. path := "/width:100/plain/http://images.dev/lorem/ipsum.jpg"
  182. o, _, err := s.parser().ParsePath(path, make(http.Header))
  183. s.Require().NoError(err)
  184. s.Require().Equal(100, o.GetInt(keys.Width, 0))
  185. }
  186. func (s *ProcessingOptionsTestSuite) TestParsePathHeight() {
  187. path := "/height:100/plain/http://images.dev/lorem/ipsum.jpg"
  188. o, _, err := s.parser().ParsePath(path, make(http.Header))
  189. s.Require().NoError(err)
  190. s.Require().Equal(100, o.GetInt(keys.Height, 0))
  191. }
  192. func (s *ProcessingOptionsTestSuite) TestParsePathEnlarge() {
  193. path := "/enlarge:1/plain/http://images.dev/lorem/ipsum.jpg"
  194. o, _, err := s.parser().ParsePath(path, make(http.Header))
  195. s.Require().NoError(err)
  196. s.Require().True(o.GetBool(keys.Enlarge, false))
  197. }
  198. func (s *ProcessingOptionsTestSuite) TestParsePathExtend() {
  199. path := "/extend:1:so:10:20/plain/http://images.dev/lorem/ipsum.jpg"
  200. o, _, err := s.parser().ParsePath(path, make(http.Header))
  201. s.Require().NoError(err)
  202. s.Require().True(o.GetBool(keys.ExtendEnabled, false))
  203. s.Require().Equal(
  204. processing.GravitySouth,
  205. options.Get(o, keys.ExtendGravityType, processing.GravityUnknown),
  206. )
  207. s.Require().InDelta(10.0, o.GetFloat(keys.ExtendGravityXOffset, 0.0), 0.0001)
  208. s.Require().InDelta(20.0, o.GetFloat(keys.ExtendGravityYOffset, 0.0), 0.0001)
  209. }
  210. func (s *ProcessingOptionsTestSuite) TestParsePathExtendSmartGravity() {
  211. path := "/extend:1:sm/plain/http://images.dev/lorem/ipsum.jpg"
  212. _, _, err := s.parser().ParsePath(path, make(http.Header))
  213. s.Require().Error(err)
  214. }
  215. func (s *ProcessingOptionsTestSuite) TestParsePathExtendReplicateGravity() {
  216. path := "/extend:1:re/plain/http://images.dev/lorem/ipsum.jpg"
  217. _, _, err := s.parser().ParsePath(path, make(http.Header))
  218. s.Require().Error(err)
  219. }
  220. func (s *ProcessingOptionsTestSuite) TestParsePathGravity() {
  221. path := "/gravity:soea/plain/http://images.dev/lorem/ipsum.jpg"
  222. o, _, err := s.parser().ParsePath(path, make(http.Header))
  223. s.Require().NoError(err)
  224. s.Require().Equal(
  225. processing.GravitySouthEast,
  226. options.Get(o, keys.GravityType, processing.GravityUnknown),
  227. )
  228. }
  229. func (s *ProcessingOptionsTestSuite) TestParsePathGravityFocusPoint() {
  230. path := "/gravity:fp:0.5:0.75/plain/http://images.dev/lorem/ipsum.jpg"
  231. o, _, err := s.parser().ParsePath(path, make(http.Header))
  232. s.Require().NoError(err)
  233. s.Require().Equal(processing.GravityFocusPoint, options.Get(o, keys.GravityType, processing.GravityUnknown))
  234. s.Require().InDelta(0.5, o.GetFloat(keys.GravityXOffset, 0.0), 0.0001)
  235. s.Require().InDelta(0.75, o.GetFloat(keys.GravityYOffset, 0.0), 0.0001)
  236. }
  237. func (s *ProcessingOptionsTestSuite) TestParsePathGravityReplicate() {
  238. path := "/gravity:re/plain/http://images.dev/lorem/ipsum.jpg"
  239. _, _, err := s.parser().ParsePath(path, make(http.Header))
  240. s.Require().Error(err)
  241. }
  242. func (s *ProcessingOptionsTestSuite) TestParsePathCrop() {
  243. path := "/crop:100:200/plain/http://images.dev/lorem/ipsum.jpg"
  244. o, _, err := s.parser().ParsePath(path, make(http.Header))
  245. s.Require().NoError(err)
  246. s.Require().InDelta(100.0, o.GetFloat(keys.CropWidth, 0.0), 0.0001)
  247. s.Require().InDelta(200.0, o.GetFloat(keys.CropHeight, 0.0), 0.0001)
  248. s.Require().Equal(
  249. processing.GravityUnknown,
  250. options.Get(o, keys.CropGravityType, processing.GravityUnknown),
  251. )
  252. s.Require().InDelta(0.0, o.GetFloat(keys.CropGravityXOffset, 0.0), 0.0001)
  253. s.Require().InDelta(0.0, o.GetFloat(keys.CropGravityYOffset, 0.0), 0.0001)
  254. }
  255. func (s *ProcessingOptionsTestSuite) TestParsePathCropGravity() {
  256. path := "/crop:100:200:nowe:10:20/plain/http://images.dev/lorem/ipsum.jpg"
  257. o, _, err := s.parser().ParsePath(path, make(http.Header))
  258. s.Require().NoError(err)
  259. s.Require().InDelta(100.0, o.GetFloat(keys.CropWidth, 0.0), 0.0001)
  260. s.Require().InDelta(200.0, o.GetFloat(keys.CropHeight, 0.0), 0.0001)
  261. s.Require().Equal(
  262. processing.GravityNorthWest,
  263. options.Get(o, keys.CropGravityType, processing.GravityUnknown),
  264. )
  265. s.Require().InDelta(10.0, o.GetFloat(keys.CropGravityXOffset, 0.0), 0.0001)
  266. s.Require().InDelta(20.0, o.GetFloat(keys.CropGravityYOffset, 0.0), 0.0001)
  267. }
  268. func (s *ProcessingOptionsTestSuite) TestParsePathCropGravityReplicate() {
  269. path := "/crop:100:200:re/plain/http://images.dev/lorem/ipsum.jpg"
  270. _, _, err := s.parser().ParsePath(path, make(http.Header))
  271. s.Require().Error(err)
  272. }
  273. func (s *ProcessingOptionsTestSuite) TestParsePathQuality() {
  274. path := "/quality:55/plain/http://images.dev/lorem/ipsum.jpg"
  275. o, _, err := s.parser().ParsePath(path, make(http.Header))
  276. s.Require().NoError(err)
  277. s.Require().Equal(55, o.GetInt(keys.Quality, 0))
  278. }
  279. func (s *ProcessingOptionsTestSuite) TestParsePathBackground() {
  280. path := "/background:128:129:130/plain/http://images.dev/lorem/ipsum.jpg"
  281. o, _, err := s.parser().ParsePath(path, make(http.Header))
  282. s.Require().NoError(err)
  283. s.Require().Equal(
  284. color.RGB{R: 128, G: 129, B: 130},
  285. options.Get(o, keys.Background, color.RGB{}),
  286. )
  287. }
  288. func (s *ProcessingOptionsTestSuite) TestParsePathBackgroundHex() {
  289. path := "/background:ffddee/plain/http://images.dev/lorem/ipsum.jpg"
  290. o, _, err := s.parser().ParsePath(path, make(http.Header))
  291. s.Require().NoError(err)
  292. s.Require().Equal(
  293. color.RGB{R: 0xff, G: 0xdd, B: 0xee},
  294. options.Get(o, keys.Background, color.RGB{}),
  295. )
  296. }
  297. func (s *ProcessingOptionsTestSuite) TestParsePathBackgroundDisable() {
  298. path := "/background:fff/background:/plain/http://images.dev/lorem/ipsum.jpg"
  299. o, _, err := s.parser().ParsePath(path, make(http.Header))
  300. s.Require().NoError(err)
  301. s.Require().False(o.Has(keys.Background))
  302. }
  303. func (s *ProcessingOptionsTestSuite) TestParsePathBlur() {
  304. path := "/blur:0.2/plain/http://images.dev/lorem/ipsum.jpg"
  305. o, _, err := s.parser().ParsePath(path, make(http.Header))
  306. s.Require().NoError(err)
  307. s.Require().InDelta(0.2, o.GetFloat(keys.Blur, 0.0), 0.0001)
  308. }
  309. func (s *ProcessingOptionsTestSuite) TestParsePathSharpen() {
  310. path := "/sharpen:0.2/plain/http://images.dev/lorem/ipsum.jpg"
  311. o, _, err := s.parser().ParsePath(path, make(http.Header))
  312. s.Require().NoError(err)
  313. s.Require().InDelta(0.2, o.GetFloat(keys.Sharpen, 0.0), 0.0001)
  314. }
  315. func (s *ProcessingOptionsTestSuite) TestParsePathDpr() {
  316. path := "/dpr:2/plain/http://images.dev/lorem/ipsum.jpg"
  317. o, _, err := s.parser().ParsePath(path, make(http.Header))
  318. s.Require().NoError(err)
  319. s.Require().InDelta(2.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
  320. }
  321. func (s *ProcessingOptionsTestSuite) TestParsePathWatermark() {
  322. path := "/watermark:0.5:soea:10:20:0.6/plain/http://images.dev/lorem/ipsum.jpg"
  323. o, _, err := s.parser().ParsePath(path, make(http.Header))
  324. s.Require().NoError(err)
  325. s.Require().InDelta(0.5, o.GetFloat(keys.WatermarkOpacity, 0.0), 0.0001)
  326. s.Require().Equal(
  327. processing.GravitySouthEast,
  328. options.Get(o, keys.WatermarkPosition, processing.GravityUnknown),
  329. )
  330. s.Require().InDelta(10.0, o.GetFloat(keys.WatermarkXOffset, 0.0), 0.0001)
  331. s.Require().InDelta(20.0, o.GetFloat(keys.WatermarkYOffset, 0.0), 0.0001)
  332. s.Require().InDelta(0.6, o.GetFloat(keys.WatermarkScale, 0.0), 0.0001)
  333. }
  334. func (s *ProcessingOptionsTestSuite) TestParsePathPreset() {
  335. s.config().Presets = []string{
  336. "test1=resizing_type:fill",
  337. "test2=blur:0.2/quality:50",
  338. }
  339. path := "/preset:test1:test2/plain/http://images.dev/lorem/ipsum.jpg"
  340. o, _, err := s.parser().ParsePath(path, make(http.Header))
  341. s.Require().NoError(err)
  342. s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
  343. s.Require().InDelta(float32(0.2), o.GetFloat(keys.Blur, 0.0), 0.0001)
  344. s.Require().Equal(50, o.GetInt(keys.Quality, 0))
  345. s.Require().ElementsMatch([]string{"test1", "test2"}, options.Get(o, keys.UsedPresets, []string{}))
  346. }
  347. func (s *ProcessingOptionsTestSuite) TestParsePathPresetDefault() {
  348. s.config().Presets = []string{
  349. "default=resizing_type:fill/blur:0.2/quality:50",
  350. }
  351. path := "/quality:70/plain/http://images.dev/lorem/ipsum.jpg"
  352. o, _, err := s.parser().ParsePath(path, make(http.Header))
  353. s.Require().NoError(err)
  354. s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
  355. s.Require().InDelta(float32(0.2), o.GetFloat(keys.Blur, 0.0), 0.0001)
  356. s.Require().Equal(70, o.GetInt(keys.Quality, 0))
  357. s.Require().ElementsMatch([]string{"default"}, options.Get(o, keys.UsedPresets, []string{}))
  358. }
  359. func (s *ProcessingOptionsTestSuite) TestParsePathPresetLoopDetection() {
  360. s.config().Presets = []string{
  361. "test1=resizing_type:fill/preset:test2",
  362. "test2=blur:0.2/preset:test1",
  363. }
  364. path := "/preset:test1/plain/http://images.dev/lorem/ipsum.jpg"
  365. o, _, err := s.parser().ParsePath(path, make(http.Header))
  366. s.Require().NoError(err)
  367. s.Require().ElementsMatch([]string{"test1", "test2"}, options.Get(o, keys.UsedPresets, []string{}))
  368. }
  369. func (s *ProcessingOptionsTestSuite) TestParsePathCachebuster() {
  370. path := "/cachebuster:123/plain/http://images.dev/lorem/ipsum.jpg"
  371. o, _, err := s.parser().ParsePath(path, make(http.Header))
  372. s.Require().NoError(err)
  373. s.Require().Equal("123", options.Get(o, keys.CacheBuster, ""))
  374. }
  375. func (s *ProcessingOptionsTestSuite) TestParsePathStripMetadata() {
  376. path := "/strip_metadata:true/plain/http://images.dev/lorem/ipsum.jpg"
  377. o, _, err := s.parser().ParsePath(path, make(http.Header))
  378. s.Require().NoError(err)
  379. s.Require().True(o.GetBool(keys.StripMetadata, false))
  380. }
  381. func (s *ProcessingOptionsTestSuite) TestParsePathWebpDetection() {
  382. s.config().AutoWebp = true
  383. path := "/plain/http://images.dev/lorem/ipsum.jpg"
  384. headers := http.Header{"Accept": []string{"image/webp"}}
  385. o, _, err := s.parser().ParsePath(path, headers)
  386. s.Require().NoError(err)
  387. s.Require().True(o.GetBool(keys.PreferWebP, false))
  388. s.Require().False(o.GetBool(keys.EnforceWebP, false))
  389. }
  390. func (s *ProcessingOptionsTestSuite) TestParsePathWebpEnforce() {
  391. s.config().EnforceWebp = true
  392. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  393. headers := http.Header{"Accept": []string{"image/webp"}}
  394. o, _, err := s.parser().ParsePath(path, headers)
  395. s.Require().NoError(err)
  396. s.Require().True(o.GetBool(keys.PreferWebP, false))
  397. s.Require().True(o.GetBool(keys.EnforceWebP, false))
  398. }
  399. func (s *ProcessingOptionsTestSuite) TestParsePathAvifDetection() {
  400. s.config().AutoAvif = true
  401. path := "/plain/http://images.dev/lorem/ipsum.jpg"
  402. headers := http.Header{"Accept": []string{"image/avif"}}
  403. o, _, err := s.parser().ParsePath(path, headers)
  404. s.Require().NoError(err)
  405. s.Require().True(o.GetBool(keys.PreferAvif, false))
  406. s.Require().False(o.GetBool(keys.EnforceAvif, false))
  407. }
  408. func (s *ProcessingOptionsTestSuite) TestParsePathAvifEnforce() {
  409. s.config().EnforceAvif = true
  410. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  411. headers := http.Header{"Accept": []string{"image/avif"}}
  412. o, _, err := s.parser().ParsePath(path, headers)
  413. s.Require().NoError(err)
  414. s.Require().True(o.GetBool(keys.PreferAvif, false))
  415. s.Require().True(o.GetBool(keys.EnforceAvif, false))
  416. }
  417. func (s *ProcessingOptionsTestSuite) TestParsePathJxlDetection() {
  418. s.config().AutoJxl = true
  419. path := "/plain/http://images.dev/lorem/ipsum.jpg"
  420. headers := http.Header{"Accept": []string{"image/jxl"}}
  421. o, _, err := s.parser().ParsePath(path, headers)
  422. s.Require().NoError(err)
  423. s.Require().True(o.GetBool(keys.PreferJxl, false))
  424. s.Require().False(o.GetBool(keys.EnforceJxl, false))
  425. }
  426. func (s *ProcessingOptionsTestSuite) TestParsePathJxlEnforce() {
  427. s.config().EnforceJxl = true
  428. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  429. headers := http.Header{"Accept": []string{"image/jxl"}}
  430. o, _, err := s.parser().ParsePath(path, headers)
  431. s.Require().NoError(err)
  432. s.Require().True(o.GetBool(keys.PreferJxl, false))
  433. s.Require().True(o.GetBool(keys.EnforceJxl, false))
  434. }
  435. func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeader() {
  436. s.config().EnableClientHints = true
  437. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  438. headers := http.Header{"Width": []string{"100"}}
  439. o, _, err := s.parser().ParsePath(path, headers)
  440. s.Require().NoError(err)
  441. s.Require().Equal(100, o.GetInt(keys.Width, 0))
  442. }
  443. func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderDisabled() {
  444. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  445. headers := http.Header{"Width": []string{"100"}}
  446. o, _, err := s.parser().ParsePath(path, headers)
  447. s.Require().NoError(err)
  448. s.Require().Equal(0, o.GetInt(keys.Width, 0))
  449. }
  450. func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderRedefine() {
  451. s.config().EnableClientHints = true
  452. path := "/width:150/plain/http://images.dev/lorem/ipsum.jpg@png"
  453. headers := http.Header{"Width": []string{"100"}}
  454. o, _, err := s.parser().ParsePath(path, headers)
  455. s.Require().NoError(err)
  456. s.Require().Equal(150, o.GetInt(keys.Width, 0))
  457. }
  458. func (s *ProcessingOptionsTestSuite) TestParsePathDprHeader() {
  459. s.config().EnableClientHints = true
  460. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  461. headers := http.Header{"Dpr": []string{"2"}}
  462. o, _, err := s.parser().ParsePath(path, headers)
  463. s.Require().NoError(err)
  464. s.Require().InDelta(2.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
  465. }
  466. func (s *ProcessingOptionsTestSuite) TestParsePathDprHeaderDisabled() {
  467. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  468. headers := http.Header{"Dpr": []string{"2"}}
  469. o, _, err := s.parser().ParsePath(path, headers)
  470. s.Require().NoError(err)
  471. s.Require().InDelta(1.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
  472. }
  473. func (s *ProcessingOptionsTestSuite) TestParsePathWidthAndDprHeaderCombined() {
  474. s.config().EnableClientHints = true
  475. path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
  476. headers := http.Header{
  477. "Width": []string{"100"},
  478. "Dpr": []string{"2"},
  479. }
  480. o, _, err := s.parser().ParsePath(path, headers)
  481. s.Require().NoError(err)
  482. s.Require().Equal(50, o.GetInt(keys.Width, 0))
  483. s.Require().InDelta(2.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
  484. }
  485. func (s *ProcessingOptionsTestSuite) TestParseSkipProcessing() {
  486. path := "/skp:jpg:png/plain/http://images.dev/lorem/ipsum.jpg"
  487. o, _, err := s.parser().ParsePath(path, make(http.Header))
  488. s.Require().NoError(err)
  489. s.Require().ElementsMatch(
  490. []imagetype.Type{imagetype.JPEG, imagetype.PNG},
  491. options.Get(o, keys.SkipProcessing, []imagetype.Type(nil)),
  492. )
  493. }
  494. func (s *ProcessingOptionsTestSuite) TestParseSkipProcessingInvalid() {
  495. path := "/skp:jpg:png:bad_format/plain/http://images.dev/lorem/ipsum.jpg"
  496. _, _, err := s.parser().ParsePath(path, make(http.Header))
  497. s.Require().Error(err)
  498. s.Require().Equal("Invalid image format in skip_processing: bad_format", err.Error())
  499. }
  500. func (s *ProcessingOptionsTestSuite) TestParseExpires() {
  501. path := "/exp:32503669200/plain/http://images.dev/lorem/ipsum.jpg"
  502. o, _, err := s.parser().ParsePath(path, make(http.Header))
  503. s.Require().NoError(err)
  504. s.Require().Equal(time.Unix(32503669200, 0), o.GetTime(keys.Expires))
  505. }
  506. func (s *ProcessingOptionsTestSuite) TestParseExpiresExpired() {
  507. path := "/exp:1609448400/plain/http://images.dev/lorem/ipsum.jpg"
  508. _, _, err := s.parser().ParsePath(path, make(http.Header))
  509. s.Require().Error(err, "Expired URL")
  510. }
  511. func (s *ProcessingOptionsTestSuite) TestParsePathOnlyPresets() {
  512. s.config().OnlyPresets = true
  513. s.config().Presets = []string{
  514. "test1=blur:0.2",
  515. "test2=quality:50",
  516. }
  517. originURL := "http://images.dev/lorem/ipsum.jpg"
  518. path := "/test1:test2/plain/" + originURL + "@png"
  519. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  520. s.Require().NoError(err)
  521. s.Require().InDelta(0.2, o.GetFloat(keys.Blur, 0.0), 0.0001)
  522. s.Require().Equal(50, o.GetInt(keys.Quality, 0))
  523. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  524. s.Require().Equal(originURL, imageURL)
  525. }
  526. func (s *ProcessingOptionsTestSuite) TestParseBase64URLOnlyPresets() {
  527. s.config().OnlyPresets = true
  528. s.config().Presets = []string{
  529. "test1=blur:0.2",
  530. "test2=quality:50",
  531. }
  532. originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
  533. path := fmt.Sprintf("/test1:test2/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  534. o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
  535. s.Require().NoError(err)
  536. s.Require().InDelta(0.2, o.GetFloat(keys.Blur, 0.0), 0.0001)
  537. s.Require().Equal(50, o.GetInt(keys.Quality, 0))
  538. s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
  539. s.Require().Equal(originURL, imageURL)
  540. }
  541. func (s *ProcessingOptionsTestSuite) TestParseAllowedOptions() {
  542. originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
  543. testCases := []struct {
  544. options string
  545. expectedError string
  546. }{
  547. {options: "w:100/h:200", expectedError: ""},
  548. {options: "w:100/h:200/blur:10", expectedError: "Forbidden processing option blur"},
  549. {options: "w:100/h:200/pr:test1", expectedError: ""},
  550. {options: "w:100/h:200/pr:test1/blur:10", expectedError: "Forbidden processing option blur"},
  551. }
  552. for _, tc := range testCases {
  553. s.Run(strings.ReplaceAll(tc.options, "/", "_"), func() {
  554. s.config().AllowedProcessingOptions = []string{"w", "h", "pr"}
  555. s.config().Presets = []string{
  556. "test1=blur:0.2",
  557. }
  558. path := fmt.Sprintf("/%s/%s.png", tc.options, base64.RawURLEncoding.EncodeToString([]byte(originURL)))
  559. _, _, err := s.parser().ParsePath(path, make(http.Header))
  560. if len(tc.expectedError) > 0 {
  561. s.Require().Error(err)
  562. s.Require().Equal(tc.expectedError, err.Error())
  563. } else {
  564. s.Require().NoError(err)
  565. }
  566. })
  567. }
  568. }
  569. // func (s *ProcessingOptionsTestSuite) TestProcessingOptionsClone() {
  570. // now := time.Now()
  571. // // Create Options using parser
  572. // original := s.parser().NewProcessingOptions()
  573. // original.SkipProcessingFormats = []imagetype.Type{
  574. // imagetype.PNG, imagetype.JPEG,
  575. // }
  576. // original.UsedPresets = []string{"preset1", "preset2"}
  577. // original.Expires = &now
  578. // // Clone the original
  579. // cloned := original.clone()
  580. // testutil.EqualButNotSame(s.T(), original, cloned)
  581. // }
  582. func TestProcessingOptions(t *testing.T) {
  583. suite.Run(t, new(ProcessingOptionsTestSuite))
  584. }