processing_handler_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. "os"
  8. "path/filepath"
  9. "regexp"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/sirupsen/logrus"
  14. "github.com/stretchr/testify/suite"
  15. "github.com/imgproxy/imgproxy/v3/config"
  16. "github.com/imgproxy/imgproxy/v3/config/configurators"
  17. "github.com/imgproxy/imgproxy/v3/etag"
  18. "github.com/imgproxy/imgproxy/v3/httpheaders"
  19. "github.com/imgproxy/imgproxy/v3/imagedata"
  20. "github.com/imgproxy/imgproxy/v3/imagetype"
  21. "github.com/imgproxy/imgproxy/v3/options"
  22. "github.com/imgproxy/imgproxy/v3/server"
  23. "github.com/imgproxy/imgproxy/v3/svg"
  24. "github.com/imgproxy/imgproxy/v3/testutil"
  25. "github.com/imgproxy/imgproxy/v3/vips"
  26. )
  27. type ProcessingHandlerTestSuite struct {
  28. suite.Suite
  29. router *server.Router
  30. }
  31. func (s *ProcessingHandlerTestSuite) SetupSuite() {
  32. config.Reset()
  33. wd, err := os.Getwd()
  34. s.Require().NoError(err)
  35. s.T().Setenv("IMGPROXY_LOCAL_FILESYSTEM_ROOT", filepath.Join(wd, "/testdata"))
  36. s.T().Setenv("IMGPROXY_CLIENT_KEEP_ALIVE_TIMEOUT", "0")
  37. err = initialize()
  38. s.Require().NoError(err)
  39. logrus.SetOutput(io.Discard)
  40. r := server.NewRouter(server.NewConfigFromEnv())
  41. s.router = buildRouter(r)
  42. }
  43. func (s *ProcessingHandlerTestSuite) TeardownSuite() {
  44. shutdown()
  45. logrus.SetOutput(os.Stdout)
  46. }
  47. func (s *ProcessingHandlerTestSuite) SetupTest() {
  48. // We don't need config.LocalFileSystemRoot anymore as it is used
  49. // only during initialization
  50. config.Reset()
  51. config.AllowLoopbackSourceAddresses = true
  52. }
  53. func (s *ProcessingHandlerTestSuite) send(path string, header ...http.Header) *httptest.ResponseRecorder {
  54. req := httptest.NewRequest(http.MethodGet, path, nil)
  55. rw := httptest.NewRecorder()
  56. if len(header) > 0 {
  57. req.Header = header[0]
  58. }
  59. s.router.ServeHTTP(rw, req)
  60. return rw
  61. }
  62. func (s *ProcessingHandlerTestSuite) readTestFile(name string) []byte {
  63. wd, err := os.Getwd()
  64. s.Require().NoError(err)
  65. data, err := os.ReadFile(filepath.Join(wd, "testdata", name))
  66. s.Require().NoError(err)
  67. return data
  68. }
  69. func (s *ProcessingHandlerTestSuite) readTestImageData(name string) imagedata.ImageData {
  70. wd, err := os.Getwd()
  71. s.Require().NoError(err)
  72. data, err := os.ReadFile(filepath.Join(wd, "testdata", name))
  73. s.Require().NoError(err)
  74. imgdata, err := imagedata.NewFromBytes(data)
  75. s.Require().NoError(err)
  76. return imgdata
  77. }
  78. func (s *ProcessingHandlerTestSuite) readImageData(imgdata imagedata.ImageData) []byte {
  79. data, err := io.ReadAll(imgdata.Reader())
  80. s.Require().NoError(err)
  81. return data
  82. }
  83. func (s *ProcessingHandlerTestSuite) sampleETagData(imgETag string) (string, imagedata.ImageData, http.Header, string) {
  84. poStr := "rs:fill:4:4"
  85. po := options.NewProcessingOptions()
  86. po.ResizingType = options.ResizeFill
  87. po.Width = 4
  88. po.Height = 4
  89. imgdata := s.readTestImageData("test1.png")
  90. headers := make(http.Header)
  91. if len(imgETag) != 0 {
  92. headers.Set(httpheaders.Etag, imgETag)
  93. }
  94. var h etag.Handler
  95. h.SetActualProcessingOptions(po)
  96. h.SetActualImageData(imgdata, headers)
  97. return poStr, imgdata, headers, h.GenerateActualETag()
  98. }
  99. func (s *ProcessingHandlerTestSuite) TestRequest() {
  100. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  101. res := rw.Result()
  102. s.Require().Equal(200, res.StatusCode)
  103. s.Require().Equal("image/png", res.Header.Get("Content-Type"))
  104. format, err := imagetype.Detect(res.Body)
  105. s.Require().NoError(err)
  106. s.Require().Equal(imagetype.PNG, format)
  107. }
  108. func (s *ProcessingHandlerTestSuite) TestSignatureValidationFailure() {
  109. config.Keys = [][]byte{[]byte("test-key")}
  110. config.Salts = [][]byte{[]byte("test-salt")}
  111. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  112. res := rw.Result()
  113. s.Require().Equal(403, res.StatusCode)
  114. }
  115. func (s *ProcessingHandlerTestSuite) TestSignatureValidationSuccess() {
  116. config.Keys = [][]byte{[]byte("test-key")}
  117. config.Salts = [][]byte{[]byte("test-salt")}
  118. rw := s.send("/My9d3xq_PYpVHsPrCyww0Kh1w5KZeZhIlWhsa4az1TI/rs:fill:4:4/plain/local:///test1.png")
  119. res := rw.Result()
  120. s.Require().Equal(200, res.StatusCode)
  121. }
  122. func (s *ProcessingHandlerTestSuite) TestSourceValidation() {
  123. imagedata.RedirectAllRequestsTo("local:///test1.png")
  124. defer imagedata.StopRedirectingRequests()
  125. tt := []struct {
  126. name string
  127. allowedSources []string
  128. requestPath string
  129. expectedError bool
  130. }{
  131. {
  132. name: "match http URL without wildcard",
  133. allowedSources: []string{"local://", "http://images.dev/"},
  134. requestPath: "/unsafe/plain/http://images.dev/lorem/ipsum.jpg",
  135. expectedError: false,
  136. },
  137. {
  138. name: "match http URL with wildcard in hostname single level",
  139. allowedSources: []string{"local://", "http://*.mycdn.dev/"},
  140. requestPath: "/unsafe/plain/http://a-1.mycdn.dev/lorem/ipsum.jpg",
  141. expectedError: false,
  142. },
  143. {
  144. name: "match http URL with wildcard in hostname multiple levels",
  145. allowedSources: []string{"local://", "http://*.mycdn.dev/"},
  146. requestPath: "/unsafe/plain/http://a-1.b-2.mycdn.dev/lorem/ipsum.jpg",
  147. expectedError: false,
  148. },
  149. {
  150. name: "no match s3 URL with allowed local and http URLs",
  151. allowedSources: []string{"local://", "http://images.dev/"},
  152. requestPath: "/unsafe/plain/s3://images/lorem/ipsum.jpg",
  153. expectedError: true,
  154. },
  155. {
  156. name: "no match http URL with wildcard in hostname including slash",
  157. allowedSources: []string{"local://", "http://*.mycdn.dev/"},
  158. requestPath: "/unsafe/plain/http://other.dev/.mycdn.dev/lorem/ipsum.jpg",
  159. expectedError: true,
  160. },
  161. }
  162. for _, tc := range tt {
  163. s.Run(tc.name, func() {
  164. exps := make([]*regexp.Regexp, len(tc.allowedSources))
  165. for i, pattern := range tc.allowedSources {
  166. exps[i] = configurators.RegexpFromPattern(pattern)
  167. }
  168. config.AllowedSources = exps
  169. rw := s.send(tc.requestPath)
  170. res := rw.Result()
  171. if tc.expectedError {
  172. s.Require().Equal(404, res.StatusCode)
  173. } else {
  174. s.Require().Equal(200, res.StatusCode)
  175. }
  176. })
  177. }
  178. }
  179. func (s *ProcessingHandlerTestSuite) TestSourceNetworkValidation() {
  180. data := s.readTestFile("test1.png")
  181. server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  182. rw.WriteHeader(200)
  183. rw.Write(data)
  184. }))
  185. defer server.Close()
  186. var rw *httptest.ResponseRecorder
  187. u := fmt.Sprintf("/unsafe/rs:fill:4:4/plain/%s/test1.png", server.URL)
  188. rw = s.send(u)
  189. s.Require().Equal(200, rw.Result().StatusCode)
  190. config.AllowLoopbackSourceAddresses = false
  191. rw = s.send(u)
  192. s.Require().Equal(404, rw.Result().StatusCode)
  193. }
  194. func (s *ProcessingHandlerTestSuite) TestSourceFormatNotSupported() {
  195. vips.DisableLoadSupport(imagetype.PNG)
  196. defer vips.ResetLoadSupport()
  197. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  198. res := rw.Result()
  199. s.Require().Equal(422, res.StatusCode)
  200. }
  201. func (s *ProcessingHandlerTestSuite) TestResultingFormatNotSupported() {
  202. vips.DisableSaveSupport(imagetype.PNG)
  203. defer vips.ResetSaveSupport()
  204. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png@png")
  205. res := rw.Result()
  206. s.Require().Equal(422, res.StatusCode)
  207. }
  208. func (s *ProcessingHandlerTestSuite) TestSkipProcessingConfig() {
  209. config.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
  210. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  211. res := rw.Result()
  212. s.Require().Equal(200, res.StatusCode)
  213. expected := s.readTestImageData("test1.png")
  214. s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  215. }
  216. func (s *ProcessingHandlerTestSuite) TestSkipProcessingPO() {
  217. rw := s.send("/unsafe/rs:fill:4:4/skp:png/plain/local:///test1.png")
  218. res := rw.Result()
  219. s.Require().Equal(200, res.StatusCode)
  220. expected := s.readTestImageData("test1.png")
  221. s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  222. }
  223. func (s *ProcessingHandlerTestSuite) TestSkipProcessingSameFormat() {
  224. config.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
  225. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png@png")
  226. res := rw.Result()
  227. s.Require().Equal(200, res.StatusCode)
  228. expected := s.readTestImageData("test1.png")
  229. s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  230. }
  231. func (s *ProcessingHandlerTestSuite) TestSkipProcessingDifferentFormat() {
  232. config.SkipProcessingFormats = []imagetype.Type{imagetype.PNG}
  233. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png@jpg")
  234. res := rw.Result()
  235. s.Require().Equal(200, res.StatusCode)
  236. expected := s.readTestImageData("test1.png")
  237. s.Require().False(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  238. }
  239. func (s *ProcessingHandlerTestSuite) TestSkipProcessingSVG() {
  240. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.svg")
  241. res := rw.Result()
  242. s.Require().Equal(200, res.StatusCode)
  243. expected, err := svg.Sanitize(s.readTestImageData("test1.svg"))
  244. s.Require().NoError(err)
  245. s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  246. }
  247. func (s *ProcessingHandlerTestSuite) TestNotSkipProcessingSVGToJPG() {
  248. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.svg@jpg")
  249. res := rw.Result()
  250. s.Require().Equal(200, res.StatusCode)
  251. expected := s.readTestImageData("test1.svg")
  252. s.Require().False(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))
  253. }
  254. func (s *ProcessingHandlerTestSuite) TestErrorSavingToSVG() {
  255. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png@svg")
  256. res := rw.Result()
  257. s.Require().Equal(422, res.StatusCode)
  258. }
  259. func (s *ProcessingHandlerTestSuite) TestCacheControlPassthroughCacheControl() {
  260. config.CacheControlPassthrough = true
  261. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  262. rw.Header().Set("Cache-Control", "max-age=1234, public")
  263. rw.Header().Set("Expires", time.Now().Add(time.Hour).UTC().Format(http.TimeFormat))
  264. rw.WriteHeader(200)
  265. rw.Write(s.readTestFile("test1.png"))
  266. }))
  267. defer ts.Close()
  268. rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  269. res := rw.Result()
  270. s.Require().Equal("max-age=1234, public", res.Header.Get("Cache-Control"))
  271. s.Require().Empty(res.Header.Get("Expires"))
  272. }
  273. func (s *ProcessingHandlerTestSuite) TestCacheControlPassthroughExpires() {
  274. config.CacheControlPassthrough = true
  275. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  276. rw.Header().Set("Expires", time.Now().Add(1239*time.Second).UTC().Format(http.TimeFormat))
  277. rw.WriteHeader(200)
  278. rw.Write(s.readTestFile("test1.png"))
  279. }))
  280. defer ts.Close()
  281. rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  282. res := rw.Result()
  283. // Use regex to allow some delay
  284. s.Require().Regexp("max-age=123[0-9], public", res.Header.Get("Cache-Control"))
  285. s.Require().Empty(res.Header.Get("Expires"))
  286. }
  287. func (s *ProcessingHandlerTestSuite) TestCacheControlPassthroughDisabled() {
  288. config.CacheControlPassthrough = false
  289. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  290. rw.Header().Set("Cache-Control", "max-age=1234, public")
  291. rw.Header().Set("Expires", time.Now().Add(time.Hour).UTC().Format(http.TimeFormat))
  292. rw.WriteHeader(200)
  293. rw.Write(s.readTestFile("test1.png"))
  294. }))
  295. defer ts.Close()
  296. rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  297. res := rw.Result()
  298. s.Require().NotEqual("max-age=1234, public", res.Header.Get("Cache-Control"))
  299. s.Require().Empty(res.Header.Get("Expires"))
  300. }
  301. func (s *ProcessingHandlerTestSuite) TestETagDisabled() {
  302. config.ETagEnabled = false
  303. rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
  304. res := rw.Result()
  305. s.Require().Equal(200, res.StatusCode)
  306. s.Require().Empty(res.Header.Get("ETag"))
  307. }
  308. func (s *ProcessingHandlerTestSuite) TestETagReqNoIfNotModified() {
  309. config.ETagEnabled = true
  310. poStr, _, headers, etag := s.sampleETagData("loremipsumdolor")
  311. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  312. s.Empty(r.Header.Get("If-None-Match"))
  313. rw.Header().Set("ETag", headers.Get(httpheaders.Etag))
  314. rw.WriteHeader(200)
  315. rw.Write(s.readTestFile("test1.png"))
  316. }))
  317. defer ts.Close()
  318. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL))
  319. res := rw.Result()
  320. s.Require().Equal(200, res.StatusCode)
  321. s.Require().Equal(etag, res.Header.Get("ETag"))
  322. }
  323. func (s *ProcessingHandlerTestSuite) TestETagDataNoIfNotModified() {
  324. config.ETagEnabled = true
  325. poStr, imgdata, _, etag := s.sampleETagData("")
  326. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  327. s.Empty(r.Header.Get("If-None-Match"))
  328. rw.WriteHeader(200)
  329. rw.Write(s.readImageData(imgdata))
  330. }))
  331. defer ts.Close()
  332. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL))
  333. res := rw.Result()
  334. s.Require().Equal(200, res.StatusCode)
  335. s.Require().Equal(etag, res.Header.Get("ETag"))
  336. }
  337. func (s *ProcessingHandlerTestSuite) TestETagReqMatch() {
  338. config.ETagEnabled = true
  339. poStr, _, headers, etag := s.sampleETagData(`"loremipsumdolor"`)
  340. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  341. s.Equal(headers.Get(httpheaders.Etag), r.Header.Get(httpheaders.IfNoneMatch))
  342. rw.WriteHeader(304)
  343. }))
  344. defer ts.Close()
  345. header := make(http.Header)
  346. header.Set("If-None-Match", etag)
  347. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL), header)
  348. res := rw.Result()
  349. s.Require().Equal(304, res.StatusCode)
  350. s.Require().Equal(etag, res.Header.Get("ETag"))
  351. }
  352. func (s *ProcessingHandlerTestSuite) TestETagDataMatch() {
  353. config.ETagEnabled = true
  354. poStr, imgdata, _, etag := s.sampleETagData("")
  355. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  356. s.Empty(r.Header.Get("If-None-Match"))
  357. rw.WriteHeader(200)
  358. rw.Write(s.readImageData(imgdata))
  359. }))
  360. defer ts.Close()
  361. header := make(http.Header)
  362. header.Set("If-None-Match", etag)
  363. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL), header)
  364. res := rw.Result()
  365. s.Require().Equal(304, res.StatusCode)
  366. s.Require().Equal(etag, res.Header.Get("ETag"))
  367. }
  368. func (s *ProcessingHandlerTestSuite) TestETagReqNotMatch() {
  369. config.ETagEnabled = true
  370. poStr, imgdata, headers, actualETag := s.sampleETagData(`"loremipsumdolor"`)
  371. _, _, _, expectedETag := s.sampleETagData(`"loremipsum"`)
  372. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  373. s.Equal(`"loremipsum"`, r.Header.Get("If-None-Match"))
  374. rw.Header().Set("ETag", headers.Get(httpheaders.Etag))
  375. rw.WriteHeader(200)
  376. rw.Write(s.readImageData(imgdata))
  377. }))
  378. defer ts.Close()
  379. header := make(http.Header)
  380. header.Set("If-None-Match", expectedETag)
  381. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL), header)
  382. res := rw.Result()
  383. s.Require().Equal(200, res.StatusCode)
  384. s.Require().Equal(actualETag, res.Header.Get("ETag"))
  385. }
  386. func (s *ProcessingHandlerTestSuite) TestETagDataNotMatch() {
  387. config.ETagEnabled = true
  388. poStr, imgdata, _, actualETag := s.sampleETagData("")
  389. // Change the data hash
  390. expectedETag := actualETag[:strings.IndexByte(actualETag, '/')] + "/Dasdbefj"
  391. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  392. s.Empty(r.Header.Get("If-None-Match"))
  393. rw.WriteHeader(200)
  394. rw.Write(s.readImageData(imgdata))
  395. }))
  396. defer ts.Close()
  397. header := make(http.Header)
  398. header.Set("If-None-Match", expectedETag)
  399. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL), header)
  400. res := rw.Result()
  401. s.Require().Equal(200, res.StatusCode)
  402. s.Require().Equal(actualETag, res.Header.Get("ETag"))
  403. }
  404. func (s *ProcessingHandlerTestSuite) TestETagProcessingOptionsNotMatch() {
  405. config.ETagEnabled = true
  406. poStr, imgdata, headers, actualETag := s.sampleETagData("")
  407. // Change the processing options hash
  408. expectedETag := "abcdefj" + actualETag[strings.IndexByte(actualETag, '/'):]
  409. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  410. s.Empty(r.Header.Get("If-None-Match"))
  411. rw.Header().Set("ETag", headers.Get(httpheaders.Etag))
  412. rw.WriteHeader(200)
  413. rw.Write(s.readImageData(imgdata))
  414. }))
  415. defer ts.Close()
  416. header := make(http.Header)
  417. header.Set("If-None-Match", expectedETag)
  418. rw := s.send(fmt.Sprintf("/unsafe/%s/plain/%s", poStr, ts.URL), header)
  419. res := rw.Result()
  420. s.Require().Equal(200, res.StatusCode)
  421. s.Require().Equal(actualETag, res.Header.Get("ETag"))
  422. }
  423. func (s *ProcessingHandlerTestSuite) TestLastModifiedEnabled() {
  424. config.LastModifiedEnabled = true
  425. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  426. rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
  427. rw.WriteHeader(200)
  428. rw.Write(s.readTestFile("test1.png"))
  429. }))
  430. defer ts.Close()
  431. rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  432. res := rw.Result()
  433. s.Require().Equal("Wed, 21 Oct 2015 07:28:00 GMT", res.Header.Get("Last-Modified"))
  434. }
  435. func (s *ProcessingHandlerTestSuite) TestLastModifiedDisabled() {
  436. config.LastModifiedEnabled = false
  437. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  438. rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
  439. rw.WriteHeader(200)
  440. rw.Write(s.readTestFile("test1.png"))
  441. }))
  442. defer ts.Close()
  443. rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  444. res := rw.Result()
  445. s.Require().Empty(res.Header.Get("Last-Modified"))
  446. }
  447. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqExactMatchLastModifiedDisabled() {
  448. config.LastModifiedEnabled = false
  449. data := s.readTestFile("test1.png")
  450. lastModified := "Wed, 21 Oct 2015 07:28:00 GMT"
  451. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  452. modifiedSince := r.Header.Get("If-Modified-Since")
  453. s.Empty(modifiedSince)
  454. rw.WriteHeader(200)
  455. rw.Write(data)
  456. }))
  457. defer ts.Close()
  458. header := make(http.Header)
  459. header.Set("If-Modified-Since", lastModified)
  460. rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  461. res := rw.Result()
  462. s.Require().Equal(200, res.StatusCode)
  463. }
  464. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqExactMatchLastModifiedEnabled() {
  465. config.LastModifiedEnabled = true
  466. lastModified := "Wed, 21 Oct 2015 07:28:00 GMT"
  467. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  468. modifiedSince := r.Header.Get("If-Modified-Since")
  469. s.Equal(lastModified, modifiedSince)
  470. rw.WriteHeader(304)
  471. }))
  472. defer ts.Close()
  473. header := make(http.Header)
  474. header.Set("If-Modified-Since", lastModified)
  475. rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  476. res := rw.Result()
  477. s.Require().Equal(304, res.StatusCode)
  478. }
  479. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareMoreRecentLastModifiedDisabled() {
  480. data := s.readTestFile("test1.png")
  481. config.LastModifiedEnabled = false
  482. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  483. modifiedSince := r.Header.Get("If-Modified-Since")
  484. s.Empty(modifiedSince)
  485. rw.WriteHeader(200)
  486. rw.Write(data)
  487. }))
  488. defer ts.Close()
  489. recentTimestamp := "Thu, 25 Feb 2021 01:45:00 GMT"
  490. header := make(http.Header)
  491. header.Set("If-Modified-Since", recentTimestamp)
  492. rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  493. res := rw.Result()
  494. s.Require().Equal(200, res.StatusCode)
  495. }
  496. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareMoreRecentLastModifiedEnabled() {
  497. config.LastModifiedEnabled = true
  498. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  499. fileLastModified, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
  500. modifiedSince := r.Header.Get("If-Modified-Since")
  501. parsedModifiedSince, err := time.Parse(http.TimeFormat, modifiedSince)
  502. s.NoError(err)
  503. s.True(fileLastModified.Before(parsedModifiedSince))
  504. rw.WriteHeader(304)
  505. }))
  506. defer ts.Close()
  507. recentTimestamp := "Thu, 25 Feb 2021 01:45:00 GMT"
  508. header := make(http.Header)
  509. header.Set("If-Modified-Since", recentTimestamp)
  510. rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  511. res := rw.Result()
  512. s.Require().Equal(304, res.StatusCode)
  513. }
  514. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifiedDisabled() {
  515. config.LastModifiedEnabled = false
  516. data := s.readTestFile("test1.png")
  517. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  518. modifiedSince := r.Header.Get("If-Modified-Since")
  519. s.Empty(modifiedSince)
  520. rw.WriteHeader(200)
  521. rw.Write(data)
  522. }))
  523. defer ts.Close()
  524. oldTimestamp := "Tue, 01 Oct 2013 17:31:00 GMT"
  525. header := make(http.Header)
  526. header.Set("If-Modified-Since", oldTimestamp)
  527. rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  528. res := rw.Result()
  529. s.Require().Equal(200, res.StatusCode)
  530. }
  531. func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifiedEnabled() {
  532. config.LastModifiedEnabled = true
  533. data := s.readTestFile("test1.png")
  534. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  535. fileLastModified, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
  536. modifiedSince := r.Header.Get("If-Modified-Since")
  537. parsedModifiedSince, err := time.Parse(http.TimeFormat, modifiedSince)
  538. s.NoError(err)
  539. s.True(fileLastModified.After(parsedModifiedSince))
  540. rw.WriteHeader(200)
  541. rw.Write(data)
  542. }))
  543. defer ts.Close()
  544. oldTimestamp := "Tue, 01 Oct 2013 17:31:00 GMT"
  545. header := make(http.Header)
  546. header.Set("If-Modified-Since", oldTimestamp)
  547. rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
  548. res := rw.Result()
  549. s.Require().Equal(200, res.StatusCode)
  550. }
  551. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvg() {
  552. config.AlwaysRasterizeSvg = true
  553. rw := s.send("/unsafe/rs:fill:40:40/plain/local:///test1.svg")
  554. res := rw.Result()
  555. s.Require().Equal(200, res.StatusCode)
  556. s.Require().Equal("image/png", res.Header.Get("Content-Type"))
  557. }
  558. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithEnforceAvif() {
  559. config.AlwaysRasterizeSvg = true
  560. config.EnforceWebp = true
  561. rw := s.send("/unsafe/plain/local:///test1.svg", http.Header{"Accept": []string{"image/webp"}})
  562. res := rw.Result()
  563. s.Require().Equal(200, res.StatusCode)
  564. s.Require().Equal("image/webp", res.Header.Get("Content-Type"))
  565. }
  566. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgDisabled() {
  567. config.AlwaysRasterizeSvg = false
  568. config.EnforceWebp = true
  569. rw := s.send("/unsafe/plain/local:///test1.svg")
  570. res := rw.Result()
  571. s.Require().Equal(200, res.StatusCode)
  572. s.Require().Equal("image/svg+xml", res.Header.Get("Content-Type"))
  573. }
  574. func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithFormat() {
  575. config.AlwaysRasterizeSvg = true
  576. config.SkipProcessingFormats = []imagetype.Type{imagetype.SVG}
  577. rw := s.send("/unsafe/plain/local:///test1.svg@svg")
  578. res := rw.Result()
  579. s.Require().Equal(200, res.StatusCode)
  580. s.Require().Equal("image/svg+xml", res.Header.Get("Content-Type"))
  581. }
  582. func (s *ProcessingHandlerTestSuite) TestMaxSrcFileSizeGlobal() {
  583. config.MaxSrcFileSize = 1
  584. ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
  585. rw.WriteHeader(200)
  586. rw.Write(s.readTestFile("test1.png"))
  587. }))
  588. defer ts.Close()
  589. rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
  590. res := rw.Result()
  591. s.Require().Equal(422, res.StatusCode)
  592. }
  593. func TestProcessingHandler(t *testing.T) {
  594. suite.Run(t, new(ProcessingHandlerTestSuite))
  595. }