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