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