gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 25cab0e1f4fdd89512088d0d02c4abd7b4d11773
parent 36b2f2b4e6e94168f5ac2b502d1af0f432198c16
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date:   Mon, 21 Mar 2022 19:46:51 +0100

[bugfix] Fix images not being processed correctly sometimes (#437)

* bump exif-terminator to latest version

* add and test giant turnip from turnip.farm

* don't error if content property is nil
Diffstat:
Mgo.mod | 4++--
Mgo.sum | 8++++----
Minternal/federation/dereferencing/dereferencer_test.go | 10++++------
Minternal/federation/dereferencing/status_test.go | 47+++++++++++++++++++++++++++++++++++++++++++++++
Minternal/media/image.go | 6+++---
Minternal/media/processingmedia.go | 2++
Atestrig/media/giant-turnip-world-record.jpg | 0
Mtestrig/testmodels.go | 148++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Dvendor/github.com/dsoprea/go-jpeg-image-structure/v2/splitter.go | 437-------------------------------------------------------------------------------
Mvendor/github.com/superseriousbusiness/exif-terminator/jpeg.go | 2+-
Avendor/github.com/superseriousbusiness/exif-terminator/logger.go | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mvendor/github.com/superseriousbusiness/exif-terminator/terminator.go | 7+++++--
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/.MODULE_ROOT -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/.MODULE_ROOT | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/LICENSE -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/LICENSE | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/README.md -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/README.md | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/markers.go -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/markers.go | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/media_parser.go -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/media_parser.go | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/segment.go -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/segment.go | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/segment_list.go -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/segment_list.go | 0
Avendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/splitter.go | 437+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/testing_common.go -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/testing_common.go | 0
Rvendor/github.com/dsoprea/go-jpeg-image-structure/v2/utility.go -> vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/utility.go | 0
Mvendor/modules.txt | 6++++--
23 files changed, 680 insertions(+), 481 deletions(-)

diff --git a/go.mod b/go.mod @@ -32,7 +32,7 @@ require ( github.com/spf13/viper v1.10.0 github.com/stretchr/testify v1.7.0 github.com/superseriousbusiness/activity v1.0.1-0.20211113133524-56560b73ace8 - github.com/superseriousbusiness/exif-terminator v0.1.0 + github.com/superseriousbusiness/exif-terminator v0.2.0 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB github.com/tdewolff/minify/v2 v2.9.22 github.com/uptrace/bun v1.0.20 @@ -59,7 +59,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b // indirect github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect - github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836 // 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 @@ -103,6 +102,7 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect github.com/tdewolff/parse/v2 v2.5.23 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/ugorji/go/codec v1.2.6 // indirect diff --git a/go.sum b/go.sum @@ -155,8 +155,6 @@ github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SN github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 h1:YDRiMEm32T60Kpm35YzOK9ZHgjsS1Qrid+XskNcsdp8= github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= -github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836 h1:KGCiMMWxODEMmI3+9Ms04l73efoqFVNKKKPbVyOvKrU= -github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836/go.mod h1:WaARaUjQuSuDCDFAiU/GwzfxMTJBulfEhqEA2Tx6B4Y= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= @@ -655,8 +653,10 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/superseriousbusiness/activity v1.0.1-0.20211113133524-56560b73ace8 h1:8Bwy6CSsT33/sF5FhjND4vr7jiJCaq4elNTAW4rUzVc= github.com/superseriousbusiness/activity v1.0.1-0.20211113133524-56560b73ace8/go.mod h1:ZY9xwFDucvp6zTvM6FQZGl8PSOofPBFIAy6gSc85XkY= -github.com/superseriousbusiness/exif-terminator v0.1.0 h1:ePzfV0vcw+tm/haSOGzKbBTKkHAvyQLbCzfsdVkb3hM= -github.com/superseriousbusiness/exif-terminator v0.1.0/go.mod h1:pmlOKzkFZWmqaucLAtrRbZG0R5F3dbrcLWOcd7gAOLI= +github.com/superseriousbusiness/exif-terminator v0.2.0 h1:C21KOUr54E37qTqYS7WJX0J83sNzzCwBEy0KXyDprqU= +github.com/superseriousbusiness/exif-terminator v0.2.0/go.mod h1:DHJuKguXqyOVqB/oyOylutEDIZCbkYsn2GZFNSUDT9E= +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/oauth2/v4 v4.3.2-SSB h1:PtW2w6budTvRV2J5QAoSvThTHBuvh8t/+BXIZFAaBSc= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo= github.com/tdewolff/minify/v2 v2.9.22 h1:PlmaAakaJHdMMdTTwjjsuSwIxKqWPTlvjTj6a/g/ILU= diff --git a/internal/federation/dereferencing/dereferencer_test.go b/internal/federation/dereferencing/dereferencer_test.go @@ -50,17 +50,15 @@ type DereferencerStandardTestSuite struct { dereferencer dereferencing.Dereferencer } -func (suite *DereferencerStandardTestSuite) SetupSuite() { +func (suite *DereferencerStandardTestSuite) SetupTest() { + testrig.InitTestConfig() + testrig.InitTestLog() + suite.testAccounts = testrig.NewTestAccounts() suite.testRemoteStatuses = testrig.NewTestFediStatuses() suite.testRemotePeople = testrig.NewTestFediPeople() suite.testRemoteGroups = testrig.NewTestFediGroups() suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media") -} - -func (suite *DereferencerStandardTestSuite) SetupTest() { - testrig.InitTestLog() - testrig.InitTestConfig() suite.db = testrig.NewTestDB() suite.storage = testrig.NewTestStorage() diff --git a/internal/federation/dereferencing/status_test.go b/internal/federation/dereferencing/status_test.go @@ -131,6 +131,53 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithMention() { suite.False(m.Silent) } +func (suite *StatusTestSuite) TestDereferenceStatusWithImageAndNoContent() { + fetchingAccount := suite.testAccounts["local_account_1"] + + statusURL := testrig.URLMustParse("https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042") + status, statusable, new, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) + suite.NoError(err) + suite.NotNil(status) + suite.NotNil(statusable) + suite.True(new) + + // status values should be set + suite.Equal("https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042", status.URI) + suite.Equal("https://turnip.farm/@turniplover6969/70c53e54-3146-42d5-a630-83c8b6c7c042", status.URL) + suite.Equal("", status.Content) + suite.Equal("https://turnip.farm/users/turniplover6969", status.AccountURI) + suite.False(status.Local) + suite.Empty(status.ContentWarning) + suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) + suite.Equal(ap.ObjectNote, status.ActivityStreamsType) + + // status should be in the database + dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) + suite.NoError(err) + suite.Equal(status.ID, dbStatus.ID) + suite.True(dbStatus.Federated) + suite.True(dbStatus.Boostable) + suite.True(dbStatus.Replyable) + suite.True(dbStatus.Likeable) + + // account should be in the database now too + account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI) + suite.NoError(err) + suite.NotNil(account) + suite.True(account.Discoverable) + suite.Equal("https://turnip.farm/users/turniplover6969", account.URI) + suite.Equal("I just think they're neat", account.Note) + suite.Equal("Turnip Lover 6969", account.DisplayName) + suite.Equal("turniplover6969", account.Username) + suite.NotNil(account.PublicKey) + suite.Nil(account.PrivateKey) + + // we should have an attachment in the database + a := &gtsmodel.MediaAttachment{} + err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "status_id", Value: status.ID}}, a) + suite.NoError(err) +} + func TestStatusTestSuite(t *testing.T) { suite.Run(t, new(StatusTestSuite)) } diff --git a/internal/media/image.go b/internal/media/image.go @@ -125,7 +125,7 @@ func deriveThumbnail(r io.Reader, contentType string, createBlurhash bool) (*ima } if err != nil { - return nil, err + return nil, fmt.Errorf("error decoding image as %s: %s", contentType, err) } if i == nil { @@ -151,7 +151,7 @@ func deriveThumbnail(r io.Reader, contentType string, createBlurhash bool) (*ima tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor) bh, err := blurhash.Encode(4, 3, tiny) if err != nil { - return nil, err + return nil, fmt.Errorf("error creating blurhash: %s", err) } im.blurhash = bh } @@ -161,7 +161,7 @@ func deriveThumbnail(r io.Reader, contentType string, createBlurhash bool) (*ima // Quality isn't extremely important for thumbnails, so 75 is "good enough" Quality: 75, }); err != nil { - return nil, err + return nil, fmt.Errorf("error encoding thumbnail: %s", err) } im.small = out.Bytes() diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go @@ -260,6 +260,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error { if err != nil { return fmt.Errorf("store: error executing data function: %s", err) } + logrus.Tracef("store: reading %d bytes from data function for media %s", fileSize, p.attachment.URL) // defer closing the reader when we're done with it defer func() { @@ -342,6 +343,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error { return p.postData(ctx) } + logrus.Tracef("store: finished storing initial data for attachment %s", p.attachment.URL) return nil } diff --git a/testrig/media/giant-turnip-world-record.jpg b/testrig/media/giant-turnip-world-record.jpg Binary files differ. diff --git a/testrig/testmodels.go b/testrig/testmodels.go @@ -1460,7 +1460,7 @@ type ActivityWithSignature struct { // A struct of accounts needs to be passed in because the activities will also be bundled along with // their requesting signatures. func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature { - dmForZork := newNote( + dmForZork := newAPNote( URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6"), URLMustParse("http://fossbros-anonymous.io/@foss_satan/5424b153-4553-4f30-9358-7b92f7cd42f6"), time.Now(), @@ -1470,15 +1470,17 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit []*url.URL{URLMustParse("http://localhost:8080/users/the_mighty_zork")}, nil, true, - []vocab.ActivityStreamsMention{}) - createDmForZork := wrapNoteInCreate( + []vocab.ActivityStreamsMention{}, + nil, + ) + createDmForZork := wrapAPNoteInCreate( URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity"), URLMustParse("http://fossbros-anonymous.io/users/foss_satan"), time.Now(), dmForZork) createDmForZorkSig, createDmForZorkDigest, creatDmForZorkDate := GetSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) - forwardedMessage := newNote( + forwardedMessage := newAPNote( URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1"), URLMustParse("http://example.org/@some_user/afaba698-5740-4e32-a702-af61aa543bc1"), time.Now(), @@ -1488,8 +1490,10 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit []*url.URL{URLMustParse(pub.PublicActivityPubIRI)}, nil, false, - []vocab.ActivityStreamsMention{}) - createForwardedMessage := wrapNoteInCreate( + []vocab.ActivityStreamsMention{}, + nil, + ) + createForwardedMessage := wrapAPNoteInCreate( URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1/activity"), URLMustParse("http://example.org/users/some_user"), time.Now(), @@ -1520,8 +1524,14 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson { } newPerson1Pub := &newPerson1Priv.PublicKey + turnipLover6969Priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(err) + } + turnipLover6969Pub := &turnipLover6969Priv.PublicKey + return map[string]vocab.ActivityStreamsPerson{ - "https://unknown-instance.com/users/brand_new_person": newPerson( + "https://unknown-instance.com/users/brand_new_person": newAPPerson( URLMustParse("https://unknown-instance.com/users/brand_new_person"), URLMustParse("https://unknown-instance.com/users/brand_new_person/following"), URLMustParse("https://unknown-instance.com/users/brand_new_person/followers"), @@ -1541,6 +1551,26 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson { "image/png", false, ), + "https://turnip.farm/users/turniplover6969": newAPPerson( + URLMustParse("https://turnip.farm/users/turniplover6969"), + URLMustParse("https://turnip.farm/users/turniplover6969/following"), + URLMustParse("https://turnip.farm/users/turniplover6969/followers"), + URLMustParse("https://turnip.farm/users/turniplover6969/inbox"), + URLMustParse("https://turnip.farm/users/turniplover6969/outbox"), + URLMustParse("https://turnip.farm/users/turniplover6969/collections/featured"), + "turniplover6969", + "Turnip Lover 6969", + "I just think they're neat", + URLMustParse("https://turnip.farm/@turniplover6969"), + true, + URLMustParse("https://turnip.farm/users/turniplover6969#main-key"), + turnipLover6969Pub, + nil, + "image/jpeg", + nil, + "image/png", + false, + ), } } @@ -1552,7 +1582,7 @@ func NewTestFediGroups() map[string]vocab.ActivityStreamsGroup { newGroup1Pub := &newGroup1Priv.PublicKey return map[string]vocab.ActivityStreamsGroup{ - "https://unknown-instance.com/groups/some_group": newGroup( + "https://unknown-instance.com/groups/some_group": newAPGroup( URLMustParse("https://unknown-instance.com/groups/some_group"), URLMustParse("https://unknown-instance.com/groups/some_group/following"), URLMustParse("https://unknown-instance.com/groups/some_group/followers"), @@ -1592,6 +1622,11 @@ func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile panic(err) } + massiveFuckingTurnipBytes, err := os.ReadFile(fmt.Sprintf("%s/giant-turnip-world-record.jpg", relativePath)) + if err != nil { + panic(err) + } + return map[string]RemoteAttachmentFile{ "https://s3-us-west-2.amazonaws.com/plushcity/media_attachments/files/106/867/380/219/163/828/original/88e8758c5f011439.jpg": { Data: beeBytes, @@ -1601,12 +1636,16 @@ func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile Data: thoughtsOfDogBytes, ContentType: "image/jpeg", }, + "https://turnip.farm/attachments/f17843c7-015e-4251-9b5a-91389c49ee57.jpg": { + Data: massiveFuckingTurnipBytes, + ContentType: "image/jpeg", + }, } } func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote { return map[string]vocab.ActivityStreamsNote{ - "https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839": newNote( + "https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839": newAPNote( URLMustParse("https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839"), URLMustParse("https://unknown-instance.com/users/@brand_new_person/01FE4NTHKWW7THT67EF10EB839"), time.Now(), @@ -1618,9 +1657,10 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote { }, []*url.URL{}, false, - []vocab.ActivityStreamsMention{}, + nil, + nil, ), - "https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV": newNote( + "https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV": newAPNote( URLMustParse("https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV"), URLMustParse("https://unknown-instance.com/users/@brand_new_person/01FE5Y30E3W4P7TRE0R98KAYQV"), time.Now(), @@ -1633,11 +1673,34 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote { []*url.URL{}, false, []vocab.ActivityStreamsMention{ - newMention( + newAPMention( URLMustParse("http://localhost:8080/users/the_mighty_zork"), "@the_mighty_zork@localhost:8080", ), }, + nil, + ), + "https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042": newAPNote( + URLMustParse("https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042"), + URLMustParse("https://turnip.farm/@turniplover6969/70c53e54-3146-42d5-a630-83c8b6c7c042"), + time.Now(), + "", + "", + URLMustParse("https://turnip.farm/users/turniplover6969"), + []*url.URL{ + URLMustParse(pub.PublicActivityPubIRI), + }, + []*url.URL{}, + false, + nil, + []vocab.ActivityStreamsImage{ + newAPImage( + URLMustParse("https://turnip.farm/attachments/f17843c7-015e-4251-9b5a-91389c49ee57.jpg"), + "image/jpeg", + "", + "", + ), + }, ), } } @@ -1799,7 +1862,7 @@ func GetSignatureForDereference(pubKeyID string, privkey crypto.PrivateKey, dest return } -func newPerson( +func newAPPerson( profileIDURI *url.URL, followingURI *url.URL, followersURI *url.URL, @@ -1982,7 +2045,7 @@ func newPerson( return person } -func newGroup( +func newAPGroup( profileIDURI *url.URL, followingURI *url.URL, followersURI *url.URL, @@ -2165,7 +2228,7 @@ func newGroup( return group } -func newMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention { +func newAPMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention { mention := streams.NewActivityStreamsMention() hrefProp := streams.NewActivityStreamsHrefProperty() @@ -2179,8 +2242,38 @@ func newMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention { return mention } -// newNote returns a new activity streams note for the given parameters -func newNote( +func newAPImage(url *url.URL, mediaType string, imageDescription string, blurhash string) vocab.ActivityStreamsImage { + image := streams.NewActivityStreamsImage() + + if url != nil { + urlProp := streams.NewActivityStreamsUrlProperty() + urlProp.AppendIRI(url) + image.SetActivityStreamsUrl(urlProp) + } + + if mediaType != "" { + mediaTypeProp := streams.NewActivityStreamsMediaTypeProperty() + mediaTypeProp.Set(mediaType) + image.SetActivityStreamsMediaType(mediaTypeProp) + } + + if imageDescription != "" { + nameProp := streams.NewActivityStreamsNameProperty() + nameProp.AppendXMLSchemaString(imageDescription) + image.SetActivityStreamsName(nameProp) + } + + if blurhash != "" { + blurhashProp := streams.NewTootBlurhashProperty() + blurhashProp.Set(blurhash) + image.SetTootBlurhash(blurhashProp) + } + + return image +} + +// newAPNote returns a new activity streams note for the given parameters +func newAPNote( noteID *url.URL, noteURL *url.URL, noteCreatedAt time.Time, @@ -2190,7 +2283,8 @@ func newNote( noteTo []*url.URL, noteCC []*url.URL, noteSensitive bool, - noteMentions []vocab.ActivityStreamsMention) vocab.ActivityStreamsNote { + noteMentions []vocab.ActivityStreamsMention, + noteAttachments []vocab.ActivityStreamsImage) vocab.ActivityStreamsNote { // create the note itself note := streams.NewActivityStreamsNote() @@ -2255,21 +2349,27 @@ func newNote( note.SetActivityStreamsCc(cc) } - // set note tags - tag := streams.NewActivityStreamsTagProperty() - // mentions + tag := streams.NewActivityStreamsTagProperty() for _, m := range noteMentions { tag.AppendActivityStreamsMention(m) } - note.SetActivityStreamsTag(tag) + // append any attachments as ActivityStreamsImage + if noteAttachments != nil { + attachmentProperty := streams.NewActivityStreamsAttachmentProperty() + for _, a := range noteAttachments { + attachmentProperty.AppendActivityStreamsImage(a) + } + note.SetActivityStreamsAttachment(attachmentProperty) + } + return note } -// wrapNoteInCreate wraps the given activity streams note in a Create activity streams action -func wrapNoteInCreate(createID *url.URL, createActor *url.URL, createPublished time.Time, createNote vocab.ActivityStreamsNote) vocab.ActivityStreamsCreate { +// wrapAPNoteInCreate wraps the given activity streams note in a Create activity streams action +func wrapAPNoteInCreate(createID *url.URL, createActor *url.URL, createPublished time.Time, createNote vocab.ActivityStreamsNote) vocab.ActivityStreamsCreate { // create the.... create create := streams.NewActivityStreamsCreate() diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/splitter.go b/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/splitter.go @@ -1,437 +0,0 @@ -package jpegstructure - -import ( - "bufio" - "bytes" - "io" - - "encoding/binary" - - "github.com/dsoprea/go-logging" -) - -// JpegSplitter uses the Go stream splitter to divide the JPEG stream into -// segments. -type JpegSplitter struct { - lastMarkerId byte - lastMarkerName string - counter int - lastIsScanData bool - visitor interface{} - - currentOffset int - segments *SegmentList - - scandataOffset int -} - -// NewJpegSplitter returns a new JpegSplitter. -func NewJpegSplitter(visitor interface{}) *JpegSplitter { - return &JpegSplitter{ - segments: NewSegmentList(nil), - visitor: visitor, - } -} - -// Segments returns all found segments. -func (js *JpegSplitter) Segments() *SegmentList { - return js.segments -} - -// MarkerId returns the ID of the last processed marker. -func (js *JpegSplitter) MarkerId() byte { - return js.lastMarkerId -} - -// MarkerName returns the name of the last-processed marker. -func (js *JpegSplitter) MarkerName() string { - return js.lastMarkerName -} - -// Counter returns the number of processed segments. -func (js *JpegSplitter) Counter() int { - return js.counter -} - -// IsScanData returns whether the last processed segment was scan-data. -func (js *JpegSplitter) IsScanData() bool { - return js.lastIsScanData -} - -func (js *JpegSplitter) processScanData(data []byte) (advanceBytes int, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // Search through the segment, past all 0xff's therein, until we encounter - // the EOI segment. - - dataLength := -1 - for i := js.scandataOffset; i < len(data); i++ { - thisByte := data[i] - - if i == 0 { - continue - } - - lastByte := data[i-1] - if lastByte != 0xff { - continue - } - - if thisByte == 0x00 || thisByte >= 0xd0 && thisByte <= 0xd8 { - continue - } - - // After all of the other checks, this means that we're on the EOF - // segment. - if thisByte != MARKER_EOI { - continue - } - - dataLength = i - 1 - break - } - - if dataLength == -1 { - // On the next pass, start on the last byte of this pass, just in case - // the first byte of the two-byte sequence is here. - js.scandataOffset = len(data) - 1 - - jpegLogger.Debugf(nil, "Scan-data not fully available (%d).", len(data)) - return 0, nil - } - - js.lastIsScanData = true - js.lastMarkerId = 0 - js.lastMarkerName = "" - - // Note that we don't increment the counter since this isn't an actual - // segment. - - jpegLogger.Debugf(nil, "End of scan-data.") - - err = js.handleSegment(0x0, "!SCANDATA", 0x0, data[:dataLength]) - log.PanicIf(err) - - return dataLength, nil -} - -func (js *JpegSplitter) readSegment(data []byte) (count int, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - if js.counter == 0 { - // Verify magic bytes. - - if len(data) < 3 { - jpegLogger.Debugf(nil, "Not enough (1)") - return 0, nil - } - - if data[0] == jpegMagic2000[0] && data[1] == jpegMagic2000[1] && data[2] == jpegMagic2000[2] { - // TODO(dustin): Revisit JPEG2000 support. - log.Panicf("JPEG2000 not supported") - } - - if data[0] != jpegMagicStandard[0] || data[1] != jpegMagicStandard[1] || data[2] != jpegMagicStandard[2] { - log.Panicf("file does not look like a JPEG: (%02x) (%02x) (%02x)", data[0], data[1], data[2]) - } - } - - chunkLength := len(data) - - jpegLogger.Debugf(nil, "SPLIT: LEN=(%d) COUNTER=(%d)", chunkLength, js.counter) - - if js.scanDataIsNext() == true { - // If the last segment was the SOS, we're currently sitting on scan data. - // Search for the EOI marker afterward in order to know how much data - // there is. Return this as its own token. - // - // REF: https://stackoverflow.com/questions/26715684/parsing-jpeg-sos-marker - - advanceBytes, err := js.processScanData(data) - log.PanicIf(err) - - // This will either return 0 and implicitly request that we need more - // data and then need to run again or will return an actual byte count - // to progress by. - - return advanceBytes, nil - } else if js.lastMarkerId == MARKER_EOI { - // We have more data following the EOI, which is unexpected. There - // might be non-standard cruft at the end of the file. Terminate the - // parse because the file-structure is, technically, complete at this - // point. - - return 0, io.EOF - } else { - js.lastIsScanData = false - } - - // If we're here, we're supposed to be sitting on the 0xff bytes at the - // beginning of a segment (just before the marker). - - if data[0] != 0xff { - log.Panicf("not on new segment marker @ (%d): (%02X)", js.currentOffset, data[0]) - } - - i := 0 - found := false - for ; i < chunkLength; i++ { - jpegLogger.Debugf(nil, "Prefix check: (%d) %02X", i, data[i]) - - if data[i] != 0xff { - found = true - break - } - } - - jpegLogger.Debugf(nil, "Skipped over leading 0xFF bytes: (%d)", i) - - if found == false || i >= chunkLength { - jpegLogger.Debugf(nil, "Not enough (3)") - return 0, nil - } - - markerId := data[i] - - js.lastMarkerName = markerNames[markerId] - - sizeLen, found := markerLen[markerId] - jpegLogger.Debugf(nil, "MARKER-ID=%x SIZELEN=%v FOUND=%v", markerId, sizeLen, found) - - i++ - - b := bytes.NewBuffer(data[i:]) - payloadLength := 0 - - // marker-ID + size => 2 + <dynamic> - headerSize := 2 + sizeLen - - if found == false { - - // It's not one of the static-length markers. Read the length. - // - // The length is an unsigned 16-bit network/big-endian. - - // marker-ID + size => 2 + 2 - headerSize = 2 + 2 - - if i+2 >= chunkLength { - jpegLogger.Debugf(nil, "Not enough (4)") - return 0, nil - } - - l := uint16(0) - err = binary.Read(b, binary.BigEndian, &l) - log.PanicIf(err) - - if l <= 2 { - log.Panicf("length of size read for non-special marker (%02x) is unexpectedly not more than two.", markerId) - } - - // (l includes the bytes of the length itself.) - payloadLength = int(l) - 2 - jpegLogger.Debugf(nil, "DataLength (dynamically-sized segment): (%d)", payloadLength) - - i += 2 - } else if sizeLen > 0 { - - // Accommodates the non-zero markers in our marker index, which only - // represent J2C extensions. - // - // The length is an unsigned 32-bit network/big-endian. - - // TODO(dustin): !! This needs to be tested, but we need an image. - - if sizeLen != 4 { - log.Panicf("known non-zero marker is not four bytes, which is not currently handled: M=(%x)", markerId) - } - - if i+4 >= chunkLength { - jpegLogger.Debugf(nil, "Not enough (5)") - return 0, nil - } - - l := uint32(0) - err = binary.Read(b, binary.BigEndian, &l) - log.PanicIf(err) - - payloadLength = int(l) - 4 - jpegLogger.Debugf(nil, "DataLength (four-byte-length segment): (%u)", l) - - i += 4 - } - - jpegLogger.Debugf(nil, "PAYLOAD-LENGTH: %d", payloadLength) - - payload := data[i:] - - if payloadLength < 0 { - log.Panicf("payload length less than zero: (%d)", payloadLength) - } - - i += int(payloadLength) - - if i > chunkLength { - jpegLogger.Debugf(nil, "Not enough (6)") - return 0, nil - } - - jpegLogger.Debugf(nil, "Found whole segment.") - - js.lastMarkerId = markerId - - payloadWindow := payload[:payloadLength] - err = js.handleSegment(markerId, js.lastMarkerName, headerSize, payloadWindow) - log.PanicIf(err) - - js.counter++ - - jpegLogger.Debugf(nil, "Returning advance of (%d)", i) - - return i, nil -} - -func (js *JpegSplitter) scanDataIsNext() bool { - return js.lastMarkerId == MARKER_SOS -} - -// Split is the base splitting function that satisfies `bufio.SplitFunc`. -func (js *JpegSplitter) Split(data []byte, atEOF bool) (advance int, token []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - for len(data) > 0 { - currentAdvance, err := js.readSegment(data) - if err != nil { - if err == io.EOF { - // We've encountered an EOI marker. - return 0, nil, err - } - - log.Panic(err) - } - - if currentAdvance == 0 { - if len(data) > 0 && atEOF == true { - // Provide a little context in the error message. - - if js.scanDataIsNext() == true { - // Yes, we've ran into this. - - log.Panicf("scan-data is unbounded; EOI not encountered before EOF") - } else { - log.Panicf("partial segment data encountered before scan-data") - } - } - - // We don't have enough data for another segment. - break - } - - data = data[currentAdvance:] - advance += currentAdvance - } - - return advance, nil, nil -} - -func (js *JpegSplitter) parseSof(data []byte) (sof *SofSegment, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - stream := bytes.NewBuffer(data) - buffer := bufio.NewReader(stream) - - bitsPerSample, err := buffer.ReadByte() - log.PanicIf(err) - - height := uint16(0) - err = binary.Read(buffer, binary.BigEndian, &height) - log.PanicIf(err) - - width := uint16(0) - err = binary.Read(buffer, binary.BigEndian, &width) - log.PanicIf(err) - - componentCount, err := buffer.ReadByte() - log.PanicIf(err) - - sof = &SofSegment{ - BitsPerSample: bitsPerSample, - Width: width, - Height: height, - ComponentCount: componentCount, - } - - return sof, nil -} - -func (js *JpegSplitter) parseAppData(markerId byte, data []byte) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - return nil -} - -func (js *JpegSplitter) handleSegment(markerId byte, markerName string, headerSize int, payload []byte) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - cloned := make([]byte, len(payload)) - copy(cloned, payload) - - s := &Segment{ - MarkerId: markerId, - MarkerName: markerName, - Offset: js.currentOffset, - Data: cloned, - } - - jpegLogger.Debugf(nil, "Encountered marker (0x%02x) [%s] at offset (%d)", markerId, markerName, js.currentOffset) - - js.currentOffset += headerSize + len(payload) - - js.segments.Add(s) - - sv, ok := js.visitor.(SegmentVisitor) - if ok == true { - err = sv.HandleSegment(js.lastMarkerId, js.lastMarkerName, js.counter, js.lastIsScanData) - log.PanicIf(err) - } - - if markerId >= MARKER_SOF0 && markerId <= MARKER_SOF15 { - ssv, ok := js.visitor.(SofSegmentVisitor) - if ok == true { - sof, err := js.parseSof(payload) - log.PanicIf(err) - - err = ssv.HandleSof(sof) - log.PanicIf(err) - } - } else if markerId >= MARKER_APP0 && markerId <= MARKER_APP15 { - err := js.parseAppData(markerId, payload) - log.PanicIf(err) - } - - return nil -} diff --git a/vendor/github.com/superseriousbusiness/exif-terminator/jpeg.go b/vendor/github.com/superseriousbusiness/exif-terminator/jpeg.go @@ -23,7 +23,7 @@ import ( "fmt" "io" - jpegstructure "github.com/dsoprea/go-jpeg-image-structure/v2" + jpegstructure "github.com/superseriousbusiness/go-jpeg-image-structure/v2" ) var markerLen = map[byte]int{ diff --git a/vendor/github.com/superseriousbusiness/exif-terminator/logger.go b/vendor/github.com/superseriousbusiness/exif-terminator/logger.go @@ -0,0 +1,47 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +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/terminator.go b/vendor/github.com/superseriousbusiness/exif-terminator/terminator.go @@ -25,7 +25,7 @@ import ( "fmt" "io" - jpegstructure "github.com/dsoprea/go-jpeg-image-structure/v2" + jpegstructure "github.com/superseriousbusiness/go-jpeg-image-structure/v2" pngstructure "github.com/dsoprea/go-png-image-structure/v2" ) @@ -109,8 +109,11 @@ func scanAndClose(scanner *bufio.Scanner, writer io.WriteCloser) { // until the pipeReader starts being read by the caller, which // is why we do this asynchronously go func() { + defer writer.Close() for scanner.Scan() { } - writer.Close() + if scanner.Err() != nil { + logger.Error(scanner.Err()) + } }() } diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/.MODULE_ROOT b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/.MODULE_ROOT diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/LICENSE b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/LICENSE diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/README.md b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/README.md diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/markers.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/markers.go diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/media_parser.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/media_parser.go diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/segment.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/segment.go diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/segment_list.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/segment_list.go diff --git a/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/splitter.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/splitter.go @@ -0,0 +1,437 @@ +package jpegstructure + +import ( + "bufio" + "bytes" + "io" + + "encoding/binary" + + "github.com/dsoprea/go-logging" +) + +// JpegSplitter uses the Go stream splitter to divide the JPEG stream into +// segments. +type JpegSplitter struct { + lastMarkerId byte + lastMarkerName string + counter int + lastIsScanData bool + visitor interface{} + + currentOffset int + segments *SegmentList + + scandataOffset int +} + +// NewJpegSplitter returns a new JpegSplitter. +func NewJpegSplitter(visitor interface{}) *JpegSplitter { + return &JpegSplitter{ + segments: NewSegmentList(nil), + visitor: visitor, + } +} + +// Segments returns all found segments. +func (js *JpegSplitter) Segments() *SegmentList { + return js.segments +} + +// MarkerId returns the ID of the last processed marker. +func (js *JpegSplitter) MarkerId() byte { + return js.lastMarkerId +} + +// MarkerName returns the name of the last-processed marker. +func (js *JpegSplitter) MarkerName() string { + return js.lastMarkerName +} + +// Counter returns the number of processed segments. +func (js *JpegSplitter) Counter() int { + return js.counter +} + +// IsScanData returns whether the last processed segment was scan-data. +func (js *JpegSplitter) IsScanData() bool { + return js.lastIsScanData +} + +func (js *JpegSplitter) processScanData(data []byte) (advanceBytes int, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // Search through the segment, past all 0xff's therein, until we encounter + // the EOI segment. + + dataLength := -1 + for i := js.scandataOffset; i < len(data); i++ { + thisByte := data[i] + + if i == 0 { + continue + } + + lastByte := data[i-1] + if lastByte != 0xff { + continue + } + + if thisByte == 0x00 || thisByte >= 0xd0 && thisByte <= 0xd8 { + continue + } + + // After all of the other checks, this means that we're on the EOF + // segment. + if thisByte != MARKER_EOI { + continue + } + + dataLength = i - 1 + break + } + + if dataLength == -1 { + // On the next pass, start on the last byte of this pass, just in case + // the first byte of the two-byte sequence is here. + js.scandataOffset = len(data) - 1 + + jpegLogger.Debugf(nil, "Scan-data not fully available (%d).", len(data)) + return 0, nil + } + + js.lastIsScanData = true + js.lastMarkerId = 0 + js.lastMarkerName = "" + + // Note that we don't increment the counter since this isn't an actual + // segment. + + jpegLogger.Debugf(nil, "End of scan-data.") + + err = js.handleSegment(0x0, "!SCANDATA", 0x0, data[:dataLength]) + log.PanicIf(err) + + return dataLength, nil +} + +func (js *JpegSplitter) readSegment(data []byte) (count int, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + if js.counter == 0 { + // Verify magic bytes. + + if len(data) < 3 { + jpegLogger.Debugf(nil, "Not enough (1)") + return 0, nil + } + + if data[0] == jpegMagic2000[0] && data[1] == jpegMagic2000[1] && data[2] == jpegMagic2000[2] { + // TODO(dustin): Revisit JPEG2000 support. + log.Panicf("JPEG2000 not supported") + } + + if data[0] != jpegMagicStandard[0] || data[1] != jpegMagicStandard[1] || data[2] != jpegMagicStandard[2] { + log.Panicf("file does not look like a JPEG: (%02x) (%02x) (%02x)", data[0], data[1], data[2]) + } + } + + chunkLength := len(data) + + jpegLogger.Debugf(nil, "SPLIT: LEN=(%d) COUNTER=(%d)", chunkLength, js.counter) + + if js.scanDataIsNext() == true { + // If the last segment was the SOS, we're currently sitting on scan data. + // Search for the EOI marker afterward in order to know how much data + // there is. Return this as its own token. + // + // REF: https://stackoverflow.com/questions/26715684/parsing-jpeg-sos-marker + + advanceBytes, err := js.processScanData(data) + log.PanicIf(err) + + // This will either return 0 and implicitly request that we need more + // data and then need to run again or will return an actual byte count + // to progress by. + + return advanceBytes, nil + } else if js.lastMarkerId == MARKER_EOI { + // We have more data following the EOI, which is unexpected. There + // might be non-standard cruft at the end of the file. Terminate the + // parse because the file-structure is, technically, complete at this + // point. + + return 0, io.EOF + } else { + js.lastIsScanData = false + } + + // If we're here, we're supposed to be sitting on the 0xff bytes at the + // beginning of a segment (just before the marker). + + if data[0] != 0xff { + log.Panicf("not on new segment marker @ (%d): (%02X)", js.currentOffset, data[0]) + } + + i := 0 + found := false + for ; i < chunkLength; i++ { + jpegLogger.Debugf(nil, "Prefix check: (%d) %02X", i, data[i]) + + if data[i] != 0xff { + found = true + break + } + } + + jpegLogger.Debugf(nil, "Skipped over leading 0xFF bytes: (%d)", i) + + if found == false || i >= chunkLength { + jpegLogger.Debugf(nil, "Not enough (3)") + return 0, nil + } + + markerId := data[i] + + js.lastMarkerName = markerNames[markerId] + + sizeLen, found := markerLen[markerId] + jpegLogger.Debugf(nil, "MARKER-ID=%x SIZELEN=%v FOUND=%v", markerId, sizeLen, found) + + i++ + + b := bytes.NewBuffer(data[i:]) + payloadLength := 0 + + // marker-ID + size => 2 + <dynamic> + headerSize := 2 + sizeLen + + if found == false { + + // It's not one of the static-length markers. Read the length. + // + // The length is an unsigned 16-bit network/big-endian. + + // marker-ID + size => 2 + 2 + headerSize = 2 + 2 + + if i+2 >= chunkLength { + jpegLogger.Debugf(nil, "Not enough (4)") + return 0, nil + } + + l := uint16(0) + err = binary.Read(b, binary.BigEndian, &l) + log.PanicIf(err) + + if l < 2 { + log.Panicf("length of size read for non-special marker (%02x) is unexpectedly less than two.", markerId) + } + + // (l includes the bytes of the length itself.) + payloadLength = int(l) - 2 + jpegLogger.Debugf(nil, "DataLength (dynamically-sized segment): (%d)", payloadLength) + + i += 2 + } else if sizeLen > 0 { + + // Accommodates the non-zero markers in our marker index, which only + // represent J2C extensions. + // + // The length is an unsigned 32-bit network/big-endian. + + // TODO(dustin): !! This needs to be tested, but we need an image. + + if sizeLen != 4 { + log.Panicf("known non-zero marker is not four bytes, which is not currently handled: M=(%x)", markerId) + } + + if i+4 >= chunkLength { + jpegLogger.Debugf(nil, "Not enough (5)") + return 0, nil + } + + l := uint32(0) + err = binary.Read(b, binary.BigEndian, &l) + log.PanicIf(err) + + payloadLength = int(l) - 4 + jpegLogger.Debugf(nil, "DataLength (four-byte-length segment): (%u)", l) + + i += 4 + } + + jpegLogger.Debugf(nil, "PAYLOAD-LENGTH: %d", payloadLength) + + payload := data[i:] + + if payloadLength < 0 { + log.Panicf("payload length less than zero: (%d)", payloadLength) + } + + i += int(payloadLength) + + if i > chunkLength { + jpegLogger.Debugf(nil, "Not enough (6)") + return 0, nil + } + + jpegLogger.Debugf(nil, "Found whole segment.") + + js.lastMarkerId = markerId + + payloadWindow := payload[:payloadLength] + err = js.handleSegment(markerId, js.lastMarkerName, headerSize, payloadWindow) + log.PanicIf(err) + + js.counter++ + + jpegLogger.Debugf(nil, "Returning advance of (%d)", i) + + return i, nil +} + +func (js *JpegSplitter) scanDataIsNext() bool { + return js.lastMarkerId == MARKER_SOS +} + +// Split is the base splitting function that satisfies `bufio.SplitFunc`. +func (js *JpegSplitter) Split(data []byte, atEOF bool) (advance int, token []byte, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + for len(data) > 0 { + currentAdvance, err := js.readSegment(data) + if err != nil { + if err == io.EOF { + // We've encountered an EOI marker. + return 0, nil, err + } + + log.Panic(err) + } + + if currentAdvance == 0 { + if len(data) > 0 && atEOF == true { + // Provide a little context in the error message. + + if js.scanDataIsNext() == true { + // Yes, we've ran into this. + + log.Panicf("scan-data is unbounded; EOI not encountered before EOF") + } else { + log.Panicf("partial segment data encountered before scan-data") + } + } + + // We don't have enough data for another segment. + break + } + + data = data[currentAdvance:] + advance += currentAdvance + } + + return advance, nil, nil +} + +func (js *JpegSplitter) parseSof(data []byte) (sof *SofSegment, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + stream := bytes.NewBuffer(data) + buffer := bufio.NewReader(stream) + + bitsPerSample, err := buffer.ReadByte() + log.PanicIf(err) + + height := uint16(0) + err = binary.Read(buffer, binary.BigEndian, &height) + log.PanicIf(err) + + width := uint16(0) + err = binary.Read(buffer, binary.BigEndian, &width) + log.PanicIf(err) + + componentCount, err := buffer.ReadByte() + log.PanicIf(err) + + sof = &SofSegment{ + BitsPerSample: bitsPerSample, + Width: width, + Height: height, + ComponentCount: componentCount, + } + + return sof, nil +} + +func (js *JpegSplitter) parseAppData(markerId byte, data []byte) (err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + return nil +} + +func (js *JpegSplitter) handleSegment(markerId byte, markerName string, headerSize int, payload []byte) (err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + cloned := make([]byte, len(payload)) + copy(cloned, payload) + + s := &Segment{ + MarkerId: markerId, + MarkerName: markerName, + Offset: js.currentOffset, + Data: cloned, + } + + jpegLogger.Debugf(nil, "Encountered marker (0x%02x) [%s] at offset (%d)", markerId, markerName, js.currentOffset) + + js.currentOffset += headerSize + len(payload) + + js.segments.Add(s) + + sv, ok := js.visitor.(SegmentVisitor) + if ok == true { + err = sv.HandleSegment(js.lastMarkerId, js.lastMarkerName, js.counter, js.lastIsScanData) + log.PanicIf(err) + } + + if markerId >= MARKER_SOF0 && markerId <= MARKER_SOF15 { + ssv, ok := js.visitor.(SofSegmentVisitor) + if ok == true { + sof, err := js.parseSof(payload) + log.PanicIf(err) + + err = ssv.HandleSof(sof) + log.PanicIf(err) + } + } else if markerId >= MARKER_APP0 && markerId <= MARKER_APP15 { + err := js.parseAppData(markerId, payload) + log.PanicIf(err) + } + + return nil +} diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/testing_common.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/testing_common.go diff --git a/vendor/github.com/dsoprea/go-jpeg-image-structure/v2/utility.go b/vendor/github.com/superseriousbusiness/go-jpeg-image-structure/v2/utility.go diff --git a/vendor/modules.txt b/vendor/modules.txt @@ -57,7 +57,6 @@ github.com/dsoprea/go-exif/v3/undefined github.com/dsoprea/go-iptc # github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836 ## explicit; go 1.12 -github.com/dsoprea/go-jpeg-image-structure/v2 # github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd ## explicit; go 1.13 github.com/dsoprea/go-logging @@ -471,9 +470,12 @@ 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.1.0 +# github.com/superseriousbusiness/exif-terminator v0.2.0 ## explicit; go 1.17 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/oauth2/v4 v4.3.2-SSB ## explicit; go 1.13 github.com/superseriousbusiness/oauth2/v4