go.image/riff: new package.

Also update package webp to use package riff.

LGTM=r
R=r
CC=golang-codereviews, pascal.massimino
https://golang.org/cl/162850043
diff --git a/riff/example_test.go b/riff/example_test.go
new file mode 100644
index 0000000..faa4975
--- /dev/null
+++ b/riff/example_test.go
@@ -0,0 +1,113 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package riff_test
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"strings"
+
+	"code.google.com/p/go.image/riff"
+)
+
+func ExampleReader() {
+	formType, r, err := riff.NewReader(strings.NewReader(data))
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("RIFF(%s)\n", formType)
+	if err := dump(r, ".\t"); err != nil {
+		log.Fatal(err)
+	}
+	// Output:
+	// RIFF(ROOT)
+	// .	ZERO ""
+	// .	ONE  "a"
+	// .	LIST(META)
+	// .	.	LIST(GOOD)
+	// .	.	.	ONE  "a"
+	// .	.	.	FIVE "klmno"
+	// .	.	ZERO ""
+	// .	.	LIST(BAD )
+	// .	.	.	THRE "def"
+	// .	TWO  "bc"
+	// .	LIST(UGLY)
+	// .	.	FOUR "ghij"
+	// .	.	SIX  "pqrstu"
+}
+
+func dump(r *riff.Reader, indent string) error {
+	for {
+		chunkID, chunkLen, chunkData, err := r.Next()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+		if chunkID == riff.LIST {
+			listType, list, err := riff.NewListReader(chunkLen, chunkData)
+			if err != nil {
+				return err
+			}
+			fmt.Printf("%sLIST(%s)\n", indent, listType)
+			if err := dump(list, indent+".\t"); err != nil {
+				return err
+			}
+			continue
+		}
+		b, err := ioutil.ReadAll(chunkData)
+		if err != nil {
+			return err
+		}
+		fmt.Printf("%s%s %q\n", indent, chunkID, b)
+	}
+}
+
+func encodeU32(u uint32) string {
+	return string([]byte{
+		byte(u >> 0),
+		byte(u >> 8),
+		byte(u >> 16),
+		byte(u >> 24),
+	})
+}
+
+func encode(chunkID, contents string) string {
+	n := len(contents)
+	if n&1 == 1 {
+		contents += "\x00"
+	}
+	return chunkID + encodeU32(uint32(n)) + contents
+}
+
+func encodeMulti(typ0, typ1 string, chunks ...string) string {
+	n := 4
+	for _, c := range chunks {
+		n += len(c)
+	}
+	s := typ0 + encodeU32(uint32(n)) + typ1
+	for _, c := range chunks {
+		s += c
+	}
+	return s
+}
+
+var (
+	d0   = encode("ZERO", "")
+	d1   = encode("ONE ", "a")
+	d2   = encode("TWO ", "bc")
+	d3   = encode("THRE", "def")
+	d4   = encode("FOUR", "ghij")
+	d5   = encode("FIVE", "klmno")
+	d6   = encode("SIX ", "pqrstu")
+	l0   = encodeMulti("LIST", "GOOD", d1, d5)
+	l1   = encodeMulti("LIST", "BAD ", d3)
+	l2   = encodeMulti("LIST", "UGLY", d4, d6)
+	l01  = encodeMulti("LIST", "META", l0, d0, l1)
+	data = encodeMulti("RIFF", "ROOT", d0, d1, l01, d2, l2)
+)
diff --git a/riff/riff.go b/riff/riff.go
new file mode 100644
index 0000000..2b50d8c
--- /dev/null
+++ b/riff/riff.go
@@ -0,0 +1,180 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package riff implements the Resource Interchange File Format, used by media
+// formats such as AVI, WAVE and WEBP.
+//
+// A RIFF stream contains a sequence of chunks. Each chunk consists of an 8-byte
+// header (containing a 4-byte chunk type and a 4-byte chunk length), the chunk
+// data (presented as an io.Reader), and some padding bytes.
+//
+// A detailed description of the format is at
+// http://www.tactilemedia.com/info/MCI_Control_Info.html
+package riff
+
+import (
+	"errors"
+	"io"
+	"io/ioutil"
+	"math"
+)
+
+var (
+	errMissingPaddingByte     = errors.New("riff: missing padding byte")
+	errMissingRIFFChunkHeader = errors.New("riff: missing RIFF chunk header")
+	errShortChunkData         = errors.New("riff: short chunk data")
+	errShortChunkHeader       = errors.New("riff: short chunk header")
+	errStaleReader            = errors.New("riff: stale reader")
+)
+
+// u32 decodes the first four bytes of b as a little-endian integer.
+func u32(b []byte) uint32 {
+	return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+}
+
+const chunkHeaderSize = 8
+
+// FourCC is a four character code.
+type FourCC [4]byte
+
+// LIST is the "LIST" FourCC.
+var LIST = FourCC{'L', 'I', 'S', 'T'}
+
+// NewReader returns the RIFF stream's form type, such as "AVI " or "WAVE", and
+// its chunks as a *Reader.
+func NewReader(r io.Reader) (formType FourCC, data *Reader, err error) {
+	var buf [chunkHeaderSize]byte
+	if _, err := io.ReadFull(r, buf[:]); err != nil {
+		if err == io.EOF || err == io.ErrUnexpectedEOF {
+			err = errMissingRIFFChunkHeader
+		}
+		return FourCC{}, nil, err
+	}
+	if buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' {
+		return FourCC{}, nil, errMissingRIFFChunkHeader
+	}
+	return NewListReader(u32(buf[4:]), r)
+}
+
+// NewListReader returns a LIST chunk's list type, such as "movi" or "wavl",
+// and its chunks as a *Reader.
+func NewListReader(chunkLen uint32, chunkData io.Reader) (listType FourCC, data *Reader, err error) {
+	if chunkLen < 4 {
+		return FourCC{}, nil, errShortChunkData
+	}
+	z := &Reader{r: chunkData}
+	if _, err := io.ReadFull(chunkData, z.buf[:4]); err != nil {
+		if err == io.EOF || err == io.ErrUnexpectedEOF {
+			err = errShortChunkData
+		}
+		return FourCC{}, nil, err
+	}
+	z.totalLen = chunkLen - 4
+	return FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}, z, nil
+}
+
+// Reader reads chunks from an underlying io.Reader.
+type Reader struct {
+	r   io.Reader
+	err error
+
+	totalLen uint32
+	chunkLen uint32
+
+	chunkReader *chunkReader
+	buf         [chunkHeaderSize]byte
+	padded      bool
+}
+
+// Next returns the next chunk's ID, length and data. It returns io.EOF if there
+// are no more chunks. The io.Reader returned becomes stale after the next Next
+// call, and should no longer be used.
+//
+// It is valid to call Next even if all of the previous chunk's data has not
+// been read.
+func (z *Reader) Next() (chunkID FourCC, chunkLen uint32, chunkData io.Reader, err error) {
+	if z.err != nil {
+		return FourCC{}, 0, nil, z.err
+	}
+
+	// Drain the rest of the previous chunk.
+	if z.chunkLen != 0 {
+		_, z.err = io.Copy(ioutil.Discard, z.chunkReader)
+		if z.err != nil {
+			return FourCC{}, 0, nil, z.err
+		}
+	}
+	z.chunkReader = nil
+	if z.padded {
+		_, z.err = io.ReadFull(z.r, z.buf[:1])
+		if z.err != nil {
+			if z.err == io.EOF {
+				z.err = errMissingPaddingByte
+			}
+			return FourCC{}, 0, nil, z.err
+		}
+		z.totalLen--
+	}
+
+	// We are done if we have no more data.
+	if z.totalLen == 0 {
+		z.err = io.EOF
+		return FourCC{}, 0, nil, z.err
+	}
+
+	// Read the next chunk header.
+	if z.totalLen < chunkHeaderSize {
+		z.err = errShortChunkHeader
+		return FourCC{}, 0, nil, z.err
+	}
+	z.totalLen -= chunkHeaderSize
+	if _, err = io.ReadFull(z.r, z.buf[:chunkHeaderSize]); err != nil {
+		if z.err == io.EOF || z.err == io.ErrUnexpectedEOF {
+			z.err = errShortChunkHeader
+		}
+		return FourCC{}, 0, nil, z.err
+	}
+	chunkID = FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}
+	chunkLen = u32(z.buf[4:])
+	z.chunkLen = chunkLen
+	z.padded = chunkLen&1 == 1
+	z.chunkReader = &chunkReader{z}
+	return chunkID, chunkLen, z.chunkReader, nil
+}
+
+type chunkReader struct {
+	z *Reader
+}
+
+func (c *chunkReader) Read(p []byte) (int, error) {
+	if c != c.z.chunkReader {
+		return 0, errStaleReader
+	}
+	z := c.z
+	if z.err != nil {
+		if z.err == io.EOF {
+			return 0, errStaleReader
+		}
+		return 0, z.err
+	}
+
+	n := int(z.chunkLen)
+	if n == 0 {
+		return 0, io.EOF
+	}
+	if n < 0 {
+		// Converting uint32 to int overflowed.
+		n = math.MaxInt32
+	}
+	if n > len(p) {
+		n = len(p)
+	}
+	n, err := z.r.Read(p[:n])
+	z.totalLen -= uint32(n)
+	z.chunkLen -= uint32(n)
+	if err != io.EOF {
+		z.err = err
+	}
+	return n, err
+}
diff --git a/webp/decode.go b/webp/decode.go
index 8cf874b..c688292 100644
--- a/webp/decode.go
+++ b/webp/decode.go
@@ -15,182 +15,171 @@
 	"image/color"
 	"io"
 
+	"code.google.com/p/go.image/riff"
 	"code.google.com/p/go.image/vp8"
 	"code.google.com/p/go.image/vp8l"
 	"code.google.com/p/go.image/webp/nycbcra"
 )
 
-// roundUp2 rounds u up to an even number.
-// https://developers.google.com/speed/webp/docs/riff_container#riff_file_format
-// says that "If Chunk Size is odd, a single padding byte... is added."
-func roundUp2(u uint32) uint32 {
-	return u + u&1
-}
+var errInvalidFormat = errors.New("webp: invalid format")
 
-const (
-	formatVP8  = 1
-	formatVP8L = 2
-	formatVP8X = 3
+var (
+	fccALPH = riff.FourCC{'A', 'L', 'P', 'H'}
+	fccVP8  = riff.FourCC{'V', 'P', '8', ' '}
+	fccVP8L = riff.FourCC{'V', 'P', '8', 'L'}
+	fccVP8X = riff.FourCC{'V', 'P', '8', 'X'}
+	fccWEBP = riff.FourCC{'W', 'E', 'B', 'P'}
 )
 
 func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
-	var b [20]byte
-	if _, err := io.ReadFull(r, b[:]); err != nil {
+	formType, riffReader, err := riff.NewReader(r)
+	if err != nil {
 		return nil, image.Config{}, err
 	}
-	format := 0
-	switch string(b[8:16]) {
-	case "WEBPVP8 ":
-		format = formatVP8
-	case "WEBPVP8L":
-		format = formatVP8L
-	case "WEBPVP8X":
-		format = formatVP8X
-	}
-	if string(b[:4]) != "RIFF" || format == 0 {
-		return nil, image.Config{}, errors.New("webp: invalid format")
-	}
-	riffLen := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24
-	dataLen := roundUp2(uint32(b[16]) | uint32(b[17])<<8 | uint32(b[18])<<16 | uint32(b[19])<<24)
-	if riffLen < dataLen+12 {
-		return nil, image.Config{}, errors.New("webp: invalid format")
-	}
-	if dataLen == 0 || dataLen >= 1<<31 {
-		return nil, image.Config{}, errors.New("webp: invalid format")
-	}
-
-	if format == formatVP8L {
-		r = &io.LimitedReader{R: r, N: int64(dataLen)}
-		if configOnly {
-			c, err := vp8l.DecodeConfig(r)
-			return nil, c, err
-		}
-		m, err := vp8l.Decode(r)
-		return m, image.Config{}, err
+	if formType != fccWEBP {
+		return nil, image.Config{}, errInvalidFormat
 	}
 
 	var (
-		alpha       []byte
-		alphaStride int
+		alpha          []byte
+		alphaStride    int
+		wantAlpha      bool
+		widthMinusOne  uint32
+		heightMinusOne uint32
+		buf            [10]byte
 	)
-	if format == formatVP8X {
-		if dataLen != 10 {
-			return nil, image.Config{}, errors.New("webp: invalid format")
+	for {
+		chunkID, chunkLen, chunkData, err := riffReader.Next()
+		if err == io.EOF {
+			err = errInvalidFormat
 		}
-		if _, err := io.ReadFull(r, b[:10]); err != nil {
-			return nil, image.Config{}, err
-		}
-		const (
-			animationBit    = 1 << 1
-			xmpMetadataBit  = 1 << 2
-			exifMetadataBit = 1 << 3
-			alphaBit        = 1 << 4
-			iccProfileBit   = 1 << 5
-		)
-		if b[0] != alphaBit {
-			return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented")
-		}
-		widthMinusOne := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16
-		heightMinusOne := uint32(b[7]) | uint32(b[8])<<8 | uint32(b[9])<<16
-		if configOnly {
-			return nil, image.Config{
-				ColorModel: nycbcra.ColorModel,
-				Width:      int(widthMinusOne) + 1,
-				Height:     int(heightMinusOne) + 1,
-			}, nil
-		}
-
-		// Read the 8-byte chunk header plus the mandatory PFC (Pre-processing,
-		// Filter, Compression) byte.
-		if _, err := io.ReadFull(r, b[:9]); err != nil {
-			return nil, image.Config{}, err
-		}
-		if b[0] != 'A' || b[1] != 'L' || b[2] != 'P' || b[3] != 'H' {
-			return nil, image.Config{}, errors.New("webp: invalid format")
-		}
-		chunkLen := roundUp2(uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24)
-		// Subtract one byte from chunkLen, since we've already read the PFC byte.
-		if chunkLen == 0 {
-			return nil, image.Config{}, errors.New("webp: invalid format")
-		}
-		chunkLen--
-		filter := (b[8] >> 2) & 0x03
-		if filter != 0 {
-			return nil, image.Config{}, errors.New("webp: VP8X Alpha filtering != 0 is not implemented")
-		}
-		compression := b[8] & 0x03
-		if compression != 1 {
-			return nil, image.Config{}, errors.New("webp: VP8X Alpha compression != 1 is not implemented")
-		}
-
-		// Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
-		// a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
-		// a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
-		// TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
-		// extract the green values to a separately allocated []byte. Fixing this
-		// will require changes to the vp8l package's API.
-		if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
-			return nil, image.Config{}, errors.New("webp: invalid format")
-		}
-		b[0] = 0x2f // VP8L magic number.
-		b[1] = uint8(widthMinusOne)
-		b[2] = uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6)
-		b[3] = uint8(heightMinusOne >> 2)
-		b[4] = uint8(heightMinusOne >> 10)
-		alphaImage, err := vp8l.Decode(io.MultiReader(
-			bytes.NewReader(b[:5]),
-			&io.LimitedReader{R: r, N: int64(chunkLen)},
-		))
 		if err != nil {
 			return nil, image.Config{}, err
 		}
-		// The green values of the inner NRGBA image are the alpha values of the outer NYCbCrA image.
-		pix := alphaImage.(*image.NRGBA).Pix
-		alpha = make([]byte, len(pix)/4)
-		for i := range alpha {
-			alpha[i] = pix[4*i+1]
-		}
-		alphaStride = int(widthMinusOne) + 1
 
-		// The rest of the image should be in the lossy format. Check the "VP8 "
-		// header and fall through.
-		if _, err := io.ReadFull(r, b[:8]); err != nil {
-			return nil, image.Config{}, err
-		}
-		if b[0] != 'V' || b[1] != 'P' || b[2] != '8' || b[3] != ' ' {
-			return nil, image.Config{}, errors.New("webp: invalid format")
-		}
-		dataLen = roundUp2(uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24)
-		if dataLen == 0 || dataLen >= 1<<31 {
-			return nil, image.Config{}, errors.New("webp: invalid format")
-		}
-	}
+		switch chunkID {
+		case fccALPH:
+			if !wantAlpha {
+				return nil, image.Config{}, errInvalidFormat
+			}
+			wantAlpha = false
+			// Read the Pre-processing | Filter | Compression byte.
+			if _, err := io.ReadFull(chunkData, buf[:1]); err != nil {
+				if err == io.EOF {
+					err = errInvalidFormat
+				}
+				return nil, image.Config{}, err
+			}
+			filter := (buf[0] >> 2) & 0x03
+			if filter != 0 {
+				return nil, image.Config{}, errors.New(
+					"webp: VP8X Alpha filtering != 0 is not implemented")
+			}
+			compression := buf[0] & 0x03
+			if compression != 1 {
+				return nil, image.Config{}, errors.New(
+					"webp: VP8X Alpha compression != 1 is not implemented")
+			}
+			// Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
+			// a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
+			// a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
+			// TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
+			// extract the green values to a separately allocated []byte. Fixing this
+			// will require changes to the vp8l package's API.
+			if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
+				return nil, image.Config{}, errors.New("webp: invalid format")
+			}
+			buf[0] = 0x2f // VP8L magic number.
+			buf[1] = uint8(widthMinusOne)
+			buf[2] = uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6)
+			buf[3] = uint8(heightMinusOne >> 2)
+			buf[4] = uint8(heightMinusOne >> 10)
+			alphaImage, err := vp8l.Decode(io.MultiReader(
+				bytes.NewReader(buf[:5]),
+				chunkData,
+			))
+			if err != nil {
+				return nil, image.Config{}, err
+			}
+			// The green values of the inner NRGBA image are the alpha values of the
+			// outer NYCbCrA image.
+			pix := alphaImage.(*image.NRGBA).Pix
+			alpha = make([]byte, len(pix)/4)
+			for i := range alpha {
+				alpha[i] = pix[4*i+1]
+			}
+			alphaStride = int(widthMinusOne) + 1
 
-	d := vp8.NewDecoder()
-	d.Init(r, int(dataLen))
-	fh, err := d.DecodeFrameHeader()
-	if err != nil {
-		return nil, image.Config{}, err
+		case fccVP8:
+			if wantAlpha {
+				return nil, image.Config{}, errInvalidFormat
+			}
+			d := vp8.NewDecoder()
+			d.Init(chunkData, int(chunkLen))
+			fh, err := d.DecodeFrameHeader()
+			if err != nil {
+				return nil, image.Config{}, err
+			}
+			if configOnly {
+				return nil, image.Config{
+					ColorModel: color.YCbCrModel,
+					Width:      fh.Width,
+					Height:     fh.Height,
+				}, nil
+			}
+			m, err := d.DecodeFrame()
+			if err != nil {
+				return nil, image.Config{}, err
+			}
+			if alpha != nil {
+				return &nycbcra.Image{
+					YCbCr:   *m,
+					A:       alpha,
+					AStride: alphaStride,
+				}, image.Config{}, nil
+			}
+			return m, image.Config{}, nil
+
+		case fccVP8L:
+			if wantAlpha || alpha != nil {
+				return nil, image.Config{}, errInvalidFormat
+			}
+			if configOnly {
+				c, err := vp8l.DecodeConfig(chunkData)
+				return nil, c, err
+			}
+			m, err := vp8l.Decode(chunkData)
+			return m, image.Config{}, err
+
+		case fccVP8X:
+			if chunkLen != 10 {
+				return nil, image.Config{}, errInvalidFormat
+			}
+			if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
+				return nil, image.Config{}, err
+			}
+			const (
+				animationBit    = 1 << 1
+				xmpMetadataBit  = 1 << 2
+				exifMetadataBit = 1 << 3
+				alphaBit        = 1 << 4
+				iccProfileBit   = 1 << 5
+			)
+			if buf[0] != alphaBit {
+				return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented")
+			}
+			widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
+			heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
+			if configOnly {
+				return nil, image.Config{
+					ColorModel: nycbcra.ColorModel,
+					Width:      int(widthMinusOne) + 1,
+					Height:     int(heightMinusOne) + 1,
+				}, nil
+			}
+			wantAlpha = true
+		}
 	}
-	if configOnly {
-		return nil, image.Config{
-			ColorModel: color.YCbCrModel,
-			Width:      fh.Width,
-			Height:     fh.Height,
-		}, nil
-	}
-	m, err := d.DecodeFrame()
-	if err != nil {
-		return nil, image.Config{}, err
-	}
-	if alpha != nil {
-		return &nycbcra.Image{
-			YCbCr:   *m,
-			A:       alpha,
-			AStride: alphaStride,
-		}, image.Config{}, nil
-	}
-	return m, image.Config{}, nil
 }
 
 // Decode reads a WEBP image from r and returns it as an image.Image.