image_data.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package imagedata
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "sync"
  9. "github.com/imgproxy/imgproxy/v3/config"
  10. "github.com/imgproxy/imgproxy/v3/ierrors"
  11. "github.com/imgproxy/imgproxy/v3/imagetype"
  12. "github.com/imgproxy/imgproxy/v3/security"
  13. )
  14. var (
  15. Watermark ImageData
  16. FallbackImage ImageData
  17. )
  18. type ImageData interface {
  19. io.Closer // Close closes the image data and releases any resources held by it
  20. Reader() io.ReadSeeker // Reader returns a new ReadSeeker for the image data
  21. Format() imagetype.Type // Format returns the image format from the metadata (shortcut)
  22. Size() (int, error) // Size returns the size of the image data in bytes
  23. AddCancel(context.CancelFunc) // AddCancel attaches a cancel function to the image data
  24. // This will be removed in the future
  25. Headers() http.Header // Headers returns the HTTP headers of the image data, will be removed in the future
  26. }
  27. // imageDataBytes represents image data stored in a byte slice in memory
  28. type imageDataBytes struct {
  29. format imagetype.Type
  30. data []byte
  31. headers http.Header
  32. cancel []context.CancelFunc
  33. cancelOnce sync.Once
  34. }
  35. func (d *imageDataBytes) Close() error {
  36. d.cancelOnce.Do(func() {
  37. for _, cancel := range d.cancel {
  38. cancel()
  39. }
  40. })
  41. return nil
  42. }
  43. // Format returns the image format based on the metadata
  44. func (d *imageDataBytes) Format() imagetype.Type {
  45. return d.format
  46. }
  47. // Reader returns an io.ReadSeeker for the image data
  48. func (d *imageDataBytes) Reader() io.ReadSeeker {
  49. return bytes.NewReader(d.data)
  50. }
  51. // Size returns the size of the image data in bytes.
  52. // NOTE: asyncbuffer implementation will .Wait() for the data to be fully read
  53. func (d *imageDataBytes) Size() (int, error) {
  54. return len(d.data), nil
  55. }
  56. func (d *imageDataBytes) Headers() http.Header {
  57. return d.headers
  58. }
  59. func (d *imageDataBytes) AddCancel(cancel context.CancelFunc) {
  60. d.cancel = append(d.cancel, cancel)
  61. }
  62. func Init() error {
  63. initRead()
  64. if err := initDownloading(); err != nil {
  65. return err
  66. }
  67. if err := loadWatermark(); err != nil {
  68. return err
  69. }
  70. if err := loadFallbackImage(); err != nil {
  71. return err
  72. }
  73. return nil
  74. }
  75. func loadWatermark() error {
  76. var err error
  77. switch {
  78. case len(config.WatermarkData) > 0:
  79. Watermark, err = NewFromBase64(config.WatermarkData, security.DefaultOptions())
  80. // NOTE: this should be something like err = ierrors.Wrap(err).WithStackDeep(0).WithPrefix("watermark")
  81. // In the NewFromBase64 all errors should be wrapped to something like
  82. // .WithPrefix("load from base64")
  83. if err != nil {
  84. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load watermark from Base64"))
  85. }
  86. case len(config.WatermarkPath) > 0:
  87. Watermark, err = NewFromPath(config.WatermarkPath, security.DefaultOptions())
  88. if err != nil {
  89. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read watermark from file"))
  90. }
  91. case len(config.WatermarkURL) > 0:
  92. Watermark, err = Download(context.Background(), config.WatermarkURL, "watermark", DownloadOptions{Header: nil, CookieJar: nil}, security.DefaultOptions())
  93. if err != nil {
  94. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't download from URL"))
  95. }
  96. default:
  97. Watermark = nil
  98. }
  99. return nil
  100. }
  101. func loadFallbackImage() (err error) {
  102. switch {
  103. case len(config.FallbackImageData) > 0:
  104. FallbackImage, err = NewFromBase64(config.FallbackImageData, security.DefaultOptions())
  105. if err != nil {
  106. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load fallback image from Base64"))
  107. }
  108. case len(config.FallbackImagePath) > 0:
  109. FallbackImage, err = NewFromPath(config.FallbackImagePath, security.DefaultOptions())
  110. if err != nil {
  111. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read fallback image from file"))
  112. }
  113. case len(config.FallbackImageURL) > 0:
  114. FallbackImage, err = Download(context.Background(), config.FallbackImageURL, "fallback image", DownloadOptions{Header: nil, CookieJar: nil}, security.DefaultOptions())
  115. if err != nil {
  116. return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't download from URL"))
  117. }
  118. default:
  119. FallbackImage = nil
  120. }
  121. if FallbackImage != nil && err == nil && config.FallbackImageTTL > 0 {
  122. FallbackImage.Headers().Set("Fallback-Image", "1")
  123. }
  124. return err
  125. }
  126. func Download(ctx context.Context, imageURL, desc string, opts DownloadOptions, secopts security.Options) (ImageData, error) {
  127. imgdata, err := download(ctx, imageURL, opts, secopts)
  128. if err != nil {
  129. return nil, ierrors.Wrap(
  130. err, 0,
  131. ierrors.WithPrefix(fmt.Sprintf("Can't download %s", desc)),
  132. )
  133. }
  134. return imgdata, nil
  135. }