processing_handler_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. package integration
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "os"
  9. "regexp"
  10. "testing"
  11. "time"
  12. "github.com/imgproxy/imgproxy/v3"
  13. "github.com/imgproxy/imgproxy/v3/config"
  14. "github.com/imgproxy/imgproxy/v3/config/configurators"
  15. "github.com/imgproxy/imgproxy/v3/fetcher"
  16. "github.com/imgproxy/imgproxy/v3/httpheaders"
  17. "github.com/imgproxy/imgproxy/v3/imagedata"
  18. "github.com/imgproxy/imgproxy/v3/imagetype"
  19. "github.com/imgproxy/imgproxy/v3/svg"
  20. "github.com/imgproxy/imgproxy/v3/testutil"
  21. "github.com/imgproxy/imgproxy/v3/vips"
  22. "github.com/sirupsen/logrus"
  23. "github.com/stretchr/testify/suite"
  24. )
  25. // ProcessingHandlerTestSuite is a test suite for testing image processing handler
  26. type ProcessingHandlerTestSuite struct {
  27. Suite
  28. testData *testutil.TestDataProvider
  29. // NOTE: lazy obj is required here because in the specific tests we sometimes
  30. // change the config values in config.go. Config instantiation should
  31. // happen afterwards. It is done via lazy obj. When all config values will be moved
  32. // to imgproxy.Config struct, this can be removed.
  33. config testutil.LazyObj[*imgproxy.Config]
  34. }
  35. func (s *ProcessingHandlerTestSuite) SetupSuite() {
  36. // Silence all the logs
  37. logrus.SetOutput(io.Discard)
  38. // Initialize test data provider (local test files)
  39. s.testData = testutil.NewTestDataProvider(s.T())
  40. }
  41. func (s *ProcessingHandlerTestSuite) TearDownSuite() {
  42. logrus.SetOutput(os.Stdout)
  43. }
  44. // setupObjs initializes lazy objects
  45. func (s *ProcessingHandlerTestSuite) setupObjs() {
  46. s.config = testutil.NewLazyObj(s.T(), func() (*imgproxy.Config, error) {
  47. c, err := imgproxy.LoadConfigFromEnv(nil)
  48. s.Require().NoError(err)
  49. c.Fetcher.Transport.Local.Root = s.testData.Root()
  50. c.Fetcher.Transport.HTTP.ClientKeepAliveTimeout = 0
  51. return c, nil
  52. })
  53. }
  54. func (s *ProcessingHandlerTestSuite) SetupTest() {
  55. config.Reset() // We reset config only at the start of each test
  56. // NOTE: This must be moved to security config
  57. config.AllowLoopbackSourceAddresses = true
  58. // NOTE: end note
  59. s.setupObjs()
  60. }
  61. func (s *ProcessingHandlerTestSuite) SetupSubTest() {
  62. // We use t.Run() a lot, so we need to reset lazy objects at the beginning of each subtest
  63. s.setupObjs()
  64. }
  65. // GET performs a GET request to the imageproxy real server
  66. // NOTE: Do not forget to move this to Suite in case of need in other future test suites
  67. func (s *ProcessingHandlerTestSuite) GET(path string, header ...http.Header) *http.Response {
  68. // In this test we start the imgproxy server instance per request
  69. addr, stopServer := s.StartImgproxy(s.config())
  70. defer stopServer()
  71. url := fmt.Sprintf("http://%s%s", addr.String(), path)
  72. // Perform GET request to an url
  73. req, _ := http.NewRequest("GET", url, nil)
  74. for h := range header {
  75. for k, v := range header[h] {
  76. req.Header.Set(k, v[0]) // only first value will go to the request
  77. }
  78. }
  79. // Do the request
  80. resp, err := http.DefaultClient.Do(req)
  81. s.Require().NoError(err)
  82. // Read the entire body into memory and replace the original body with memory reader
  83. // to avoid the defer
  84. bodyBytes, err := io.ReadAll(resp.Body)
  85. s.Require().NoError(err)
  86. resp.Body.Close()
  87. resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
  88. return resp
  89. }
  90. func (s *ProcessingHandlerTestSuite) TestSignatureValidationFailure() {
  91. config.Keys = [][]byte{[]byte("test-key")}
  92. config.Salts = [][]byte{[]byte("test-salt")}
  93. tt := []struct {
  94. name string
  95. url string
  96. statusCode int
  97. }{
  98. {
  99. name: "NoSignature",
  100. url: "/unsafe/rs:fill:4:4/plain/local:///test1.png",
  101. statusCode: http.StatusForbidden,
  102. },
  103. {
  104. name: "BadSignature",
  105. url: "/bad-signature/rs:fill:4:4/plain/local:///test1.png",
  106. statusCode: http.StatusForbidden,
  107. },
  108. {
  109. name: "ValidSignature",
  110. url: "/My9d3xq_PYpVHsPrCyww0Kh1w5KZeZhIlWhsa4az1TI/rs:fill:4:4/plain/local:///test1.png",
  111. statusCode: http.StatusOK,
  112. },
  113. }
  114. for _, tc := range tt {
  115. s.Run(tc.name, func() {
  116. res := s.GET(tc.url)
  117. s.Require().Equal(tc.statusCode, res.StatusCode)
  118. })
  119. }
  120. }
  121. func (s *ProcessingHandlerTestSuite) TestSourceValidation() {
  122. imagedata.RedirectAllRequestsTo("local:///test1.png")
  123. defer imagedata.StopRedirectingRequests()
  124. tt := []struct {
  125. name string
  126. allowedSources []string
  127. requestPath string
  128. expectedError bool
  129. }{
  130. {
  131. name: "match http URL without wildcard",
  132. allowedSources: []string{"local://", "http://images.dev/"},
  133. requestPath: "/unsafe/plain/http://images.dev/lorem/ipsum.jpg",
  134. },
  135. {
  136. name: "match http URL with wildcard in hostname single level",
  137. allowedSources: []string{"local://", "http://*.mycdn.dev/"},
  138. requestPath: "/unsafe/plain/http://a-1.mycdn.dev/lorem/ipsum.jpg",
  139. },
  140. {
  141. name: "match http URL with wildcard in hostname multiple levels",
  142. allowedSources: []string{"local://", "http://*.mycdn.dev/"},
  143. requestPath: "/unsafe/plain/http://a-1.b-2.mycdn.dev/lorem/ipsum.jpg",
  144. },
  145. {
  146. name: "no match s3 URL with allowed local and http URLs",
  147. allowedSources: []string{"local://", "http://images.dev/"},
  148. requestPath: "/unsafe/plain/s3://images/lorem/ipsum.jpg",
  149. expectedError: true,
  150. },
  151. {
  152. name: "no match http URL with wildcard in hostname including slash",
  153. allowedSources: []string{"local://", "http://*.mycdn.dev/"},
  154. requestPath: "/unsafe/plain/http://other.dev/.mycdn.dev/lorem/ipsum.jpg",
  155. expectedError: true,
  156. },
  157. }
  158. for _, tc := range tt {
  159. s.Run(tc.name, func() {
  160. config.AllowedSources = make([]*regexp.Regexp, len(tc.allowedSources))
  161. for i, pattern := range tc.allowedSources {
  162. config.AllowedSources[i] = configurators.RegexpFromPattern(pattern)
  163. }
  164. res := s.GET(tc.requestPath)
  165. if tc.expectedError {
  166. s.Require().Equal(http.StatusNotFound, res.StatusCode)
  167. } else {
  168. s.Require().Equal(http.StatusOK, res.StatusCode)
  169. }
  170. })
  171. }
  172. }
  173. func (s *ProcessingHandlerTestSuite) TestSourceNetworkValidation() {
  174. data := s.testData.Read("test1.png")
  175. server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  176. rw.WriteHeader(200)
  177. rw.Write(data)
  178. }))
  179. defer server.Close()
  180. url := fmt.Sprintf("/unsafe/rs:fill:4:4/plain/%s/test1.png", server.URL)
  181. // We wrap this in a subtest to reset s.router()
  182. s.Run("AllowLoopbackSourceAddressesTrue", func() {
  183. config.AllowLoopbackSourceAddresses = true
  184. res := s.GET(url)
  185. s.Require().Equal(http.StatusOK, res.StatusCode)
  186. })
  187. s.Run("AllowLoopbackSourceAddressesFalse", func() {
  188. config.AllowLoopbackSourceAddresses = false
  189. res := s.GET(url)
  190. s.Require().Equal(http.StatusNotFound, res.StatusCode)
  191. })
  192. }
  193. func (s *ProcessingHandlerTestSuite) TestSourceFormatNotSupported() {
  194. vips.DisableLoadSupport(imagetype.PNG)
  195. defer vips.ResetLoadSupport()
  196. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  197. s.Require().Equal(http.StatusUnprocessableEntity, res.StatusCode)
  198. }
  199. func (s *ProcessingHandlerTestSuite) TestResultingFormatNotSupported() {
  200. vips.DisableSaveSupport(imagetype.PNG)
  201. defer vips.ResetSaveSupport()
  202. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png@png")
  203. s.Require().Equal(http.StatusUnprocessableEntity, res.StatusCode)
  204. }
  205. func (s *ProcessingHandlerTestSuite) TestSkipProcessingConfig() {
  206. config.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
  207. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  208. s.Require().Equal(http.StatusOK, res.StatusCode)
  209. s.Require().True(s.testData.FileEqualsToReader("test1.png", res.Body))
  210. }
  211. func (s *ProcessingHandlerTestSuite) TestSkipProcessingPO() {
  212. res := s.GET("/unsafe/rs:fill:4:4/skp:png/plain/local:///test1.png")
  213. s.Require().Equal(http.StatusOK, res.StatusCode)
  214. s.Require().True(s.testData.FileEqualsToReader("test1.png", res.Body))
  215. }
  216. func (s *ProcessingHandlerTestSuite) TestSkipProcessingSameFormat() {
  217. config.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
  218. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png@png")
  219. s.Require().Equal(http.StatusOK, res.StatusCode)
  220. s.Require().True(s.testData.FileEqualsToReader("test1.png", res.Body))
  221. }
  222. func (s *ProcessingHandlerTestSuite) TestSkipProcessingDifferentFormat() {
  223. config.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
  224. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png@jpg")
  225. s.Require().Equal(http.StatusOK, res.StatusCode)
  226. s.Require().False(s.testData.FileEqualsToReader("test1.png", res.Body))
  227. }
  228. func (s *ProcessingHandlerTestSuite) TestSkipProcessingSVG() {
  229. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.svg")
  230. s.Require().Equal(http.StatusOK, res.StatusCode)
  231. c := fetcher.NewDefaultConfig()
  232. f, err := fetcher.New(&c)
  233. s.Require().NoError(err)
  234. idf := imagedata.NewFactory(f)
  235. data, err := idf.NewFromBytes(s.testData.Read("test1.svg"))
  236. s.Require().NoError(err)
  237. expected, err := svg.Sanitize(data)
  238. s.Require().NoError(err)
  239. s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  240. }
  241. func (s *ProcessingHandlerTestSuite) TestNotSkipProcessingSVGToJPG() {
  242. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.svg@jpg")
  243. s.Require().Equal(http.StatusOK, res.StatusCode)
  244. s.Require().False(s.testData.FileEqualsToReader("test1.svg", res.Body))
  245. }
  246. func (s *ProcessingHandlerTestSuite) TestErrorSavingToSVG() {
  247. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png@svg")
  248. s.Require().Equal(http.StatusUnprocessableEntity, res.StatusCode)
  249. }
  250. func (s *ProcessingHandlerTestSuite) TestCacheControlPassthroughCacheControl() {
  251. s.config().HeaderWriter.CacheControlPassthrough = true
  252. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  253. rw.Header().Set(httpheaders.CacheControl, "max-age=1234, public")
  254. rw.Header().Set(httpheaders.Expires, time.Now().Add(time.Hour).UTC().Format(http.TimeFormat))
  255. rw.WriteHeader(200)
  256. rw.Write(s.testData.Read("test1.png"))
  257. }))
  258. defer ts.Close()
  259. res := s.GET("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  260. s.Require().Equal(http.StatusOK, res.StatusCode)
  261. s.Require().Equal("max-age=1234, public", res.Header.Get(httpheaders.CacheControl))
  262. s.Require().Empty(res.Header.Get(httpheaders.Expires))
  263. }
  264. func (s *ProcessingHandlerTestSuite) TestCacheControlPassthroughExpires() {
  265. config.CacheControlPassthrough = true
  266. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  267. rw.Header().Set(httpheaders.Expires, time.Now().Add(1239*time.Second).UTC().Format(http.TimeFormat))
  268. rw.WriteHeader(200)
  269. rw.Write(s.testData.Read("test1.png"))
  270. }))
  271. defer ts.Close()
  272. res := s.GET("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  273. // Use regex to allow some delay
  274. s.Require().Regexp("max-age=123[0-9], public", res.Header.Get(httpheaders.CacheControl))
  275. s.Require().Empty(res.Header.Get(httpheaders.Expires))
  276. }
  277. func (s *ProcessingHandlerTestSuite) TestCacheControlPassthroughDisabled() {
  278. config.CacheControlPassthrough = false
  279. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  280. rw.Header().Set(httpheaders.CacheControl, "max-age=1234, public")
  281. rw.Header().Set(httpheaders.Expires, time.Now().Add(time.Hour).UTC().Format(http.TimeFormat))
  282. rw.WriteHeader(200)
  283. rw.Write(s.testData.Read("test1.png"))
  284. }))
  285. defer ts.Close()
  286. res := s.GET("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  287. s.Require().NotEqual("max-age=1234, public", res.Header.Get(httpheaders.CacheControl))
  288. s.Require().Empty(res.Header.Get(httpheaders.Expires))
  289. }
  290. func (s *ProcessingHandlerTestSuite) TestETagDisabled() {
  291. config.ETagEnabled = false
  292. res := s.GET("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  293. s.Require().Equal(200, res.StatusCode)
  294. s.Require().Empty(res.Header.Get(httpheaders.Etag))
  295. }
  296. func (s *ProcessingHandlerTestSuite) TestETagDataMatch() {
  297. config.ETagEnabled = true
  298. etag := `"loremipsumdolor"`
  299. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  300. s.NotEmpty(r.Header.Get(httpheaders.IfNoneMatch))
  301. rw.Header().Set(httpheaders.Etag, etag)
  302. rw.WriteHeader(http.StatusNotModified)
  303. }))
  304. defer ts.Close()
  305. header := make(http.Header)
  306. header.Set(httpheaders.IfNoneMatch, etag)
  307. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  308. s.Require().Equal(304, res.StatusCode)
  309. s.Require().Equal(etag, res.Header.Get(httpheaders.Etag))
  310. }
  311. func (s *ProcessingHandlerTestSuite) TestLastModifiedEnabled() {
  312. config.LastModifiedEnabled = true
  313. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  314. rw.Header().Set(httpheaders.LastModified, "Wed, 21 Oct 2015 07:28:00 GMT")
  315. rw.WriteHeader(200)
  316. rw.Write(s.testData.Read("test1.png"))
  317. }))
  318. defer ts.Close()
  319. res := s.GET("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  320. s.Require().Equal("Wed, 21 Oct 2015 07:28:00 GMT", res.Header.Get(httpheaders.LastModified))
  321. }
  322. func (s *ProcessingHandlerTestSuite) TestLastModifiedDisabled() {
  323. config.LastModifiedEnabled = false
  324. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  325. rw.Header().Set(httpheaders.LastModified, "Wed, 21 Oct 2015 07:28:00 GMT")
  326. rw.WriteHeader(200)
  327. rw.Write(s.testData.Read("test1.png"))
  328. }))
  329. defer ts.Close()
  330. res := s.GET("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  331. s.Require().Empty(res.Header.Get(httpheaders.LastModified))
  332. }
  333. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqExactMatchLastModifiedDisabled() {
  334. config.LastModifiedEnabled = false
  335. data := s.testData.Read("test1.png")
  336. lastModified := "Wed, 21 Oct 2015 07:28:00 GMT"
  337. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  338. modifiedSince := r.Header.Get(httpheaders.IfModifiedSince)
  339. s.Empty(modifiedSince)
  340. rw.WriteHeader(200)
  341. rw.Write(data)
  342. }))
  343. defer ts.Close()
  344. header := make(http.Header)
  345. header.Set(httpheaders.IfModifiedSince, lastModified)
  346. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  347. s.Require().Equal(200, res.StatusCode)
  348. }
  349. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqExactMatchLastModifiedEnabled() {
  350. config.LastModifiedEnabled = true
  351. lastModified := "Wed, 21 Oct 2015 07:28:00 GMT"
  352. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  353. modifiedSince := r.Header.Get(httpheaders.IfModifiedSince)
  354. s.Equal(lastModified, modifiedSince)
  355. rw.WriteHeader(304)
  356. }))
  357. defer ts.Close()
  358. header := make(http.Header)
  359. header.Set(httpheaders.IfModifiedSince, lastModified)
  360. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  361. s.Require().Equal(304, res.StatusCode)
  362. }
  363. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareMoreRecentLastModifiedDisabled() {
  364. data := s.testData.Read("test1.png")
  365. config.LastModifiedEnabled = false
  366. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  367. modifiedSince := r.Header.Get(httpheaders.IfModifiedSince)
  368. s.Empty(modifiedSince)
  369. rw.WriteHeader(200)
  370. rw.Write(data)
  371. }))
  372. defer ts.Close()
  373. recentTimestamp := "Thu, 25 Feb 2021 01:45:00 GMT"
  374. header := make(http.Header)
  375. header.Set(httpheaders.IfModifiedSince, recentTimestamp)
  376. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  377. s.Require().Equal(200, res.StatusCode)
  378. }
  379. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareMoreRecentLastModifiedEnabled() {
  380. config.LastModifiedEnabled = true
  381. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  382. fileLastModified, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
  383. modifiedSince := r.Header.Get(httpheaders.IfModifiedSince)
  384. parsedModifiedSince, err := time.Parse(http.TimeFormat, modifiedSince)
  385. s.NoError(err)
  386. s.True(fileLastModified.Before(parsedModifiedSince))
  387. rw.WriteHeader(304)
  388. }))
  389. defer ts.Close()
  390. recentTimestamp := "Thu, 25 Feb 2021 01:45:00 GMT"
  391. header := make(http.Header)
  392. header.Set(httpheaders.IfModifiedSince, recentTimestamp)
  393. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  394. s.Require().Equal(304, res.StatusCode)
  395. }
  396. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifiedDisabled() {
  397. s.config().ProcessingHandler.LastModifiedEnabled = false
  398. data := s.testData.Read("test1.png")
  399. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  400. modifiedSince := r.Header.Get(httpheaders.IfModifiedSince)
  401. s.Empty(modifiedSince)
  402. rw.WriteHeader(200)
  403. rw.Write(data)
  404. }))
  405. defer ts.Close()
  406. oldTimestamp := "Tue, 01 Oct 2013 17:31:00 GMT"
  407. header := make(http.Header)
  408. header.Set(httpheaders.IfModifiedSince, oldTimestamp)
  409. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  410. s.Require().Equal(200, res.StatusCode)
  411. }
  412. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifiedEnabled() {
  413. config.LastModifiedEnabled = true
  414. data := s.testData.Read("test1.png")
  415. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  416. fileLastModified, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
  417. modifiedSince := r.Header.Get(httpheaders.IfModifiedSince)
  418. parsedModifiedSince, err := time.Parse(http.TimeFormat, modifiedSince)
  419. s.NoError(err)
  420. s.True(fileLastModified.After(parsedModifiedSince))
  421. rw.WriteHeader(200)
  422. rw.Write(data)
  423. }))
  424. defer ts.Close()
  425. oldTimestamp := "Tue, 01 Oct 2013 17:31:00 GMT"
  426. header := make(http.Header)
  427. header.Set(httpheaders.IfModifiedSince, oldTimestamp)
  428. res := s.GET(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  429. s.Require().Equal(200, res.StatusCode)
  430. }
  431. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvg() {
  432. config.AlwaysRasterizeSvg = true
  433. res := s.GET("/unsafe/rs:fill:40:40/plain/local:///test1.svg")
  434. s.Require().Equal(200, res.StatusCode)
  435. s.Require().Equal("image/png", res.Header.Get(httpheaders.ContentType))
  436. }
  437. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithEnforceAvif() {
  438. config.AlwaysRasterizeSvg = true
  439. config.EnforceWebp = true
  440. res := s.GET("/unsafe/plain/local:///test1.svg", http.Header{"Accept": []string{"image/webp"}})
  441. s.Require().Equal(200, res.StatusCode)
  442. s.Require().Equal("image/webp", res.Header.Get(httpheaders.ContentType))
  443. }
  444. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgDisabled() {
  445. config.AlwaysRasterizeSvg = false
  446. config.EnforceWebp = true
  447. res := s.GET("/unsafe/plain/local:///test1.svg")
  448. s.Require().Equal(200, res.StatusCode)
  449. s.Require().Equal("image/svg+xml", res.Header.Get(httpheaders.ContentType))
  450. }
  451. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithFormat() {
  452. config.AlwaysRasterizeSvg = true
  453. config.SkipProcessingFormats = []imagetype.Type{imagetype.SVG}
  454. res := s.GET("/unsafe/plain/local:///test1.svg@svg")
  455. s.Require().Equal(200, res.StatusCode)
  456. s.Require().Equal("image/svg+xml", res.Header.Get(httpheaders.ContentType))
  457. }
  458. func (s *ProcessingHandlerTestSuite) TestMaxSrcFileSizeGlobal() {
  459. config.MaxSrcFileSize = 1
  460. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  461. rw.WriteHeader(200)
  462. rw.Write(s.testData.Read("test1.png"))
  463. }))
  464. defer ts.Close()
  465. res := s.GET("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  466. s.Require().Equal(422, res.StatusCode)
  467. }
  468. func TestProcessingHandler(t *testing.T) {
  469. suite.Run(t, new(ProcessingHandlerTestSuite))
  470. }