git clone https://git.lucas.co/go_mono.git
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>
ccitt/reader.go | 34 ++++++++++++++++++++---
ccitt/reader_test.go | 11 ++++++--
ccitt/testdata/bw-gopher-truncated0.ccitt_group3 | Bin 0 -> 375 bytes
ccitt/testdata/bw-gopher-truncated0.ccitt_group4 | 3 ++
ccitt/testdata/bw-gopher-truncated1.ccitt_group3 | Bin 0 -> 375 bytes
ccitt/testdata/bw-gopher-truncated1.ccitt_group4 | 3 ++
6 files changed, 45 insertions(+), 6 deletions(-)
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 @@ import (
)
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 @@ func decode(b *bitReader, decodeTable [][2]int16) (uint32, error) {
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 @@ type reader struct {
// 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 @@ func (z *reader) Read(p []byte) (int, error) {
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 @@ func (z *reader) finishDecode() error {
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 @@ func (z *reader) finishDecode() error {
} 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 @@ func (z *reader) decodeEOL() error {
// 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 @@ func (z *reader) decodeEOL() error {
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 @@ func (z *reader) decodeRow() error {
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 @@ func DecodeIntoGray(dst *image.Gray, r io.Reader, order Order, sf SubFormat, opt
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 @@ func TestRead(t *testing.T) {
"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 @@ func TestDecodeIntoGray(t *testing.T) {
}{
{"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 @@ func testDecodeIntoGray(t *testing.T, fileName string, order Order, sf SubFormat
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
Binary files /dev/null and b/ccitt/testdata/bw-gopher-truncated0.ccitt_group3 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
@@ -0,0 +1,3 @@
+;Zp@���{H�L"<�N��~I6?z
����i�[+,��":
+���*��}�auVW��}�#�K�Q ���#�v)��P|XEՔ6?
+�������i�e�4��a�e';�vUEB��.#��VQ���|��!��dX��|*�Ĉl�T{�T���Yg���u��� m(ej�����.~P֖8
\ No newline at end of file
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
Binary files /dev/null and b/ccitt/testdata/bw-gopher-truncated1.ccitt_group3 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
@@ -0,0 +1,3 @@
+;Zp@���{H�L"<�N��~I6?z
����i�[+,��":
+���*��}�auVW��}�#�K�Q ���#�v)��P|XEՔ6?
+�������i�e�4��a�e';�vUEB��.#��VQ���|��!��dX��|*�Ĉl�T{�T���Yg���u��� m(ej�����.~P֖?
\ No newline at end of file