1package favicon
2
3import (
4 "errors"
5 "image"
6 "math"
7
8 "github.com/bnema/purego-webp/libwebp"
9)
10
11const (
12 // maxWebPInputBytes bounds untrusted compressed favicon input before it is
13 // handed to libwebp.
14 maxWebPInputBytes = 5 << 20
15 // maxWebPDimension and maxWebPPixels bound the final RGBA allocation.
16 maxWebPDimension = 4096
17 maxWebPPixels = 4 << 20
18)
19
20var (
21 errWebPInputTooLarge = errors.New("webp favicon input exceeds size limit")
22 errWebPImageTooLarge = errors.New("webp favicon dimensions exceed size limit")
23 errWebPInvalidMetadata = errors.New("invalid webp favicon metadata")
24)
25
26// webpRuntime is the narrow infrastructure seam around the dynamically loaded
27// libwebp binding. It deliberately exposes only Go values, keeping backend
28// types out of ports and the domain.
29type webpRuntime interface {
30 Info(data []byte) (width, height int, err error)
31 DecodeRGBAInto(data, pix []byte, stride, width, height int) error
32}
33
34type libwebpRuntime struct{}
35
36func (libwebpRuntime) Info(data []byte) (int, int, error) {
37 width, height, ok, err := libwebp.WebPGetInfo(data)
38 if err != nil {
39 return 0, 0, err
40 }
41 if !ok {
42 return 0, 0, errWebPInvalidMetadata
43 }
44 return width, height, nil
45}
46
47func (libwebpRuntime) DecodeRGBAInto(data, pix []byte, stride, width, height int) error {
48 return libwebp.WebPDecodeRGBAIntoWithInfo(data, pix, stride, width, height)
49}
50
51// webPDecoder owns the failure boundary for the optional libwebp runtime.
52type webPDecoder struct{ runtime webpRuntime }
53
54func newWebPDecoder(runtime webpRuntime) *webPDecoder { return &webPDecoder{runtime: runtime} }
55
56func newDefaultWebPDecoder() *webPDecoder { return newWebPDecoder(libwebpRuntime{}) }
57
58func (d *webPDecoder) Decode(data []byte) (*image.NRGBA, error) {
59 if len(data) == 0 {
60 return nil, errWebPInvalidMetadata
61 }
62 if len(data) > maxWebPInputBytes {
63 return nil, errWebPInputTooLarge
64 }
65 if d == nil || d.runtime == nil {
66 return nil, errors.New("webp decoder unavailable")
67 }
68 width, height, err := d.runtime.Info(data)
69 if err != nil {
70 return nil, err
71 }
72 if width <= 0 || height <= 0 {
73 return nil, errWebPInvalidMetadata
74 }
75 if width > maxWebPDimension || height > maxWebPDimension || width > maxWebPPixels/height {
76 return nil, errWebPImageTooLarge
77 }
78 if width > math.MaxInt/4 {
79 return nil, errWebPImageTooLarge
80 }
81 stride := width * 4
82 if height > math.MaxInt/stride {
83 return nil, errWebPImageTooLarge
84 }
85
86 // Allocate exactly the destination buffer used by libwebp; no native image
87 // buffer or second decoded copy crosses this adapter boundary.
88 img := image.NewNRGBA(image.Rect(0, 0, width, height))
89 if err := d.runtime.DecodeRGBAInto(data, img.Pix, img.Stride, width, height); err != nil {
90 return nil, err
91 }
92 return img, nil
93}