download.go 2.3 KB

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