1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gif
6
7import (
8	"bytes"
9	"compress/lzw"
10	"encoding/hex"
11	"image"
12	"image/color"
13	"image/color/palette"
14	"io"
15	"os"
16	"reflect"
17	"runtime"
18	"runtime/debug"
19	"strings"
20	"testing"
21)
22
23// header, palette and trailer are parts of a valid 2x1 GIF image.
24const (
25	headerStr = "GIF89a" +
26		"\x02\x00\x01\x00" + // width=2, height=1
27		"\x80\x00\x00" // headerFields=(a color table of 2 pixels), backgroundIndex, aspect
28	paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette
29	trailerStr = "\x3b"
30)
31
32// lzw.NewReader wants an io.ByteReader, this ensures we're compatible.
33var _ io.ByteReader = (*blockReader)(nil)
34
35// lzwEncode returns an LZW encoding (with 2-bit literals) of in.
36func lzwEncode(in []byte) []byte {
37	b := &bytes.Buffer{}
38	w := lzw.NewWriter(b, lzw.LSB, 2)
39	if _, err := w.Write(in); err != nil {
40		panic(err)
41	}
42	if err := w.Close(); err != nil {
43		panic(err)
44	}
45	return b.Bytes()
46}
47
48func TestDecode(t *testing.T) {
49	// extra contains superfluous bytes to inject into the GIF, either at the end
50	// of an existing data sub-block (past the LZW End of Information code) or in
51	// a separate data sub-block. The 0x02 values are arbitrary.
52	const extra = "\x02\x02\x02\x02"
53
54	testCases := []struct {
55		nPix int // The number of pixels in the image data.
56		// If non-zero, write this many extra bytes inside the data sub-block
57		// containing the LZW end code.
58		extraExisting int
59		// If non-zero, write an extra block of this many bytes.
60		extraSeparate int
61		wantErr       error
62	}{
63		{0, 0, 0, errNotEnough},
64		{1, 0, 0, errNotEnough},
65		{2, 0, 0, nil},
66		// An extra data sub-block after the compressed section with 1 byte which we
67		// silently skip.
68		{2, 0, 1, nil},
69		// An extra data sub-block after the compressed section with 2 bytes. In
70		// this case we complain that there is too much data.
71		{2, 0, 2, errTooMuch},
72		// Too much pixel data.
73		{3, 0, 0, errTooMuch},
74		// An extra byte after LZW data, but inside the same data sub-block.
75		{2, 1, 0, nil},
76		// Two extra bytes after LZW data, but inside the same data sub-block.
77		{2, 2, 0, nil},
78		// Extra data exists in the final sub-block with LZW data, AND there is
79		// a bogus sub-block following.
80		{2, 1, 1, errTooMuch},
81	}
82	for _, tc := range testCases {
83		b := &bytes.Buffer{}
84		b.WriteString(headerStr)
85		b.WriteString(paletteStr)
86		// Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
87		// then this should result in an invalid GIF image. First, write a
88		// magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
89		// byte, and 2-bit LZW literals.
90		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
91		if tc.nPix > 0 {
92			enc := lzwEncode(make([]byte, tc.nPix))
93			if len(enc)+tc.extraExisting > 0xff {
94				t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large",
95					tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc))
96				continue
97			}
98
99			// Write the size of the data sub-block containing the LZW data.
100			b.WriteByte(byte(len(enc) + tc.extraExisting))
101
102			// Write the LZW data.
103			b.Write(enc)
104
105			// Write extra bytes inside the same data sub-block where LZW data
106			// ended. Each arbitrarily 0x02.
107			b.WriteString(extra[:tc.extraExisting])
108		}
109
110		if tc.extraSeparate > 0 {
111			// Data sub-block size. This indicates how many extra bytes follow.
112			b.WriteByte(byte(tc.extraSeparate))
113			b.WriteString(extra[:tc.extraSeparate])
114		}
115		b.WriteByte(0x00) // An empty block signifies the end of the image data.
116		b.WriteString(trailerStr)
117
118		got, err := Decode(b)
119		if err != tc.wantErr {
120			t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot  %v\nwant %v",
121				tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr)
122		}
123
124		if tc.wantErr != nil {
125			continue
126		}
127		want := &image.Paletted{
128			Pix:    []uint8{0, 0},
129			Stride: 2,
130			Rect:   image.Rect(0, 0, 2, 1),
131			Palette: color.Palette{
132				color.RGBA{0x10, 0x20, 0x30, 0xff},
133				color.RGBA{0x40, 0x50, 0x60, 0xff},
134			},
135		}
136		if !reflect.DeepEqual(got, want) {
137			t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot  %v\nwant %v",
138				tc.nPix, tc.extraExisting, tc.extraSeparate, got, want)
139		}
140	}
141}
142
143func TestTransparentIndex(t *testing.T) {
144	b := &bytes.Buffer{}
145	b.WriteString(headerStr)
146	b.WriteString(paletteStr)
147	for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
148		if transparentIndex < 2 {
149			// Write the graphic control for the transparent index.
150			b.WriteString("\x21\xf9\x04\x01\x00\x00")
151			b.WriteByte(byte(transparentIndex))
152			b.WriteByte(0)
153		}
154		// Write an image with bounds 2x1, as per TestDecode.
155		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
156		enc := lzwEncode([]byte{0x00, 0x00})
157		if len(enc) > 0xff {
158			t.Fatalf("compressed length %d is too large", len(enc))
159		}
160		b.WriteByte(byte(len(enc)))
161		b.Write(enc)
162		b.WriteByte(0x00)
163	}
164	b.WriteString(trailerStr)
165
166	g, err := DecodeAll(b)
167	if err != nil {
168		t.Fatalf("DecodeAll: %v", err)
169	}
170	c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
171	c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
172	cz := color.RGBA{}
173	wants := []color.Palette{
174		{cz, c1},
175		{c0, cz},
176		{c0, c1},
177	}
178	if len(g.Image) != len(wants) {
179		t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
180	}
181	for i, want := range wants {
182		got := g.Image[i].Palette
183		if !reflect.DeepEqual(got, want) {
184			t.Errorf("palette #%d:\ngot  %v\nwant %v", i, got, want)
185		}
186	}
187}
188
189// testGIF is a simple GIF that we can modify to test different scenarios.
190var testGIF = []byte{
191	'G', 'I', 'F', '8', '9', 'a',
192	1, 0, 1, 0, // w=1, h=1 (6)
193	128, 0, 0, // headerFields, bg, aspect (10)
194	0, 0, 0, 1, 1, 1, // color table and graphics control (13)
195	0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
196	// frame 1 (0,0 - 1,1)
197	0x2c,
198	0x00, 0x00, 0x00, 0x00,
199	0x01, 0x00, 0x01, 0x00, // (32)
200	0x00,
201	0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels
202	// trailer
203	0x3b,
204}
205
206func try(t *testing.T, b []byte, want string) {
207	_, err := DecodeAll(bytes.NewReader(b))
208	var got string
209	if err != nil {
210		got = err.Error()
211	}
212	if got != want {
213		t.Fatalf("got %v, want %v", got, want)
214	}
215}
216
217func TestBounds(t *testing.T) {
218	// Make a local copy of testGIF.
219	gif := make([]byte, len(testGIF))
220	copy(gif, testGIF)
221	// Make the bounds too big, just by one.
222	gif[32] = 2
223	want := "gif: frame bounds larger than image bounds"
224	try(t, gif, want)
225
226	// Make the bounds too small; does not trigger bounds
227	// check, but now there's too much data.
228	gif[32] = 0
229	want = "gif: too much image data"
230	try(t, gif, want)
231	gif[32] = 1
232
233	// Make the bounds really big, expect an error.
234	want = "gif: frame bounds larger than image bounds"
235	for i := 0; i < 4; i++ {
236		gif[32+i] = 0xff
237	}
238	try(t, gif, want)
239}
240
241func TestNoPalette(t *testing.T) {
242	b := &bytes.Buffer{}
243
244	// Manufacture a GIF with no palette, so any pixel at all
245	// will be invalid.
246	b.WriteString(headerStr[:len(headerStr)-3])
247	b.WriteString("\x00\x00\x00") // No global palette.
248
249	// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
250	b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
251
252	// Encode the pixels: neither is in range, because there is no palette.
253	enc := lzwEncode([]byte{0x00, 0x03})
254	b.WriteByte(byte(len(enc)))
255	b.Write(enc)
256	b.WriteByte(0x00) // An empty block signifies the end of the image data.
257
258	b.WriteString(trailerStr)
259
260	try(t, b.Bytes(), "gif: no color table")
261}
262
263func TestPixelOutsidePaletteRange(t *testing.T) {
264	for _, pval := range []byte{0, 1, 2, 3} {
265		b := &bytes.Buffer{}
266
267		// Manufacture a GIF with a 2 color palette.
268		b.WriteString(headerStr)
269		b.WriteString(paletteStr)
270
271		// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
272		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
273
274		// Encode the pixels; some pvals trigger the expected error.
275		enc := lzwEncode([]byte{pval, pval})
276		b.WriteByte(byte(len(enc)))
277		b.Write(enc)
278		b.WriteByte(0x00) // An empty block signifies the end of the image data.
279
280		b.WriteString(trailerStr)
281
282		// No error expected, unless the pixels are beyond the 2 color palette.
283		want := ""
284		if pval >= 2 {
285			want = "gif: invalid pixel value"
286		}
287		try(t, b.Bytes(), want)
288	}
289}
290
291func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
292	b := &bytes.Buffer{}
293
294	// Manufacture a GIF with a 2 color palette.
295	b.WriteString(headerStr)
296	b.WriteString(paletteStr)
297
298	// Graphic Control Extension: transparency, transparent color index = 3.
299	//
300	// This index, 3, is out of range of the global palette and there is no
301	// local palette in the subsequent image descriptor. This is an error
302	// according to the spec, but Firefox and Google Chrome seem OK with this.
303	//
304	// See golang.org/issue/15059.
305	b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00")
306
307	// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
308	b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
309
310	// Encode the pixels.
311	enc := lzwEncode([]byte{0x03, 0x03})
312	b.WriteByte(byte(len(enc)))
313	b.Write(enc)
314	b.WriteByte(0x00) // An empty block signifies the end of the image data.
315
316	b.WriteString(trailerStr)
317
318	try(t, b.Bytes(), "")
319}
320
321func TestLoopCount(t *testing.T) {
322	testCases := []struct {
323		name      string
324		data      []byte
325		loopCount int
326	}{
327		{
328			"loopcount-missing",
329			[]byte("GIF89a000\x00000" +
330				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
331				"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 0 image data & trailer
332			-1,
333		},
334		{
335			"loopcount-0",
336			[]byte("GIF89a000\x00000" +
337				"!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00" + // loop count = 0
338				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
339				"\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
340				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
341				"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
342			0,
343		},
344		{
345			"loopcount-1",
346			[]byte("GIF89a000\x00000" +
347				"!\xff\vNETSCAPE2.0\x03\x01\x01\x00\x00" + // loop count = 1
348				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
349				"\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
350				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
351				"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
352			1,
353		},
354	}
355
356	for _, tc := range testCases {
357		t.Run(tc.name, func(t *testing.T) {
358			img, err := DecodeAll(bytes.NewReader(tc.data))
359			if err != nil {
360				t.Fatal("DecodeAll:", err)
361			}
362			w := new(bytes.Buffer)
363			err = EncodeAll(w, img)
364			if err != nil {
365				t.Fatal("EncodeAll:", err)
366			}
367			img1, err := DecodeAll(w)
368			if err != nil {
369				t.Fatal("DecodeAll:", err)
370			}
371			if img.LoopCount != tc.loopCount {
372				t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, tc.loopCount)
373			}
374			if img.LoopCount != img1.LoopCount {
375				t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount)
376			}
377		})
378	}
379}
380
381func TestUnexpectedEOF(t *testing.T) {
382	for i := len(testGIF) - 1; i >= 0; i-- {
383		_, err := DecodeAll(bytes.NewReader(testGIF[:i]))
384		if err == errNotEnough {
385			continue
386		}
387		text := ""
388		if err != nil {
389			text = err.Error()
390		}
391		if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") {
392			t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err)
393		}
394	}
395}
396
397// See golang.org/issue/22237
398func TestDecodeMemoryConsumption(t *testing.T) {
399	const frames = 3000
400	img := image.NewPaletted(image.Rectangle{Max: image.Point{1, 1}}, palette.WebSafe)
401	hugeGIF := &GIF{
402		Image:    make([]*image.Paletted, frames),
403		Delay:    make([]int, frames),
404		Disposal: make([]byte, frames),
405	}
406	for i := 0; i < frames; i++ {
407		hugeGIF.Image[i] = img
408		hugeGIF.Delay[i] = 60
409	}
410	buf := new(bytes.Buffer)
411	if err := EncodeAll(buf, hugeGIF); err != nil {
412		t.Fatal("EncodeAll:", err)
413	}
414	s0, s1 := new(runtime.MemStats), new(runtime.MemStats)
415	runtime.GC()
416	defer debug.SetGCPercent(debug.SetGCPercent(5))
417	runtime.ReadMemStats(s0)
418	if _, err := Decode(buf); err != nil {
419		t.Fatal("Decode:", err)
420	}
421	runtime.ReadMemStats(s1)
422	if heapDiff := int64(s1.HeapAlloc - s0.HeapAlloc); heapDiff > 30<<20 {
423		t.Fatalf("Decode of %d frames increased heap by %dMB", frames, heapDiff>>20)
424	}
425}
426
427func BenchmarkDecode(b *testing.B) {
428	data, err := os.ReadFile("../testdata/video-001.gif")
429	if err != nil {
430		b.Fatal(err)
431	}
432	cfg, err := DecodeConfig(bytes.NewReader(data))
433	if err != nil {
434		b.Fatal(err)
435	}
436	b.SetBytes(int64(cfg.Width * cfg.Height))
437	b.ReportAllocs()
438	b.ResetTimer()
439	for i := 0; i < b.N; i++ {
440		Decode(bytes.NewReader(data))
441	}
442}
443
444func TestReencodeExtendedPalette(t *testing.T) {
445	data, err := hex.DecodeString("4749463839616c02020157220221ff0b280154ffffffff00000021474946306127dc213000ff84ff840000000000800021ffffffff8f4e4554530041508f8f0202020000000000000000000000000202020202020207020202022f31050000000000000021f904ab2c3826002c00000000c00001009800462b07fc1f02061202020602020202220202930202020202020202020202020286090222202222222222222222222222222222222222222222222222222220222222222222222222222222222222222222222222222222221a22222222332223222222222222222222222222222222222222224b222222222222002200002b474946312829021f0000000000cbff002f0202073121f904ab2c2c000021f92c3803002c00e0c0000000f932")
446	if err != nil {
447		t.Fatal(err)
448	}
449	img, err := Decode(bytes.NewReader(data))
450	if err != nil {
451		t.Fatal(err)
452	}
453	err = Encode(io.Discard, img, &Options{NumColors: 1})
454	if err != nil {
455		t.Fatal(err)
456	}
457}
458