mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-10-04 18:33:36 +02:00
196 lines
5.5 KiB
Go
196 lines
5.5 KiB
Go
package imagetype
|
|
|
|
import (
|
|
"io"
|
|
"slices"
|
|
|
|
"github.com/imgproxy/imgproxy/v3/bufreader"
|
|
)
|
|
|
|
const (
|
|
// maxDetectionLimit is maximum bytes detectors allowed to read from the source
|
|
maxDetectionLimit = 32 * 1024
|
|
)
|
|
|
|
// TypeDesc is used to store metadata about an image type.
|
|
// It represents the minimal information needed to make imgproxy to
|
|
// work with the type.
|
|
type TypeDesc struct {
|
|
String string
|
|
Ext string
|
|
Mime string
|
|
IsVector bool
|
|
SupportsAlpha bool
|
|
SupportsColourProfile bool
|
|
SupportsQuality bool
|
|
SupportsAnimationLoad bool
|
|
SupportsAnimationSave bool
|
|
SupportsThumbnail bool
|
|
}
|
|
|
|
// DetectFunc is a function that detects the image type from byte data
|
|
type DetectFunc func(r bufreader.ReadPeeker) (Type, error)
|
|
|
|
// detector is a struct that holds a detection function and its priority
|
|
type detector struct {
|
|
priority int // priority of the detector, lower is better
|
|
fn DetectFunc // function that detects the image type
|
|
}
|
|
|
|
// Registry holds the type registry
|
|
type registry struct {
|
|
detectors []detector
|
|
types []*TypeDesc
|
|
typesByName map[string]Type // maps type string to Type
|
|
}
|
|
|
|
// globalRegistry is the default registry instance
|
|
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.
|
|
// It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
|
|
func RegisterType(desc *TypeDesc) Type {
|
|
return globalRegistry.RegisterType(desc)
|
|
}
|
|
|
|
// GetTypeDesc returns the TypeDesc for the given Type.
|
|
// Returns nil if the type is not registered.
|
|
func GetTypeDesc(t Type) *TypeDesc {
|
|
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.
|
|
// 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 {
|
|
r.types = append(r.types, desc)
|
|
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.
|
|
// Returns nil if the type is not registered.
|
|
func (r *registry) GetTypeDesc(t Type) *TypeDesc {
|
|
if t <= 0 { // This would be "default" type
|
|
return nil
|
|
}
|
|
|
|
if int(t-1) >= len(r.types) {
|
|
return nil
|
|
}
|
|
|
|
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
|
|
// Detectors are tried in the order they were registered
|
|
func RegisterDetector(priority int, fn DetectFunc) {
|
|
globalRegistry.RegisterDetector(priority, fn)
|
|
}
|
|
|
|
// RegisterMagicBytes registers magic bytes for a specific image type
|
|
// Magic byte detectors are always tried before custom detectors
|
|
func RegisterMagicBytes(typ Type, signature ...[]byte) {
|
|
globalRegistry.RegisterMagicBytes(typ, signature...)
|
|
}
|
|
|
|
// Detect attempts to detect the image type from a reader.
|
|
// It first tries magic byte detection, then custom detectors in registration order
|
|
func Detect(r io.Reader) (Type, error) {
|
|
return globalRegistry.Detect(r)
|
|
}
|
|
|
|
// RegisterDetector registers a custom detector function on this registry instance
|
|
func (r *registry) RegisterDetector(priority int, fn DetectFunc) {
|
|
r.detectors = append(r.detectors, detector{
|
|
priority: priority,
|
|
fn: fn,
|
|
})
|
|
// Sort detectors by priority.
|
|
// We don't expect a huge number of detectors, and detectors should be registered at startup,
|
|
// so sorting them at each registration is okay.
|
|
slices.SortStableFunc(r.detectors, func(a, b detector) int {
|
|
return a.priority - b.priority
|
|
})
|
|
}
|
|
|
|
// RegisterMagicBytes registers magic bytes for a specific image type on this registry instance
|
|
func (r *registry) RegisterMagicBytes(typ Type, signature ...[]byte) {
|
|
r.RegisterDetector(-1, func(r bufreader.ReadPeeker) (Type, error) {
|
|
for _, sig := range signature {
|
|
b, err := r.Peek(len(sig))
|
|
if err != nil {
|
|
return Unknown, err
|
|
}
|
|
|
|
if hasMagicBytes(b, sig) {
|
|
return typ, nil
|
|
}
|
|
}
|
|
|
|
return Unknown, nil
|
|
})
|
|
}
|
|
|
|
// Detect runs image format detection
|
|
func (r *registry) Detect(re io.Reader) (Type, error) {
|
|
br := bufreader.New(io.LimitReader(re, maxDetectionLimit))
|
|
|
|
for _, d := range r.detectors {
|
|
br.Rewind()
|
|
typ, err := d.fn(br)
|
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
|
return Unknown, newTypeDetectionError(err)
|
|
}
|
|
if err == nil && typ != Unknown {
|
|
return typ, nil
|
|
}
|
|
}
|
|
|
|
return Unknown, newUnknownFormatError()
|
|
}
|
|
|
|
// hasMagicBytes checks if the data matches a magic byte signature
|
|
// Supports '?' characters in signature which match any byte
|
|
func hasMagicBytes(data []byte, magic []byte) bool {
|
|
if len(data) < len(magic) {
|
|
return false
|
|
}
|
|
|
|
for i, c := range magic {
|
|
if c != data[i] && c != '?' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|