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/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/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/dsoprea/go-png-image-structure/v2/.MODULE_ROOT b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/.MODULE_ROOT similarity index 100% rename from vendor/github.com/dsoprea/go-png-image-structure/v2/.MODULE_ROOT rename to vendor/github.com/superseriousbusiness/go-png-image-structure/v2/.MODULE_ROOT diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/LICENSE b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/LICENSE similarity index 100% rename from vendor/github.com/dsoprea/go-png-image-structure/v2/LICENSE rename to vendor/github.com/superseriousbusiness/go-png-image-structure/v2/LICENSE 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/dsoprea/go-png-image-structure/v2/utility.go b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go similarity index 53% rename from vendor/github.com/dsoprea/go-png-image-structure/v2/utility.go rename to vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go index dbff145a6..cac6020f2 100644 --- a/vendor/github.com/dsoprea/go-png-image-structure/v2/utility.go +++ b/vendor/github.com/superseriousbusiness/go-png-image-structure/v2/utility.go @@ -3,8 +3,6 @@ package pngstructure import ( "bytes" "fmt" - - "github.com/dsoprea/go-logging" ) func DumpBytes(data []byte) { @@ -32,34 +30,38 @@ func DumpBytesClause(data []byte) { fmt.Printf(" }\n") } -func DumpBytesToString(data []byte) string { +func DumpBytesToString(data []byte) (string, error) { b := new(bytes.Buffer) for i, x := range data { - _, err := b.WriteString(fmt.Sprintf("%02x", x)) - log.PanicIf(err) + if _, err := b.WriteString(fmt.Sprintf("%02x", x)); err != nil { + return "", err + } if i < len(data)-1 { - _, err := b.WriteRune(' ') - log.PanicIf(err) + if _, err := b.WriteRune(' '); err != nil { + return "", err + } } } - return b.String() + return b.String(), nil } -func DumpBytesClauseToString(data []byte) string { +func DumpBytesClauseToString(data []byte) (string, error) { b := new(bytes.Buffer) for i, x := range data { - _, err := b.WriteString(fmt.Sprintf("0x%02x", x)) - log.PanicIf(err) + if _, err := b.WriteString(fmt.Sprintf("0x%02x", x)); err != nil { + return "", err + } if i < len(data)-1 { - _, err := b.WriteString(", ") - log.PanicIf(err) + if _, err := b.WriteString(", "); err != nil { + return "", err + } } } - return b.String() + 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