123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- 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
- }
|