Skip to content

Commit ee5039a

Browse files
committed
Fix the Timeout option
1 parent 4fd8961 commit ee5039a

File tree

5 files changed

+113
-79
lines changed

5 files changed

+113
-79
lines changed

‎helpers.go‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import (
88
"encoding"
99
"errors"
1010
"fmt"
11-
"io"
1211
"math"
13-
"runtime"
1412
"strconv"
1513
"strings"
1614
"time"
@@ -471,12 +469,6 @@ func trimBytesNulls(b []byte) []byte {
471469
return b[lo : hi+1]
472470
}
473471

474-
func printStackTrace(w io.Writer) {
475-
buf := make([]byte, 1<<16)
476-
runtime.Stack(buf, true)
477-
fmt.Fprintf(w, "%s", buf)
478-
}
479-
480472
func typeAssertSlice[T any](ctx valueConverterContext, v any) ([]T, bool) {
481473
vv, ok := v.([]T)
482474
if ok {

‎imagemeta.go‎

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"io"
1010
"maps"
1111
"math"
12-
"os"
1312
"strings"
1413
"time"
1514
)
@@ -51,49 +50,65 @@ const (
5150
func Decode(opts Options) (err error) {
5251
var base *baseStreamingDecoder
5352

54-
defer func() {
55-
if r := recover(); r != nil {
56-
if errp, ok := r.(error); ok {
57-
if isInvalidFormatErrorCandidate(errp) {
58-
err = newInvalidFormatError(errp)
59-
} else {
60-
err = errp
61-
if err != errStop {
62-
printStackTrace(os.Stderr)
63-
}
64-
}
65-
} else {
66-
err = fmt.Errorf("unknown panic: %v", r)
67-
printStackTrace(os.Stderr)
68-
}
53+
errFinal := func(err2 error) error {
54+
if err2 == nil {
55+
return nil
6956
}
7057

71-
if err == ErrStopWalking {
72-
err = nil
73-
return
58+
if err2 == ErrStopWalking {
59+
return nil
7460
}
7561

76-
if err == errStop {
77-
err = nil
62+
if err2 == errStop {
63+
return nil
7864
}
7965

80-
if err == nil {
66+
if err2 == nil {
8167
if base != nil {
82-
err = base.streamErr()
68+
err2 = base.streamErr()
8369
}
8470
}
8571

86-
if err == nil {
87-
return
72+
if err2 == nil {
73+
return nil
8874
}
8975

90-
if err == io.EOF {
91-
err = nil
92-
return
76+
if err2 == io.EOF {
77+
return nil
9378
}
9479

95-
if isInvalidFormatErrorCandidate(err) {
96-
err = newInvalidFormatError(err)
80+
if isInvalidFormatErrorCandidate(err2) {
81+
err2 = newInvalidFormatError(err2)
82+
}
83+
84+
return err2
85+
}
86+
87+
defer func() {
88+
err = errFinal(err)
89+
}()
90+
91+
errFromRecover := func(r any) (err2 error) {
92+
if r == nil {
93+
return nil
94+
}
95+
if errp, ok := r.(error); ok {
96+
if isInvalidFormatErrorCandidate(errp) {
97+
err2 = newInvalidFormatError(errp)
98+
} else {
99+
err2 = errp
100+
}
101+
} else {
102+
err2 = fmt.Errorf("unknown panic: %v", r)
103+
}
104+
105+
return
106+
}
107+
108+
defer func() {
109+
err2 := errFromRecover(recover())
110+
if err == nil {
111+
err = err2
97112
}
98113
}()
99114

@@ -195,32 +210,26 @@ func Decode(opts Options) (err error) {
195210
dec = &imageDecoderPNG{baseStreamingDecoder: base}
196211
}
197212

198-
if opts.Timeout > 0 {
213+
decode := func() chan error {
199214
errc := make(chan error, 1)
200215
go func() {
201216
defer func() {
202-
if r := recover(); r != nil {
203-
if errp, ok := r.(error); ok {
204-
if errp != errStop {
205-
printStackTrace(os.Stderr)
206-
}
207-
errc <- errp
208-
} else {
209-
errc <- fmt.Errorf("unknown panic: %v", r)
210-
printStackTrace(os.Stderr)
211-
}
217+
err2 := errFromRecover(recover())
218+
if err == nil {
219+
err = err2
212220
}
213221
}()
214-
select {
215-
case <-time.After(opts.Timeout):
216-
printStackTrace(os.Stderr)
217-
errc <- fmt.Errorf("timed out after %s", opts.Timeout)
218-
case errc <- dec.decode():
219-
}
222+
errc <- dec.decode()
220223
}()
224+
return errc
225+
}
221226

222-
err = <-errc
223-
227+
if opts.Timeout > 0 {
228+
select {
229+
case <-time.After(opts.Timeout):
230+
err = fmt.Errorf("timed out after %s", opts.Timeout)
231+
case err = <-decode():
232+
}
224233
} else {
225234
err = dec.decode()
226235
}
@@ -277,7 +286,7 @@ type Options struct {
277286
// Tag values larger than this will be skipped without notice.
278287
// Note that this limit is not relevant for the XMP source.
279288
// Default value is 10000.
280-
LimitTagSize uint16
289+
LimitTagSize uint32
281290
}
282291

283292
// TagInfo contains information about a tag.

‎imagemeta_fuzz_test.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"os"
99
"path/filepath"
10+
"strings"
1011
"testing"
1112
"time"
1213

@@ -67,9 +68,9 @@ func FuzzDecodeTIFF(f *testing.F) {
6768

6869
func fuzzDecodeBytes(t *testing.T, imageBytes []byte, f imagemeta.ImageFormat) error {
6970
r := bytes.NewReader(imageBytes)
70-
err := imagemeta.Decode(imagemeta.Options{R: r, ImageFormat: f, Sources: imagemeta.EXIF | imagemeta.IPTC | imagemeta.XMP, Timeout: 10 * time.Second})
71+
err := imagemeta.Decode(imagemeta.Options{R: r, ImageFormat: f, Sources: imagemeta.EXIF | imagemeta.IPTC | imagemeta.XMP, Timeout: 600 * time.Millisecond})
7172
if err != nil {
72-
if !imagemeta.IsInvalidFormat(err) {
73+
if !imagemeta.IsInvalidFormat(err) && !strings.Contains(err.Error(), "timed out") {
7374
t.Fatalf("unknown error in Decode: %v %T", err, err)
7475
}
7576
}

‎imagemeta_test.go‎

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strconv"
1919
"strings"
2020
"testing"
21+
"time"
2122

2223
"github.com/bep/imagemeta"
2324
"github.com/rwcarlsen/goexif/exif"
@@ -61,7 +62,8 @@ func TestDecodeAllImageFormats(t *testing.T) {
6162

6263
func TestDecodeWebP(t *testing.T) {
6364
c := qt.New(t)
64-
tags := extractTags(t, "sunrise.webp", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP)
65+
tags, err := extractTags(t, "sunrise.webp", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP)
66+
c.Assert(err, qt.IsNil)
6567

6668
c.Assert(tags.EXIF()["Copyright"].Value, qt.Equals, "Bjørn Erik Pedersen")
6769
c.Assert(tags.EXIF()["ApertureValue"].Value, eq, 5.6)
@@ -83,7 +85,8 @@ func TestDecodeJPEG(t *testing.T) {
8385
return true
8486
}
8587

86-
tags := extractTagsWithFilter(t, "sunrise.jpg", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP, shouldInclude)
88+
tags, err := extractTagsWithFilter(t, "sunrise.jpg", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP, shouldInclude)
89+
c.Assert(err, qt.IsNil)
8790

8891
c.Assert(tags.EXIF()["Copyright"].Value, qt.Equals, "Bjørn Erik Pedersen")
8992
c.Assert(tags.EXIF()["ApertureValue"].Value, eq, 5.6)
@@ -99,7 +102,8 @@ func TestDecodePNG(t *testing.T) {
99102
return true
100103
}
101104

102-
tags := extractTagsWithFilter(t, "sunrise.png", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP, shouldInclude)
105+
tags, err := extractTagsWithFilter(t, "sunrise.png", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP, shouldInclude)
106+
c.Assert(err, qt.IsNil)
103107

104108
c.Assert(len(tags.EXIF()), qt.Equals, 61)
105109
c.Assert(len(tags.IPTC()), qt.Equals, 14)
@@ -119,7 +123,8 @@ func TestThumbnailOffset(t *testing.T) {
119123
}
120124

121125
offset := func(filename string) uint32 {
122-
tags := extractTagsWithFilter(t, filename, imagemeta.EXIF, shouldHandle)
126+
tags, err := extractTagsWithFilter(t, filename, imagemeta.EXIF, shouldHandle)
127+
c.Assert(err, qt.IsNil)
123128
return tags.EXIF()["ThumbnailOffset"].Value.(uint32)
124129
}
125130

@@ -132,7 +137,8 @@ func TestThumbnailOffset(t *testing.T) {
132137
func TestDecodeTIFF(t *testing.T) {
133138
c := qt.New(t)
134139

135-
tags := extractTags(t, "sunrise.tif", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP)
140+
tags, err := extractTags(t, "sunrise.tif", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP)
141+
c.Assert(err, qt.IsNil)
136142

137143
c.Assert(len(tags.EXIF()), qt.Equals, 76)
138144
c.Assert(len(tags.XMP()), qt.Equals, 146)
@@ -307,7 +313,8 @@ func TestDecodeNamespace(t *testing.T) {
307313
return true
308314
}
309315

310-
tags := extractTagsWithFilter(t, "sunrise.jpg", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP, shouldInclude)
316+
tags, err := extractTagsWithFilter(t, "sunrise.jpg", imagemeta.EXIF|imagemeta.IPTC|imagemeta.XMP, shouldInclude)
317+
c.Assert(err, qt.IsNil)
311318

312319
c.Assert(tags.EXIF()["Artist"].Namespace, qt.Equals, "IFD0")
313320
c.Assert(tags.EXIF()["GPSLatitude"].Namespace, qt.Equals, "IFD0/GPSInfoIFD")
@@ -376,10 +383,25 @@ func TestDecodeIPTCOrientationOnly(t *testing.T) {
376383
c.Assert(len(tags.IPTC()), qt.Equals, 1)
377384
}
378385

386+
func TestDecodeLargeExifTimeout(t *testing.T) {
387+
c := qt.New(t)
388+
389+
withOpts := func(opts *imagemeta.Options) {
390+
opts.Timeout = time.Duration(500 * time.Millisecond)
391+
392+
// Set the limits to something high to make sure we time out.
393+
opts.LimitNumTags = 1000000
394+
opts.LimitTagSize = 10000000
395+
}
396+
_, err := extractTags(t, "largeexif.png", imagemeta.EXIF, withOpts)
397+
c.Assert(err, qt.ErrorMatches, "timed out after 500ms")
398+
}
399+
379400
func TestDecodeXMPJPG(t *testing.T) {
380401
c := qt.New(t)
381402

382-
tags := extractTags(t, "sunrise.jpg", imagemeta.XMP)
403+
tags, err := extractTags(t, "sunrise.jpg", imagemeta.XMP)
404+
c.Assert(err, qt.IsNil)
383405

384406
c.Assert(len(tags.EXIF()) == 0, qt.IsTrue)
385407
c.Assert(len(tags.IPTC()) == 0, qt.IsTrue)
@@ -437,14 +459,15 @@ func TestGoldenTagCountXMP(t *testing.T) {
437459
func TestLatLong(t *testing.T) {
438460
c := qt.New(t)
439461

440-
tags := extractTags(t, "sunrise.jpg", imagemeta.EXIF)
462+
tags, err := extractTags(t, "sunrise.jpg", imagemeta.EXIF)
441463

442464
lat, long, err := tags.GetLatLong()
443465
c.Assert(err, qt.IsNil)
444466
c.Assert(lat, eq, float64(36.59744166))
445467
c.Assert(long, eq, float64(-4.50846))
446468

447-
tags = extractTags(t, "goexif/geodegrees_as_string.jpg", imagemeta.EXIF)
469+
tags, err = extractTags(t, "goexif/geodegrees_as_string.jpg", imagemeta.EXIF)
470+
c.Assert(err, qt.IsNil)
448471
lat, long, err = tags.GetLatLong()
449472
c.Assert(err, qt.IsNil)
450473
c.Assert(lat, eq, float64(52.013888888))
@@ -454,7 +477,7 @@ func TestLatLong(t *testing.T) {
454477
func TestGetDateTime(t *testing.T) {
455478
c := qt.New(t)
456479

457-
tags := extractTags(t, "sunrise.jpg", imagemeta.EXIF)
480+
tags, err := extractTags(t, "sunrise.jpg", imagemeta.EXIF)
458481
d, err := tags.GetDateTime()
459482
c.Assert(err, qt.IsNil)
460483
c.Assert(d.Format("2006-01-02"), qt.Equals, "2017-10-27")
@@ -512,7 +535,8 @@ func assertGoldenInfoTagCount(t testing.TB, filename string, sources imagemeta.S
512535
return true
513536
}
514537

515-
tags := extractTagsWithFilter(t, filename, sources, shouldHandle)
538+
tags, err := extractTagsWithFilter(t, filename, sources, shouldHandle)
539+
c.Assert(err, qt.IsNil)
516540
all := tags.All()
517541

518542
// Our XMP parsing is currently a little limited so be a little lenient with the assertions.
@@ -587,7 +611,8 @@ func assertGoldenInfoTagCount(t testing.TB, filename string, sources imagemeta.S
587611

588612
func compareWithExiftoolOutput(t testing.TB, filename string, sources imagemeta.Source) {
589613
c := qt.New(t)
590-
tags := extractTags(t, filename, sources)
614+
tags, err := extractTags(t, filename, sources)
615+
c.Assert(err, qt.IsNil)
591616
all := tags.All()
592617
tagsGolden := readGoldenInfo(t, filename)
593618

@@ -731,15 +756,17 @@ func extToFormat(ext string) imagemeta.ImageFormat {
731756
}
732757
}
733758

734-
func extractTags(t testing.TB, filename string, sources imagemeta.Source) imagemeta.Tags {
759+
func extractTags(t testing.TB, filename string, sources imagemeta.Source, opts ...withOptions) (imagemeta.Tags, error) {
735760
shouldHandle := func(ti imagemeta.TagInfo) bool {
736761
// Drop the thumbnail tags.
737762
return ti.Namespace != "IFD1"
738763
}
739-
return extractTagsWithFilter(t, filename, sources, shouldHandle)
764+
return extractTagsWithFilter(t, filename, sources, shouldHandle, opts...)
740765
}
741766

742-
func extractTagsWithFilter(t testing.TB, filename string, sources imagemeta.Source, shouldHandle func(ti imagemeta.TagInfo) bool) imagemeta.Tags {
767+
type withOptions func(opts *imagemeta.Options)
768+
769+
func extractTagsWithFilter(t testing.TB, filename string, sources imagemeta.Source, shouldHandle func(ti imagemeta.TagInfo) bool, opts ...withOptions) (imagemeta.Tags, error) {
743770
t.Helper()
744771
if !filepath.IsAbs(filename) {
745772
filename = filepath.Join("testdata", "images", filename)
@@ -770,9 +797,14 @@ func extractTagsWithFilter(t testing.TB, filename string, sources imagemeta.Sour
770797
panic(errors.New(s))
771798
}
772799

773-
err = imagemeta.Decode(imagemeta.Options{R: f, ImageFormat: imageFormat, ShouldHandleTag: shouldHandle, HandleTag: handleTag, Warnf: warnf, Sources: sources})
800+
imgOpts := imagemeta.Options{R: f, ImageFormat: imageFormat, ShouldHandleTag: shouldHandle, HandleTag: handleTag, Warnf: warnf, Sources: sources}
801+
for _, opt := range opts {
802+
opt(&imgOpts)
803+
}
804+
805+
err = imagemeta.Decode(imgOpts)
774806
if err != nil {
775-
t.Fatal(fmt.Errorf("failed to decode %q: %w", filename, err))
807+
return tags, err
776808
}
777809

778810
// See https://github.com/gohugoio/hugo/issues/12741 and https://github.com/golang/go/issues/59627
@@ -788,7 +820,7 @@ func extractTagsWithFilter(t testing.TB, filename string, sources imagemeta.Sour
788820
t.Fatal(fmt.Errorf("failed to marshal tags in %q to JSON: %w", filename, err))
789821
}
790822

791-
return tags
823+
return tags, nil
792824
}
793825

794826
func readGoldenInfo(t testing.TB, filename string) goldenFileInfo {

‎metadecoder_exif.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ func (e *metaDecoderEXIF) decodeTag(namespace string) error {
433433
if isIFDPointer {
434434
offset, ok := val.(uint32)
435435
if !ok {
436-
return newInvalidFormatErrorf("invalid IFD pointer value: %v", val)
436+
return newInvalidFormatErrorf("invalid IFD pointer value")
437437
}
438438
namespace := path.Join(namespace, ifd)
439439
return e.decodeTagsAt(namespace, int64(offset))

0 commit comments

Comments
 (0)