factory.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package imagedata
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "github.com/imgproxy/imgproxy/v3/asyncbuffer"
  11. "github.com/imgproxy/imgproxy/v3/ierrors"
  12. "github.com/imgproxy/imgproxy/v3/imagefetcher"
  13. "github.com/imgproxy/imgproxy/v3/imagetype"
  14. "github.com/imgproxy/imgproxy/v3/security"
  15. )
  16. // NewFromBytesWithFormat creates a new ImageData instance from the provided format
  17. // and byte slice.
  18. func NewFromBytesWithFormat(format imagetype.Type, b []byte) ImageData {
  19. return &imageDataBytes{
  20. data: b,
  21. format: format,
  22. cancel: nil,
  23. }
  24. }
  25. // NewFromBytes creates a new ImageData instance from the provided byte slice.
  26. func NewFromBytes(b []byte) (ImageData, error) {
  27. r := bytes.NewReader(b)
  28. format, err := imagetype.Detect(r)
  29. if err != nil {
  30. return nil, err
  31. }
  32. return NewFromBytesWithFormat(format, b), nil
  33. }
  34. // NewFromPath creates a new ImageData from an os.File
  35. func NewFromPath(path string) (ImageData, error) {
  36. fl, err := os.Open(path)
  37. if err != nil {
  38. return nil, err
  39. }
  40. defer fl.Close()
  41. b, err := io.ReadAll(fl)
  42. if err != nil {
  43. return nil, err
  44. }
  45. return NewFromBytes(b)
  46. }
  47. // NewFromBase64 creates a new ImageData from a base64 encoded byte slice
  48. func NewFromBase64(encoded string) (ImageData, error) {
  49. b, err := base64.StdEncoding.DecodeString(encoded)
  50. if err != nil {
  51. return nil, err
  52. }
  53. return NewFromBytes(b)
  54. }
  55. // sendRequest is a common logic between sync and async download.
  56. func sendRequest(ctx context.Context, url string, opts DownloadOptions) (*imagefetcher.Request, *http.Response, http.Header, error) {
  57. h := make(http.Header)
  58. // NOTE: This will be removed in the future when our test context gets better isolation
  59. if len(redirectAllRequestsTo) > 0 {
  60. url = redirectAllRequestsTo
  61. }
  62. req, err := Fetcher.BuildRequest(ctx, url, opts.Header, opts.CookieJar)
  63. if err != nil {
  64. return req, nil, h, err
  65. }
  66. res, err := req.FetchImage()
  67. if res != nil {
  68. h = res.Header.Clone()
  69. }
  70. if err != nil {
  71. if res != nil {
  72. res.Body.Close()
  73. }
  74. req.Cancel()
  75. return req, nil, h, err
  76. }
  77. res, err = security.LimitResponseSize(res, opts.MaxSrcFileSize)
  78. if err != nil {
  79. if res != nil {
  80. res.Body.Close()
  81. }
  82. req.Cancel()
  83. return req, nil, h, err
  84. }
  85. return req, res, h, nil
  86. }
  87. // DownloadSync downloads the image synchronously and returns the ImageData and HTTP headers.
  88. func downloadSync(ctx context.Context, imageURL string, opts DownloadOptions) (ImageData, http.Header, error) {
  89. if opts.DownloadFinished != nil {
  90. defer opts.DownloadFinished()
  91. }
  92. req, res, h, err := sendRequest(ctx, imageURL, opts)
  93. if res != nil {
  94. defer res.Body.Close()
  95. }
  96. if req != nil {
  97. defer req.Cancel()
  98. }
  99. if err != nil {
  100. return nil, h, err
  101. }
  102. b, err := io.ReadAll(res.Body)
  103. if err != nil {
  104. return nil, h, err
  105. }
  106. format, err := imagetype.Detect(bytes.NewReader(b))
  107. if err != nil {
  108. return nil, h, err
  109. }
  110. d := NewFromBytesWithFormat(format, b)
  111. return d, h, err
  112. }
  113. // downloadAsync downloads the image asynchronously and returns the ImageData
  114. // backed by AsyncBuffer and HTTP headers.
  115. func downloadAsync(ctx context.Context, imageURL string, opts DownloadOptions) (ImageData, http.Header, error) {
  116. // We pass this responsibility to AsyncBuffer
  117. //nolint:bodyclose
  118. req, res, h, err := sendRequest(ctx, imageURL, opts)
  119. if err != nil {
  120. if opts.DownloadFinished != nil {
  121. defer opts.DownloadFinished()
  122. }
  123. return nil, h, err
  124. }
  125. b := asyncbuffer.New(res.Body, opts.DownloadFinished)
  126. format, err := imagetype.Detect(b.Reader())
  127. if err != nil {
  128. b.Close()
  129. req.Cancel()
  130. return nil, h, err
  131. }
  132. d := &imageDataAsyncBuffer{
  133. b: b,
  134. format: format,
  135. cancel: nil,
  136. }
  137. d.AddCancel(req.Cancel) // request will be closed when the image data is consumed
  138. return d, h, err
  139. }
  140. // DownloadSyncWithDesc downloads the image synchronously and returns the ImageData, but
  141. // wraps errors with desc.
  142. func DownloadSync(ctx context.Context, imageURL, desc string, opts DownloadOptions) (ImageData, http.Header, error) {
  143. imgdata, h, err := downloadSync(ctx, imageURL, opts)
  144. if err != nil {
  145. return nil, h, ierrors.Wrap(
  146. err, 0,
  147. ierrors.WithPrefix(fmt.Sprintf("can't download %s", desc)),
  148. )
  149. }
  150. return imgdata, h, nil
  151. }
  152. // DownloadSyncWithDesc downloads the image synchronously and returns the ImageData, but
  153. // wraps errors with desc.
  154. func DownloadAsync(ctx context.Context, imageURL, desc string, opts DownloadOptions) (ImageData, http.Header, error) {
  155. imgdata, h, err := downloadAsync(ctx, imageURL, opts)
  156. if err != nil {
  157. return nil, h, ierrors.Wrap(
  158. err, 0,
  159. ierrors.WithPrefix(fmt.Sprintf("can't download %s", desc)),
  160. )
  161. }
  162. return imgdata, h, nil
  163. }