Skip to content

Commit

Permalink
[bugfix] 2643 bug search for account url doesnt always work when redi…
Browse files Browse the repository at this point in the history
…rected (superseriousbusiness#2673)

* update activity library so dereferencer returns full response and checks *final* link to allow for redirects

* temporarily add bodged fixed library

* remove unused code

* update getAccountFeatured() to use dereferenceCollectionPage()

* make sure to release map

* perform a 2nd decode to ensure reader is empty after primary decode

* add comment explaining choice of using Decode() instead of Unmarshal()

* update embedded activity library to latest matching superseriousbusiness/activity#21

* add checks to look for changed URI and re-check database if redirected

* update max iteration count to 512, add checks during dereferenceAncestors() for indirect URLs

* remove doubled-up code

* fix use of status instead of current

* use URIs for checking equality for security

* use the latest known URI for boost_of_uri in case original was an indirect

* add dereferenceCollection() function for dereferenceAccountFeatured()

* pull in latest github.com/superseriousbusiness/activity version (and remove the bodge!!)

* fix typo in code comments

* update decodeType() to accept a readcloser and handle body closing

* switch to checking using BoostOfID and add note why not using BoostOfURI

* ensure InReplyTo gets unset when deleting status parent in case currently stubbed

* add tests for Collection and CollectionPage iterators
  • Loading branch information
NyaaaWhatsUpDoc authored Feb 23, 2024
1 parent 37a39b9 commit 1d51e3c
Show file tree
Hide file tree
Showing 25 changed files with 813 additions and 281 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ require (
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
github.com/superseriousbusiness/activity v1.6.0-gts
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240221151241-5d56c04088d4
github.com/superseriousbusiness/httpsig v1.2.0-SSB
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
github.com/tdewolff/minify/v2 v2.20.17
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I=
github.com/superseriousbusiness/activity v1.6.0-gts h1:SwrTpqof0bIzYYsNyM7WH9Vxqz+6kN4BGQjzKvlIN1Y=
github.com/superseriousbusiness/activity v1.6.0-gts/go.mod h1:AZw0Xb4Oju8rmaJCZ21gc5CPg47MmNgyac+Hx5jo8VM=
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240221151241-5d56c04088d4 h1:kPjQR/hVZtROTzkxptp/EIR7Wm58O8jppwpCFrZ7sVU=
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240221151241-5d56c04088d4/go.mod h1:AZw0Xb4Oju8rmaJCZ21gc5CPg47MmNgyac+Hx5jo8VM=
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=
Expand Down
7 changes: 6 additions & 1 deletion internal/ap/ap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
package ap_test

import (
"bytes"
"context"
"encoding/json"
"io"

"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/activity/pub"
Expand Down Expand Up @@ -187,7 +189,10 @@ func (suite *APTestSuite) noteWithHashtags1() ap.Statusable {
}
}`)

statusable, err := ap.ResolveStatusable(context.Background(), noteJson)
statusable, err := ap.ResolveStatusable(
context.Background(),
io.NopCloser(bytes.NewReader(noteJson)),
)
if err != nil {
suite.FailNow(err.Error())
}
Expand Down
112 changes: 112 additions & 0 deletions internal/ap/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/paging"
)

// TODO: replace must of this logic with just
// using extractIRIs() on the iterator types.

// ToCollectionIterator attempts to resolve the given vocab type as a Collection
// like object and wrap in a standardised interface in order to iterate its contents.
func ToCollectionIterator(t vocab.Type) (CollectionIterator, error) {
switch name := t.GetTypeName(); name {
case ObjectCollection:
t := t.(vocab.ActivityStreamsCollection)
return WrapCollection(t), nil
case ObjectOrderedCollection:
t := t.(vocab.ActivityStreamsOrderedCollection)
return WrapOrderedCollection(t), nil
default:
return nil, fmt.Errorf("%T(%s) was not Collection-like", t, name)
}
}

// ToCollectionPageIterator attempts to resolve the given vocab type as a CollectionPage
// like object and wrap in a standardised interface in order to iterate its contents.
func ToCollectionPageIterator(t vocab.Type) (CollectionPageIterator, error) {
Expand All @@ -41,6 +59,16 @@ func ToCollectionPageIterator(t vocab.Type) (CollectionPageIterator, error) {
}
}

// WrapCollection wraps an ActivityStreamsCollection in a standardised collection interface.
func WrapCollection(collection vocab.ActivityStreamsCollection) CollectionIterator {
return &regularCollectionIterator{ActivityStreamsCollection: collection}
}

// WrapOrderedCollection wraps an ActivityStreamsOrderedCollection in a standardised collection interface.
func WrapOrderedCollection(collection vocab.ActivityStreamsOrderedCollection) CollectionIterator {
return &orderedCollectionIterator{ActivityStreamsOrderedCollection: collection}
}

// WrapCollectionPage wraps an ActivityStreamsCollectionPage in a standardised collection page interface.
func WrapCollectionPage(page vocab.ActivityStreamsCollectionPage) CollectionPageIterator {
return &regularCollectionPageIterator{ActivityStreamsCollectionPage: page}
Expand All @@ -51,6 +79,90 @@ func WrapOrderedCollectionPage(page vocab.ActivityStreamsOrderedCollectionPage)
return &orderedCollectionPageIterator{ActivityStreamsOrderedCollectionPage: page}
}

// regularCollectionIterator implements CollectionIterator
// for the vocab.ActivitiyStreamsCollection type.
type regularCollectionIterator struct {
vocab.ActivityStreamsCollection
items vocab.ActivityStreamsItemsPropertyIterator
once bool // only init items once
}

func (iter *regularCollectionIterator) NextItem() TypeOrIRI {
if !iter.initItems() {
return nil
}
cur := iter.items
iter.items = iter.items.Next()
return cur
}

func (iter *regularCollectionIterator) PrevItem() TypeOrIRI {
if !iter.initItems() {
return nil
}
cur := iter.items
iter.items = iter.items.Prev()
return cur
}

func (iter *regularCollectionIterator) initItems() bool {
if iter.once {
return (iter.items != nil)
}
iter.once = true
if iter.ActivityStreamsCollection == nil {
return false // no page set
}
items := iter.GetActivityStreamsItems()
if items == nil {
return false // no items found
}
iter.items = items.Begin()
return (iter.items != nil)
}

// orderedCollectionIterator implements CollectionIterator
// for the vocab.ActivitiyStreamsOrderedCollection type.
type orderedCollectionIterator struct {
vocab.ActivityStreamsOrderedCollection
items vocab.ActivityStreamsOrderedItemsPropertyIterator
once bool // only init items once
}

func (iter *orderedCollectionIterator) NextItem() TypeOrIRI {
if !iter.initItems() {
return nil
}
cur := iter.items
iter.items = iter.items.Next()
return cur
}

func (iter *orderedCollectionIterator) PrevItem() TypeOrIRI {
if !iter.initItems() {
return nil
}
cur := iter.items
iter.items = iter.items.Prev()
return cur
}

func (iter *orderedCollectionIterator) initItems() bool {
if iter.once {
return (iter.items != nil)
}
iter.once = true
if iter.ActivityStreamsOrderedCollection == nil {
return false // no page set
}
items := iter.GetActivityStreamsOrderedItems()
if items == nil {
return false // no items found
}
iter.items = items.Begin()
return (iter.items != nil)
}

// regularCollectionPageIterator implements CollectionPageIterator
// for the vocab.ActivitiyStreamsCollectionPage type.
type regularCollectionPageIterator struct {
Expand Down
148 changes: 148 additions & 0 deletions internal/ap/collections_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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 ap_test

import (
"net/url"
"slices"
"testing"

"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
)

var testIteratorIRIs = [][]string{
{
"https://google.com",
"https://mastodon.social",
"http://naughty.naughty.website/heres/the/porn",
"https://god.monarchies.suck?yes=they&really=do",
},
{
// zero length
},
{
"https://superseriousbusiness.org",
"http://gotosocial.tv/@slothsgonewild",
},
}

func TestToCollectionIterator(t *testing.T) {
for _, iris := range testIteratorIRIs {
testToCollectionIterator(t, toCollection(iris), "", iris)
testToCollectionIterator(t, toOrderedCollection(iris), "", iris)
}
testToCollectionIterator(t, streams.NewActivityStreamsAdd(), "*typeadd.ActivityStreamsAdd(Add) was not Collection-like", nil)
testToCollectionIterator(t, streams.NewActivityStreamsBlock(), "*typeblock.ActivityStreamsBlock(Block) was not Collection-like", nil)
}

func TestToCollectionPageIterator(t *testing.T) {
for _, iris := range testIteratorIRIs {
testToCollectionPageIterator(t, toCollectionPage(iris), "", iris)
testToCollectionPageIterator(t, toOrderedCollectionPage(iris), "", iris)
}
testToCollectionPageIterator(t, streams.NewActivityStreamsAdd(), "*typeadd.ActivityStreamsAdd(Add) was not CollectionPage-like", nil)
testToCollectionPageIterator(t, streams.NewActivityStreamsBlock(), "*typeblock.ActivityStreamsBlock(Block) was not CollectionPage-like", nil)
}

func testToCollectionIterator(t *testing.T, in vocab.Type, expectErr string, expectIRIs []string) {
collect, err := ap.ToCollectionIterator(in)
if !errCheck(err, expectErr) {
t.Fatalf("did not return expected error: expect=%v receive=%v", expectErr, err)
}
iris := gatherFromIterator(collect)
if !slices.Equal(iris, expectIRIs) {
t.Fatalf("did not return expected iris: expect=%v receive=%v", expectIRIs, iris)
}
}

func testToCollectionPageIterator(t *testing.T, in vocab.Type, expectErr string, expectIRIs []string) {
page, err := ap.ToCollectionPageIterator(in)
if !errCheck(err, expectErr) {
t.Fatalf("did not return expected error: expect=%v receive=%v", expectErr, err)
}
iris := gatherFromIterator(page)
if !slices.Equal(iris, expectIRIs) {
t.Fatalf("did not return expected iris: expect=%v receive=%v", expectIRIs, iris)
}
}

func toCollection(iris []string) vocab.ActivityStreamsCollection {
collect := streams.NewActivityStreamsCollection()
collect.SetActivityStreamsItems(toItems(iris))
return collect
}

func toOrderedCollection(iris []string) vocab.ActivityStreamsOrderedCollection {
collect := streams.NewActivityStreamsOrderedCollection()
collect.SetActivityStreamsOrderedItems(toOrderedItems(iris))
return collect
}

func toCollectionPage(iris []string) vocab.ActivityStreamsCollectionPage {
page := streams.NewActivityStreamsCollectionPage()
page.SetActivityStreamsItems(toItems(iris))
return page
}

func toOrderedCollectionPage(iris []string) vocab.ActivityStreamsOrderedCollectionPage {
page := streams.NewActivityStreamsOrderedCollectionPage()
page.SetActivityStreamsOrderedItems(toOrderedItems(iris))
return page
}

func toItems(iris []string) vocab.ActivityStreamsItemsProperty {
items := streams.NewActivityStreamsItemsProperty()
for _, iri := range iris {
u, _ := url.Parse(iri)
items.AppendIRI(u)
}
return items
}

func toOrderedItems(iris []string) vocab.ActivityStreamsOrderedItemsProperty {
items := streams.NewActivityStreamsOrderedItemsProperty()
for _, iri := range iris {
u, _ := url.Parse(iri)
items.AppendIRI(u)
}
return items
}

func gatherFromIterator(iter ap.CollectionIterator) []string {
var iris []string
if iter == nil {
return nil
}
for item := iter.NextItem(); item != nil; item = iter.NextItem() {
id, _ := pub.ToId(item)
if id != nil {
iris = append(iris, id.String())
}
}
return iris
}

func errCheck(err error, str string) bool {
if err == nil {
return str == ""
}
return err.Error() == str
}
9 changes: 9 additions & 0 deletions internal/ap/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@ type ReplyToable interface {
WithInReplyTo
}

// CollectionIterator represents the minimum interface for interacting with a
// wrapped Collection or OrderedCollection in order to access next / prev items.
type CollectionIterator interface {
vocab.Type

NextItem() TypeOrIRI
PrevItem() TypeOrIRI
}

// CollectionPageIterator represents the minimum interface for interacting with a wrapped
// CollectionPage or OrderedCollectionPage in order to access both next / prev pages and items.
type CollectionPageIterator interface {
Expand Down
Loading

0 comments on commit 1d51e3c

Please sign in to comment.