From 0108463e7b179af1c796042dd10f4fe1020b9075 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:50:28 +0100 Subject: [bugfix] Update exif-terminator (fix png issue) (#2391) * [bugfix] Update exif-terminator (fix png issue) * bump exif terminator * fix tests --- go.mod | 4 +- go.sum | 8 +- internal/media/manager.go | 3 +- internal/media/manager_test.go | 2 +- internal/media/processingmedia.go | 2 +- .../media/test/test-png-alphachannel-processed.png | Bin 18904 -> 18904 bytes .../dsoprea/go-png-image-structure/v2/.MODULE_ROOT | 0 .../dsoprea/go-png-image-structure/v2/LICENSE | 9 - .../go-png-image-structure/v2/chunk_decoder.go | 87 ----- .../go-png-image-structure/v2/media_parser.go | 118 ------ .../dsoprea/go-png-image-structure/v2/png.go | 416 --------------------- .../go-png-image-structure/v2/testing_common.go | 64 ---- .../dsoprea/go-png-image-structure/v2/utility.go | 65 ---- .../superseriousbusiness/exif-terminator/logger.go | 47 --- .../superseriousbusiness/exif-terminator/png.go | 68 ++-- .../exif-terminator/terminator.go | 94 +++-- .../go-png-image-structure/v2/.MODULE_ROOT | 0 .../go-png-image-structure/v2/LICENSE | 9 + .../go-png-image-structure/v2/chunk_decoder.go | 81 ++++ .../go-png-image-structure/v2/media_parser.go | 85 +++++ .../go-png-image-structure/v2/png.go | 386 +++++++++++++++++++ .../go-png-image-structure/v2/testing_common.go | 77 ++++ .../go-png-image-structure/v2/utility.go | 67 ++++ vendor/modules.txt | 10 +- 24 files changed, 812 insertions(+), 890 deletions(-) delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/.MODULE_ROOT delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/LICENSE delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/chunk_decoder.go delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/media_parser.go delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/png.go delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/testing_common.go delete mode 100644 vendor/github.com/dsoprea/go-png-image-structure/v2/utility.go delete mode 100644 vendor/github.com/superseriousbusiness/exif-terminator/logger.go create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/.MODULE_ROOT create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/LICENSE create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/chunk_decoder.go create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/media_parser.go create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/png.go create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/testing_common.go create mode 100644 vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go diff --git a/go.mod b/go.mod index 8b8c2f5ef..e16507254 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/superseriousbusiness/activity v1.4.0-gts - github.com/superseriousbusiness/exif-terminator v0.5.0 + github.com/superseriousbusiness/exif-terminator v0.6.0 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 github.com/tdewolff/minify/v2 v2.20.7 github.com/technologize/otel-go-contrib v1.1.0 @@ -101,7 +101,6 @@ require ( github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d // indirect - github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d // indirect github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -160,6 +159,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect + github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect github.com/tdewolff/parse/v2 v2.7.5 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 6f5357513..a6468fa49 100644 --- a/go.sum +++ b/go.sum @@ -152,8 +152,6 @@ github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6 github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d h1:dg6UMHa50VI01WuPWXPbNJpO8QSyvIF5T5n2IZiqX3A= github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= -github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d h1:2zNIgrJTspLxUKoJGl0Ln24+hufPKSjP3cu4++5MeSE= -github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d/go.mod h1:scnx0wQSM7UiCMK66dSdiPZvL2hl6iF5DvpZ7uT59MY= github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e h1:IxIbA7VbCNrwumIYjDoMOdf4KOSkMC6NJE4s8oRbE7E= github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= @@ -505,10 +503,12 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= github.com/superseriousbusiness/activity v1.4.0-gts h1:9r95sYy80tuGWWpDDNlLwa/k6dKZdyP/k+rhVA+VjdQ= github.com/superseriousbusiness/activity v1.4.0-gts/go.mod h1:AZw0Xb4Oju8rmaJCZ21gc5CPg47MmNgyac+Hx5jo8VM= -github.com/superseriousbusiness/exif-terminator v0.5.0 h1:57SO/geyaOl2v/lJSQLVcQbdghpyFuK8ZTtaHL81fUQ= -github.com/superseriousbusiness/exif-terminator v0.5.0/go.mod h1:d5IkskXco/3XRXzOrI73uGYn+wahJEqPlQSSqn6jxSw= +github.com/superseriousbusiness/exif-terminator v0.6.0 h1:f8FM4R/Au7iB0PGfSKjKAEYMGvM2HHbd1qLxrfmuNFk= +github.com/superseriousbusiness/exif-terminator v0.6.0/go.mod h1:HjbpsIyuK6PguA/Rla+Hz0+A9pF6iWf9qGLhqpAN68k= github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe h1:ksl2oCx/Qo8sNDc3Grb8WGKBM9nkvhCm25uvlT86azE= github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4= +github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB h1:8psprYSK1KdOSH7yQ4PbJq0YYaGQY+gzdW/B0ExDb/8= +github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB/go.mod h1:ymKGfy9kg4dIdraeZRAdobMS/flzLk3VcRPLpEWOAXg= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo= github.com/tdewolff/minify/v2 v2.20.7 h1:NUkuzJ9dvQUNJjSdmmrfELa/ZpnMdyMR/ZKU2bw7N/E= diff --git a/internal/media/manager.go b/internal/media/manager.go index dfae37d80..5f496a71d 100644 --- a/internal/media/manager.go +++ b/internal/media/manager.go @@ -53,8 +53,7 @@ type Manager struct { // NewManager returns a media manager with given state. func NewManager(state *state.State) *Manager { - m := &Manager{state: state} - return m + return &Manager{state: state} } // PreProcessMedia begins the process of decoding diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go index 4a3d3c886..f90c182fe 100644 --- a/internal/media/manager_test.go +++ b/internal/media/manager_test.go @@ -404,7 +404,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessPartial() { // Since we're cutting off the byte stream // halfway through, we should get an error here. - suite.EqualError(err, "finish: error decoding image: unexpected EOF") + suite.EqualError(err, "store: error writing media to storage: scan-data is unbounded; EOI not encountered before EOF") suite.NotNil(attachment) // make sure it's got the stuff set on it that we expect diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go index 3627d8cd3..74745305c 100644 --- a/internal/media/processingmedia.go +++ b/internal/media/processingmedia.go @@ -29,7 +29,7 @@ import ( "codeberg.org/gruf/go-runners" "github.com/disintegration/imaging" "github.com/h2non/filetype" - terminator "github.com/superseriousbusiness/exif-terminator" + "github.com/superseriousbusiness/exif-terminator" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" diff --git a/internal/media/test/test-png-alphachannel-processed.png b/internal/media/test/test-png-alphachannel-processed.png index d3b67e66d..9d05d45ef 100644 Binary files a/internal/media/test/test-png-alphachannel-processed.png and b/internal/media/test/test-png-alphachannel-processed.png differ diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/.MODULE_ROOT b/vendor/github.com/dsoprea/go-png-image-structure/v2/.MODULE_ROOT deleted file mode 100644 index e69de29bb..000000000 diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/LICENSE b/vendor/github.com/dsoprea/go-png-image-structure/v2/LICENSE deleted file mode 100644 index 163291ed6..000000000 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT LICENSE - -Copyright 2020 Dustin Oprea - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/chunk_decoder.go b/vendor/github.com/dsoprea/go-png-image-structure/v2/chunk_decoder.go deleted file mode 100644 index b5e0b1b16..000000000 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/chunk_decoder.go +++ /dev/null @@ -1,87 +0,0 @@ -package pngstructure - -import ( - "bytes" - "fmt" - - "encoding/binary" - - "github.com/dsoprea/go-logging" -) - -type ChunkDecoder struct { -} - -func NewChunkDecoder() *ChunkDecoder { - return new(ChunkDecoder) -} - -func (cd *ChunkDecoder) Decode(c *Chunk) (decoded interface{}, err error) { - defer func() { - if state := recover(); state != nil { - err := log.Wrap(state.(error)) - log.Panic(err) - } - }() - - switch c.Type { - case "IHDR": - ihdr, err := cd.decodeIHDR(c) - log.PanicIf(err) - - return ihdr, nil - } - - // We don't decode this particular type. - return nil, nil -} - -type ChunkIHDR struct { - Width uint32 - Height uint32 - BitDepth uint8 - ColorType uint8 - CompressionMethod uint8 - FilterMethod uint8 - InterlaceMethod uint8 -} - -func (ihdr *ChunkIHDR) String() string { - return fmt.Sprintf("IHDR", ihdr.Width, ihdr.Height, ihdr.BitDepth, ihdr.ColorType, ihdr.CompressionMethod, ihdr.FilterMethod, ihdr.InterlaceMethod) -} - -func (cd *ChunkDecoder) decodeIHDR(c *Chunk) (ihdr *ChunkIHDR, err error) { - defer func() { - if state := recover(); state != nil { - err := log.Wrap(state.(error)) - log.Panic(err) - } - }() - - b := bytes.NewBuffer(c.Data) - - ihdr = new(ChunkIHDR) - - err = binary.Read(b, binary.BigEndian, &ihdr.Width) - log.PanicIf(err) - - err = binary.Read(b, binary.BigEndian, &ihdr.Height) - log.PanicIf(err) - - err = binary.Read(b, binary.BigEndian, &ihdr.BitDepth) - log.PanicIf(err) - - err = binary.Read(b, binary.BigEndian, &ihdr.ColorType) - log.PanicIf(err) - - err = binary.Read(b, binary.BigEndian, &ihdr.CompressionMethod) - log.PanicIf(err) - - err = binary.Read(b, binary.BigEndian, &ihdr.FilterMethod) - log.PanicIf(err) - - err = binary.Read(b, binary.BigEndian, &ihdr.InterlaceMethod) - log.PanicIf(err) - - return ihdr, nil -} diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/media_parser.go b/vendor/github.com/dsoprea/go-png-image-structure/v2/media_parser.go deleted file mode 100644 index c0e287365..000000000 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/media_parser.go +++ /dev/null @@ -1,118 +0,0 @@ -package pngstructure - -import ( - "bufio" - "bytes" - "image" - "io" - "os" - - "image/png" - - "github.com/dsoprea/go-logging" - "github.com/dsoprea/go-utility/v2/image" -) - -// PngMediaParser knows how to parse a PNG stream. -type PngMediaParser struct { -} - -// NewPngMediaParser returns a new `PngMediaParser` struct. -func NewPngMediaParser() *PngMediaParser { - - // TODO(dustin): Add test - - return new(PngMediaParser) -} - -// Parse parses a PNG stream given a `io.ReadSeeker`. -func (pmp *PngMediaParser) Parse(rs io.ReadSeeker, size int) (mc riimage.MediaContext, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - ps := NewPngSplitter() - - err = ps.readHeader(rs) - log.PanicIf(err) - - s := bufio.NewScanner(rs) - - // Since each segment can be any size, our buffer must be allowed to grow - // as large as the file. - buffer := []byte{} - s.Buffer(buffer, size) - s.Split(ps.Split) - - for s.Scan() != false { - } - - log.PanicIf(s.Err()) - - return ps.Chunks(), nil -} - -// ParseFile parses a PNG stream given a file-path. -func (pmp *PngMediaParser) ParseFile(filepath string) (mc riimage.MediaContext, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - f, err := os.Open(filepath) - log.PanicIf(err) - - defer f.Close() - - stat, err := f.Stat() - log.PanicIf(err) - - size := stat.Size() - - chunks, err := pmp.Parse(f, int(size)) - log.PanicIf(err) - - return chunks, nil -} - -// ParseBytes parses a PNG stream given a byte-slice. -func (pmp *PngMediaParser) ParseBytes(data []byte) (mc riimage.MediaContext, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - br := bytes.NewReader(data) - - chunks, err := pmp.Parse(br, len(data)) - log.PanicIf(err) - - return chunks, nil -} - -// LooksLikeFormat returns a boolean indicating whether the stream looks like a -// PNG image. -func (pmp *PngMediaParser) LooksLikeFormat(data []byte) bool { - return bytes.Compare(data[:len(PngSignature)], PngSignature[:]) == 0 -} - -// GetImage returns an image.Image-compatible struct. -func (pmp *PngMediaParser) GetImage(r io.Reader) (img image.Image, err error) { - img, err = png.Decode(r) - log.PanicIf(err) - - return img, nil -} - -var ( - // Enforce interface conformance. - _ riimage.MediaParser = new(PngMediaParser) -) diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go b/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go deleted file mode 100644 index fbb022887..000000000 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go +++ /dev/null @@ -1,416 +0,0 @@ -package pngstructure - -import ( - "bytes" - "errors" - "fmt" - "io" - - "encoding/binary" - "hash/crc32" - - "github.com/dsoprea/go-exif/v3" - "github.com/dsoprea/go-exif/v3/common" - "github.com/dsoprea/go-logging" - "github.com/dsoprea/go-utility/v2/image" -) - -var ( - PngSignature = [8]byte{137, 'P', 'N', 'G', '\r', '\n', 26, '\n'} - EXifChunkType = "eXIf" - IHDRChunkType = "IHDR" -) - -var ( - ErrNotPng = errors.New("not png data") - ErrCrcFailure = errors.New("crc failure") -) - -// ChunkSlice encapsulates a slice of chunks. -type ChunkSlice struct { - chunks []*Chunk -} - -func NewChunkSlice(chunks []*Chunk) *ChunkSlice { - if len(chunks) == 0 { - log.Panicf("ChunkSlice must be initialized with at least one chunk (IHDR)") - } else if chunks[0].Type != IHDRChunkType { - log.Panicf("first chunk in any ChunkSlice must be an IHDR") - } - - return &ChunkSlice{ - chunks: chunks, - } -} - -func NewPngChunkSlice() *ChunkSlice { - - ihdrChunk := &Chunk{ - Type: IHDRChunkType, - } - - ihdrChunk.UpdateCrc32() - - return NewChunkSlice([]*Chunk{ihdrChunk}) -} - -func (cs *ChunkSlice) String() string { - return fmt.Sprintf("ChunkSlize", len(cs.chunks)) -} - -// Chunks exposes the actual slice. -func (cs *ChunkSlice) Chunks() []*Chunk { - return cs.chunks -} - -// Write encodes and writes all chunks. -func (cs *ChunkSlice) WriteTo(w io.Writer) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - _, err = w.Write(PngSignature[:]) - log.PanicIf(err) - - // TODO(dustin): !! This should respect the safe-to-copy characteristic. - for _, c := range cs.chunks { - _, err := c.WriteTo(w) - log.PanicIf(err) - } - - return nil -} - -// Index returns a map of chunk types to chunk slices, grouping all like chunks. -func (cs *ChunkSlice) Index() (index map[string][]*Chunk) { - index = make(map[string][]*Chunk) - for _, c := range cs.chunks { - if grouped, found := index[c.Type]; found == true { - index[c.Type] = append(grouped, c) - } else { - index[c.Type] = []*Chunk{c} - } - } - - return index -} - -// FindExif returns the the segment that hosts the EXIF data. -func (cs *ChunkSlice) FindExif() (chunk *Chunk, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - index := cs.Index() - - if chunks, found := index[EXifChunkType]; found == true { - return chunks[0], nil - } - - log.Panic(exif.ErrNoExif) - - // Never called. - return nil, nil -} - -// Exif returns an `exif.Ifd` instance with the existing tags. -func (cs *ChunkSlice) Exif() (rootIfd *exif.Ifd, data []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - chunk, err := cs.FindExif() - log.PanicIf(err) - - im, err := exifcommon.NewIfdMappingWithStandard() - log.PanicIf(err) - - ti := exif.NewTagIndex() - - // TODO(dustin): Refactor and support `exif.GetExifData()`. - - _, index, err := exif.Collect(im, ti, chunk.Data) - log.PanicIf(err) - - return index.RootIfd, chunk.Data, nil -} - -// ConstructExifBuilder returns an `exif.IfdBuilder` instance (needed for -// modifying) preloaded with all existing tags. -func (cs *ChunkSlice) ConstructExifBuilder() (rootIb *exif.IfdBuilder, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rootIfd, _, err := cs.Exif() - log.PanicIf(err) - - ib := exif.NewIfdBuilderFromExistingChain(rootIfd) - - return ib, nil -} - -// SetExif encodes and sets EXIF data into this segment. -func (cs *ChunkSlice) SetExif(ib *exif.IfdBuilder) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // Encode. - - ibe := exif.NewIfdByteEncoder() - - exifData, err := ibe.EncodeToExif(ib) - log.PanicIf(err) - - // Set. - - exifChunk, err := cs.FindExif() - if err == nil { - // EXIF chunk already exists. - - exifChunk.Data = exifData - exifChunk.Length = uint32(len(exifData)) - } else { - if log.Is(err, exif.ErrNoExif) != true { - log.Panic(err) - } - - // Add a EXIF chunk for the first time. - - exifChunk = &Chunk{ - Type: EXifChunkType, - Data: exifData, - Length: uint32(len(exifData)), - } - - // Insert it after the IHDR chunk (it's a reliably appropriate place to - // put it). - cs.chunks = append(cs.chunks[:1], append([]*Chunk{exifChunk}, cs.chunks[1:]...)...) - } - - exifChunk.UpdateCrc32() - - return nil -} - -// PngSplitter hosts the princpal `Split()` method uses by `bufio.Scanner`. -type PngSplitter struct { - chunks []*Chunk - currentOffset int - - doCheckCrc bool - crcErrors []string -} - -func (ps *PngSplitter) Chunks() *ChunkSlice { - return NewChunkSlice(ps.chunks) -} - -func (ps *PngSplitter) DoCheckCrc(doCheck bool) { - ps.doCheckCrc = doCheck -} - -func (ps *PngSplitter) CrcErrors() []string { - return ps.crcErrors -} - -func NewPngSplitter() *PngSplitter { - return &PngSplitter{ - chunks: make([]*Chunk, 0), - doCheckCrc: true, - crcErrors: make([]string, 0), - } -} - -// Chunk describes a single chunk. -type Chunk struct { - Offset int - Length uint32 - Type string - Data []byte - Crc uint32 -} - -func (c *Chunk) String() string { - return fmt.Sprintf("Chunk", c.Offset, c.Length, c.Type, c.Crc) -} - -func calculateCrc32(chunk *Chunk) uint32 { - c := crc32.NewIEEE() - - c.Write([]byte(chunk.Type)) - c.Write(chunk.Data) - - return c.Sum32() -} - -func (c *Chunk) UpdateCrc32() { - c.Crc = calculateCrc32(c) -} - -func (c *Chunk) CheckCrc32() bool { - expected := calculateCrc32(c) - return c.Crc == expected -} - -// Bytes encodes and returns the bytes for this chunk. -func (c *Chunk) Bytes() []byte { - defer func() { - if state := recover(); state != nil { - err := log.Wrap(state.(error)) - log.Panic(err) - } - }() - - if len(c.Data) != int(c.Length) { - log.Panicf("length of data not correct") - } - - preallocated := make([]byte, 0, 4+4+c.Length+4) - b := bytes.NewBuffer(preallocated) - - err := binary.Write(b, binary.BigEndian, c.Length) - log.PanicIf(err) - - _, err = b.Write([]byte(c.Type)) - log.PanicIf(err) - - if c.Data != nil { - _, err = b.Write(c.Data) - log.PanicIf(err) - } - - err = binary.Write(b, binary.BigEndian, c.Crc) - log.PanicIf(err) - - return b.Bytes() -} - -// Write encodes and writes the bytes for this chunk. -func (c *Chunk) WriteTo(w io.Writer) (count int, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - if len(c.Data) != int(c.Length) { - log.Panicf("length of data not correct") - } - - err = binary.Write(w, binary.BigEndian, c.Length) - log.PanicIf(err) - - _, err = w.Write([]byte(c.Type)) - log.PanicIf(err) - - _, err = w.Write(c.Data) - log.PanicIf(err) - - err = binary.Write(w, binary.BigEndian, c.Crc) - log.PanicIf(err) - - return 4 + len(c.Type) + len(c.Data) + 4, nil -} - -// readHeader verifies that the PNG header bytes appear next. -func (ps *PngSplitter) readHeader(r io.Reader) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - len_ := len(PngSignature) - header := make([]byte, len_) - - _, err = r.Read(header) - log.PanicIf(err) - - ps.currentOffset += len_ - - if bytes.Compare(header, PngSignature[:]) != 0 { - log.Panic(ErrNotPng) - } - - return nil -} - -// Split fulfills the `bufio.SplitFunc` function definition for -// `bufio.Scanner`. -func (ps *PngSplitter) Split(data []byte, atEOF bool) (advance int, token []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // We might have more than one chunk's worth, and, if `atEOF` is true, we - // won't be called again. We'll repeatedly try to read additional chunks, - // but, when we run out of the data we were given then we'll return the - // number of bytes fo rthe chunks we've already completely read. Then, - // we'll be called again from theend ofthose bytes, at which point we'll - // indicate that we don't yet have enough for another chunk, and we should - // be then called with more. - for { - len_ := len(data) - if len_ < 8 { - return advance, nil, nil - } - - length := binary.BigEndian.Uint32(data[:4]) - type_ := string(data[4:8]) - chunkSize := (8 + int(length) + 4) - - if len_ < chunkSize { - return advance, nil, nil - } - - crcIndex := 8 + length - crc := binary.BigEndian.Uint32(data[crcIndex : crcIndex+4]) - - content := make([]byte, length) - copy(content, data[8:8+length]) - - c := &Chunk{ - Length: length, - Type: type_, - Data: content, - Crc: crc, - Offset: ps.currentOffset, - } - - ps.chunks = append(ps.chunks, c) - - if c.CheckCrc32() == false { - ps.crcErrors = append(ps.crcErrors, type_) - - if ps.doCheckCrc == true { - log.Panic(ErrCrcFailure) - } - } - - advance += chunkSize - ps.currentOffset += chunkSize - - data = data[chunkSize:] - } - - return advance, nil, nil -} - -var ( - // Enforce interface conformance. - _ riimage.MediaContext = new(ChunkSlice) -) diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/testing_common.go b/vendor/github.com/dsoprea/go-png-image-structure/v2/testing_common.go deleted file mode 100644 index 9df13a858..000000000 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/testing_common.go +++ /dev/null @@ -1,64 +0,0 @@ -package pngstructure - -import ( - "os" - "path" - - "github.com/dsoprea/go-logging" -) - -var ( - assetsPath = "" -) - -func getModuleRootPath() string { - moduleRootPath := os.Getenv("PNG_MODULE_ROOT_PATH") - if moduleRootPath != "" { - return moduleRootPath - } - - currentWd, err := os.Getwd() - log.PanicIf(err) - - currentPath := currentWd - visited := make([]string, 0) - - for { - tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT") - - _, err := os.Stat(tryStampFilepath) - if err != nil && os.IsNotExist(err) != true { - log.Panic(err) - } else if err == nil { - break - } - - visited = append(visited, tryStampFilepath) - - currentPath = path.Dir(currentPath) - if currentPath == "/" { - log.Panicf("could not find module-root: %v", visited) - } - } - - return currentPath -} - -func getTestAssetsPath() string { - if assetsPath == "" { - moduleRootPath := getModuleRootPath() - assetsPath = path.Join(moduleRootPath, "assets") - } - - return assetsPath -} - -func getTestBasicImageFilepath() string { - assetsPath := getTestAssetsPath() - return path.Join(assetsPath, "libpng.png") -} - -func getTestExifImageFilepath() string { - assetsPath := getTestAssetsPath() - return path.Join(assetsPath, "exif.png") -} diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/utility.go b/vendor/github.com/dsoprea/go-png-image-structure/v2/utility.go deleted file mode 100644 index dbff145a6..000000000 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/utility.go +++ /dev/null @@ -1,65 +0,0 @@ -package pngstructure - -import ( - "bytes" - "fmt" - - "github.com/dsoprea/go-logging" -) - -func DumpBytes(data []byte) { - fmt.Printf("DUMP: ") - for _, x := range data { - fmt.Printf("%02x ", x) - } - - fmt.Printf("\n") -} - -func DumpBytesClause(data []byte) { - fmt.Printf("DUMP: ") - - fmt.Printf("[]byte { ") - - for i, x := range data { - fmt.Printf("0x%02x", x) - - if i < len(data)-1 { - fmt.Printf(", ") - } - } - - fmt.Printf(" }\n") -} - -func DumpBytesToString(data []byte) string { - b := new(bytes.Buffer) - - for i, x := range data { - _, err := b.WriteString(fmt.Sprintf("%02x", x)) - log.PanicIf(err) - - if i < len(data)-1 { - _, err := b.WriteRune(' ') - log.PanicIf(err) - } - } - - return b.String() -} - -func DumpBytesClauseToString(data []byte) string { - b := new(bytes.Buffer) - - for i, x := range data { - _, err := b.WriteString(fmt.Sprintf("0x%02x", x)) - log.PanicIf(err) - - if i < len(data)-1 { - _, err := b.WriteString(", ") - log.PanicIf(err) - } - } - - return b.String() -} diff --git a/vendor/github.com/superseriousbusiness/exif-terminator/logger.go b/vendor/github.com/superseriousbusiness/exif-terminator/logger.go deleted file mode 100644 index e607c55dd..000000000 --- a/vendor/github.com/superseriousbusiness/exif-terminator/logger.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - exif-terminator - Copyright (C) 2022 SuperSeriousBusiness admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package terminator - -import "fmt" - -var logger ErrorLogger - -func init() { - logger = &defaultErrorLogger{} -} - -// ErrorLogger denotes a generic error logging function. -type ErrorLogger interface { - Error(args ...interface{}) -} - -type defaultErrorLogger struct{} - -func (d *defaultErrorLogger) Error(args ...interface{}) { - fmt.Println(args...) -} - -// SetErrorLogger allows a user of the exif-terminator library -// to set the logger that will be used for error logging. -// -// If it is not set, the default error logger will be used, which -// just prints errors to stdout. -func SetErrorLogger(errorLogger ErrorLogger) { - logger = errorLogger -} diff --git a/vendor/github.com/superseriousbusiness/exif-terminator/png.go b/vendor/github.com/superseriousbusiness/exif-terminator/png.go index 4a1ac5bf1..774ec0ed6 100644 --- a/vendor/github.com/superseriousbusiness/exif-terminator/png.go +++ b/vendor/github.com/superseriousbusiness/exif-terminator/png.go @@ -19,10 +19,9 @@ package terminator import ( - "encoding/binary" "io" - pngstructure "github.com/dsoprea/go-png-image-structure/v2" + pngstructure "github.com/superseriousbusiness/go-png-image-structure/v2" ) type pngVisitor struct { @@ -45,49 +44,50 @@ func (v *pngVisitor) split(data []byte, atEOF bool) (int, []byte, error) { } } - // check if the splitter has any new chunks in it that we haven't written yet - chunkSlice := v.ps.Chunks() + // Check if the splitter now has + // any new chunks in it for us. + chunkSlice, err := v.ps.Chunks() + if err != nil { + return advance, token, err + } + + // Write each chunk by passing it + // through our custom write func, + // which strips out exif and fixes + // the CRC of each chunk. chunks := chunkSlice.Chunks() for i, chunk := range chunks { - // look through all the chunks in the splitter - if i > v.lastWrittenChunk { - // we've got a chunk we haven't written yet! write it... - if err := v.writeChunk(chunk); err != nil { - return advance, token, err - } - // then remove the data - chunk.Data = chunk.Data[:0] - // and update - v.lastWrittenChunk = i + if i <= v.lastWrittenChunk { + // Skip already + // written chunks. + continue } + + // Write this new chunk. + if err := v.writeChunk(chunk); err != nil { + return advance, token, err + } + v.lastWrittenChunk = i + + // Zero data; here you + // go garbage collector. + chunk.Data = nil } return advance, token, err } func (v *pngVisitor) writeChunk(chunk *pngstructure.Chunk) error { - if err := binary.Write(v.writer, binary.BigEndian, chunk.Length); err != nil { - return err - } - - if _, err := v.writer.Write([]byte(chunk.Type)); err != nil { - return err - } - if chunk.Type == pngstructure.EXifChunkType { - blank := make([]byte, len(chunk.Data)) - if _, err := v.writer.Write(blank); err != nil { - return err - } - } else { - if _, err := v.writer.Write(chunk.Data); err != nil { - return err - } + // Replace exif data + // with zero bytes. + clear(chunk.Data) } - if err := binary.Write(v.writer, binary.BigEndian, chunk.Crc); err != nil { - return err - } + // Fix CRC of each chunk. + chunk.UpdateCrc32() - return nil + // finally, write chunk to writer. + _, err := chunk.WriteTo(v.writer) + return err } diff --git a/vendor/github.com/superseriousbusiness/exif-terminator/terminator.go b/vendor/github.com/superseriousbusiness/exif-terminator/terminator.go index 9d9e6e743..7dd3d9ad7 100644 --- a/vendor/github.com/superseriousbusiness/exif-terminator/terminator.go +++ b/vendor/github.com/superseriousbusiness/exif-terminator/terminator.go @@ -25,29 +25,34 @@ import ( "fmt" "io" - pngstructure "github.com/dsoprea/go-png-image-structure/v2" jpegstructure "github.com/superseriousbusiness/go-jpeg-image-structure/v2" + pngstructure "github.com/superseriousbusiness/go-png-image-structure/v2" ) func Terminate(in io.Reader, fileSize int, mediaType string) (io.Reader, error) { - // to avoid keeping too much stuff in memory we want to pipe data directly + // To avoid keeping too much stuff + // in memory we want to pipe data + // directly to the reader. pipeReader, pipeWriter := io.Pipe() - // we don't know ahead of time how long segments might be: they could be as large as - // the file itself, so unfortunately we need to allocate a buffer here that'scanner as large - // as the file + // We don't know ahead of time how long + // segments might be: they could be as + // large as the file itself, so we need + // a buffer with generous overhead. scanner := bufio.NewScanner(in) scanner.Buffer([]byte{}, fileSize) - var err error + var err error switch mediaType { case "image/jpeg", "jpeg", "jpg": err = terminateJpeg(scanner, pipeWriter, fileSize) + case "image/webp", "webp": err = terminateWebp(scanner, pipeWriter) + case "image/png", "png": - // for pngs we need to skip the header bytes, so read them in - // and check we're really dealing with a png here + // For pngs we need to skip the header bytes, so read + // them in and check we're really dealing with a png. header := make([]byte, len(pngstructure.PngSignature)) if _, headerError := in.Read(header); headerError != nil { err = headerError @@ -67,68 +72,87 @@ func Terminate(in io.Reader, fileSize int, mediaType string) (io.Reader, error) return pipeReader, err } -func terminateJpeg(scanner *bufio.Scanner, writer io.WriteCloser, expectedFileSize int) error { - // jpeg visitor is where the spicy hack of streaming the de-exifed data is contained +func terminateJpeg(scanner *bufio.Scanner, writer *io.PipeWriter, expectedFileSize int) error { v := &jpegVisitor{ writer: writer, expectedFileSize: expectedFileSize, } - // provide the visitor to the splitter so that it triggers on every section scan + // Provide the visitor to the splitter so + // that it triggers on every section scan. js := jpegstructure.NewJpegSplitter(v) - // the visitor also needs to read back the list of segments: for this it needs - // to know what jpeg splitter it's attached to, so give it a pointer to the splitter + // The visitor also needs to read back the + // list of segments: for this it needs to + // know what jpeg splitter it's attached to, + // so give it a pointer to the splitter. v.js = js - // use the jpeg splitters 'split' function, which satisfies the bufio.SplitFunc interface + // Jpeg visitor's 'split' function + // satisfies bufio.SplitFunc{}. scanner.Split(js.Split) - scanAndClose(scanner, writer) + go scanAndClose(scanner, writer) return nil } -func terminateWebp(scanner *bufio.Scanner, writer io.WriteCloser) error { +func terminateWebp(scanner *bufio.Scanner, writer *io.PipeWriter) error { v := &webpVisitor{ writer: writer, } - // use the webp visitor's 'split' function, which satisfies the bufio.SplitFunc interface + // Webp visitor's 'split' function + // satisfies bufio.SplitFunc{}. scanner.Split(v.split) - scanAndClose(scanner, writer) + go scanAndClose(scanner, writer) return nil } -func terminatePng(scanner *bufio.Scanner, writer io.WriteCloser) error { +func terminatePng(scanner *bufio.Scanner, writer *io.PipeWriter) error { ps := pngstructure.NewPngSplitter() + // Don't bother checking CRC; + // we're overwriting it anyway. + ps.DoCheckCrc(false) + v := &pngVisitor{ ps: ps, writer: writer, lastWrittenChunk: -1, } - // use the png visitor's 'split' function, which satisfies the bufio.SplitFunc interface + // Png visitor's 'split' function + // satisfies bufio.SplitFunc{}. scanner.Split(v.split) - scanAndClose(scanner, writer) + go scanAndClose(scanner, writer) return nil } -func scanAndClose(scanner *bufio.Scanner, writer io.WriteCloser) { - // scan asynchronously until there's nothing left to scan, and then close the writer - // so that the reader on the other side knows that we're done - // - // due to the nature of io.Pipe, writing won't actually work - // until the pipeReader starts being read by the caller, which - // is why we do this asynchronously - go func() { - defer writer.Close() - for scanner.Scan() { - } - if scanner.Err() != nil { - logger.Error(scanner.Err()) - } +// scanAndClose scans through the given scanner until there's +// nothing left to scan, and then closes the writer so that the +// reader on the other side of the pipe knows that we're done. +// +// Any error encountered when scanning will be logged by terminator. +// +// Due to the nature of io.Pipe, writing won't actually work +// until the pipeReader starts being read by the caller, which +// is why this function should always be called asynchronously. +func scanAndClose(scanner *bufio.Scanner, writer *io.PipeWriter) { + var err error + + defer func() { + // Always close writer, using returned + // scanner error (if any). If err is nil + // then the standard io.EOF will be used. + // (this will not overwrite existing). + writer.CloseWithError(err) }() + + for scanner.Scan() { + } + + // Set error on return. + err = scanner.Err() } diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/.MODULE_ROOT b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/.MODULE_ROOT new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/LICENSE b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/LICENSE new file mode 100644 index 000000000..163291ed6 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/LICENSE @@ -0,0 +1,9 @@ +MIT LICENSE + +Copyright 2020 Dustin Oprea + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/chunk_decoder.go b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/chunk_decoder.go new file mode 100644 index 000000000..518bc91ad --- /dev/null +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/chunk_decoder.go @@ -0,0 +1,81 @@ +package pngstructure + +import ( + "bytes" + "fmt" + + "encoding/binary" +) + +type ChunkDecoder struct { +} + +func NewChunkDecoder() *ChunkDecoder { + return new(ChunkDecoder) +} + +func (cd *ChunkDecoder) Decode(c *Chunk) (decoded interface{}, err error) { + switch c.Type { + case "IHDR": + return cd.decodeIHDR(c) + } + + // We don't decode this type. + return nil, nil +} + +type ChunkIHDR struct { + Width uint32 + Height uint32 + BitDepth uint8 + ColorType uint8 + CompressionMethod uint8 + FilterMethod uint8 + InterlaceMethod uint8 +} + +func (ihdr *ChunkIHDR) String() string { + return fmt.Sprintf("IHDR", + ihdr.Width, ihdr.Height, ihdr.BitDepth, ihdr.ColorType, ihdr.CompressionMethod, ihdr.FilterMethod, ihdr.InterlaceMethod, + ) +} + +func (cd *ChunkDecoder) decodeIHDR(c *Chunk) (*ChunkIHDR, error) { + var ( + b = bytes.NewBuffer(c.Data) + ihdr = new(ChunkIHDR) + readf = func(data interface{}) error { + return binary.Read(b, binary.BigEndian, data) + } + ) + + if err := readf(&ihdr.Width); err != nil { + return nil, err + } + + if err := readf(&ihdr.Height); err != nil { + return nil, err + } + + if err := readf(&ihdr.BitDepth); err != nil { + return nil, err + } + + if err := readf(&ihdr.ColorType); err != nil { + return nil, err + } + + if err := readf(&ihdr.CompressionMethod); err != nil { + return nil, err + } + + if err := readf(&ihdr.FilterMethod); err != nil { + return nil, err + } + + if err := readf(&ihdr.InterlaceMethod); err != nil { + return nil, err + } + + return ihdr, nil +} diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/media_parser.go b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/media_parser.go new file mode 100644 index 000000000..4c8421905 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/media_parser.go @@ -0,0 +1,85 @@ +package pngstructure + +import ( + "bufio" + "bytes" + "image" + "io" + "os" + + "image/png" + + riimage "github.com/dsoprea/go-utility/v2/image" +) + +// PngMediaParser knows how to parse a PNG stream. +type PngMediaParser struct { +} + +// NewPngMediaParser returns a new `PngMediaParser`. +func NewPngMediaParser() riimage.MediaParser { + return new(PngMediaParser) +} + +// Parse parses a PNG stream given a `io.ReadSeeker`. +func (pmp *PngMediaParser) Parse( + rs io.ReadSeeker, + size int, +) (riimage.MediaContext, error) { + ps := NewPngSplitter() + if err := ps.readHeader(rs); err != nil { + return nil, err + } + + s := bufio.NewScanner(rs) + + // Since each segment can be any + // size, our buffer must be allowed + // to grow as large as the file. + buffer := []byte{} + s.Buffer(buffer, size) + s.Split(ps.Split) + + for s.Scan() { + } + + if err := s.Err(); err != nil { + return nil, err + } + + return ps.Chunks() +} + +// ParseFile parses a PNG stream given a file-path. +func (pmp *PngMediaParser) ParseFile(filepath string) (riimage.MediaContext, error) { + f, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer f.Close() + + stat, err := f.Stat() + if err != nil { + return nil, err + } + + size := stat.Size() + return pmp.Parse(f, int(size)) +} + +// ParseBytes parses a PNG stream given a byte-slice. +func (pmp *PngMediaParser) ParseBytes(data []byte) (riimage.MediaContext, error) { + br := bytes.NewReader(data) + return pmp.Parse(br, len(data)) +} + +// LooksLikeFormat returns a boolean indicating +// whether the stream looks like a PNG image. +func (pmp *PngMediaParser) LooksLikeFormat(data []byte) bool { + return bytes.Equal(data[:len(PngSignature)], PngSignature[:]) +} + +// GetImage returns an image.Image-compatible struct. +func (pmp *PngMediaParser) GetImage(r io.Reader) (img image.Image, err error) { + return png.Decode(r) +} diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/png.go b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/png.go new file mode 100644 index 000000000..dfe773b71 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/png.go @@ -0,0 +1,386 @@ +package pngstructure + +import ( + "bytes" + "errors" + "fmt" + "io" + + "encoding/binary" + "hash/crc32" + + "github.com/dsoprea/go-exif/v3" + exifcommon "github.com/dsoprea/go-exif/v3/common" + riimage "github.com/dsoprea/go-utility/v2/image" +) + +var ( + PngSignature = [8]byte{137, 'P', 'N', 'G', '\r', '\n', 26, '\n'} + EXifChunkType = "eXIf" + IHDRChunkType = "IHDR" +) + +var ( + ErrNotPng = errors.New("not png data") + ErrCrcFailure = errors.New("crc failure") +) + +// ChunkSlice encapsulates a slice of chunks. +type ChunkSlice struct { + chunks []*Chunk +} + +func NewChunkSlice(chunks []*Chunk) (*ChunkSlice, error) { + if len(chunks) == 0 { + err := errors.New("ChunkSlice must be initialized with at least one chunk (IHDR)") + return nil, err + } else if chunks[0].Type != IHDRChunkType { + err := errors.New("first chunk in any ChunkSlice must be an IHDR") + return nil, err + } + + return &ChunkSlice{chunks}, nil +} + +func NewPngChunkSlice() (*ChunkSlice, error) { + ihdrChunk := &Chunk{ + Type: IHDRChunkType, + } + + ihdrChunk.UpdateCrc32() + + return NewChunkSlice([]*Chunk{ihdrChunk}) +} + +func (cs *ChunkSlice) String() string { + return fmt.Sprintf("ChunkSlize", len(cs.chunks)) +} + +// Chunks exposes the actual slice. +func (cs *ChunkSlice) Chunks() []*Chunk { + return cs.chunks +} + +// Write encodes and writes all chunks. +func (cs *ChunkSlice) WriteTo(w io.Writer) error { + if _, err := w.Write(PngSignature[:]); err != nil { + return err + } + + // TODO(dustin): !! This should respect + // the safe-to-copy characteristic. + for _, c := range cs.chunks { + if _, err := c.WriteTo(w); err != nil { + return err + } + } + + return nil +} + +// Index returns a map of chunk types to chunk slices, grouping all like chunks. +func (cs *ChunkSlice) Index() (index map[string][]*Chunk) { + index = make(map[string][]*Chunk) + for _, c := range cs.chunks { + if grouped, found := index[c.Type]; found { + index[c.Type] = append(grouped, c) + } else { + index[c.Type] = []*Chunk{c} + } + } + + return index +} + +// FindExif returns the the segment that hosts the EXIF data. +func (cs *ChunkSlice) FindExif() (chunk *Chunk, err error) { + index := cs.Index() + if chunks, found := index[EXifChunkType]; found { + return chunks[0], nil + } + + return nil, exif.ErrNoExif +} + +// Exif returns an `exif.Ifd` instance with the existing tags. +func (cs *ChunkSlice) Exif() (*exif.Ifd, []byte, error) { + chunk, err := cs.FindExif() + if err != nil { + return nil, nil, err + } + + im, err := exifcommon.NewIfdMappingWithStandard() + if err != nil { + return nil, nil, err + } + + ti := exif.NewTagIndex() + + _, index, err := exif.Collect(im, ti, chunk.Data) + if err != nil { + return nil, nil, err + } + + return index.RootIfd, chunk.Data, nil +} + +// ConstructExifBuilder returns an `exif.IfdBuilder` instance +// (needed for modifying) preloaded with all existing tags. +func (cs *ChunkSlice) ConstructExifBuilder() (*exif.IfdBuilder, error) { + rootIfd, _, err := cs.Exif() + if err != nil { + return nil, err + } + + return exif.NewIfdBuilderFromExistingChain(rootIfd), nil +} + +// SetExif encodes and sets EXIF data into this segment. +func (cs *ChunkSlice) SetExif(ib *exif.IfdBuilder) error { + // Encode. + + ibe := exif.NewIfdByteEncoder() + + exifData, err := ibe.EncodeToExif(ib) + if err != nil { + return err + } + + // Set. + + exifChunk, err := cs.FindExif() + + switch { + case err == nil: + // EXIF chunk already exists. + exifChunk.Data = exifData + exifChunk.Length = uint32(len(exifData)) + + case errors.Is(err, exif.ErrNoExif): + // Add a EXIF chunk for the first time. + exifChunk = &Chunk{ + Type: EXifChunkType, + Data: exifData, + Length: uint32(len(exifData)), + } + + // Insert exif after the IHDR chunk; it's + // a reliably appropriate place to put it. + cs.chunks = append( + cs.chunks[:1], + append( + []*Chunk{exifChunk}, + cs.chunks[1:]..., + )..., + ) + + default: + return err + } + + exifChunk.UpdateCrc32() + return nil +} + +// PngSplitter hosts the princpal `Split()` +// method uses by `bufio.Scanner`. +type PngSplitter struct { + chunks []*Chunk + currentOffset int + + doCheckCrc bool + crcErrors []string +} + +func (ps *PngSplitter) Chunks() (*ChunkSlice, error) { + return NewChunkSlice(ps.chunks) +} + +func (ps *PngSplitter) DoCheckCrc(doCheck bool) { + ps.doCheckCrc = doCheck +} + +func (ps *PngSplitter) CrcErrors() []string { + return ps.crcErrors +} + +func NewPngSplitter() *PngSplitter { + return &PngSplitter{ + chunks: make([]*Chunk, 0), + doCheckCrc: true, + crcErrors: make([]string, 0), + } +} + +// Chunk describes a single chunk. +type Chunk struct { + Offset int + Length uint32 + Type string + Data []byte + Crc uint32 +} + +func (c *Chunk) String() string { + return fmt.Sprintf("Chunk", c.Offset, c.Length, c.Type, c.Crc) +} + +func calculateCrc32(chunk *Chunk) uint32 { + c := crc32.NewIEEE() + + c.Write([]byte(chunk.Type)) + c.Write(chunk.Data) + + return c.Sum32() +} + +func (c *Chunk) UpdateCrc32() { + c.Crc = calculateCrc32(c) +} + +func (c *Chunk) CheckCrc32() bool { + expected := calculateCrc32(c) + return c.Crc == expected +} + +// Bytes encodes and returns the bytes for this chunk. +func (c *Chunk) Bytes() ([]byte, error) { + if len(c.Data) != int(c.Length) { + return nil, errors.New("length of data not correct") + } + b := make([]byte, 0, 4+4+c.Length+4) + b = binary.BigEndian.AppendUint32(b, c.Length) + b = append(b, c.Type...) + b = append(b, c.Data...) + b = binary.BigEndian.AppendUint32(b, c.Crc) + return b, nil +} + +// Write encodes and writes the bytes for this chunk. +func (c *Chunk) WriteTo(w io.Writer) (int, error) { + if len(c.Data) != int(c.Length) { + return 0, errors.New("length of data not correct") + } + + var n int + + b := make([]byte, 4) // uint32 buf + + binary.BigEndian.PutUint32(b, c.Length) + if nn, err := w.Write(b); err != nil { + return n + nn, err + } + + n += len(b) + + if nn, err := io.WriteString(w, c.Type); err != nil { + return n + nn, err + } + + n += len(c.Type) + + if nn, err := w.Write(c.Data); err != nil { + return n + nn, err + } + + n += len(c.Data) + + binary.BigEndian.PutUint32(b, c.Crc) + if nn, err := w.Write(b); err != nil { + return n + nn, err + } + + n += len(b) + + return n, nil +} + +// readHeader verifies that the PNG header bytes appear next. +func (ps *PngSplitter) readHeader(r io.Reader) error { + var ( + sigLen = len(PngSignature) + header = make([]byte, sigLen) + ) + + if _, err := r.Read(header); err != nil { + return err + } + + ps.currentOffset += sigLen + if !bytes.Equal(header, PngSignature[:]) { + return ErrNotPng + } + + return nil +} + +// Split fulfills the `bufio.SplitFunc` +// function definition for `bufio.Scanner`. +func (ps *PngSplitter) Split( + data []byte, + atEOF bool, +) ( + advance int, + token []byte, + err error, +) { + // We might have more than one chunk's worth, and, + // if `atEOF` is true, we won't be called again. + // We'll repeatedly try to read additional chunks, + // but, when we run out of the data we were given + // then we'll return the number of bytes for the + // chunks we've already completely read. Then, we'll + // be called again from the end ofthose bytes, at + // which point we'll indicate that we don't yet have + // enough for another chunk, and we should be then + // called with more. + for { + len_ := len(data) + if len_ < 8 { + return advance, nil, nil + } + + length := binary.BigEndian.Uint32(data[:4]) + type_ := string(data[4:8]) + chunkSize := (8 + int(length) + 4) + + if len_ < chunkSize { + return advance, nil, nil + } + + crcIndex := 8 + length + crc := binary.BigEndian.Uint32(data[crcIndex : crcIndex+4]) + + content := make([]byte, length) + copy(content, data[8:8+length]) + + c := &Chunk{ + Length: length, + Type: type_, + Data: content, + Crc: crc, + Offset: ps.currentOffset, + } + + ps.chunks = append(ps.chunks, c) + + if !c.CheckCrc32() { + ps.crcErrors = append(ps.crcErrors, type_) + + if ps.doCheckCrc { + err = ErrCrcFailure + return + } + } + + advance += chunkSize + ps.currentOffset += chunkSize + + data = data[chunkSize:] + } +} + +var ( + // Enforce interface conformance. + _ riimage.MediaContext = new(ChunkSlice) +) diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/testing_common.go b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/testing_common.go new file mode 100644 index 000000000..42f28d282 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/testing_common.go @@ -0,0 +1,77 @@ +package pngstructure + +import ( + "fmt" + "os" + "path" +) + +var ( + assetsPath = "assets" +) + +func getModuleRootPath() (string, error) { + moduleRootPath := os.Getenv("PNG_MODULE_ROOT_PATH") + if moduleRootPath != "" { + return moduleRootPath, nil + } + + currentWd, err := os.Getwd() + if err != nil { + return "", err + } + + currentPath := currentWd + visited := make([]string, 0) + + for { + tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT") + + _, err := os.Stat(tryStampFilepath) + if err != nil && !os.IsNotExist(err) { + return "", err + } else if err == nil { + break + } + + visited = append(visited, tryStampFilepath) + + currentPath = path.Dir(currentPath) + if currentPath == "/" { + return "", fmt.Errorf("could not find module-root: %v", visited) + } + } + + return currentPath, nil +} + +func getTestAssetsPath() (string, error) { + if assetsPath == "" { + moduleRootPath, err := getModuleRootPath() + if err != nil { + return "", err + } + + assetsPath = path.Join(moduleRootPath, "assets") + } + + return assetsPath, nil +} + +func getTestBasicImageFilepath() (string, error) { + assetsPath, err := getTestAssetsPath() + if err != nil { + return "", err + } + + return path.Join(assetsPath, "libpng.png"), nil +} + +func getTestExifImageFilepath() (string, error) { + assetsPath, err := getTestAssetsPath() + if err != nil { + return "", err + } + + return path.Join(assetsPath, "exif.png"), nil +} diff --git a/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go new file mode 100644 index 000000000..cac6020f2 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go @@ -0,0 +1,67 @@ +package pngstructure + +import ( + "bytes" + "fmt" +) + +func DumpBytes(data []byte) { + fmt.Printf("DUMP: ") + for _, x := range data { + fmt.Printf("%02x ", x) + } + + fmt.Printf("\n") +} + +func DumpBytesClause(data []byte) { + fmt.Printf("DUMP: ") + + fmt.Printf("[]byte { ") + + for i, x := range data { + fmt.Printf("0x%02x", x) + + if i < len(data)-1 { + fmt.Printf(", ") + } + } + + fmt.Printf(" }\n") +} + +func DumpBytesToString(data []byte) (string, error) { + b := new(bytes.Buffer) + + for i, x := range data { + if _, err := b.WriteString(fmt.Sprintf("%02x", x)); err != nil { + return "", err + } + + if i < len(data)-1 { + if _, err := b.WriteRune(' '); err != nil { + return "", err + } + } + } + + return b.String(), nil +} + +func DumpBytesClauseToString(data []byte) (string, error) { + b := new(bytes.Buffer) + + for i, x := range data { + if _, err := b.WriteString(fmt.Sprintf("0x%02x", x)); err != nil { + return "", err + } + + if i < len(data)-1 { + if _, err := b.WriteString(", "); err != nil { + return "", err + } + } + } + + return b.String(), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6d43a4011..bdf6e5d39 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -167,9 +167,6 @@ github.com/dsoprea/go-logging # github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d ## explicit; go 1.13 github.com/dsoprea/go-photoshop-info-format -# github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d -## explicit; go 1.12 -github.com/dsoprea/go-png-image-structure/v2 # github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e ## explicit; go 1.12 github.com/dsoprea/go-utility/v2/filesystem @@ -658,12 +655,15 @@ github.com/superseriousbusiness/activity/streams/values/rfc2045 github.com/superseriousbusiness/activity/streams/values/rfc5988 github.com/superseriousbusiness/activity/streams/values/string github.com/superseriousbusiness/activity/streams/vocab -# github.com/superseriousbusiness/exif-terminator v0.5.0 -## explicit; go 1.17 +# github.com/superseriousbusiness/exif-terminator v0.6.0 +## explicit; go 1.21 github.com/superseriousbusiness/exif-terminator # github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe ## explicit; go 1.17 github.com/superseriousbusiness/go-jpeg-image-structure/v2 +# github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB +## explicit; go 1.12 +github.com/superseriousbusiness/go-png-image-structure/v2 # github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 ## explicit; go 1.13 github.com/superseriousbusiness/oauth2/v4 -- cgit v1.3