ccitt: accept missing multiple-EOL trailer

The new bw-gopher-truncatedX.ccitt_groupY files were derived from the
existing bw-gopher.ccitt_groupY files, after dropping the final 6
consecutive EOL's or 2 consecutive EOL's (depending on whether Y is 3 or
4) and then padding to a byte boundary with either all 0 bits or all 1
bits (depending on X). Each EOL code is 12 bits long: 0000_0000_0001.

Fixes golang/go#34809

Change-Id: Ibb2d964b5205b28f5e2adb5d30647b92aec53c77
Reviewed-on: https://go-review.googlesource.com/c/image/+/211037
Reviewed-by: Benny Siegert <bsiegert@gmail.com>
Reviewed-by: Horst Rutter <hhrutter@gmail.com>
Run-TryBot: Benny Siegert <bsiegert@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/ccitt/reader.go b/ccitt/reader.go
index 16bd495..9326e1c 100644
--- a/ccitt/reader.go
+++ b/ccitt/reader.go
@@ -16,6 +16,7 @@
 )
 
 var (
+	errIncompleteCode          = errors.New("ccitt: incomplete code")
 	errInvalidBounds           = errors.New("ccitt: invalid bounds")
 	errInvalidCode             = errors.New("ccitt: invalid code")
 	errInvalidMode             = errors.New("ccitt: invalid mode")
@@ -205,6 +206,9 @@
 	for {
 		bit, err := b.nextBit()
 		if err != nil {
+			if err == io.EOF {
+				err = errIncompleteCode
+			}
 			return 0, err
 		}
 		bitsRead |= bit << (63 - nBitsRead)
@@ -269,6 +273,14 @@
 	// seenStartOfImage is whether we've called the startDecode method.
 	seenStartOfImage bool
 
+	// truncated is whether the input is missing the final 6 consecutive EOL's
+	// (for Group3) or 2 consecutive EOL's (for Group4). Omitting that trailer
+	// (but otherwise padding to a byte boundary, with either all 0 bits or all
+	// 1 bits) is invalid according to the spec, but happens in practice when
+	// exporting from Adobe Acrobat to TIFF + CCITT. This package silently
+	// ignores CCITT input that has been truncated in that fashion.
+	truncated bool
+
 	// readErr is a sticky error for the Read method.
 	readErr error
 }
@@ -301,7 +313,7 @@
 				z.readErr = io.EOF
 				break
 			}
-			if z.readErr = z.decodeRow(); z.readErr != nil {
+			if z.readErr = z.decodeRow(z.rowsRemaining == 1); z.readErr != nil {
 				break
 			}
 			z.rowsRemaining--
@@ -355,6 +367,9 @@
 	numberOfEOLs := 0
 	switch z.subFormat {
 	case Group3:
+		if z.truncated {
+			return nil
+		}
 		// The stream ends with a RTC (Return To Control) of 6 consecutive
 		// EOL's, but we should have already just seen an EOL, either in
 		// z.startDecode (for a zero-height image) or in z.decodeRow.
@@ -369,6 +384,9 @@
 		} else if err == errInvalidCode {
 			// Try again, this time starting from a byte boundary.
 			z.br.alignToByteBoundary()
+		} else if err == errMissingEOL {
+			z.truncated = true
+			return nil
 		} else {
 			return err
 		}
@@ -391,6 +409,9 @@
 	// cater for optional byte-alignment, or an arbitrary number (potentially
 	// more than 8) of 0-valued padding bits.
 	if mode, err := decode(&z.br, modeDecodeTable[:]); err != nil {
+		if err == errIncompleteCode {
+			return errMissingEOL
+		}
 		return err
 	} else if mode != modeEOL {
 		return errMissingEOL
@@ -398,7 +419,7 @@
 	return nil
 }
 
-func (z *reader) decodeRow() error {
+func (z *reader) decodeRow(finalRow bool) error {
 	z.wi = 0
 	z.atStartOfRow = true
 	z.penColorIsWhite = true
@@ -414,7 +435,12 @@
 				return err
 			}
 		}
-		return z.decodeEOL()
+		err := z.decodeEOL()
+		if finalRow && (err == errMissingEOL) {
+			z.truncated = true
+			return nil
+		}
+		return err
 
 	case Group4:
 		for ; z.wi < len(z.curr); z.atStartOfRow = false {
@@ -654,7 +680,7 @@
 	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
 		p := (y - bounds.Min.Y) * dst.Stride
 		z.curr = dst.Pix[p : p+width]
-		if err := z.decodeRow(); err != nil {
+		if err := z.decodeRow(y+1 == bounds.Max.Y); err != nil {
 			return err
 		}
 		z.curr, z.prev = nil, z.curr
diff --git a/ccitt/reader_test.go b/ccitt/reader_test.go
index 3427a49..d9b56fc 100644
--- a/ccitt/reader_test.go
+++ b/ccitt/reader_test.go
@@ -381,6 +381,10 @@
 		"testdata/bw-gopher-aligned.ccitt_group4",
 		"testdata/bw-gopher-inverted.ccitt_group4",
 		"testdata/bw-gopher-inverted-aligned.ccitt_group4",
+		"testdata/bw-gopher-truncated0.ccitt_group3",
+		"testdata/bw-gopher-truncated0.ccitt_group4",
+		"testdata/bw-gopher-truncated1.ccitt_group3",
+		"testdata/bw-gopher-truncated1.ccitt_group4",
 	} {
 		subFormat := Group3
 		if strings.HasSuffix(fileName, "group4") {
@@ -400,6 +404,10 @@
 	}{
 		{"testdata/bw-gopher.ccitt_group3", Group3, 153, 55},
 		{"testdata/bw-gopher.ccitt_group4", Group4, 153, 55},
+		{"testdata/bw-gopher-truncated0.ccitt_group3", Group3, 153, 55},
+		{"testdata/bw-gopher-truncated0.ccitt_group4", Group4, 153, 55},
+		{"testdata/bw-gopher-truncated1.ccitt_group3", Group3, 153, 55},
+		{"testdata/bw-gopher-truncated1.ccitt_group4", Group4, 153, 55},
 	} {
 		t.Run(tt.fileName, func(t *testing.T) {
 			testDecodeIntoGray(t, tt.fileName, MSB, tt.sf, tt.w, tt.h, nil)
@@ -421,8 +429,7 @@
 		t.Fatalf("DecodeIntoGray: %v", err)
 	}
 
-	baseName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
-	want, err := decodePNG(baseName + ".png")
+	want, err := decodePNG("testdata/bw-gopher.png")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/ccitt/testdata/bw-gopher-truncated0.ccitt_group3 b/ccitt/testdata/bw-gopher-truncated0.ccitt_group3
new file mode 100644
index 0000000..2ea66b0
--- /dev/null
+++ b/ccitt/testdata/bw-gopher-truncated0.ccitt_group3
Binary files differ
diff --git a/ccitt/testdata/bw-gopher-truncated0.ccitt_group4 b/ccitt/testdata/bw-gopher-truncated0.ccitt_group4
new file mode 100644
index 0000000..45d7da6
--- /dev/null
+++ b/ccitt/testdata/bw-gopher-truncated0.ccitt_group4
Binary files differ
diff --git a/ccitt/testdata/bw-gopher-truncated1.ccitt_group3 b/ccitt/testdata/bw-gopher-truncated1.ccitt_group3
new file mode 100644
index 0000000..d1fa636
--- /dev/null
+++ b/ccitt/testdata/bw-gopher-truncated1.ccitt_group3
Binary files differ
diff --git a/ccitt/testdata/bw-gopher-truncated1.ccitt_group4 b/ccitt/testdata/bw-gopher-truncated1.ccitt_group4
new file mode 100644
index 0000000..91c1f0d
--- /dev/null
+++ b/ccitt/testdata/bw-gopher-truncated1.ccitt_group4
Binary files differ