download.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. package imagedata
  2. import (
  3. "context"
  4. "net/http"
  5. "slices"
  6. "github.com/imgproxy/imgproxy/v3/ierrors"
  7. "github.com/imgproxy/imgproxy/v3/imagefetcher"
  8. "github.com/imgproxy/imgproxy/v3/security"
  9. "github.com/imgproxy/imgproxy/v3/transport"
  10. "go.withmatt.com/httpheaders"
  11. )
  12. var (
  13. Fetcher *imagefetcher.Fetcher
  14. // For tests
  15. redirectAllRequestsTo string
  16. // keepResponseHeaders is a list of HTTP headers that should be preserved in the response
  17. keepResponseHeaders = []string{
  18. httpheaders.CacheControl,
  19. httpheaders.Expires,
  20. httpheaders.LastModified,
  21. // NOTE:
  22. // httpheaders.Etag == "Etag".
  23. // Http header names are case-insensitive, but we rely on the case in most cases.
  24. // We must migrate to http.Headers and the subsequent methods everywhere.
  25. httpheaders.Etag,
  26. }
  27. )
  28. type DownloadOptions struct {
  29. Header http.Header
  30. CookieJar http.CookieJar
  31. }
  32. func initDownloading() error {
  33. ts, err := transport.NewTransport()
  34. if err != nil {
  35. return err
  36. }
  37. Fetcher, err = imagefetcher.NewFetcher(ts, imagefetcher.NewConfigFromEnv())
  38. if err != nil {
  39. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't create image fetcher"))
  40. }
  41. return nil
  42. }
  43. func download(ctx context.Context, imageURL string, opts DownloadOptions, secopts security.Options) (*ImageData, http.Header, error) {
  44. // We use this for testing
  45. if len(redirectAllRequestsTo) > 0 {
  46. imageURL = redirectAllRequestsTo
  47. }
  48. req, err := Fetcher.BuildRequest(ctx, imageURL, opts.Header, opts.CookieJar)
  49. if err != nil {
  50. return nil, nil, err
  51. }
  52. defer req.Cancel()
  53. res, err := req.FetchImage()
  54. if err != nil {
  55. if res != nil {
  56. res.Body.Close()
  57. }
  58. return nil, nil, err
  59. }
  60. res, err = security.LimitResponseSize(res, secopts)
  61. if res != nil {
  62. defer res.Body.Close()
  63. }
  64. if err != nil {
  65. return nil, nil, err
  66. }
  67. imgdata, err := readAndCheckImage(res.Body, int(res.ContentLength), secopts)
  68. if err != nil {
  69. return nil, nil, ierrors.Wrap(err, 0)
  70. }
  71. h := make(map[string]string)
  72. for k := range res.Header {
  73. if !slices.Contains(keepResponseHeaders, k) {
  74. continue
  75. }
  76. // TODO: Fix Etag/ETag inconsistency
  77. if k == "Etag" {
  78. h["ETag"] = res.Header.Get(k)
  79. } else {
  80. h[k] = res.Header.Get(k)
  81. }
  82. }
  83. imgdata.Headers = h
  84. return imgdata, res.Header, nil
  85. }
  86. func RedirectAllRequestsTo(u string) {
  87. redirectAllRequestsTo = u
  88. }
  89. func StopRedirectingRequests() {
  90. redirectAllRequestsTo = ""
  91. }