|
@@ -3,12 +3,9 @@ package imagedata
|
|
|
import (
|
|
|
"bytes"
|
|
|
"context"
|
|
|
- "encoding/base64"
|
|
|
"fmt"
|
|
|
"io"
|
|
|
"net/http"
|
|
|
- "os"
|
|
|
- "strings"
|
|
|
"sync"
|
|
|
|
|
|
"github.com/imgproxy/imgproxy/v3/config"
|
|
@@ -18,23 +15,35 @@ import (
|
|
|
)
|
|
|
|
|
|
var (
|
|
|
- Watermark *ImageData
|
|
|
- FallbackImage *ImageData
|
|
|
+ Watermark ImageData
|
|
|
+ FallbackImage ImageData
|
|
|
)
|
|
|
|
|
|
-type ImageData struct {
|
|
|
+type ImageData interface {
|
|
|
+ io.Closer // Close closes the image data and releases any resources held by it
|
|
|
+ Reader() io.ReadSeeker // Reader returns a new ReadSeeker for the image data
|
|
|
+ Format() imagetype.Type // Format returns the image format from the metadata (shortcut)
|
|
|
+ Size() (int, error) // Size returns the size of the image data in bytes
|
|
|
+ WithCancel(context.CancelFunc) ImageData // WithCancel attaches a cancel function to the image data
|
|
|
+
|
|
|
+ // This will be removed in the future
|
|
|
+ Headers() http.Header // Headers returns the HTTP headers of the image data, will be removed in the future
|
|
|
+}
|
|
|
+
|
|
|
+// imageDataBytes represents image data stored in a byte slice in memory
|
|
|
+type imageDataBytes struct {
|
|
|
format imagetype.Type
|
|
|
data []byte
|
|
|
headers http.Header
|
|
|
|
|
|
- cancel context.CancelFunc
|
|
|
+ cancel []context.CancelFunc
|
|
|
cancelOnce sync.Once
|
|
|
}
|
|
|
|
|
|
-func (d *ImageData) Close() error {
|
|
|
+func (d *imageDataBytes) Close() error {
|
|
|
d.cancelOnce.Do(func() {
|
|
|
- if d.cancel != nil {
|
|
|
- d.cancel()
|
|
|
+ for _, cancel := range d.cancel {
|
|
|
+ cancel()
|
|
|
}
|
|
|
})
|
|
|
|
|
@@ -42,27 +51,28 @@ func (d *ImageData) Close() error {
|
|
|
}
|
|
|
|
|
|
// Format returns the image format based on the metadata
|
|
|
-func (d *ImageData) Format() imagetype.Type {
|
|
|
+func (d *imageDataBytes) Format() imagetype.Type {
|
|
|
return d.format
|
|
|
}
|
|
|
|
|
|
// Reader returns an io.ReadSeeker for the image data
|
|
|
-func (d *ImageData) Reader() io.ReadSeeker {
|
|
|
+func (d *imageDataBytes) Reader() io.ReadSeeker {
|
|
|
return bytes.NewReader(d.data)
|
|
|
}
|
|
|
|
|
|
// Size returns the size of the image data in bytes.
|
|
|
// NOTE: asyncbuffer implementation will .Wait() for the data to be fully read
|
|
|
-func (d *ImageData) Size() (int, error) {
|
|
|
+func (d *imageDataBytes) Size() (int, error) {
|
|
|
return len(d.data), nil
|
|
|
}
|
|
|
|
|
|
-func (d *ImageData) Headers() http.Header {
|
|
|
+func (d *imageDataBytes) Headers() http.Header {
|
|
|
return d.headers
|
|
|
}
|
|
|
|
|
|
-func (d *ImageData) SetCancel(cancel context.CancelFunc) {
|
|
|
- d.cancel = cancel
|
|
|
+func (d *imageDataBytes) WithCancel(cancel context.CancelFunc) ImageData {
|
|
|
+ d.cancel = append(d.cancel, cancel)
|
|
|
+ return d
|
|
|
}
|
|
|
|
|
|
func Init() error {
|
|
@@ -83,20 +93,32 @@ func Init() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func loadWatermark() (err error) {
|
|
|
+func loadWatermark() error {
|
|
|
+ var err error
|
|
|
+
|
|
|
if len(config.WatermarkData) > 0 {
|
|
|
- Watermark, err = FromBase64(config.WatermarkData, "watermark", security.DefaultOptions())
|
|
|
- return
|
|
|
+ Watermark, err = NewFromBase64(config.WatermarkData, security.DefaultOptions())
|
|
|
+
|
|
|
+ // NOTE: this should be something like err = ierrors.Wrap(err).WithStackDeep(0).WithPrefix("watermark")
|
|
|
+ // In the NewFromBase64 all errors should be wrapped to something like
|
|
|
+ // .WithPrefix("load from base64")
|
|
|
+ if err != nil {
|
|
|
+ return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load watermark from Base64"))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if len(config.WatermarkPath) > 0 {
|
|
|
- Watermark, err = FromFile(config.WatermarkPath, "watermark", security.DefaultOptions())
|
|
|
- return
|
|
|
+ Watermark, err = NewFromPath(config.WatermarkPath, security.DefaultOptions())
|
|
|
+ if err != nil {
|
|
|
+ return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read watermark from file"))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if len(config.WatermarkURL) > 0 {
|
|
|
Watermark, err = Download(context.Background(), config.WatermarkURL, "watermark", DownloadOptions{Header: nil, CookieJar: nil}, security.DefaultOptions())
|
|
|
- return
|
|
|
+ if err != nil {
|
|
|
+ return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't download from URL"))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return nil
|
|
@@ -105,9 +127,17 @@ func loadWatermark() (err error) {
|
|
|
func loadFallbackImage() (err error) {
|
|
|
switch {
|
|
|
case len(config.FallbackImageData) > 0:
|
|
|
- FallbackImage, err = FromBase64(config.FallbackImageData, "fallback image", security.DefaultOptions())
|
|
|
+ FallbackImage, err = NewFromBase64(config.FallbackImageData, security.DefaultOptions())
|
|
|
+ if err != nil {
|
|
|
+ return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load fallback image from Base64"))
|
|
|
+ }
|
|
|
+
|
|
|
case len(config.FallbackImagePath) > 0:
|
|
|
- FallbackImage, err = FromFile(config.FallbackImagePath, "fallback image", security.DefaultOptions())
|
|
|
+ FallbackImage, err = NewFromPath(config.FallbackImagePath, security.DefaultOptions())
|
|
|
+ if err != nil {
|
|
|
+ return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read fallback image from file"))
|
|
|
+ }
|
|
|
+
|
|
|
case len(config.FallbackImageURL) > 0:
|
|
|
FallbackImage, err = Download(context.Background(), config.FallbackImageURL, "fallback image", DownloadOptions{Header: nil, CookieJar: nil}, security.DefaultOptions())
|
|
|
default:
|
|
@@ -115,44 +145,13 @@ func loadFallbackImage() (err error) {
|
|
|
}
|
|
|
|
|
|
if FallbackImage != nil && err == nil && config.FallbackImageTTL > 0 {
|
|
|
- FallbackImage.headers.Set("Fallback-Image", "1")
|
|
|
+ FallbackImage.Headers().Set("Fallback-Image", "1")
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-func FromBase64(encoded, desc string, secopts security.Options) (*ImageData, error) {
|
|
|
- dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))
|
|
|
- size := 4 * (len(encoded)/3 + 1)
|
|
|
-
|
|
|
- imgdata, err := readAndCheckImage(dec, size, secopts)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("Can't decode %s: %s", desc, err)
|
|
|
- }
|
|
|
-
|
|
|
- return imgdata, nil
|
|
|
-}
|
|
|
-
|
|
|
-func FromFile(path, desc string, secopts security.Options) (*ImageData, error) {
|
|
|
- f, err := os.Open(path)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("Can't read %s: %s", desc, err)
|
|
|
- }
|
|
|
-
|
|
|
- fi, err := f.Stat()
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("Can't read %s: %s", desc, err)
|
|
|
- }
|
|
|
-
|
|
|
- imgdata, err := readAndCheckImage(f, int(fi.Size()), secopts)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("Can't read %s: %s", desc, err)
|
|
|
- }
|
|
|
-
|
|
|
- return imgdata, nil
|
|
|
-}
|
|
|
-
|
|
|
-func Download(ctx context.Context, imageURL, desc string, opts DownloadOptions, secopts security.Options) (*ImageData, error) {
|
|
|
+func Download(ctx context.Context, imageURL, desc string, opts DownloadOptions, secopts security.Options) (ImageData, error) {
|
|
|
imgdata, err := download(ctx, imageURL, opts, secopts)
|
|
|
if err != nil {
|
|
|
return nil, ierrors.Wrap(
|