Motivation
The current Decode function in the image package provides no way to configure the underlying
format decoder. The registration system used by image makes it difficult to extend the
decoding behaviour of the registered format handlers. However there are many open issues that
could be resolved with a small number of decoding options. This proposal describes a way of
extending the existing format registration system to enable options to be passed to the individual
format decoders. It introduces a DecodeOptions type to serve as an extension point.
There are two broad areas of configuration that are considered. This proposal tackles them both
but they are orthogonal and may be independently evaluated and implemented. The areas are:
Avoiding large allocations on unprocessable images
This class of problem is generally caused by faulty or malicious header information in the image file.
Typically this could be a very large x or y dimension that causes a huge buffer to be allocated in
preparation for reading a stream of pixel data. This is currently possible to mitigate by first
decoding the image config to check the dimensions before proceeding to a full decode. However this has
the disadvantage of either reading the input twice or buffering and re-reading to decode. Discussion in
the relevant issues suggested that a 16k buffer would be suitable. The following issues refer to this
use case (with #5050 being the overarching issue)
- 5050 - image/gif: decoding untrusted (very large) images can cause huge memory allocations
- 10790 - x/image/webp: excessive memory consumption
- 12512 - image/png: limit memory Decode can use while decoding
- 10399 - x/image/bmp: out of memory
Being more tolerant of invalid input
The image decoders in the standard library are strict and fail on invalid input. There are classes
of invalid input that may be acceptable for some uses. The following issues suggest that a lenient
decoding mode would be helpful:
- 10447 - image/jpeg: add options to partially decode or tolerantly decode invalid images?
- 20804 - image/gif: decoding gif returns
unknown block type: 0x01 error
- 20899 - image/png: Decode failing on bitmap
- 20856 - image/gif: decoding gif returns
frame bounds larger than image bounds error
Other options related issues
In addition, the following issues suggest other areas that could benefit from decoding options but are
not considered further in this proposal:
- 8055 - image: decode / resize into an existing buffer
- 18098 - proposal: add Validate functions to image/jpeg, image/png etc.
- 4341 - image/jpeg: correct for EXIF orientation?
- 18365 - image/png: no support for setting and retrieving the PPI/DPI
Package Changes
The primary extensibility point is a new type. Its fields are discussed at the end of this section.
type DecodeOptions struct
Format decoders are registered via a new package level function RegisterFormatDecoder
func RegisterFormatDecoder(f FormatDecoder)
The FormatDecoder interface needs to be implemented by any package providing image format decoding:
type FormatDecoder interface {
// Name is the name of the format, like "jpeg" or "png".
Name() string
// Prefix is the magic prefix that identifies the format's encoding. The magic
// string can contain "?" wildcards that each match any one byte.
Prefix() string
// Decode decodes an image using the supplied options.
Decode(r io.Reader, o *DecodeOptions) (Image, string, error)
// DecodeConfig decodes the color model and dimensions of an image using the supplied options.
DecodeConfig(r io.Reader, o *DecodeOptions) (Config, string, error)
}
A new exported Decoder type is introduced:
type Decoder struct {
DecodeOptions
}
with a basic constructor function.
func NewDecoder() *Decoder
The Decoder type has the following method set:
// Decode decodes an image.
Decode(r io.Reader) (Image, string, error)
// DecodeConfig decodes the color model and dimensions of an image.
DecodeConfig(r io.Reader) (Config, string, error)
These Decode methods will sniff the type of the image from the reader and look up an appropriate
registered FormatDecoder. If one is available then its corresponding Decode method is called,
passing the Reader and the Decoder's options. This requires the Decode to have access to package
level registered format decoders.
To configure decoding the developer will create a new Decoder and then set the appropriate fields
before calling Decode or DecodeConfig.
The following options are proposed.
MaxHeight and MaxWidth
MaxHeight and MaxWidth are DecodeOptions fields that set the maximum allowable dimensions of a decoded image.
These fields restrict the allocation of large amounts of memory for faulty or malicious images.
MaxHeight int
MaxWidth int
These options are only used by the Decode method. When a Decoder attempts to decode an image with dimensions
exceeding either MaxHeight or MaxWidth then it should stop processing and return an error. A new error in the
image package ErrDimensionsExceeded could be defined as standard or it could be left to the decoder to return
its own error.
DecodeConfig should return a Config containing the width and height described in the image data stream
regardless of the config options.
ReturnPartial
ReturnPartial is a DecodeOptions field that instructs the decoder that it may return a partially decoded
image when an error is encountered.
The current Decode behaviour is that no image data is returned on error. When this option is true the caller
may receive a partial image from the decoding process when the decoder encounters a format related error
during the decoding process.
Backwards compatibility with existing registered formats
Existing callers of RegisterFormat will be supported by adapting the existing format type and
passing it to RegisterFormatDecoder, along these lines:
func (f *format) Name() {
return f.name
}
func (f *format) Prefix() {
return f.magic
}
func (f *format) Decode(r io.Reader, o *Options) (Image, string, error) {
m, err := f.decode(rr)
return m, f.name, err
}
func (f *format) DecodeConfig(r io.Reader, o *Options) (Config, string, error) {
m, err := f.decodeConfig(rr)
return m, f.name, err
}
The existing package level Decode and DecodeConfig functions will be rewritten to create a decoder and call it
without any options:
func Decode(r io.Reader) (Image, string, error) {
return NewDecoder().Decode(r)
}
func DecodeConfig(r io.Reader) (Config, string, error) {
return NewDecoder().DecodeConfig(r)
}
Deprecation of RegisterFormat
RegisterFormat would be marked as deprecated in favour of RegisterFormatDecoder.
Motivation
The current
Decodefunction in theimagepackage provides no way to configure the underlyingformat decoder. The registration system used by
imagemakes it difficult to extend thedecoding behaviour of the registered format handlers. However there are many open issues that
could be resolved with a small number of decoding options. This proposal describes a way of
extending the existing format registration system to enable options to be passed to the individual
format decoders. It introduces a
DecodeOptionstype to serve as an extension point.There are two broad areas of configuration that are considered. This proposal tackles them both
but they are orthogonal and may be independently evaluated and implemented. The areas are:
Avoiding large allocations on unprocessable images
This class of problem is generally caused by faulty or malicious header information in the image file.
Typically this could be a very large x or y dimension that causes a huge buffer to be allocated in
preparation for reading a stream of pixel data. This is currently possible to mitigate by first
decoding the image config to check the dimensions before proceeding to a full decode. However this has
the disadvantage of either reading the input twice or buffering and re-reading to decode. Discussion in
the relevant issues suggested that a 16k buffer would be suitable. The following issues refer to this
use case (with #5050 being the overarching issue)
Being more tolerant of invalid input
The image decoders in the standard library are strict and fail on invalid input. There are classes
of invalid input that may be acceptable for some uses. The following issues suggest that a lenient
decoding mode would be helpful:
unknown block type: 0x01errorframe bounds larger than image boundserrorOther options related issues
In addition, the following issues suggest other areas that could benefit from decoding options but are
not considered further in this proposal:
Package Changes
The primary extensibility point is a new type. Its fields are discussed at the end of this section.
Format decoders are registered via a new package level function
RegisterFormatDecoderThe FormatDecoder interface needs to be implemented by any package providing image format decoding:
A new exported Decoder type is introduced:
with a basic constructor function.
The Decoder type has the following method set:
These Decode methods will sniff the type of the image from the reader and look up an appropriate
registered FormatDecoder. If one is available then its corresponding Decode method is called,
passing the Reader and the Decoder's options. This requires the Decode to have access to package
level registered format decoders.
To configure decoding the developer will create a new Decoder and then set the appropriate fields
before calling Decode or DecodeConfig.
The following options are proposed.
MaxHeight and MaxWidth
MaxHeight and MaxWidth are DecodeOptions fields that set the maximum allowable dimensions of a decoded image.
These fields restrict the allocation of large amounts of memory for faulty or malicious images.
These options are only used by the Decode method. When a Decoder attempts to decode an image with dimensions
exceeding either MaxHeight or MaxWidth then it should stop processing and return an error. A new error in the
image package
ErrDimensionsExceededcould be defined as standard or it could be left to the decoder to returnits own error.
DecodeConfig should return a Config containing the width and height described in the image data stream
regardless of the config options.
ReturnPartial
ReturnPartial is a DecodeOptions field that instructs the decoder that it may return a partially decoded
image when an error is encountered.
The current Decode behaviour is that no image data is returned on error. When this option is true the caller
may receive a partial image from the decoding process when the decoder encounters a format related error
during the decoding process.
Backwards compatibility with existing registered formats
Existing callers of RegisterFormat will be supported by adapting the existing
formattype andpassing it to RegisterFormatDecoder, along these lines:
The existing package level
DecodeandDecodeConfigfunctions will be rewritten to create a decoder and call itwithout any options:
Deprecation of RegisterFormat
RegisterFormat would be marked as deprecated in favour of RegisterFormatDecoder.