mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-26 19:46:43 +02:00
IMG-23: replace an old imagetype with the new one (#1482)
* imagetype (new) -> imagetype * Always fallback in C-D, GetTypeMap -> GetTypeByName * Do not send origin ContentType
This commit is contained in:
@@ -132,48 +132,60 @@ func URLPath(s *string, name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImageTypes(it *[]imagetype.Type, name string) error {
|
func ImageTypes(it *[]imagetype.Type, name string) error {
|
||||||
if env := os.Getenv(name); len(env) > 0 {
|
// Get image types from environment variable
|
||||||
parts := strings.Split(env, ",")
|
env := os.Getenv(name)
|
||||||
|
if len(env) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
*it = make([]imagetype.Type, 0, len(parts))
|
parts := strings.Split(env, ",")
|
||||||
|
*it = make([]imagetype.Type, 0, len(parts))
|
||||||
|
|
||||||
for _, p := range parts {
|
for _, p := range parts {
|
||||||
pt := strings.TrimSpace(p)
|
part := strings.TrimSpace(p)
|
||||||
if t, ok := imagetype.Types[pt]; ok {
|
|
||||||
*it = append(*it, t)
|
// For every part passed through the environment variable,
|
||||||
} else {
|
// check if it matches any of the image types defined in
|
||||||
return fmt.Errorf("Unknown image format: %s", pt)
|
// the imagetype package or return error.
|
||||||
}
|
t, ok := imagetype.GetTypeByName(part)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown image format: %s", part)
|
||||||
}
|
}
|
||||||
|
*it = append(*it, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImageTypesQuality(m map[imagetype.Type]int, name string) error {
|
func ImageTypesQuality(m map[imagetype.Type]int, name string) error {
|
||||||
if env := os.Getenv(name); len(env) > 0 {
|
env := os.Getenv(name)
|
||||||
parts := strings.Split(env, ",")
|
if len(env) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range parts {
|
parts := strings.Split(env, ",")
|
||||||
i := strings.Index(p, "=")
|
|
||||||
if i < 0 {
|
|
||||||
return fmt.Errorf("Invalid format quality string: %s", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
imgtypeStr, qStr := strings.TrimSpace(p[:i]), strings.TrimSpace(p[i+1:])
|
for _, p := range parts {
|
||||||
|
i := strings.Index(p, "=")
|
||||||
imgtype, ok := imagetype.Types[imgtypeStr]
|
if i < 0 {
|
||||||
if !ok {
|
return fmt.Errorf("invalid format quality string: %s", p)
|
||||||
return fmt.Errorf("Invalid format: %s", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
q, err := strconv.Atoi(qStr)
|
|
||||||
if err != nil || q <= 0 || q > 100 {
|
|
||||||
return fmt.Errorf("Invalid quality: %s", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
m[imgtype] = q
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split the string into image type and quality
|
||||||
|
imgtypeStr, qStr := strings.TrimSpace(p[:i]), strings.TrimSpace(p[i+1:])
|
||||||
|
|
||||||
|
// Check if quality is a valid integer
|
||||||
|
q, err := strconv.Atoi(qStr)
|
||||||
|
if err != nil || q <= 0 || q > 100 {
|
||||||
|
return fmt.Errorf("invalid quality: %s", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, ok := imagetype.GetTypeByName(imgtypeStr)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown image format: %s", imgtypeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
m[t] = q
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
76
httpheaders/cdv.go
Normal file
76
httpheaders/cdv.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package httpheaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mime"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// fallbackStem is used when the stem cannot be determined from the URL.
|
||||||
|
fallbackStem = "image"
|
||||||
|
|
||||||
|
// Content-Disposition header format
|
||||||
|
contentDispositionsHeader = "%s; filename=\"%s%s\""
|
||||||
|
|
||||||
|
// "inline" disposition types
|
||||||
|
inlineDisposition = "inline"
|
||||||
|
|
||||||
|
// "attachment" disposition type
|
||||||
|
attachmentDisposition = "attachment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContentDispositionValue generates the content-disposition header value.
|
||||||
|
//
|
||||||
|
// It uses the following priorities:
|
||||||
|
// 1. By default, it uses the filename and extension from the URL.
|
||||||
|
// 2. If `filename` is provided, it overrides the URL filename.
|
||||||
|
// 3. If `contentType` is provided, it tries to determine the extension from the content type.
|
||||||
|
// 4. If `ext` is provided, it overrides any extension determined from the URL or header.
|
||||||
|
// 5. If the filename is still empty, it uses fallback stem.
|
||||||
|
func ContentDispositionValue(url, filename, ext, contentType string, returnAttachment bool) string {
|
||||||
|
// By default, let's use the URL filename and extension
|
||||||
|
_, urlFilename := filepath.Split(url)
|
||||||
|
urlExt := filepath.Ext(urlFilename)
|
||||||
|
|
||||||
|
var rStem string
|
||||||
|
|
||||||
|
// Avoid strings.TrimSuffix allocation by using slice operation
|
||||||
|
if urlExt != "" {
|
||||||
|
rStem = urlFilename[:len(urlFilename)-len(urlExt)]
|
||||||
|
} else {
|
||||||
|
rStem = urlFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
var rExt = urlExt
|
||||||
|
|
||||||
|
// If filename is provided explicitly, use it
|
||||||
|
if len(filename) > 0 {
|
||||||
|
rStem = filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ext is provided explicitly, use it
|
||||||
|
if len(ext) > 0 {
|
||||||
|
rExt = ext
|
||||||
|
} else if len(contentType) > 0 {
|
||||||
|
exts, err := mime.ExtensionsByType(contentType)
|
||||||
|
if err == nil && len(exts) != 0 {
|
||||||
|
rExt = exts[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If fallback is requested, and filename is still empty, override it with fallbackStem
|
||||||
|
if len(rStem) == 0 {
|
||||||
|
rStem = fallbackStem
|
||||||
|
}
|
||||||
|
|
||||||
|
disposition := inlineDisposition
|
||||||
|
|
||||||
|
// Create the content-disposition header value
|
||||||
|
if returnAttachment {
|
||||||
|
disposition = attachmentDisposition
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(contentDispositionsHeader, disposition, strings.ReplaceAll(rStem, `"`, "%22"), rExt)
|
||||||
|
}
|
127
httpheaders/cdv_test.go
Normal file
127
httpheaders/cdv_test.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package httpheaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContentDispositionValue(t *testing.T) {
|
||||||
|
// Test cases for ContentDispositionValue function that generates content-disposition headers
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
filename string
|
||||||
|
ext string
|
||||||
|
returnAttachment bool
|
||||||
|
expected string
|
||||||
|
contentType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "BasicURL",
|
||||||
|
url: "http://example.com/test.jpg",
|
||||||
|
filename: "",
|
||||||
|
ext: "",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"test.jpg\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyFilename",
|
||||||
|
url: "http://example.com/path/to/",
|
||||||
|
filename: "",
|
||||||
|
ext: "",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"image\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyFilenameWithExt",
|
||||||
|
url: "http://example.com/path/to/",
|
||||||
|
filename: "",
|
||||||
|
ext: ".png",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"image.png\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyFilenameWithFilenameAndExt",
|
||||||
|
url: "http://example.com/path/to/",
|
||||||
|
filename: "example",
|
||||||
|
ext: ".png",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"example.png\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyFilenameWithFilenameOverride",
|
||||||
|
url: "http://example.com/path/to/",
|
||||||
|
filename: "example",
|
||||||
|
ext: ".jpg",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"example.jpg\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PresentFilenameWithExtOverride",
|
||||||
|
url: "http://example.com/path/to/face.png",
|
||||||
|
filename: "",
|
||||||
|
ext: ".jpg",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"face.jpg\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PresentFilenameWithFilenameOverride",
|
||||||
|
url: "http://example.com/path/to/123.png",
|
||||||
|
filename: "face",
|
||||||
|
ext: ".jpg",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"face.jpg\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyFilenameWithFallback",
|
||||||
|
url: "http://example.com/path/to/",
|
||||||
|
filename: "",
|
||||||
|
ext: ".png",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"image.png\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AttachmentDisposition",
|
||||||
|
url: "http://example.com/test.jpg",
|
||||||
|
filename: "",
|
||||||
|
ext: "",
|
||||||
|
contentType: "",
|
||||||
|
returnAttachment: true,
|
||||||
|
expected: "attachment; filename=\"test.jpg\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FilenameWithQuotes",
|
||||||
|
url: "http://example.com/test.jpg",
|
||||||
|
filename: "my\"file",
|
||||||
|
ext: ".png",
|
||||||
|
returnAttachment: false,
|
||||||
|
contentType: "",
|
||||||
|
expected: "inline; filename=\"my%22file.png\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FilenameWithContentType",
|
||||||
|
url: "http://example.com/test",
|
||||||
|
filename: "my\"file",
|
||||||
|
ext: "",
|
||||||
|
contentType: "image/png",
|
||||||
|
returnAttachment: false,
|
||||||
|
expected: "inline; filename=\"my%22file.png\"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := ContentDispositionValue(tc.url, tc.filename, tc.ext, tc.contentType, tc.returnAttachment)
|
||||||
|
require.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
var (
|
var (
|
||||||
JPEG = RegisterType(&TypeDesc{
|
JPEG = RegisterType(&TypeDesc{
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
@@ -1,181 +0,0 @@
|
|||||||
// Code generated by gen_imagetype.go; DO NOT EDIT.
|
|
||||||
|
|
||||||
package imagetype
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Type int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Unknown Type = iota
|
|
||||||
JPEG
|
|
||||||
JXL
|
|
||||||
PNG
|
|
||||||
WEBP
|
|
||||||
GIF
|
|
||||||
ICO
|
|
||||||
SVG
|
|
||||||
HEIC
|
|
||||||
AVIF
|
|
||||||
BMP
|
|
||||||
TIFF
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
contentDispositionFilenameFallback = "image"
|
|
||||||
contentDispositionsFmt = "%s; filename=\"%s%s\""
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Types = map[string]Type{
|
|
||||||
"jpeg": JPEG,
|
|
||||||
"jpg": JPEG,
|
|
||||||
"jxl": JXL,
|
|
||||||
"png": PNG,
|
|
||||||
"webp": WEBP,
|
|
||||||
"gif": GIF,
|
|
||||||
"ico": ICO,
|
|
||||||
"svg": SVG,
|
|
||||||
"heic": HEIC,
|
|
||||||
"avif": AVIF,
|
|
||||||
"bmp": BMP,
|
|
||||||
"tiff": TIFF,
|
|
||||||
}
|
|
||||||
|
|
||||||
mimes = map[Type]string{
|
|
||||||
JPEG: "image/jpeg",
|
|
||||||
JXL: "image/jxl",
|
|
||||||
PNG: "image/png",
|
|
||||||
WEBP: "image/webp",
|
|
||||||
GIF: "image/gif",
|
|
||||||
ICO: "image/x-icon",
|
|
||||||
SVG: "image/svg+xml",
|
|
||||||
HEIC: "image/heif",
|
|
||||||
AVIF: "image/avif",
|
|
||||||
BMP: "image/bmp",
|
|
||||||
TIFF: "image/tiff",
|
|
||||||
}
|
|
||||||
|
|
||||||
extensions = map[Type]string{
|
|
||||||
JPEG: ".jpg",
|
|
||||||
JXL: ".jxl",
|
|
||||||
PNG: ".png",
|
|
||||||
WEBP: ".webp",
|
|
||||||
GIF: ".gif",
|
|
||||||
ICO: ".ico",
|
|
||||||
SVG: ".svg",
|
|
||||||
HEIC: ".heic",
|
|
||||||
AVIF: ".avif",
|
|
||||||
BMP: ".bmp",
|
|
||||||
TIFF: ".tiff",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (it Type) String() string {
|
|
||||||
// JPEG has two names, we should use only the full one
|
|
||||||
if it == JPEG {
|
|
||||||
return "jpeg"
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range Types {
|
|
||||||
if v == it {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) Ext() string {
|
|
||||||
if ext, ok := extensions[it]; ok {
|
|
||||||
return ext
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) MarshalJSON() ([]byte, error) {
|
|
||||||
for k, v := range Types {
|
|
||||||
if v == it {
|
|
||||||
return []byte(fmt.Sprintf("%q", k)), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []byte("null"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) Mime() string {
|
|
||||||
if mime, ok := mimes[it]; ok {
|
|
||||||
return mime
|
|
||||||
}
|
|
||||||
|
|
||||||
return "application/octet-stream"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) ContentDisposition(filename string, returnAttachment bool) string {
|
|
||||||
return ContentDisposition(filename, it.Ext(), returnAttachment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) ContentDispositionFromURL(imageURL string, returnAttachment bool) string {
|
|
||||||
url, err := url.Parse(imageURL)
|
|
||||||
if err != nil {
|
|
||||||
return it.ContentDisposition(contentDispositionFilenameFallback, returnAttachment)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, filename := filepath.Split(url.Path)
|
|
||||||
if len(filename) == 0 {
|
|
||||||
return it.ContentDisposition(contentDispositionFilenameFallback, returnAttachment)
|
|
||||||
}
|
|
||||||
|
|
||||||
return it.ContentDisposition(strings.TrimSuffix(filename, filepath.Ext(filename)), returnAttachment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) IsVector() bool {
|
|
||||||
return it == SVG
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) SupportsAlpha() bool {
|
|
||||||
return it != JPEG
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) SupportsAnimationLoad() bool {
|
|
||||||
return it == GIF || it == WEBP || it == JXL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) SupportsAnimationSave() bool {
|
|
||||||
return it == GIF || it == WEBP
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) SupportsColourProfile() bool {
|
|
||||||
return it == JPEG ||
|
|
||||||
it == JXL ||
|
|
||||||
it == PNG ||
|
|
||||||
it == WEBP ||
|
|
||||||
it == HEIC ||
|
|
||||||
it == AVIF
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) SupportsQuality() bool {
|
|
||||||
return it == JPEG ||
|
|
||||||
it == WEBP ||
|
|
||||||
it == HEIC ||
|
|
||||||
it == AVIF ||
|
|
||||||
it == JXL ||
|
|
||||||
it == TIFF
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it Type) SupportsThumbnail() bool {
|
|
||||||
return it == HEIC || it == AVIF
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContentDisposition(filename, ext string, returnAttachment bool) string {
|
|
||||||
disposition := "inline"
|
|
||||||
|
|
||||||
if returnAttachment {
|
|
||||||
disposition = "attachment"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(contentDispositionsFmt, disposition, strings.ReplaceAll(filename, `"`, "%22"), ext)
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -31,13 +31,22 @@ type TypeDesc struct {
|
|||||||
type DetectFunc func(r bufreader.ReadPeeker) (Type, error)
|
type DetectFunc func(r bufreader.ReadPeeker) (Type, error)
|
||||||
|
|
||||||
// Registry holds the type registry
|
// Registry holds the type registry
|
||||||
type Registry struct {
|
type registry struct {
|
||||||
detectors []DetectFunc
|
detectors []DetectFunc
|
||||||
types []*TypeDesc
|
types []*TypeDesc
|
||||||
|
typesByName map[string]Type // maps type string to Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// globalRegistry is the default registry instance
|
// globalRegistry is the default registry instance
|
||||||
var globalRegistry = &Registry{}
|
var globalRegistry = NewRegistry()
|
||||||
|
|
||||||
|
// NewRegistry creates a new image type registry.
|
||||||
|
func NewRegistry() *registry {
|
||||||
|
return ®istry{
|
||||||
|
types: nil,
|
||||||
|
typesByName: make(map[string]Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterType registers a new image type in the global registry.
|
// RegisterType registers a new image type in the global registry.
|
||||||
// It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
|
// It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
|
||||||
@@ -51,16 +60,33 @@ func GetTypeDesc(t Type) *TypeDesc {
|
|||||||
return globalRegistry.GetTypeDesc(t)
|
return globalRegistry.GetTypeDesc(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTypes returns all registered image types.
|
||||||
|
func GetTypeByName(name string) (Type, bool) {
|
||||||
|
return globalRegistry.GetTypeByName(name)
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterType registers a new image type in this registry.
|
// RegisterType registers a new image type in this registry.
|
||||||
// It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
|
// It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
|
||||||
func (r *Registry) RegisterType(desc *TypeDesc) Type {
|
func (r *registry) RegisterType(desc *TypeDesc) Type {
|
||||||
r.types = append(r.types, desc)
|
r.types = append(r.types, desc)
|
||||||
return Type(len(r.types)) // 0 is unknown
|
typ := Type(len(r.types)) // 0 is unknown
|
||||||
|
r.typesByName[desc.String] = typ
|
||||||
|
|
||||||
|
// NOTE: this is a special case for JPEG. The problem is that JPEG is using
|
||||||
|
// several alternative extensions and processing_options.go is using extension to
|
||||||
|
// find a type by key. There might be not the only case (e.g. ".tif/.tiff").
|
||||||
|
// We need to handle this case in a more generic way.
|
||||||
|
if desc.String == "jpeg" {
|
||||||
|
// JPEG is a special case, we need to alias it
|
||||||
|
r.typesByName["jpg"] = typ
|
||||||
|
}
|
||||||
|
|
||||||
|
return typ
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTypeDesc returns the TypeDesc for the given Type.
|
// GetTypeDesc returns the TypeDesc for the given Type.
|
||||||
// Returns nil if the type is not registered.
|
// Returns nil if the type is not registered.
|
||||||
func (r *Registry) GetTypeDesc(t Type) *TypeDesc {
|
func (r *registry) GetTypeDesc(t Type) *TypeDesc {
|
||||||
if t <= 0 { // This would be "default" type
|
if t <= 0 { // This would be "default" type
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -72,6 +98,12 @@ func (r *Registry) GetTypeDesc(t Type) *TypeDesc {
|
|||||||
return r.types[t-1]
|
return r.types[t-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTypeByName returns Type by it's name
|
||||||
|
func (r *registry) GetTypeByName(name string) (Type, bool) {
|
||||||
|
typ, ok := r.typesByName[name]
|
||||||
|
return typ, ok
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterDetector registers a custom detector function
|
// RegisterDetector registers a custom detector function
|
||||||
// Detectors are tried in the order they were registered
|
// Detectors are tried in the order they were registered
|
||||||
func RegisterDetector(detector DetectFunc) {
|
func RegisterDetector(detector DetectFunc) {
|
||||||
@@ -91,12 +123,12 @@ func Detect(r io.Reader) (Type, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterDetector registers a custom detector function on this registry instance
|
// RegisterDetector registers a custom detector function on this registry instance
|
||||||
func (r *Registry) RegisterDetector(detector DetectFunc) {
|
func (r *registry) RegisterDetector(detector DetectFunc) {
|
||||||
r.detectors = append(r.detectors, detector)
|
r.detectors = append(r.detectors, detector)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterMagicBytes registers magic bytes for a specific image type on this registry instance
|
// RegisterMagicBytes registers magic bytes for a specific image type on this registry instance
|
||||||
func (r *Registry) RegisterMagicBytes(typ Type, signature ...[]byte) {
|
func (r *registry) RegisterMagicBytes(typ Type, signature ...[]byte) {
|
||||||
r.detectors = append(r.detectors, func(r bufreader.ReadPeeker) (Type, error) {
|
r.detectors = append(r.detectors, func(r bufreader.ReadPeeker) (Type, error) {
|
||||||
for _, sig := range signature {
|
for _, sig := range signature {
|
||||||
b, err := r.Peek(len(sig))
|
b, err := r.Peek(len(sig))
|
||||||
@@ -114,7 +146,7 @@ func (r *Registry) RegisterMagicBytes(typ Type, signature ...[]byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Detect runs image format detection
|
// Detect runs image format detection
|
||||||
func (r *Registry) Detect(re io.Reader) (Type, error) {
|
func (r *registry) Detect(re io.Reader) (Type, error) {
|
||||||
br := bufreader.New(io.LimitReader(re, maxDetectionLimit))
|
br := bufreader.New(io.LimitReader(re, maxDetectionLimit))
|
||||||
|
|
||||||
for _, fn := range globalRegistry.detectors {
|
for _, fn := range globalRegistry.detectors {
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func TestRegisterType(t *testing.T) {
|
func TestRegisterType(t *testing.T) {
|
||||||
// Create a separate registry for testing to avoid conflicts with global registry
|
// Create a separate registry for testing to avoid conflicts with global registry
|
||||||
testRegistry := &Registry{}
|
testRegistry := NewRegistry()
|
||||||
|
|
||||||
// Register a custom type
|
// Register a custom type
|
||||||
customDesc := &TypeDesc{
|
customDesc := &TypeDesc{
|
||||||
@@ -141,7 +141,7 @@ func TestTypeProperties(t *testing.T) {
|
|||||||
|
|
||||||
func TestRegisterDetector(t *testing.T) {
|
func TestRegisterDetector(t *testing.T) {
|
||||||
// Create a test registry to avoid interfering with global state
|
// Create a test registry to avoid interfering with global state
|
||||||
testRegistry := &Registry{}
|
testRegistry := NewRegistry()
|
||||||
|
|
||||||
// Create a test detector function
|
// Create a test detector function
|
||||||
testDetector := func(r bufreader.ReadPeeker) (Type, error) {
|
testDetector := func(r bufreader.ReadPeeker) (Type, error) {
|
||||||
@@ -165,7 +165,7 @@ func TestRegisterDetector(t *testing.T) {
|
|||||||
|
|
||||||
func TestRegisterMagicBytes(t *testing.T) {
|
func TestRegisterMagicBytes(t *testing.T) {
|
||||||
// Create a test registry to avoid interfering with global state
|
// Create a test registry to avoid interfering with global state
|
||||||
testRegistry := &Registry{}
|
testRegistry := NewRegistry()
|
||||||
|
|
||||||
require.Empty(t, testRegistry.detectors)
|
require.Empty(t, testRegistry.detectors)
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@@ -1,4 +1,4 @@
|
|||||||
package imagetype_new
|
package imagetype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
@@ -570,7 +570,7 @@ func applyFormatQualityOption(po *ProcessingOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < argsLen; i += 2 {
|
for i := 0; i < argsLen; i += 2 {
|
||||||
f, ok := imagetype.Types[args[i]]
|
f, ok := imagetype.GetTypeByName(args[i])
|
||||||
if !ok {
|
if !ok {
|
||||||
return newOptionArgumentError("Invalid image format: %s", args[i])
|
return newOptionArgumentError("Invalid image format: %s", args[i])
|
||||||
}
|
}
|
||||||
@@ -754,7 +754,7 @@ func applyFormatOption(po *ProcessingOptions, args []string) error {
|
|||||||
return newOptionArgumentError("Invalid format arguments: %v", args)
|
return newOptionArgumentError("Invalid format arguments: %v", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f, ok := imagetype.Types[args[0]]; ok {
|
if f, ok := imagetype.GetTypeByName(args[0]); ok {
|
||||||
po.Format = f
|
po.Format = f
|
||||||
} else {
|
} else {
|
||||||
return newOptionArgumentError("Invalid image format: %s", args[0])
|
return newOptionArgumentError("Invalid image format: %s", args[0])
|
||||||
@@ -775,7 +775,7 @@ func applyCacheBusterOption(po *ProcessingOptions, args []string) error {
|
|||||||
|
|
||||||
func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) error {
|
func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) error {
|
||||||
for _, format := range args {
|
for _, format := range args {
|
||||||
if f, ok := imagetype.Types[format]; ok {
|
if f, ok := imagetype.GetTypeByName(format); ok {
|
||||||
po.SkipProcessingFormats = append(po.SkipProcessingFormats, f)
|
po.SkipProcessingFormats = append(po.SkipProcessingFormats, f)
|
||||||
} else {
|
} else {
|
||||||
return newOptionArgumentError("Invalid image format in skip processing: %s", format)
|
return newOptionArgumentError("Invalid image format in skip processing: %s", format)
|
||||||
|
@@ -158,12 +158,13 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
|
|||||||
checkErr(r.Context(), "image_data_size", err)
|
checkErr(r.Context(), "image_data_size", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentDisposition string
|
contentDisposition := httpheaders.ContentDispositionValue(
|
||||||
if len(po.Filename) > 0 {
|
originURL,
|
||||||
contentDisposition = resultData.Format().ContentDisposition(po.Filename, po.ReturnAttachment)
|
po.Filename,
|
||||||
} else {
|
resultData.Format().Ext(),
|
||||||
contentDisposition = resultData.Format().ContentDispositionFromURL(originURL, po.ReturnAttachment)
|
"",
|
||||||
}
|
po.ReturnAttachment,
|
||||||
|
)
|
||||||
|
|
||||||
rw.Header().Set(httpheaders.ContentType, resultData.Format().Mime())
|
rw.Header().Set(httpheaders.ContentType, resultData.Format().Mime())
|
||||||
rw.Header().Set(httpheaders.ContentDisposition, contentDisposition)
|
rw.Header().Set(httpheaders.ContentDisposition, contentDisposition)
|
||||||
|
32
stream.go
32
stream.go
@@ -3,9 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -13,8 +11,8 @@ import (
|
|||||||
|
|
||||||
"github.com/imgproxy/imgproxy/v3/config"
|
"github.com/imgproxy/imgproxy/v3/config"
|
||||||
"github.com/imgproxy/imgproxy/v3/cookies"
|
"github.com/imgproxy/imgproxy/v3/cookies"
|
||||||
|
"github.com/imgproxy/imgproxy/v3/httpheaders"
|
||||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
|
||||||
"github.com/imgproxy/imgproxy/v3/metrics"
|
"github.com/imgproxy/imgproxy/v3/metrics"
|
||||||
"github.com/imgproxy/imgproxy/v3/metrics/stats"
|
"github.com/imgproxy/imgproxy/v3/metrics/stats"
|
||||||
"github.com/imgproxy/imgproxy/v3/options"
|
"github.com/imgproxy/imgproxy/v3/options"
|
||||||
@@ -92,26 +90,14 @@ func streamOriginImage(ctx context.Context, reqID string, r *http.Request, rw ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode < 300 {
|
if res.StatusCode < 300 {
|
||||||
var filename, ext, mimetype string
|
contentDisposition := httpheaders.ContentDispositionValue(
|
||||||
|
req.URL().Path,
|
||||||
_, filename = filepath.Split(req.URL().Path)
|
po.Filename,
|
||||||
ext = filepath.Ext(filename)
|
"",
|
||||||
|
rw.Header().Get(httpheaders.ContentType),
|
||||||
if len(po.Filename) > 0 {
|
po.ReturnAttachment,
|
||||||
filename = po.Filename
|
)
|
||||||
} else {
|
rw.Header().Set("Content-Disposition", contentDisposition)
|
||||||
filename = filename[:len(filename)-len(ext)]
|
|
||||||
}
|
|
||||||
|
|
||||||
mimetype = rw.Header().Get("Content-Type")
|
|
||||||
|
|
||||||
if len(ext) == 0 && len(mimetype) > 0 {
|
|
||||||
if exts, err := mime.ExtensionsByType(mimetype); err == nil && len(exts) != 0 {
|
|
||||||
ext = exts[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Disposition", imagetype.ContentDisposition(filename, ext, po.ReturnAttachment))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheControl(rw, po.Expires, res.Header)
|
setCacheControl(rw, po.Expires, res.Header)
|
||||||
|
Reference in New Issue
Block a user