1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-09-16 09:36:18 +02:00

IMG-13: http.Header, *ImageData -> ImageData (#1473)

* ImageData.Headers()

* *ImageData -> ImageData

* withmatt -> httpheaders of our own

* .Clone() headers, nil

* NewFromBytesWithFormat -> nil

* svg.go -> do not Clone()
This commit is contained in:
Victor Sokolov
2025-08-01 15:44:21 +02:00
committed by GitHub
parent e40851e460
commit 3d14c85de3
39 changed files with 356 additions and 258 deletions

View File

@@ -12,6 +12,7 @@ import (
"sync"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/httpheaders"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/options"
)
@@ -106,10 +107,10 @@ func (h *Handler) ImageEtagExpected() string {
return h.imgEtagExpected
}
func (h *Handler) SetActualImageData(imgdata *imagedata.ImageData) (bool, error) {
func (h *Handler) SetActualImageData(imgdata imagedata.ImageData) (bool, error) {
var haveActualImgETag bool
h.imgEtagActual, haveActualImgETag = imgdata.Headers["ETag"]
haveActualImgETag = haveActualImgETag && len(h.imgEtagActual) > 0
h.imgEtagActual = imgdata.Headers().Get(httpheaders.Etag)
haveActualImgETag = len(h.imgEtagActual) > 0
// Just in case server didn't check ETag properly and returned the same one
// as we expected

View File

@@ -2,7 +2,6 @@ package etag
import (
"io"
"net/http"
"os"
"strings"
"testing"
@@ -11,6 +10,7 @@ import (
"github.com/stretchr/testify/suite"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/httpheaders"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/options"
)
@@ -24,8 +24,8 @@ type EtagTestSuite struct {
suite.Suite
po *options.ProcessingOptions
imgWithETag *imagedata.ImageData
imgWithoutETag *imagedata.ImageData
imgWithETag imagedata.ImageData
imgWithoutETag imagedata.ImageData
h Handler
}
@@ -37,10 +37,11 @@ func (s *EtagTestSuite) SetupSuite() {
d, err := os.ReadFile("../testdata/test1.jpg")
s.Require().NoError(err)
imgWithETag, err := imagedata.NewFromBytes(d, http.Header{"ETag": []string{`"loremipsumdolor"`}})
imgWithETag, err := imagedata.NewFromBytes(d)
s.Require().NoError(err)
imgWithETag.Headers().Add(httpheaders.Etag, `"loremipsumdolor"`)
imgWithoutETag, err := imagedata.NewFromBytes(d, make(http.Header))
imgWithoutETag, err := imagedata.NewFromBytes(d)
s.Require().NoError(err)
s.imgWithETag = imgWithETag
@@ -101,7 +102,7 @@ func (s *EtagTestSuite) TestImageETagExpectedPresent() {
s.h.ParseExpectedETag(etagReq)
//nolint:testifylint // False-positive expected-actual
s.Require().Equal(s.imgWithETag.Headers["ETag"], s.h.ImageEtagExpected())
s.Require().Equal(s.imgWithETag.Headers().Get(httpheaders.Etag), s.h.ImageEtagExpected())
}
func (s *EtagTestSuite) TestImageETagExpectedBlank() {

1
go.mod
View File

@@ -205,7 +205,6 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.withmatt.com/httpheaders v1.0.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.39.0 // indirect

2
go.sum
View File

@@ -560,8 +560,6 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.withmatt.com/httpheaders v1.0.0 h1:xZhtLWyIWCd8FT3CvUBRQLhQpgZaMmHNfIIT0wwNc1A=
go.withmatt.com/httpheaders v1.0.0/go.mod h1:bKAYNgm9s2ViHIoGOnMKo4F2zJXBdvpfGuSEJQYF8pQ=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=

64
httpheaders/headers.go Normal file
View File

@@ -0,0 +1,64 @@
// Inspired by https://github.com/mattrobenolt/go-httpheaders
// Thanks, Matt Robenolt!
package httpheaders
const (
Accept = "Accept"
AcceptCharset = "Accept-Charset"
AcceptEncoding = "Accept-Encoding"
AcceptLanguage = "Accept-Language"
AcceptRanges = "Accept-Ranges"
AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
AccessControlAllowHeaders = "Access-Control-Allow-Headers"
AccessControlAllowMethods = "Access-Control-Allow-Methods"
AccessControlAllowOrigin = "Access-Control-Allow-Origin"
AccessControlMaxAge = "Access-Control-Max-Age"
Age = "Age"
AltSvc = "Alt-Svc"
Authorization = "Authorization"
CacheControl = "Cache-Control"
Connection = "Connection"
ContentDisposition = "Content-Disposition"
ContentEncoding = "Content-Encoding"
ContentLanguage = "Content-Language"
ContentLength = "Content-Length"
ContentRange = "Content-Range"
ContentSecurityPolicy = "Content-Security-Policy"
ContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
ContentType = "Content-Type"
Cookie = "Cookie"
Date = "Date"
Dnt = "Dnt"
Etag = "Etag"
Expect = "Expect"
ExpectCt = "Expect-Ct"
Expires = "Expires"
Forwarded = "Forwarded"
Host = "Host"
IfMatch = "If-Match"
IfModifiedSince = "If-Modified-Since"
IfNoneMatch = "If-None-Match"
IfUnmodifiedSince = "If-Unmodified-Since"
KeepAlive = "Keep-Alive"
LastModified = "Last-Modified"
Link = "Link"
Location = "Location"
Origin = "Origin"
Pragma = "Pragma"
Referer = "Referer"
RequestId = "Request-Id"
RetryAfter = "Retry-After"
Server = "Server"
SetCookie = "Set-Cookie"
StrictTransportSecurity = "Strict-Transport-Security"
Upgrade = "Upgrade"
UserAgent = "User-Agent"
Vary = "Vary"
Via = "Via"
WwwAuthenticate = "Www-Authenticate"
XContentTypeOptions = "X-Content-Type-Options"
XForwardedFor = "X-Forwarded-For"
XForwardedHost = "X-Forwarded-Host"
XForwardedProto = "X-Forwarded-Proto"
XFrameOptions = "X-Frame-Options"
)

View File

@@ -37,7 +37,7 @@ func initDownloading() error {
return nil
}
func download(ctx context.Context, imageURL string, opts DownloadOptions, secopts security.Options) (*ImageData, error) {
func download(ctx context.Context, imageURL string, opts DownloadOptions, secopts security.Options) (ImageData, error) {
// We use this for testing
if len(redirectAllRequestsTo) > 0 {
imageURL = redirectAllRequestsTo
@@ -70,23 +70,13 @@ func download(ctx context.Context, imageURL string, opts DownloadOptions, secopt
return nil, ierrors.Wrap(err, 0)
}
h := make(map[string]string)
for k := range res.Header {
value := res.Header.Get(k)
h[k] = value
// This is temporary workaround, will be addressed in the subsequent PR
if k == "Etag" {
h["ETag"] = value
}
if k == "ETag" {
h["Etag"] = value
// NOTE: This will be removed in the future in favor of headers/image data separation
for k, v := range res.Header {
for _, v := range v {
imgdata.Headers().Add(k, v)
}
}
imgdata.Headers = h
return imgdata, nil
}

View File

@@ -2,15 +2,37 @@ package imagedata
import (
"bytes"
"encoding/base64"
"io"
"net/http"
"strings"
"os"
"github.com/imgproxy/imgproxy/v3/imagemeta"
"github.com/imgproxy/imgproxy/v3/imagetype"
"github.com/imgproxy/imgproxy/v3/security"
)
// NewFromBytesWithFormat creates a new ImageData instance from the provided format,
// http headers and byte slice.
func NewFromBytesWithFormat(format imagetype.Type, b []byte, headers http.Header) ImageData {
var h http.Header
if headers == nil {
h = make(http.Header)
} else {
h = headers.Clone()
}
return &imageDataBytes{
data: b,
format: format,
headers: h,
cancel: nil,
}
}
// NewFromBytes creates a new ImageData instance from the provided byte slice.
func NewFromBytes(b []byte, headers http.Header) (*ImageData, error) {
func NewFromBytes(b []byte) (ImageData, error) {
r := bytes.NewReader(b)
meta, err := imagemeta.DecodeMeta(r)
@@ -18,20 +40,64 @@ func NewFromBytes(b []byte, headers http.Header) (*ImageData, error) {
return nil, err
}
return NewFromBytesWithFormat(meta.Format(), b, headers)
return NewFromBytesWithFormat(meta.Format(), b, nil), nil
}
// NewFromBytesWithFormat creates a new ImageData instance from the provided format and byte slice.
func NewFromBytesWithFormat(format imagetype.Type, b []byte, headers http.Header) (*ImageData, error) {
// Temporary workaround for the old ImageData interface
h := make(map[string]string, len(headers))
for k, v := range headers {
h[k] = strings.Join(v, ", ")
// NewFromPath creates a new ImageData from an os.File
func NewFromPath(path string, secopts security.Options) (ImageData, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
fr, err := security.LimitFileSize(f, secopts)
if err != nil {
return nil, err
}
return &ImageData{
data: b,
format: format,
Headers: h,
}, nil
b, err := io.ReadAll(fr)
if err != nil {
return nil, err
}
r := bytes.NewReader(b)
// NOTE: This will be removed in the future in favor of VIPS metadata extraction
// It's here temporarily to maintain compatibility with existing code
meta, err := imagemeta.DecodeMeta(r)
if err != nil {
return nil, err
}
err = security.CheckMeta(meta, secopts)
if err != nil {
return nil, err
}
return NewFromBytes(b)
}
// NewFromBase64 creates a new ImageData from a base64 encoded byte slice
func NewFromBase64(encoded string, secopts security.Options) (ImageData, error) {
b, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return nil, err
}
r := bytes.NewReader(b)
// NOTE: This will be removed in the future in favor of VIPS metadata extraction
// It's here temporarily to maintain compatibility with existing code
meta, err := imagemeta.DecodeMeta(r)
if err != nil {
return nil, err
}
err = security.CheckMeta(meta, secopts)
if err != nil {
return nil, err
}
return NewFromBytes(b)
}

View File

@@ -3,11 +3,9 @@ package imagedata
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"os"
"strings"
"net/http"
"sync"
"github.com/imgproxy/imgproxy/v3/config"
@@ -17,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
AddCancel(context.CancelFunc) // AddCancel 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 map[string]string
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()
}
})
@@ -41,23 +51,27 @@ 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) SetCancel(cancel context.CancelFunc) {
d.cancel = cancel
func (d *imageDataBytes) Headers() http.Header {
return d.headers
}
func (d *imageDataBytes) AddCancel(cancel context.CancelFunc) {
d.cancel = append(d.cancel, cancel)
}
func Init() error {
@@ -78,20 +92,34 @@ func Init() error {
return nil
}
func loadWatermark() (err error) {
if len(config.WatermarkData) > 0 {
Watermark, err = FromBase64(config.WatermarkData, "watermark", security.DefaultOptions())
return
}
func loadWatermark() error {
var err error
if len(config.WatermarkPath) > 0 {
Watermark, err = FromFile(config.WatermarkPath, "watermark", security.DefaultOptions())
return
}
switch {
case len(config.WatermarkData) > 0:
Watermark, err = NewFromBase64(config.WatermarkData, security.DefaultOptions())
if len(config.WatermarkURL) > 0 {
// 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"))
}
case len(config.WatermarkPath) > 0:
Watermark, err = NewFromPath(config.WatermarkPath, security.DefaultOptions())
if err != nil {
return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't read watermark from file"))
}
case 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"))
}
default:
Watermark = nil
}
return nil
@@ -100,57 +128,35 @@ 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())
if err != nil {
return ierrors.Wrap(err, 0, ierrors.WithPrefix("can't download from URL"))
}
default:
FallbackImage, err = nil, nil
FallbackImage = nil
}
if FallbackImage != nil && err == nil && config.FallbackImageTTL > 0 {
if FallbackImage.Headers == nil {
FallbackImage.Headers = make(map[string]string)
}
FallbackImage.Headers["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(

View File

@@ -1,22 +0,0 @@
package imagedata
import (
"io"
"net/http"
"github.com/imgproxy/imgproxy/v3/imagemeta"
"github.com/imgproxy/imgproxy/v3/imagetype"
)
// NOTE: This is temporary naming, will be fixed in the subsequent PR
// ImageDataI is an interface that defines methods for reading image data and metadata
type ImageDataI 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
Meta() imagemeta.Meta // Meta returns the metadata of 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
// 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
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/imgproxy/imgproxy/v3/ierrors"
"github.com/imgproxy/imgproxy/v3/imagetype"
"github.com/imgproxy/imgproxy/v3/security"
"github.com/imgproxy/imgproxy/v3/testutil"
)
type ImageDataTestSuite struct {
@@ -94,7 +95,7 @@ func (s *ImageDataTestSuite) TestDownloadStatusOK() {
s.Require().NoError(err)
s.Require().NotNil(imgdata)
s.Require().Equal(s.defaultData, imgdata.data)
s.Require().True(testutil.ReadersEqual(s.T(), bytes.NewReader(s.defaultData), imgdata.Reader()))
s.Require().Equal(imagetype.JPEG, imgdata.Format())
}
@@ -165,7 +166,7 @@ func (s *ImageDataTestSuite) TestDownloadStatusPartialContent() {
} else {
s.Require().NoError(err)
s.Require().NotNil(imgdata)
s.Require().Equal(s.defaultData, imgdata.data)
s.Require().True(testutil.ReadersEqual(s.T(), bytes.NewReader(s.defaultData), imgdata.Reader()))
s.Require().Equal(imagetype.JPEG, imgdata.Format())
}
})
@@ -278,27 +279,27 @@ func (s *ImageDataTestSuite) TestDownloadGzip() {
s.Require().NoError(err)
s.Require().NotNil(imgdata)
s.Require().Equal(s.defaultData, imgdata.data)
s.Require().True(testutil.ReadersEqual(s.T(), bytes.NewReader(s.defaultData), imgdata.Reader()))
s.Require().Equal(imagetype.JPEG, imgdata.Format())
}
func (s *ImageDataTestSuite) TestFromFile() {
imgdata, err := FromFile("../testdata/test1.jpg", "Test image", security.DefaultOptions())
imgdata, err := NewFromPath("../testdata/test1.jpg", security.DefaultOptions())
s.Require().NoError(err)
s.Require().NotNil(imgdata)
s.Require().Equal(s.defaultData, imgdata.data)
s.Require().True(testutil.ReadersEqual(s.T(), bytes.NewReader(s.defaultData), imgdata.Reader()))
s.Require().Equal(imagetype.JPEG, imgdata.Format())
}
func (s *ImageDataTestSuite) TestFromBase64() {
b64 := base64.StdEncoding.EncodeToString(s.defaultData)
imgdata, err := FromBase64(b64, "Test image", security.DefaultOptions())
imgdata, err := NewFromBase64(b64, security.DefaultOptions())
s.Require().NoError(err)
s.Require().NotNil(imgdata)
s.Require().Equal(s.defaultData, imgdata.data)
s.Require().True(testutil.ReadersEqual(s.T(), bytes.NewReader(s.defaultData), imgdata.Reader()))
s.Require().Equal(imagetype.JPEG, imgdata.Format())
}

View File

@@ -19,7 +19,7 @@ func initRead() {
downloadBufPool = bufpool.New("download", config.Workers, config.DownloadBufferSize)
}
func readAndCheckImage(r io.Reader, contentLength int, secopts security.Options) (*ImageData, error) {
func readAndCheckImage(r io.Reader, contentLength int, secopts security.Options) (ImageData, error) {
buf := downloadBufPool.Get(contentLength, false)
cancel := func() { downloadBufPool.Put(buf) }
@@ -49,11 +49,9 @@ func readAndCheckImage(r io.Reader, contentLength int, secopts security.Options)
return nil, imagefetcher.WrapError(err)
}
return &ImageData{
data: buf.Bytes(),
format: meta.Format(),
cancel: cancel,
}, nil
i := NewFromBytesWithFormat(meta.Format(), buf.Bytes(), nil)
i.AddCancel(cancel)
return i, nil
}
func BorrowBuffer() (*bytes.Buffer, context.CancelFunc) {

View File

@@ -8,9 +8,9 @@ import (
"time"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/httpheaders"
"github.com/imgproxy/imgproxy/v3/transport"
"github.com/imgproxy/imgproxy/v3/transport/common"
"go.withmatt.com/httpheaders"
)
const (

View File

@@ -12,7 +12,7 @@ import (
"strings"
"time"
"go.withmatt.com/httpheaders"
"github.com/imgproxy/imgproxy/v3/httpheaders"
)
var (

View File

@@ -6,7 +6,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func applyFilters(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func applyFilters(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if po.Blur == 0 && po.Sharpen == 0 && po.Pixelate <= 1 {
return nil
}

View File

@@ -32,7 +32,7 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav
return img.Crop(left, top, cropWidth, cropHeight)
}
func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
width, height := pctx.cropWidth, pctx.cropHeight
opts := pctx.cropGravity
@@ -47,6 +47,6 @@ func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions,
return cropImage(img, width, height, &opts, 1.0)
}
func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
return cropImage(img, pctx.resultCropWidth, pctx.resultCropHeight, &po.Gravity, pctx.dprScale)
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func exportColorProfile(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func exportColorProfile(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
keepProfile := !po.StripColorProfile && po.Format.SupportsColourProfile()
if img.IsLinear() {

View File

@@ -25,7 +25,7 @@ func extendImage(img *vips.Image, width, height int, gravity *options.GravityOpt
return img.Embed(width, height, offX, offY)
}
func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.Extend.Enabled {
return nil
}
@@ -34,7 +34,7 @@ func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOption
return extendImage(img, width, height, &po.Extend.Gravity, pctx.dprScale)
}
func extendAspectRatio(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func extendAspectRatio(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.ExtendAspectRatio.Enabled {
return nil
}

View File

@@ -91,7 +91,7 @@ func fixIcoSize(img *vips.Image) error {
return nil
}
func fixSize(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func fixSize(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
switch po.Format {
case imagetype.WEBP:
return fixWebpSize(img)

View File

@@ -6,7 +6,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func flatten(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func flatten(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.Flatten && po.Format.SupportsAlpha() {
return nil
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func importColorProfile(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func importColorProfile(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if img.ColourProfileImported() {
return nil
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func padding(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func padding(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.Padding.Enabled {
return nil
}

View File

@@ -60,10 +60,10 @@ type pipelineContext struct {
extendAspectRatioHeight int
}
type pipelineStep func(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error
type pipelineStep func(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error
type pipeline []pipelineStep
func (p pipeline) Run(ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func (p pipeline) Run(ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
pctx := pipelineContext{
ctx: ctx,

View File

@@ -248,7 +248,7 @@ func (pctx *pipelineContext) limitScale(widthToScale, heightToScale int, po *opt
}
}
func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
pctx.imgtype = imagetype.Unknown
if imgdata != nil {
pctx.imgtype = imgdata.Format()

View File

@@ -97,7 +97,7 @@ func getImageSize(img *vips.Image) (int, int) {
return width, height
}
func transformAnimated(ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func transformAnimated(ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if po.Trim.Enabled {
log.Warning("Trim is not supported for animated images")
po.Trim.Enabled = false
@@ -207,7 +207,7 @@ func transformAnimated(ctx context.Context, img *vips.Image, po *options.Process
return nil
}
func saveImageToFitBytes(ctx context.Context, po *options.ProcessingOptions, img *vips.Image) (*imagedata.ImageData, error) {
func saveImageToFitBytes(ctx context.Context, po *options.ProcessingOptions, img *vips.Image) (imagedata.ImageData, error) {
var diff float64
quality := po.GetQuality()
@@ -248,7 +248,7 @@ func saveImageToFitBytes(ctx context.Context, po *options.ProcessingOptions, img
}
}
func ProcessImage(ctx context.Context, imgdata *imagedata.ImageData, po *options.ProcessingOptions) (*imagedata.ImageData, error) {
func ProcessImage(ctx context.Context, imgdata imagedata.ImageData, po *options.ProcessingOptions) (imagedata.ImageData, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -348,7 +348,7 @@ func ProcessImage(ctx context.Context, imgdata *imagedata.ImageData, po *options
}
var (
outData *imagedata.ImageData
outData imagedata.ImageData
err error
)
@@ -359,13 +359,10 @@ func ProcessImage(ctx context.Context, imgdata *imagedata.ImageData, po *options
}
if err == nil {
if outData.Headers == nil {
outData.Headers = make(map[string]string)
}
outData.Headers["X-Origin-Width"] = strconv.Itoa(originWidth)
outData.Headers["X-Origin-Height"] = strconv.Itoa(originHeight)
outData.Headers["X-Result-Width"] = strconv.Itoa(img.Width())
outData.Headers["X-Result-Height"] = strconv.Itoa(img.Height())
outData.Headers().Set("X-Origin-Width", strconv.Itoa(originWidth))
outData.Headers().Set("X-Origin-Height", strconv.Itoa(originHeight))
outData.Headers().Set("X-Result-Width", strconv.Itoa(img.Width()))
outData.Headers().Set("X-Result-Height", strconv.Itoa(img.Height()))
}
return outData, err

View File

@@ -31,7 +31,7 @@ func (s *ProcessingTestSuite) SetupSuite() {
logrus.SetOutput(io.Discard)
}
func (s *ProcessingTestSuite) openFile(name string) *imagedata.ImageData {
func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
secopts := security.Options{
MaxSrcResolution: 10 * 1024 * 1024,
MaxSrcFileSize: 10 * 1024 * 1024,
@@ -43,13 +43,13 @@ func (s *ProcessingTestSuite) openFile(name string) *imagedata.ImageData {
s.Require().NoError(err)
path := filepath.Join(wd, "..", "testdata", name)
imagedata, err := imagedata.FromFile(path, "test image", secopts)
imagedata, err := imagedata.NewFromPath(path, secopts)
s.Require().NoError(err)
return imagedata
}
func (s *ProcessingTestSuite) checkSize(imgdata *imagedata.ImageData, width, height int) {
func (s *ProcessingTestSuite) checkSize(imgdata imagedata.ImageData, width, height int) {
img := new(vips.Image)
err := img.Load(imgdata, 1, 1, 1)
s.Require().NoError(err)

View File

@@ -6,7 +6,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func rotateAndFlip(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func rotateAndFlip(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if pctx.angle%360 == 0 && po.Rotate%360 == 0 && !pctx.flip {
return nil
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func scale(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func scale(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if pctx.wscale == 1 && pctx.hscale == 1 {
return nil
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func canScaleOnLoad(pctx *pipelineContext, imgdata *imagedata.ImageData, scale float64) bool {
func canScaleOnLoad(pctx *pipelineContext, imgdata imagedata.ImageData, scale float64) bool {
if imgdata == nil || pctx.trimmed || scale == 1 {
return false
}
@@ -45,7 +45,7 @@ func calcJpegShink(shrink float64) int {
return 1
}
func scaleOnLoad(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func scaleOnLoad(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
wshrink := float64(pctx.srcWidth) / float64(imath.Scale(pctx.srcWidth, pctx.wscale))
hshrink := float64(pctx.srcHeight) / float64(imath.Scale(pctx.srcHeight, pctx.hscale))
preshrink := math.Min(wshrink, hshrink)

View File

@@ -105,7 +105,7 @@ func stripXMP(img *vips.Image) []byte {
return xmpData
}
func stripMetadata(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func stripMetadata(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.StripMetadata {
return nil
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips"
)
func trim(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func trim(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.Trim.Enabled {
return nil
}

View File

@@ -20,7 +20,7 @@ var watermarkPipeline = pipeline{
padding,
}
func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options.WatermarkOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.WatermarkOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
return err
}
@@ -81,7 +81,7 @@ func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options
return wm.StripAll()
}
func applyWatermark(img *vips.Image, wmData *imagedata.ImageData, opts *options.WatermarkOptions, offsetScale float64, framesCount int) error {
func applyWatermark(img *vips.Image, wmData imagedata.ImageData, opts *options.WatermarkOptions, offsetScale float64, framesCount int) error {
wm := new(vips.Image)
defer wm.Clear()
@@ -162,7 +162,7 @@ func applyWatermark(img *vips.Image, wmData *imagedata.ImageData, opts *options.
return nil
}
func watermark(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
func watermark(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.Watermark.Enabled || imagedata.Watermark == nil {
return nil
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/imgproxy/imgproxy/v3/cookies"
"github.com/imgproxy/imgproxy/v3/errorreport"
"github.com/imgproxy/imgproxy/v3/etag"
"github.com/imgproxy/imgproxy/v3/httpheaders"
"github.com/imgproxy/imgproxy/v3/ierrors"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/imagefetcher"
@@ -66,7 +67,7 @@ func initProcessingHandler() {
headerVaryValue = strings.Join(vary, ", ")
}
func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map[string]string) {
func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders http.Header) {
ttl := -1
if _, ok := originHeaders["Fallback-Image"]; ok && config.FallbackImageTTL > 0 {
@@ -78,12 +79,12 @@ func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map
}
if config.CacheControlPassthrough && ttl < 0 && originHeaders != nil {
if val, ok := originHeaders["Cache-Control"]; ok && len(val) > 0 {
rw.Header().Set("Cache-Control", val)
if val := originHeaders.Get(httpheaders.CacheControl); len(val) > 0 {
rw.Header().Set(httpheaders.CacheControl, val)
return
}
if val, ok := originHeaders["Expires"]; ok && len(val) > 0 {
if val := originHeaders.Get(httpheaders.Expires); len(val) > 0 {
if t, err := time.Parse(http.TimeFormat, val); err == nil {
ttl = imath.Max(0, int(time.Until(t).Seconds()))
}
@@ -95,23 +96,23 @@ func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map
}
if ttl > 0 {
rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", ttl))
rw.Header().Set(httpheaders.CacheControl, fmt.Sprintf("max-age=%d, public", ttl))
} else {
rw.Header().Set("Cache-Control", "no-cache")
rw.Header().Set(httpheaders.CacheControl, "no-cache")
}
}
func setLastModified(rw http.ResponseWriter, originHeaders map[string]string) {
func setLastModified(rw http.ResponseWriter, originHeaders http.Header) {
if config.LastModifiedEnabled {
if val, ok := originHeaders["Last-Modified"]; ok && len(val) != 0 {
rw.Header().Set("Last-Modified", val)
if val := originHeaders.Get(httpheaders.LastModified); len(val) != 0 {
rw.Header().Set(httpheaders.LastModified, val)
}
}
}
func setVary(rw http.ResponseWriter) {
if len(headerVaryValue) > 0 {
rw.Header().Set("Vary", headerVaryValue)
rw.Header().Set(httpheaders.Vary, headerVaryValue)
}
}
@@ -124,7 +125,7 @@ func setCanonical(rw http.ResponseWriter, originURL string) {
}
}
func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, statusCode int, resultData *imagedata.ImageData, po *options.ProcessingOptions, originURL string, originData *imagedata.ImageData) {
func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, statusCode int, resultData imagedata.ImageData, po *options.ProcessingOptions, originURL string, originData imagedata.ImageData) {
var contentDisposition string
if len(po.Filename) > 0 {
contentDisposition = resultData.Format().ContentDisposition(po.Filename, po.ReturnAttachment)
@@ -135,8 +136,8 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
rw.Header().Set("Content-Type", resultData.Format().Mime())
rw.Header().Set("Content-Disposition", contentDisposition)
setCacheControl(rw, po.Expires, originData.Headers)
setLastModified(rw, originData.Headers)
setCacheControl(rw, po.Expires, originData.Headers())
setLastModified(rw, originData.Headers())
setVary(rw)
setCanonical(rw, originURL)
@@ -147,10 +148,10 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
}
rw.Header().Set("X-Origin-Content-Length", strconv.Itoa(originSize))
rw.Header().Set("X-Origin-Width", resultData.Headers["X-Origin-Width"])
rw.Header().Set("X-Origin-Height", resultData.Headers["X-Origin-Height"])
rw.Header().Set("X-Result-Width", resultData.Headers["X-Result-Width"])
rw.Header().Set("X-Result-Height", resultData.Headers["X-Result-Height"])
rw.Header().Set("X-Origin-Width", resultData.Headers().Get("X-Origin-Width"))
rw.Header().Set("X-Origin-Height", resultData.Headers().Get("X-Origin-Height"))
rw.Header().Set("X-Result-Width", resultData.Headers().Get("X-Result-Width"))
rw.Header().Set("X-Result-Height", resultData.Headers().Get("X-Result-Height"))
}
rw.Header().Set("Content-Security-Policy", "script-src 'none'")
@@ -184,7 +185,7 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
)
}
func respondWithNotModified(reqID string, r *http.Request, rw http.ResponseWriter, po *options.ProcessingOptions, originURL string, originHeaders map[string]string) {
func respondWithNotModified(reqID string, r *http.Request, rw http.ResponseWriter, po *options.ProcessingOptions, originURL string, originHeaders http.Header) {
setCacheControl(rw, po.Expires, originHeaders)
setVary(rw)
@@ -347,7 +348,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
statusCode := http.StatusOK
originData, err := func() (*imagedata.ImageData, error) {
originData, err := func() (imagedata.ImageData, error) {
defer metrics.StartDownloadingSegment(ctx, metrics.Meta{
metrics.MetaSourceImageURL: metricsMeta[metrics.MetaSourceImageURL],
metrics.MetaSourceImageOrigin: metricsMeta[metrics.MetaSourceImageOrigin],
@@ -374,15 +375,10 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
case errors.As(err, &nmErr):
if config.ETagEnabled && len(etagHandler.ImageEtagExpected()) != 0 {
rw.Header().Set("ETag", etagHandler.GenerateExpectedETag())
rw.Header().Set(httpheaders.Etag, etagHandler.GenerateExpectedETag())
}
h := make(map[string]string)
for k := range nmErr.Headers() {
h[k] = nmErr.Headers().Get(k)
}
respondWithNotModified(reqID, r, rw, po, imageURL, h)
respondWithNotModified(reqID, r, rw, po, imageURL, nmErr.Headers())
return
default:
@@ -426,7 +422,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("ETag", etagHandler.GenerateActualETag())
if imgDataMatch && etagHandler.ProcessingOptionsMatch() {
respondWithNotModified(reqID, r, rw, po, imageURL, originData.Headers)
respondWithNotModified(reqID, r, rw, po, imageURL, originData.Headers())
return
}
}
@@ -471,7 +467,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
))
}
resultData, err := func() (*imagedata.ImageData, error) {
resultData, err := func() (imagedata.ImageData, error) {
defer metrics.StartProcessingSegment(ctx, metrics.Meta{
metrics.MetaProcessingOptions: metricsMeta[metrics.MetaProcessingOptions],
})()

View File

@@ -18,6 +18,7 @@ import (
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/config/configurators"
"github.com/imgproxy/imgproxy/v3/etag"
"github.com/imgproxy/imgproxy/v3/httpheaders"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/imagemeta"
"github.com/imgproxy/imgproxy/v3/imagetype"
@@ -86,26 +87,26 @@ func (s *ProcessingHandlerTestSuite) readTestFile(name string) []byte {
return data
}
func (s *ProcessingHandlerTestSuite) readTestImageData(name string) *imagedata.ImageData {
func (s *ProcessingHandlerTestSuite) readTestImageData(name string) imagedata.ImageData {
wd, err := os.Getwd()
s.Require().NoError(err)
data, err := os.ReadFile(filepath.Join(wd, "testdata", name))
s.Require().NoError(err)
imgdata, err := imagedata.NewFromBytes(data, make(http.Header))
imgdata, err := imagedata.NewFromBytes(data)
s.Require().NoError(err)
return imgdata
}
func (s *ProcessingHandlerTestSuite) readImageData(imgdata *imagedata.ImageData) []byte {
func (s *ProcessingHandlerTestSuite) readImageData(imgdata imagedata.ImageData) []byte {
data, err := io.ReadAll(imgdata.Reader())
s.Require().NoError(err)
return data
}
func (s *ProcessingHandlerTestSuite) sampleETagData(imgETag string) (string, *imagedata.ImageData, string) {
func (s *ProcessingHandlerTestSuite) sampleETagData(imgETag string) (string, imagedata.ImageData, string) {
poStr := "rs:fill:4:4"
po := options.NewProcessingOptions()
@@ -116,7 +117,7 @@ func (s *ProcessingHandlerTestSuite) sampleETagData(imgETag string) (string, *im
imgdata := s.readTestImageData("test1.png")
if len(imgETag) != 0 {
imgdata.Headers = map[string]string{"ETag": imgETag}
imgdata.Headers().Set(httpheaders.Etag, imgETag)
}
var h etag.Handler
@@ -416,7 +417,7 @@ func (s *ProcessingHandlerTestSuite) TestETagReqNoIfNotModified() {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
s.Empty(r.Header.Get("If-None-Match"))
rw.Header().Set("ETag", imgdata.Headers["ETag"])
rw.Header().Set("ETag", imgdata.Headers().Get(httpheaders.Etag))
rw.WriteHeader(200)
rw.Write(s.readTestFile("test1.png"))
}))
@@ -455,7 +456,7 @@ func (s *ProcessingHandlerTestSuite) TestETagReqMatch() {
poStr, imgdata, etag := s.sampleETagData(`"loremipsumdolor"`)
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
s.Equal(imgdata.Headers["ETag"], r.Header.Get("If-None-Match"))
s.Equal(imgdata.Headers().Get(httpheaders.Etag), r.Header.Get(httpheaders.IfNoneMatch))
rw.WriteHeader(304)
}))
@@ -503,7 +504,7 @@ func (s *ProcessingHandlerTestSuite) TestETagReqNotMatch() {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
s.Equal(`"loremipsum"`, r.Header.Get("If-None-Match"))
rw.Header().Set("ETag", imgdata.Headers["ETag"])
rw.Header().Set("ETag", imgdata.Headers().Get(httpheaders.Etag))
rw.WriteHeader(200)
rw.Write(s.readImageData(imgdata))
}))
@@ -554,7 +555,7 @@ func (s *ProcessingHandlerTestSuite) TestETagProcessingOptionsNotMatch() {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
s.Empty(r.Header.Get("If-None-Match"))
rw.Header().Set("ETag", imgdata.Headers["ETag"])
rw.Header().Set("ETag", imgdata.Headers().Get(httpheaders.Etag))
rw.WriteHeader(200)
rw.Write(s.readImageData(imgdata))
}))

View File

@@ -1,6 +1,7 @@
package security
import (
"github.com/imgproxy/imgproxy/v3/imagemeta"
"github.com/imgproxy/imgproxy/v3/imath"
)
@@ -19,3 +20,7 @@ func CheckDimensions(width, height, frames int, opts Options) error {
return nil
}
func CheckMeta(meta imagemeta.Meta, opts Options) error {
return CheckDimensions(meta.Width(), meta.Height(), 1, opts)
}

View File

@@ -3,6 +3,7 @@ package security
import (
"io"
"net/http"
"os"
)
// hardLimitReadCloser is a wrapper around io.ReadCloser
@@ -49,3 +50,23 @@ func LimitResponseSize(r *http.Response, opts Options) (*http.Response, error) {
return r, nil
}
// LimitFileSize limits the size of the file to MaxSrcFileSize (if set).
// It calls f.Stat() to get the file to get its size and returns an error
// if the size exceeds MaxSrcFileSize.
func LimitFileSize(f *os.File, opts Options) (*os.File, error) {
if opts.MaxSrcFileSize == 0 {
return f, nil
}
s, err := f.Stat()
if err != nil {
return nil, err
}
if int(s.Size()) > opts.MaxSrcFileSize {
return nil, newFileSizeError()
}
return f, nil
}

View File

@@ -113,10 +113,7 @@ func streamOriginImage(ctx context.Context, reqID string, r *http.Request, rw ht
rw.Header().Set("Content-Disposition", imagetype.ContentDisposition(filename, ext, po.ReturnAttachment))
}
setCacheControl(rw, po.Expires, map[string]string{
"Cache-Control": res.Header.Get("Cache-Control"),
"Expires": res.Header.Get("Expires"),
})
setCacheControl(rw, po.Expires, res.Header)
setCanonical(rw, imageURL)
rw.Header().Set("Content-Security-Policy", "script-src 'none'")

View File

@@ -2,7 +2,6 @@ package svg
import (
"io"
"net/http"
"strings"
"github.com/tdewolff/parse/v2"
@@ -12,16 +11,7 @@ import (
"github.com/imgproxy/imgproxy/v3/imagetype"
)
func cloneHeaders(src map[string]string) http.Header {
h := make(http.Header, len(src))
for k, v := range src {
h.Set(k, v)
}
return h
}
func Sanitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
func Sanitize(data imagedata.ImageData) (imagedata.ImageData, error) {
r := data.Reader()
l := xml.NewLexer(parse.NewInput(r))
@@ -55,15 +45,12 @@ func Sanitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
return nil, l.Err()
}
newData, err := imagedata.NewFromBytesWithFormat(
newData := imagedata.NewFromBytesWithFormat(
imagetype.SVG,
buf.Bytes(),
cloneHeaders(data.Headers),
data.Headers(),
)
if err != nil {
return nil, err
}
newData.SetCancel(cancel)
newData.AddCancel(cancel)
return newData, nil
case xml.StartTagToken:

View File

@@ -1,15 +1,14 @@
package svg
import (
"net/http"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/suite"
"go.withmatt.com/httpheaders"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/httpheaders"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/testutil"
)
@@ -25,18 +24,16 @@ func (s *SvgTestSuite) SetupSuite() {
s.Require().NoError(err)
}
func (s *SvgTestSuite) readTestFile(name string) *imagedata.ImageData {
func (s *SvgTestSuite) readTestFile(name string) imagedata.ImageData {
wd, err := os.Getwd()
s.Require().NoError(err)
data, err := os.ReadFile(filepath.Join(wd, "..", "testdata", name))
s.Require().NoError(err)
h := make(http.Header)
h.Set(httpheaders.ContentType, "image/svg+xml")
h.Set(httpheaders.CacheControl, "public, max-age=12345")
d, err := imagedata.NewFromBytes(data, h)
d, err := imagedata.NewFromBytes(data)
d.Headers().Set(httpheaders.ContentType, "image/svg+xml")
d.Headers().Set(httpheaders.CacheControl, "public, max-age=12345")
s.Require().NoError(err)
return d
@@ -49,7 +46,7 @@ func (s *SvgTestSuite) TestSanitize() {
s.Require().NoError(err)
s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), actual.Reader()))
s.Require().Equal(origin.Headers, actual.Headers)
s.Require().Equal(origin.Headers(), actual.Headers())
}
func TestSvg(t *testing.T) {

View File

@@ -353,7 +353,7 @@ func (img *Image) Pages() int {
return p
}
func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64, pages int) error {
func (img *Image) Load(imgdata imagedata.ImageData, shrink int, scale float64, pages int) error {
var tmp *C.VipsImage
err := C.int(0)
@@ -403,7 +403,7 @@ func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64,
return nil
}
func (img *Image) LoadThumbnail(imgdata *imagedata.ImageData) error {
func (img *Image) LoadThumbnail(imgdata imagedata.ImageData) error {
if imgdata.Format() != imagetype.HEIC && imgdata.Format() != imagetype.AVIF {
return newVipsError("Usupported image type to load thumbnail")
}
@@ -423,7 +423,7 @@ func (img *Image) LoadThumbnail(imgdata *imagedata.ImageData) error {
return nil
}
func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageData, error) {
func (img *Image) Save(imgtype imagetype.Type, quality int) (imagedata.ImageData, error) {
target := C.vips_target_new_to_memory()
cancel := func() {
@@ -470,15 +470,10 @@ func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageDat
b := ptrToBytes(ptr, int(imgsize))
imgdata, ierr := imagedata.NewFromBytesWithFormat(imgtype, b, make(http.Header))
if ierr != nil {
cancel()
return nil, ierr
}
i := imagedata.NewFromBytesWithFormat(imgtype, b, nil)
i.AddCancel(cancel)
imgdata.SetCancel(cancel)
return imgdata, nil
return i, nil
}
func (img *Image) Clear() {