[bugfix] stricter property value counts (#4905)
- removes some duplicate logic in ./internal/ap and moves more of the functions to ./internal/ap/properties.go with standardized interfaces - updates various areas of the codebase to rely on these GetOne___() functions - pulls in latest gopkg for log.Formatted() function for nicer log formatting of variables in errors Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4905
This commit is contained in:
@@ -11,16 +11,16 @@ replace github.com/gin-gonic/gin => codeberg.org/superseriousbusiness/gin v1.11.
|
||||
require (
|
||||
code.superseriousbusiness.org/activity v1.19.0
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.4
|
||||
code.superseriousbusiness.org/gopkg v0.0.0-20260520114201-7a01c90db818
|
||||
code.superseriousbusiness.org/gopkg v0.0.0-20260623123713-4ead213a2d22
|
||||
code.superseriousbusiness.org/httpsig v1.5.0
|
||||
code.superseriousbusiness.org/oauth2/v4 v4.5.4-0.20250812115401-3961e46a7384
|
||||
codeberg.org/gruf/go-bytesize v1.0.4
|
||||
codeberg.org/gruf/go-byteutil v1.3.0
|
||||
codeberg.org/gruf/go-cache/v3 v3.6.2
|
||||
codeberg.org/gruf/go-caller v0.0.0-20250806133437-db8d0b1f71cf
|
||||
codeberg.org/gruf/go-caller v0.0.0-20260615202027-bc5a6ff3cb31
|
||||
codeberg.org/gruf/go-debug v1.3.0
|
||||
codeberg.org/gruf/go-errors/v2 v2.3.4
|
||||
codeberg.org/gruf/go-fastcopy v1.1.3
|
||||
codeberg.org/gruf/go-fastcopy v1.2.1
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.1
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.20
|
||||
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
|
||||
|
||||
@@ -6,8 +6,8 @@ code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
|
||||
code.superseriousbusiness.org/gopkg v0.0.0-20260520114201-7a01c90db818 h1:fvX4P0KrflQxJzfZ2S2rvyFNscBkoOdutBPi2Q4uMgs=
|
||||
code.superseriousbusiness.org/gopkg v0.0.0-20260520114201-7a01c90db818/go.mod h1:PNtiv80R4b+z7HvnZbX69dAPmgRSDGts3SNcSC6Ti30=
|
||||
code.superseriousbusiness.org/gopkg v0.0.0-20260623123713-4ead213a2d22 h1:Pz5KlaHuNUpuJzIcnqmvsnJ68MrB35gffvFs2NGJfZo=
|
||||
code.superseriousbusiness.org/gopkg v0.0.0-20260623123713-4ead213a2d22/go.mod h1:f2KFg2HOM6YHEXR4wjnab9xPY2PmphjZaz7S6/+Y1Xc=
|
||||
code.superseriousbusiness.org/httpsig v1.5.0 h1:jw/qc//yYWSoOYytTZXHvW7yh8kceCipNIBfUeXQghA=
|
||||
code.superseriousbusiness.org/httpsig v1.5.0/go.mod h1:i2AKpj/WbA/o/UTvia9TAREzt0jP1AH3T1Uxjyhdzlw=
|
||||
code.superseriousbusiness.org/oauth2/v4 v4.5.4-0.20250812115401-3961e46a7384 h1:eJzULGUyhHGk2DdQxX/jbH9FKZOyoIF90p3dzukCfLA=
|
||||
@@ -18,14 +18,14 @@ codeberg.org/gruf/go-byteutil v1.3.0 h1:nRqJnCcRQ7xbfU6azw7zOzJrSMDIJHBqX6FL9vEM
|
||||
codeberg.org/gruf/go-byteutil v1.3.0/go.mod h1:chgnZz1LUcfaObaIFglxF5MRYQkJGjQf4WwVz95ccCM=
|
||||
codeberg.org/gruf/go-cache/v3 v3.6.2 h1:fQn7Dkj5gQpSNjlnoFeotXHKwbAh1PTH4qrD5BMm5ZA=
|
||||
codeberg.org/gruf/go-cache/v3 v3.6.2/go.mod h1:yjmrOyda2K8B5sAfCpPPAAqI3oewf4mNQmvxygmP+g8=
|
||||
codeberg.org/gruf/go-caller v0.0.0-20250806133437-db8d0b1f71cf h1:Rzu7WLpscj2w1N+ClIHlJoTYf9SuqZrZ7E4f9T7jGdw=
|
||||
codeberg.org/gruf/go-caller v0.0.0-20250806133437-db8d0b1f71cf/go.mod h1:jEyYiqCzH1TaxfclSFYthE32oI0dsMnRS6EHqy6y0uo=
|
||||
codeberg.org/gruf/go-caller v0.0.0-20260615202027-bc5a6ff3cb31 h1:yGDC2A1/WD2NuQ+dT9/7JNgPnTI/WAyEYX4y6ipjsA0=
|
||||
codeberg.org/gruf/go-caller v0.0.0-20260615202027-bc5a6ff3cb31/go.mod h1:jEyYiqCzH1TaxfclSFYthE32oI0dsMnRS6EHqy6y0uo=
|
||||
codeberg.org/gruf/go-debug v1.3.0 h1:PIRxQiWUFKtGOGZFdZ3Y0pqyfI0Xr87j224IYe2snZs=
|
||||
codeberg.org/gruf/go-debug v1.3.0/go.mod h1:N+vSy9uJBQgpQcJUqjctvqFz7tBHJf+S/PIjLILzpLg=
|
||||
codeberg.org/gruf/go-errors/v2 v2.3.4 h1:eC0UYpEx9y+aQgxv26JxjtYyN48OMil9JQ9cbHmw0ys=
|
||||
codeberg.org/gruf/go-errors/v2 v2.3.4/go.mod h1:SpaIhBvi+hd4ohaS3UQJxA1OVmzApHBy4IufOn7OkD0=
|
||||
codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7xD1g=
|
||||
codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
|
||||
codeberg.org/gruf/go-fastcopy v1.2.1 h1:RUpiim4H7CJKo+4aj0RIaS9EOYJ/hb1S5DGdH9wwsyg=
|
||||
codeberg.org/gruf/go-fastcopy v1.2.1/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.1 h1:smCq3mpYiPibH4Na3SEN2rtr5oPmrY+cVRQ8nFCznJ4=
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.1/go.mod h1:ABH9P8jc/bUub/RW9yTI7i+Gm/Oqe6ZHcIGEumvAOKk=
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.20 h1:4Rc9yQLH1GTdMeiGPlMR1JUGNMJSjw6mRIxd+rxqDCo=
|
||||
|
||||
+26
-143
@@ -91,9 +91,11 @@ func ExtractActivityObjectsAndInstruments(
|
||||
rawJSON map[string]any,
|
||||
) ([]TypeOrIRI, []any) {
|
||||
switch typeName := activity.GetTypeName(); {
|
||||
|
||||
// Activity: has "object"
|
||||
// and/or "instrument".
|
||||
case isActivity(typeName):
|
||||
|
||||
// Gather object types and raw json.
|
||||
objTypes, objJSON := extractObjectTypesAndJSON(activity, rawJSON)
|
||||
|
||||
@@ -312,26 +314,6 @@ func ExtractName(i WithName) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtractInReplyToURI extracts the first inReplyTo URI
|
||||
// property it can find from an interface. Will return
|
||||
// nil if no valid URI can be found.
|
||||
func ExtractInReplyToURI(i WithInReplyTo) *url.URL {
|
||||
inReplyToProp := i.GetActivityStreamsInReplyTo()
|
||||
if inReplyToProp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() {
|
||||
iri, err := pub.ToId(iter)
|
||||
if err == nil && iri != nil {
|
||||
// Found one we can use.
|
||||
return iri
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractItemsURIs extracts each URI it can
|
||||
// find for an item from the provided WithItems.
|
||||
func ExtractItemsURIs(i WithItems) []*url.URL {
|
||||
@@ -352,64 +334,6 @@ func ExtractItemsURIs(i WithItems) []*url.URL {
|
||||
return uris
|
||||
}
|
||||
|
||||
// ExtractToURIs returns a slice of URIs
|
||||
// that the given WithTo addresses as To.
|
||||
func ExtractToURIs(i WithTo) []*url.URL {
|
||||
toProp := i.GetActivityStreamsTo()
|
||||
if toProp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uris := make([]*url.URL, 0, toProp.Len())
|
||||
for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() {
|
||||
uri, err := pub.ToId(iter)
|
||||
if err == nil {
|
||||
// Found one we can use.
|
||||
uris = append(uris, uri)
|
||||
}
|
||||
}
|
||||
|
||||
return uris
|
||||
}
|
||||
|
||||
// ExtractCcURIs returns a slice of URIs
|
||||
// that the given WithCC addresses as Cc.
|
||||
func ExtractCcURIs(i WithCc) []*url.URL {
|
||||
ccProp := i.GetActivityStreamsCc()
|
||||
if ccProp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
urls := make([]*url.URL, 0, ccProp.Len())
|
||||
for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() {
|
||||
uri, err := pub.ToId(iter)
|
||||
if err == nil {
|
||||
// Found one we can use.
|
||||
urls = append(urls, uri)
|
||||
}
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
// ExtractAttributedToURI returns the first URI it can find in the
|
||||
// given WithAttributedTo, or an error if no URI can be found.
|
||||
func ExtractAttributedToURI(i WithAttributedTo) (*url.URL, error) {
|
||||
attributedToProp := i.GetActivityStreamsAttributedTo()
|
||||
if attributedToProp == nil {
|
||||
return nil, gtserror.New("attributedToProp was nil")
|
||||
}
|
||||
|
||||
for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() {
|
||||
id, err := pub.ToId(iter)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, gtserror.New("couldn't find iri for attributed to")
|
||||
}
|
||||
|
||||
// ExtractIconURI extracts the first URI it can find from
|
||||
// the given WithIcon which links to a supported image file.
|
||||
// Input will look something like this:
|
||||
@@ -691,21 +615,20 @@ func ExtractPubKeyFromActor(i WithPublicKey) (
|
||||
return nil, nil, nil, gtserror.New("public key property was nil")
|
||||
}
|
||||
|
||||
// Take the first public key we can find.
|
||||
for iter := pubKeyProp.Begin(); iter != pubKeyProp.End(); iter = iter.Next() {
|
||||
if !iter.IsW3IDSecurityV1PublicKey() {
|
||||
continue
|
||||
}
|
||||
|
||||
pkey := iter.Get()
|
||||
if pkey == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return ExtractPubKeyFromKey(pkey)
|
||||
if l := pubKeyProp.Len(); l != 1 {
|
||||
return nil, nil, nil, gtserror.Newf("unexpected public key property length: %d", l)
|
||||
}
|
||||
|
||||
return nil, nil, nil, gtserror.New("couldn't find valid public key")
|
||||
// Start property iterator.
|
||||
iter := pubKeyProp.Begin()
|
||||
|
||||
// Ensure this is a expected valid key.
|
||||
if !iter.IsW3IDSecurityV1PublicKey() {
|
||||
return nil, nil, nil, gtserror.New("couldn't find valid public key")
|
||||
}
|
||||
|
||||
// Extract RSA details from key type.
|
||||
return ExtractPubKeyFromKey(iter.Get())
|
||||
}
|
||||
|
||||
// ExtractPubKeyFromActor extracts the public key, public key ID, and public
|
||||
@@ -718,32 +641,32 @@ func ExtractPubKeyFromKey(pkey vocab.W3IDSecurityV1PublicKey) (
|
||||
) {
|
||||
pubKeyID, err := pub.GetId(pkey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.New("no id set on public key")
|
||||
return nil, nil, nil, gtserror.New("no id set on public key")
|
||||
}
|
||||
|
||||
pubKeyOwnerProp := pkey.GetW3IDSecurityV1Owner()
|
||||
if pubKeyOwnerProp == nil {
|
||||
return nil, nil, nil, errors.New("nil pubKeyOwnerProp")
|
||||
return nil, nil, nil, gtserror.New("nil pubKeyOwnerProp")
|
||||
}
|
||||
|
||||
pubKeyOwner := pubKeyOwnerProp.GetIRI()
|
||||
if pubKeyOwner == nil {
|
||||
return nil, nil, nil, errors.New("nil iri on pubKeyOwnerProp")
|
||||
return nil, nil, nil, gtserror.New("nil iri on pubKeyOwnerProp")
|
||||
}
|
||||
|
||||
pubKeyPemProp := pkey.GetW3IDSecurityV1PublicKeyPem()
|
||||
if pubKeyPemProp == nil {
|
||||
return nil, nil, nil, errors.New("nil pubKeyPemProp")
|
||||
return nil, nil, nil, gtserror.New("nil pubKeyPemProp")
|
||||
}
|
||||
|
||||
pkeyPem := pubKeyPemProp.Get()
|
||||
if pkeyPem == "" {
|
||||
return nil, nil, nil, errors.New("empty pubKeyPemProp")
|
||||
return nil, nil, nil, gtserror.New("empty pubKeyPemProp")
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(pkeyPem))
|
||||
if block == nil {
|
||||
return nil, nil, nil, errors.New("nil pubKeyPem")
|
||||
return nil, nil, nil, gtserror.New("nil pubKeyPem")
|
||||
}
|
||||
|
||||
var p crypto.PublicKey
|
||||
@@ -753,20 +676,19 @@ func ExtractPubKeyFromKey(pkey vocab.W3IDSecurityV1PublicKey) (
|
||||
case "RSA PUBLIC KEY":
|
||||
p, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
default:
|
||||
err = fmt.Errorf("unknown block type: %q", block.Type)
|
||||
err = gtserror.Newf("unknown block type: %q", block.Type)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse public key from block bytes: %w", err)
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, gtserror.Newf("could not parse public key from block bytes: %w", err)
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return nil, nil, nil, fmt.Errorf("returned public key was empty")
|
||||
return nil, nil, nil, gtserror.New("returned public key was empty")
|
||||
}
|
||||
|
||||
pubKey, ok := p.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("could not type pubKey to *rsa.PublicKey")
|
||||
return nil, nil, nil, gtserror.New("could not type pubKey to *rsa.PublicKey")
|
||||
}
|
||||
|
||||
return pubKey, pubKeyID, pubKeyOwner, nil
|
||||
@@ -1209,45 +1131,6 @@ func ExtractActorURI(withActor WithActor) (*url.URL, error) {
|
||||
return nil, gtserror.New("no iri found for actor prop")
|
||||
}
|
||||
|
||||
// ExtractObjectURI extracts the first Object URI
|
||||
// it can find from a WithObject interface.
|
||||
func ExtractObjectURI(withObject WithObject) (*url.URL, error) {
|
||||
objectProp := withObject.GetActivityStreamsObject()
|
||||
if objectProp == nil {
|
||||
return nil, gtserror.New("object property was nil")
|
||||
}
|
||||
|
||||
for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() {
|
||||
id, err := pub.ToId(iter)
|
||||
if err == nil {
|
||||
// Found one we can use.
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, gtserror.New("no iri found for object prop")
|
||||
}
|
||||
|
||||
// ExtractObjectURIs extracts the URLs of each Object
|
||||
// it can find from a WithObject interface.
|
||||
func ExtractObjectURIs(withObject WithObject) ([]*url.URL, error) {
|
||||
objectProp := withObject.GetActivityStreamsObject()
|
||||
if objectProp == nil {
|
||||
return nil, gtserror.New("object property was nil")
|
||||
}
|
||||
|
||||
urls := make([]*url.URL, 0, objectProp.Len())
|
||||
for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() {
|
||||
id, err := pub.ToId(iter)
|
||||
if err == nil {
|
||||
// Found one we can use.
|
||||
urls = append(urls, id)
|
||||
}
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
// ExtractVisibility extracts the gtsmodel.Visibility
|
||||
// of a given addressable with a To and CC property.
|
||||
//
|
||||
@@ -1258,8 +1141,8 @@ func ExtractObjectURIs(withObject WithObject) ([]*url.URL, error) {
|
||||
// eg., `https://example.org/users/whoever/followers`.
|
||||
func ExtractVisibility(addressable Addressable, actorFollowersURI string) (gtsmodel.Visibility, error) {
|
||||
var (
|
||||
to = ExtractToURIs(addressable)
|
||||
cc = ExtractCcURIs(addressable)
|
||||
to = GetTo(addressable)
|
||||
cc = GetCc(addressable)
|
||||
)
|
||||
|
||||
if len(to) == 0 && len(cc) == 0 {
|
||||
|
||||
+96
-13
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"code.superseriousbusiness.org/activity/streams"
|
||||
"code.superseriousbusiness.org/activity/streams/vocab"
|
||||
"code.superseriousbusiness.org/gopkg/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
@@ -84,7 +85,7 @@ func SetJSONLDIdStr(with WithJSONLDId, id string) error {
|
||||
// GetTo returns the IRIs contained in the To property of 'with'. Panics on entries with missing ID.
|
||||
func GetTo(with WithTo) []*url.URL {
|
||||
toProp := with.GetActivityStreamsTo()
|
||||
return getIRIs[vocab.ActivityStreamsToPropertyIterator](toProp)
|
||||
return getIRIs(toProp)
|
||||
}
|
||||
|
||||
// AppendTo appends the given IRIs to the To property of 'with'.
|
||||
@@ -102,7 +103,7 @@ func AppendTo(with WithTo, to ...*url.URL) {
|
||||
// GetCc returns the IRIs contained in the Cc property of 'with'. Panics on entries with missing ID.
|
||||
func GetCc(with WithCc) []*url.URL {
|
||||
ccProp := with.GetActivityStreamsCc()
|
||||
return extractIRIs[vocab.ActivityStreamsCcPropertyIterator](ccProp)
|
||||
return extractIRIs(ccProp)
|
||||
}
|
||||
|
||||
// AppendCc appends the given IRIs to the Cc property of 'with'.
|
||||
@@ -120,7 +121,7 @@ func AppendCc(with WithCc, cc ...*url.URL) {
|
||||
// GetBcc returns the IRIs contained in the Bcc property of 'with'. Panics on entries with missing ID.
|
||||
func GetBcc(with WithBcc) []*url.URL {
|
||||
bccProp := with.GetActivityStreamsBcc()
|
||||
return extractIRIs[vocab.ActivityStreamsBccPropertyIterator](bccProp)
|
||||
return extractIRIs(bccProp)
|
||||
}
|
||||
|
||||
// AppendBcc appends the given IRIs to the Bcc property of 'with'.
|
||||
@@ -190,10 +191,24 @@ func AppendURL(with WithURL, url ...*url.URL) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetOneActorIRI extracts a single `actor` property IRI from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 actor value.
|
||||
func GetOneActorIRI(with WithActor) (*url.URL, error) {
|
||||
actors := GetActorIRIs(with)
|
||||
if len(actors) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single actor, received: %v", log.Formatted(actors))
|
||||
}
|
||||
return actors[0], nil
|
||||
}
|
||||
|
||||
// If > 1 is accepted, but only first wanted, use:
|
||||
//
|
||||
// func GetFirstActorIRI(...) (*url.URL, error) {}
|
||||
|
||||
// GetActorIRIs returns the IRIs contained in the Actor property of 'with'.
|
||||
func GetActorIRIs(with WithActor) []*url.URL {
|
||||
actorProp := with.GetActivityStreamsActor()
|
||||
return extractIRIs[vocab.ActivityStreamsActorPropertyIterator](actorProp)
|
||||
return extractIRIs(actorProp)
|
||||
}
|
||||
|
||||
// AppendActorIRIs appends the given IRIs to the Actor property of 'with'.
|
||||
@@ -208,10 +223,24 @@ func AppendActorIRIs(with WithActor, actor ...*url.URL) {
|
||||
}, actor...)
|
||||
}
|
||||
|
||||
// GetOneObjectIRI extracts a single `object` property IRI from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 object value.
|
||||
func GetOneObjectIRI(with WithObject) (*url.URL, error) {
|
||||
objects := GetObjectIRIs(with)
|
||||
if len(objects) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single object, received: %v", log.Formatted(objects))
|
||||
}
|
||||
return objects[0], nil
|
||||
}
|
||||
|
||||
// If > 1 is accepted, but only first wanted, use:
|
||||
//
|
||||
// func GetFirstObjectIRI(...) (*url.URL, error) {}
|
||||
|
||||
// GetObjectIRIs returns the IRIs contained in the Object property of 'with'.
|
||||
func GetObjectIRIs(with WithObject) []*url.URL {
|
||||
objectProp := with.GetActivityStreamsObject()
|
||||
return extractIRIs[vocab.ActivityStreamsObjectPropertyIterator](objectProp)
|
||||
return extractIRIs(objectProp)
|
||||
}
|
||||
|
||||
// AppendObjectIRIs appends the given IRIs to the Object property of 'with'.
|
||||
@@ -238,6 +267,16 @@ func AppendInstrumentIRIs(with WithInstrument, instrument ...*url.URL) {
|
||||
}, instrument...)
|
||||
}
|
||||
|
||||
// GetOneResultIRI extracts a single `result` property IRI from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 result value.
|
||||
func GetOneResultIRI(with WithResult) (*url.URL, error) {
|
||||
results := GetResultIRIs(with)
|
||||
if len(results) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single result, received: %v", log.Formatted(results))
|
||||
}
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
// GetResultIRIs returns the IRIs contained in the `result` property of 'with'.
|
||||
func GetResultIRIs(with WithResult) []*url.URL {
|
||||
resultProp := with.GetActivityStreamsResult()
|
||||
@@ -256,13 +295,23 @@ func AppendResultIRIs(with WithResult, result ...*url.URL) {
|
||||
}, result...)
|
||||
}
|
||||
|
||||
// GetTargetIRIs returns the IRIs contained in the Target property of 'with'.
|
||||
func GetTargetIRIs(with WithTarget) []*url.URL {
|
||||
targetProp := with.GetActivityStreamsTarget()
|
||||
return extractIRIs[vocab.ActivityStreamsTargetPropertyIterator](targetProp)
|
||||
// GetOneTargetIRI extracts a single `target` IRI property from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 target value.
|
||||
func GetOneTargetIRI(with WithTarget) (*url.URL, error) {
|
||||
targets := GetTargetIRIs(with)
|
||||
if len(targets) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single target, received: %v", log.Formatted(targets))
|
||||
}
|
||||
return targets[0], nil
|
||||
}
|
||||
|
||||
// AppendTargetIRIs appends the given IRIs to the Target property of 'with'.
|
||||
// GetTargetIRIs returns the IRIs contained in the `target` property of 'with'.
|
||||
func GetTargetIRIs(with WithTarget) []*url.URL {
|
||||
targetProp := with.GetActivityStreamsTarget()
|
||||
return extractIRIs(targetProp)
|
||||
}
|
||||
|
||||
// AppendTargetIRIs appends the given IRIs to the `target` property of 'with'.
|
||||
func AppendTargetIRIs(with WithTarget, target ...*url.URL) {
|
||||
appendIRIs(func() Property[vocab.ActivityStreamsTargetPropertyIterator] {
|
||||
targetProp := with.GetActivityStreamsTarget()
|
||||
@@ -274,10 +323,24 @@ func AppendTargetIRIs(with WithTarget, target ...*url.URL) {
|
||||
}, target...)
|
||||
}
|
||||
|
||||
// GetOneAttributedTo extracts a single attributedTo property from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 attributedTo value.
|
||||
func GetOneAttributedTo(with WithAttributedTo) (*url.URL, error) {
|
||||
attribTo := GetAttributedTo(with)
|
||||
if len(attribTo) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single attributedTo, received: %v", log.Formatted(attribTo))
|
||||
}
|
||||
return attribTo[0], nil
|
||||
}
|
||||
|
||||
// If > 1 is accepted, but only first wanted, use:
|
||||
//
|
||||
// func GetFirstAttributedTo(...) (*url.URL, error) {}
|
||||
|
||||
// GetAttributedTo returns the IRIs contained in the AttributedTo property of 'with'.
|
||||
func GetAttributedTo(with WithAttributedTo) []*url.URL {
|
||||
attribProp := with.GetActivityStreamsAttributedTo()
|
||||
return extractIRIs[vocab.ActivityStreamsAttributedToPropertyIterator](attribProp)
|
||||
return extractIRIs(attribProp)
|
||||
}
|
||||
|
||||
// AppendAttributedTo appends the given IRIs to the AttributedTo property of 'with'.
|
||||
@@ -292,6 +355,16 @@ func AppendAttributedTo(with WithAttributedTo, attribTo ...*url.URL) {
|
||||
}, attribTo...)
|
||||
}
|
||||
|
||||
// GetOneInteractingObject extracts a single interactingObject property from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 interactingObject value.
|
||||
func GetOneInteractingObject(with WithInteractingObject) (*url.URL, error) {
|
||||
object := GetInteractingObject(with)
|
||||
if len(object) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single interactingObject, received: %v", log.Formatted(object))
|
||||
}
|
||||
return object[0], nil
|
||||
}
|
||||
|
||||
// GetInteractingObject returns IRIs contained in the interactingObject property of 'with'.
|
||||
func GetInteractingObject(with WithInteractingObject) []*url.URL {
|
||||
intObjProp := with.GetGoToSocialInteractingObject()
|
||||
@@ -310,6 +383,16 @@ func AppendInteractingObject(with WithInteractingObject, interactingObject ...*u
|
||||
}, interactingObject...)
|
||||
}
|
||||
|
||||
// GetOneInteractionTarget extracts a single interactingTarget property from 'with', else returns error.
|
||||
// Note: it is an error for the passed type to return any count other than 1 interactionTarget value.
|
||||
func GetOneInteractionTarget(with WithInteractionTarget) (*url.URL, error) {
|
||||
target := GetInteractionTarget(with)
|
||||
if len(target) != 1 {
|
||||
return nil, gtserror.NewfAt(3, "expected single interactionTarget, received: %v", log.Formatted(target))
|
||||
}
|
||||
return target[0], nil
|
||||
}
|
||||
|
||||
// GetInteractionTarget returns IRIs contained in the interactionTarget property of 'with'.
|
||||
func GetInteractionTarget(with WithInteractionTarget) []*url.URL {
|
||||
intTargetProp := with.GetGoToSocialInteractionTarget()
|
||||
@@ -331,7 +414,7 @@ func AppendInteractionTarget(with WithInteractionTarget, interactionTarget ...*u
|
||||
// GetInReplyTo returns the IRIs contained in the InReplyTo property of 'with'.
|
||||
func GetInReplyTo(with WithInReplyTo) []*url.URL {
|
||||
replyProp := with.GetActivityStreamsInReplyTo()
|
||||
return extractIRIs[vocab.ActivityStreamsInReplyToPropertyIterator](replyProp)
|
||||
return extractIRIs(replyProp)
|
||||
}
|
||||
|
||||
// AppendInReplyTo appends the given IRIs to the InReplyTo property of 'with'.
|
||||
@@ -463,7 +546,7 @@ func SetMovedTo(with WithMovedTo, movedTo *url.URL) {
|
||||
// GetAlsoKnownAs returns the IRI contained in the alsoKnownAs property of 'with'.
|
||||
func GetAlsoKnownAs(with WithAlsoKnownAs) []*url.URL {
|
||||
alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
|
||||
return getIRIs[vocab.ActivityStreamsAlsoKnownAsPropertyIterator](alsoKnownAsProp)
|
||||
return getIRIs(alsoKnownAsProp)
|
||||
}
|
||||
|
||||
// SetAlsoKnownAs sets the given IRIs on the alsoKnownAs property of 'with'.
|
||||
|
||||
@@ -254,7 +254,7 @@ func (d *Dereferencer) getStatusByURI(
|
||||
// Search the database
|
||||
// for existing status.
|
||||
uriStr := uri.String()
|
||||
status, err = d.getStatusDBOnly(ctx, uriStr)
|
||||
status, err = d.getStatusFromDB(ctx, uriStr)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
@@ -401,7 +401,7 @@ func (d *Dereferencer) enrichAndStoreStatusSafely(
|
||||
|
||||
// DATA RACE! We likely lost out to another goroutine
|
||||
// in a call to db.Put(Status). Look again in DB by URI.
|
||||
latest, err = d.getStatusDBOnly(ctx, status.URI)
|
||||
latest, err = d.getStatusFromDB(ctx, status.URI)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error getting status %s from database after race: %w", uriStr, err)
|
||||
}
|
||||
@@ -450,12 +450,11 @@ func (d *Dereferencer) enrichAndStoreStatus(
|
||||
return nil, nil, isNew, gtserror.Newf("couldn't create transport: %w", err)
|
||||
}
|
||||
|
||||
// Dereference statusable from remote,
|
||||
// checking if we already had this
|
||||
// status stored under a different URI
|
||||
// (ie., the final URI after redirects).
|
||||
var alreadyStatus *gtsmodel.Status
|
||||
statusable, alreadyStatus, err = d.retrieveStatusable(ctx, tsport, uri)
|
||||
var existing *gtsmodel.Status
|
||||
|
||||
// Dereference statusable from remote, checking if we already had this
|
||||
// status stored under a different URI (ie., the final URI after redirects).
|
||||
statusable, existing, uri, err = d.retrieveStatusable(ctx, tsport, uri)
|
||||
if err != nil {
|
||||
return nil, nil, isNew, err
|
||||
}
|
||||
@@ -466,8 +465,8 @@ func (d *Dereferencer) enrichAndStoreStatus(
|
||||
//
|
||||
// Continue with this status and mark it as not
|
||||
// new so we don't try to store it again below.
|
||||
if alreadyStatus != nil {
|
||||
status = alreadyStatus
|
||||
if existing != nil {
|
||||
status = existing
|
||||
isNew = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"code.superseriousbusiness.org/gopkg/xslices"
|
||||
"code.superseriousbusiness.org/gopkg/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/ap"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
|
||||
@@ -35,10 +35,11 @@ import (
|
||||
// getStatusDBOnly checks in the database for
|
||||
// status with the given URI (or URL), without
|
||||
// doing any external dereferencing.
|
||||
func (d *Dereferencer) getStatusDBOnly(
|
||||
func (d *Dereferencer) getStatusFromDB(
|
||||
ctx context.Context,
|
||||
uriStr string,
|
||||
) (*gtsmodel.Status, error) {
|
||||
|
||||
// For both queries request a barebones
|
||||
// object, as it will be later populated
|
||||
// in the enrichAndStoreSafely() function.
|
||||
@@ -77,23 +78,27 @@ func (d *Dereferencer) getStatusDBOnly(
|
||||
//
|
||||
// Will return malformed if the final redirected URI is not
|
||||
// either the AP ID/URI or the URL of the dereffed statusable.
|
||||
//
|
||||
// The final returned URI is the dereferenced
|
||||
// ActivityPub status object's JSON-LD ID.
|
||||
func (d *Dereferencer) retrieveStatusable(
|
||||
ctx context.Context,
|
||||
tsport transport.Transport,
|
||||
uri *url.URL,
|
||||
) (
|
||||
statusable ap.Statusable,
|
||||
alreadyStatus *gtsmodel.Status,
|
||||
existing *gtsmodel.Status,
|
||||
statusURI *url.URL,
|
||||
err error,
|
||||
) {
|
||||
// Save this for later comparison.
|
||||
// Save for later comparison.
|
||||
initialURIStr := uri.String()
|
||||
|
||||
// Dereference latest version of the status.
|
||||
// Dereference latest version of status.
|
||||
rsp, err := tsport.Dereference(ctx, uri)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error dereferencing %s: %w", uri, err)
|
||||
return nil, nil, gtserror.SetUnretrievable(err)
|
||||
err := gtserror.Newf("error dereferencing %s: %w", initialURIStr, err)
|
||||
return nil, nil, nil, gtserror.SetUnretrievable(err)
|
||||
}
|
||||
|
||||
// Attempt to resolve ActivityPub status from response.
|
||||
@@ -105,8 +110,9 @@ func (d *Dereferencer) retrieveStatusable(
|
||||
if err != nil {
|
||||
// ResolveStatusable will set gtserror.WrongType
|
||||
// on the returned error, so we don't need to do it here.
|
||||
err := gtserror.Newf("error resolving statusable %s: %w", uri, err)
|
||||
return nil, nil, err
|
||||
err := gtserror.Newf("error resolving statusable %s: %w",
|
||||
initialURIStr, err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Check whether input URI and final returned URI
|
||||
@@ -114,72 +120,55 @@ func (d *Dereferencer) retrieveStatusable(
|
||||
//
|
||||
// NOTE: this URI check + database call is performed
|
||||
// AFTER reading and closing body, for performance.
|
||||
var (
|
||||
finalURI = rsp.Request.URL
|
||||
finalURIStr = rsp.Request.URL.String()
|
||||
redirected = finalURIStr != initialURIStr
|
||||
)
|
||||
finalURI := rsp.Request.URL
|
||||
finalURIStr := finalURI.String()
|
||||
redirected := finalURIStr != initialURIStr
|
||||
|
||||
if redirected {
|
||||
// Update passed-in URI
|
||||
// for benefit of the caller.
|
||||
uri = finalURI
|
||||
|
||||
// Check whether we have this status
|
||||
// stored under *final* URI and return
|
||||
// it to the caller if so.
|
||||
var err error
|
||||
alreadyStatus, err = d.getStatusDBOnly(ctx, finalURIStr)
|
||||
|
||||
// Check whether we have this status stored under
|
||||
// *final* determined URI, preferring this for return.
|
||||
existing, err = d.getStatusFromDB(ctx, finalURIStr)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting status after redirects: %w", err)
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the json-ld ID, i.e. the
|
||||
// actual ActivityPub URI ID of status.
|
||||
jsonldID := ap.GetJSONLDId(statusable)
|
||||
|
||||
// Ensure the final URI we fetched the status
|
||||
// from matches either (one of) the URL(s) or
|
||||
// the ID/URI of the dereferenced statusable.
|
||||
okURIs := append(
|
||||
ap.GetURL(statusable), // status URL(s)
|
||||
ap.GetJSONLDId(statusable), // status URI
|
||||
)
|
||||
matches, err := util.URIMatches(finalURI, okURIs...)
|
||||
uris := append(ap.GetURL(statusable), jsonldID)
|
||||
matches, err := util.URIMatches(finalURI, uris...)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error checking final dereferenced status uri %s: %w", finalURIStr, err)
|
||||
return nil, nil, err
|
||||
err := gtserror.Newf("error checking uri matches %s: %w", finalURIStr, err)
|
||||
return nil, nil, nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
if !matches {
|
||||
// There's not a match, so the remote is doing
|
||||
// something weird. Gather URI strings we would
|
||||
// have accepted into nice slice for logging.
|
||||
okURIStrs := xslices.Gather(
|
||||
nil,
|
||||
okURIs,
|
||||
func(u *url.URL) string {
|
||||
return u.String()
|
||||
},
|
||||
)
|
||||
|
||||
// Construct error to give a bit more information
|
||||
// in case there were one or more redirects.
|
||||
var err error
|
||||
// For error, include
|
||||
// redirect for context.
|
||||
uristr := finalURIStr
|
||||
if redirected {
|
||||
err = gtserror.Newf(
|
||||
"final http URI %s, after redirect(s) from initial URI %s, does not match dereferenced statusable id or url(s) %+v",
|
||||
finalURIStr, initialURIStr, okURIStrs,
|
||||
)
|
||||
} else {
|
||||
err = gtserror.Newf(
|
||||
"http URI %s does not match dereferenced statusable id or url(s) %+v",
|
||||
initialURIStr, okURIStrs,
|
||||
)
|
||||
uristr += " (redirected from " + initialURIStr + ")"
|
||||
}
|
||||
|
||||
// Set malformed on the returned error.
|
||||
return nil, nil, gtserror.SetMalformed(err)
|
||||
// No URI match, remote is doing something weird. Return malformed error type.
|
||||
err := gtserror.Newf("fetch uri %s does not match known status uri(s): %v",
|
||||
uristr, log.Formatted(uris))
|
||||
return nil, nil, nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// For the final returned URI we set
|
||||
// status' canonical JSON-LD URI, as
|
||||
// checks against its URI won't work
|
||||
// if we return an alternative URL.
|
||||
statusURI = jsonldID
|
||||
return
|
||||
}
|
||||
|
||||
@@ -189,13 +178,21 @@ func (d *Dereferencer) retrieveStatusable(
|
||||
func (d *Dereferencer) convertStatusable(
|
||||
ctx context.Context,
|
||||
requestUser string,
|
||||
uri *url.URL,
|
||||
statusURI *url.URL,
|
||||
statusable ap.Statusable,
|
||||
) (*gtsmodel.Status, error) {
|
||||
// Get the attributed-to ID/URI in order to fetch account.
|
||||
attributedTo, err := ap.ExtractAttributedToURI(statusable)
|
||||
|
||||
// Get attributedTo URI from statusable to fetch account.
|
||||
attributedTo, err := ap.GetOneAttributedTo(statusable)
|
||||
if err != nil {
|
||||
return nil, gtserror.New("attributedTo was empty")
|
||||
return nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// The status author, and the status
|
||||
// JSON-LD ID must have the same host.
|
||||
if attributedTo.Host != statusURI.Host {
|
||||
err := gtserror.Newf("id and attributedTo hostnames differ: id=%s attributedTo=%s", attributedTo.Host, statusURI.Host)
|
||||
return nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// Ensure we have the author account of the status dereferenced
|
||||
@@ -204,13 +201,13 @@ func (d *Dereferencer) convertStatusable(
|
||||
|
||||
// Note that we specifically DO NOT wrap the error, instead collapsing it as string.
|
||||
// Errors fetching an account do not necessarily relate to dereferencing the status.
|
||||
return nil, gtserror.Newf("failed to dereference status author %s: %v", uri, err)
|
||||
return nil, gtserror.Newf("failed to dereference status author %s: %v", statusURI, err)
|
||||
}
|
||||
|
||||
// Convert AP model to our GTS model.
|
||||
// Convert ActivityPub model to our internal GTS model.
|
||||
status, err := d.converter.ASStatusToStatus(ctx, statusable)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error converting statusable to gts model for status %s: %w", uri, err)
|
||||
return nil, gtserror.Newf("error converting statusable to gts model for status %s: %w", statusURI, err)
|
||||
}
|
||||
|
||||
// Ensure final status isn't attempting
|
||||
|
||||
@@ -724,17 +724,13 @@ func (d *Dereferencer) isValidAuthURI(
|
||||
// as the ID of the Accept we were returned.
|
||||
switch {
|
||||
case rspURL.Host != authIRI.Host:
|
||||
l.Errorf(
|
||||
"final deref host %s did not match authIRI host",
|
||||
rspURL.Host,
|
||||
)
|
||||
l.Errorf("final deref host %s did not match authIRI host",
|
||||
rspURL.Host)
|
||||
return false, nil
|
||||
|
||||
case authIDStr != rspURLStr:
|
||||
l.Errorf(
|
||||
"final deref uri %s did not match returned ID %s",
|
||||
rspURLStr, authIDStr,
|
||||
)
|
||||
l.Errorf("final deref uri %s did not match returned ID %s",
|
||||
rspURLStr, authIDStr)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
@@ -744,27 +740,33 @@ func (d *Dereferencer) isValidAuthURI(
|
||||
|
||||
// First try to parse type as Authorization stamp.
|
||||
if authable, ok := ap.ToAuthorizationable(t); ok {
|
||||
return isValidAuthorization(
|
||||
ctx,
|
||||
if err := isValidAuthorization(
|
||||
interactionType,
|
||||
authable,
|
||||
authID,
|
||||
expectActorURIStr, // actor
|
||||
expectObjectURIStr, // object
|
||||
expectTargetURIStr, // target
|
||||
)
|
||||
); err != nil {
|
||||
log.Warn(ctx, err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Fall back to parsing as a simple Accept.
|
||||
if acceptable, ok := ap.ToAcceptable(t); ok {
|
||||
return isValidAcceptable(
|
||||
ctx,
|
||||
if err := isValidAcceptable(
|
||||
acceptable,
|
||||
authID,
|
||||
expectActorURIStr, // actor
|
||||
expectObjectURIStr, // object
|
||||
expectTargetURIStr, // target
|
||||
)
|
||||
); err != nil {
|
||||
log.Warn(ctx, err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Type wasn't something we
|
||||
@@ -777,94 +779,85 @@ func (d *Dereferencer) isValidAuthURI(
|
||||
}
|
||||
|
||||
func isValidAcceptable(
|
||||
ctx context.Context,
|
||||
acceptable ap.Acceptable,
|
||||
acceptID *url.URL,
|
||||
expectActorURIStr string, // actor Eg., "https://example.org/users/someone"
|
||||
expectObjectURIStr string, // object Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R"
|
||||
expectTargetURIStr string, // target Eg., "https://example.org/users/someone/statuses/01JM4REQTJ1BZ1R4BPYP1W4R9E"
|
||||
) (bool, error) {
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
WithField("accept", acceptID.String())
|
||||
) error {
|
||||
|
||||
// Extract the actor IRI and string from Accept.
|
||||
actorIRIs := ap.GetActorIRIs(acceptable)
|
||||
actorIRI, actorIRIStr := extractIRI(actorIRIs)
|
||||
switch {
|
||||
case actorIRIStr == "":
|
||||
l.Error("Accept missing actor IRI")
|
||||
return false, nil
|
||||
// Extract the actor IRI from acceptable type.
|
||||
actorIRI, err := ap.GetOneActorIRI(acceptable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize actor ID just once.
|
||||
actorIRIStr := actorIRI.String()
|
||||
|
||||
// Ensure the Accept Actor is on
|
||||
// the instance hosting the Accept.
|
||||
case actorIRI.Host != acceptID.Host:
|
||||
l.Errorf(
|
||||
"actor %s not on the same host as Accept",
|
||||
actorIRIStr,
|
||||
)
|
||||
return false, nil
|
||||
if actorIRI.Host != acceptID.Host {
|
||||
return gtserror.Newf("actor %s not on the same host as Accept",
|
||||
actorIRIStr)
|
||||
}
|
||||
|
||||
// Ensure the Accept Actor is who we expect
|
||||
// it to be, and not someone else trying to
|
||||
// do an Accept for an interaction with a
|
||||
// statusable they don't own.
|
||||
case actorIRIStr != expectActorURIStr:
|
||||
l.Errorf(
|
||||
"actor %s was not the same as expected actor %s",
|
||||
actorIRIStr, expectActorURIStr,
|
||||
)
|
||||
return false, nil
|
||||
if actorIRIStr != expectActorURIStr {
|
||||
return gtserror.Newf("actor %s was not the same as expected actor %s",
|
||||
actorIRIStr, expectActorURIStr)
|
||||
}
|
||||
|
||||
// Extract the object IRI string from Accept.
|
||||
objectIRIs := ap.GetObjectIRIs(acceptable)
|
||||
_, objectIRIStr := extractIRI(objectIRIs)
|
||||
switch {
|
||||
case objectIRIStr == "":
|
||||
l.Error("missing Accept object IRI")
|
||||
return false, nil
|
||||
// Extract the object IRI from acceptable type.
|
||||
objectIRI, err := ap.GetOneObjectIRI(acceptable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize object ID just once.
|
||||
objectIRIStr := objectIRI.String()
|
||||
|
||||
// Ensure the Accept Object is what we expect
|
||||
// it to be, ie., it's Accepting the interaction
|
||||
// we need it to Accept, and not something else.
|
||||
case objectIRIStr != expectObjectURIStr:
|
||||
l.Errorf(
|
||||
"resolved Accept object IRI %s was not the same as expected object %s",
|
||||
objectIRIStr, expectObjectURIStr,
|
||||
)
|
||||
return false, nil
|
||||
if objectIRIStr != expectObjectURIStr {
|
||||
return gtserror.Newf("resolved Accept object IRI %s was not the same as expected object %s",
|
||||
objectIRIStr, expectObjectURIStr)
|
||||
}
|
||||
|
||||
// Extract the target IRI from acceptable type.
|
||||
targetIRI, err := ap.GetOneTargetIRI(acceptable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize target ID just once.
|
||||
targetIRIStr := targetIRI.String()
|
||||
|
||||
// If there's a Target set then verify it's
|
||||
// what we expect it to be, ie., it should point
|
||||
// back to the post that's being interacted with.
|
||||
targetIRIs := ap.GetTargetIRIs(acceptable)
|
||||
_, targetIRIStr := extractIRI(targetIRIs)
|
||||
if targetIRIStr != "" && targetIRIStr != expectTargetURIStr {
|
||||
l.Errorf(
|
||||
"resolved Accept target IRI %s was not the same as expected target %s",
|
||||
targetIRIStr, expectTargetURIStr,
|
||||
)
|
||||
return false, nil
|
||||
if targetIRIStr != expectTargetURIStr {
|
||||
return gtserror.Newf("resolved Accept target IRI %s was not the same as expected target %s",
|
||||
targetIRIStr, expectTargetURIStr)
|
||||
}
|
||||
|
||||
// Everything looks OK.
|
||||
return true, nil
|
||||
// Everything
|
||||
// looks OK.
|
||||
return nil
|
||||
}
|
||||
|
||||
func isValidAuthorization(
|
||||
ctx context.Context,
|
||||
interactionType gtsmodel.InteractionType,
|
||||
auth ap.Authorizationable,
|
||||
authID *url.URL,
|
||||
expectActorURIStr string, // actor Eg., "https://example.org/users/someone"
|
||||
expectObjectURIStr string, // object Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R"
|
||||
expectTargetURIStr string, // target Eg., "https://example.org/users/someone/statuses/01JM4REQTJ1BZ1R4BPYP1W4R9E"
|
||||
) (bool, error) {
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
WithField("auth", authID.String())
|
||||
) error {
|
||||
|
||||
// Check that the type of the Authorization
|
||||
// matches the interaction it's approving.
|
||||
@@ -875,83 +868,71 @@ func isValidAuthorization(
|
||||
// All good baby!
|
||||
default:
|
||||
// There's a mismatch.
|
||||
l.Errorf(
|
||||
"authorization type %s cannot approve %s",
|
||||
tn, interactionType.String(),
|
||||
)
|
||||
return false, nil
|
||||
return gtserror.Newf("authorization type %s cannot approve %s",
|
||||
tn, interactionType.String())
|
||||
}
|
||||
|
||||
// Extract the actor IRI and string from Approval.
|
||||
actorIRIs := ap.GetAttributedTo(auth)
|
||||
actorIRI, actorIRIStr := extractIRI(actorIRIs)
|
||||
switch {
|
||||
case actorIRIStr == "":
|
||||
l.Error("authorization missing attributedTo IRI")
|
||||
return false, nil
|
||||
// Extract the actor IRI from authorization.
|
||||
actorIRI, err := ap.GetOneAttributedTo(auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize actor ID just once.
|
||||
actorIRIStr := actorIRI.String()
|
||||
|
||||
// Ensure the authorization actor is on
|
||||
// the instance hosting the Approval.
|
||||
case actorIRI.Host != authID.Host:
|
||||
l.Errorf(
|
||||
"actor %s not on the same host as authorization",
|
||||
actorIRIStr,
|
||||
)
|
||||
return false, nil
|
||||
if actorIRI.Host != authID.Host {
|
||||
return gtserror.Newf("actor %s not on the same host as authorization",
|
||||
actorIRIStr)
|
||||
}
|
||||
|
||||
// Ensure the auth actor is who we expect
|
||||
// it to be, and not someone else trying to
|
||||
// do an auth for an interaction with a
|
||||
// statusable they don't own.
|
||||
case actorIRIStr != expectActorURIStr:
|
||||
l.Errorf(
|
||||
"actor %s was not the same as expected actor %s",
|
||||
actorIRIStr, expectActorURIStr,
|
||||
)
|
||||
return false, nil
|
||||
if actorIRIStr != expectActorURIStr {
|
||||
return gtserror.Newf("actor %s was not the same as expected actor %s",
|
||||
actorIRIStr, expectActorURIStr)
|
||||
}
|
||||
|
||||
// Extract the object IRI string from authorization.
|
||||
objectIRIs := ap.GetInteractingObject(auth)
|
||||
_, objectIRIStr := extractIRI(objectIRIs)
|
||||
switch {
|
||||
case objectIRIStr == "":
|
||||
l.Error("missing authorization interactingObject IRI")
|
||||
return false, nil
|
||||
// Extract interacting object IRI from authorization.
|
||||
objectIRI, err := ap.GetOneInteractingObject(auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize object ID just once.
|
||||
objectIRIStr := objectIRI.String()
|
||||
|
||||
// Ensure the authorization object is what we expect
|
||||
// it to be, ie., it's approving the interaction
|
||||
// we need it to approve, and not something else.
|
||||
case objectIRIStr != expectObjectURIStr:
|
||||
l.Errorf(
|
||||
if objectIRIStr != expectObjectURIStr {
|
||||
return gtserror.Newf(
|
||||
"resolved authorization interactingObject IRI %s was not the same as expected object %s",
|
||||
objectIRIStr, expectObjectURIStr,
|
||||
)
|
||||
return false, nil
|
||||
objectIRIStr, expectObjectURIStr)
|
||||
}
|
||||
|
||||
// Extract interaction target IRI from authorization.
|
||||
targetIRI, err := ap.GetOneInteractionTarget(auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize target ID just once.
|
||||
targetIRIStr := targetIRI.String()
|
||||
|
||||
// Ensure the authorization target is what we expect,
|
||||
// ie., it should be the status being interacted with.
|
||||
targetIRIs := ap.GetInteractionTarget(auth)
|
||||
_, targetIRIStr := extractIRI(targetIRIs)
|
||||
if targetIRIStr != "" && targetIRIStr != expectTargetURIStr {
|
||||
l.Errorf(
|
||||
if targetIRIStr != expectTargetURIStr {
|
||||
return gtserror.Newf(
|
||||
"resolved authorization interactionTarget IRI %s was not the same as expected target %s",
|
||||
targetIRIStr, expectTargetURIStr,
|
||||
)
|
||||
return false, nil
|
||||
targetIRIStr, expectTargetURIStr)
|
||||
}
|
||||
|
||||
// Everything looks OK.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// extractIRI is shorthand to extract the first IRI
|
||||
// url.URL{} object and serialized form from slice.
|
||||
func extractIRI(iris []*url.URL) (*url.URL, string) {
|
||||
if len(iris) == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
u := iris[0]
|
||||
return u, u.String()
|
||||
// Everything
|
||||
// looks OK.
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ func (d *Dereferencer) GetRelayedStatus(
|
||||
unlock = util.DoOnce(unlock)
|
||||
defer unlock()
|
||||
|
||||
// Search the database for existing status.
|
||||
if status, err := d.getStatusDBOnly(ctx, uriStr); err != nil {
|
||||
// Search the database for an existing status under URI / URL.
|
||||
if status, err := d.getStatusFromDB(ctx, uriStr); err != nil {
|
||||
return nil, err
|
||||
} else if status != nil {
|
||||
// If we already have the status,
|
||||
@@ -92,7 +92,8 @@ func (d *Dereferencer) GetRelayedStatus(
|
||||
// We don't have the relayed status
|
||||
// stored locally, go dereference it.
|
||||
status, statusable, err := d.dereferenceRelayableStatus(
|
||||
ctx, l,
|
||||
ctx,
|
||||
&l,
|
||||
instanceAcct,
|
||||
relayAcct,
|
||||
uri,
|
||||
@@ -193,7 +194,7 @@ func (d *Dereferencer) GetRelayedAnnounce(
|
||||
return nil, gtserror.SetUnretrievable(err)
|
||||
}
|
||||
|
||||
// Create logger.
|
||||
// Prepare log entry with fields.
|
||||
uriStr := boostWrapper.BoostOfURIStr
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
@@ -204,8 +205,8 @@ func (d *Dereferencer) GetRelayedAnnounce(
|
||||
unlock = util.DoOnce(unlock)
|
||||
defer unlock()
|
||||
|
||||
// Search the database for existing status.
|
||||
if status, err := d.getStatusDBOnly(ctx, uriStr); err != nil {
|
||||
// Search the database for an existing status under URI / URL.
|
||||
if status, err := d.getStatusFromDB(ctx, uriStr); err != nil {
|
||||
return nil, err
|
||||
} else if status != nil {
|
||||
// If we already have the status,
|
||||
@@ -218,7 +219,8 @@ func (d *Dereferencer) GetRelayedAnnounce(
|
||||
// We don't have the relayed status
|
||||
// stored locally, go dereference it.
|
||||
status, statusable, err := d.dereferenceRelayableStatus(
|
||||
ctx, l,
|
||||
ctx,
|
||||
&l,
|
||||
instanceAcct,
|
||||
relayAcct,
|
||||
uri,
|
||||
@@ -333,11 +335,15 @@ func (d *Dereferencer) GetRelayedAnnounce(
|
||||
|
||||
func (d *Dereferencer) dereferenceRelayableStatus(
|
||||
ctx context.Context,
|
||||
l log.Entry,
|
||||
l *log.Entry,
|
||||
instanceAcct *gtsmodel.Account,
|
||||
relayAcct *gtsmodel.Account,
|
||||
uri *url.URL,
|
||||
) (*gtsmodel.Status, ap.Statusable, error) {
|
||||
) (
|
||||
*gtsmodel.Status,
|
||||
ap.Statusable,
|
||||
error,
|
||||
) {
|
||||
// Don't have the status locally, so we need to deref.
|
||||
//
|
||||
// Create transport on behalf of our instance account,
|
||||
@@ -350,20 +356,19 @@ func (d *Dereferencer) dereferenceRelayableStatus(
|
||||
return nil, nil, gtserror.Newf("couldn't create transport: %w", err)
|
||||
}
|
||||
|
||||
// Dereference statusable from remote, checking
|
||||
// if we already had this status stored under a
|
||||
// different URI (ie., final URI after redirects).
|
||||
statusable, alreadyStatus, err := d.retrieveStatusable(ctx, tsport, uri)
|
||||
// Dereference statusable from remote, checking if we already had this
|
||||
// status stored under a different URI (ie., final URI after redirects).
|
||||
statusable, existing, uri, err := d.retrieveStatusable(ctx, tsport, uri)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If alreadyStatus was returned, it means
|
||||
// If existing was returned, it means
|
||||
// we already had this status stored in the
|
||||
// db under its final reachable URI and not
|
||||
// the URI passed in. No need to go further.
|
||||
if alreadyStatus != nil {
|
||||
return alreadyStatus, statusable, nil
|
||||
if existing != nil {
|
||||
return existing, statusable, nil
|
||||
}
|
||||
|
||||
// We didn't have the status yet + we dereffed it.
|
||||
@@ -373,7 +378,9 @@ func (d *Dereferencer) dereferenceRelayableStatus(
|
||||
//
|
||||
// This will also fetch the status author account.
|
||||
status, err := d.convertStatusable(ctx,
|
||||
instanceAcct.Username, uri, statusable,
|
||||
instanceAcct.Username,
|
||||
uri,
|
||||
statusable,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, statusable, err
|
||||
@@ -382,8 +389,8 @@ func (d *Dereferencer) dereferenceRelayableStatus(
|
||||
// We're only interested in relayed statuses that
|
||||
// are public or unlisted, as followers-only statuses
|
||||
// will be definition by sent to followers anyway.
|
||||
vis := status.Visibility
|
||||
if !(vis == gtsmodel.VisibilityPublic ||
|
||||
if vis := status.Visibility; //
|
||||
!(vis == gtsmodel.VisibilityPublic ||
|
||||
vis == gtsmodel.VisibilityUnlocked) {
|
||||
l.Debug("status neither public nor unlisted")
|
||||
return nil, statusable, nil
|
||||
@@ -394,7 +401,6 @@ func (d *Dereferencer) dereferenceRelayableStatus(
|
||||
// if the author is the same as for this status.
|
||||
var inReplyToAccountURI string
|
||||
if status.InReplyToURI != "" {
|
||||
var err error
|
||||
inReplyToAccountURI, err = d.retrieveInReplyToAccountURI(ctx, tsport, status)
|
||||
if err != nil {
|
||||
return nil, statusable, err
|
||||
@@ -466,9 +472,8 @@ func (d *Dereferencer) retrieveInReplyToAccountURI(
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// We don't have the parent stored, try to fetch but
|
||||
// *don't* store it, we only want to check for now.
|
||||
parentStatusable, parentStatus, err := d.retrieveStatusable(ctx, tsport, parentURI)
|
||||
// We don't have the parent stored, try to fetch but *don't* store it, only check for now.
|
||||
parentStatusable, parentStatus, _, err := d.retrieveStatusable(ctx, tsport, parentURI)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error retrieving %s: %w", inReplyToURI, err)
|
||||
return "", err
|
||||
@@ -480,11 +485,11 @@ func (d *Dereferencer) retrieveInReplyToAccountURI(
|
||||
return parentStatus.AccountURI, nil
|
||||
}
|
||||
|
||||
attributedTo := ap.GetAttributedTo(parentStatusable)
|
||||
if len(attributedTo) == 0 {
|
||||
err := gtserror.Newf("parent %s had no attributedTo", inReplyToURI)
|
||||
return "", err
|
||||
// Get attributedTo URI from the dereferenced parent status.
|
||||
attributedTo, err := ap.GetOneAttributedTo(parentStatusable)
|
||||
if err != nil {
|
||||
return "", gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
return attributedTo[0].String(), nil
|
||||
return attributedTo.String(), nil
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithNonMatchingURI() {
|
||||
testrig.URLMustParse(remoteAltURI),
|
||||
)
|
||||
expectErrStr := fmt.Sprintf(
|
||||
"retrieveStatusable: http URI %s does not match dereferenced statusable id or url(s) [%s %s]",
|
||||
"retrieveStatusable: fetch uri %s does not match known status uri(s): [%s,%s]",
|
||||
remoteAltURI, remoteURL, remoteURI,
|
||||
)
|
||||
suite.Equal(expectErrStr, err.Error())
|
||||
|
||||
@@ -625,22 +625,20 @@ func (f *DB) parseAcceptInteractionRequestable(
|
||||
|
||||
// Ensure we have actor IRI on
|
||||
// the interaction requestable.
|
||||
actors := ap.GetActorIRIs(intRequestable)
|
||||
if len(actors) != 1 {
|
||||
actorURI, err := ap.GetOneActorIRI(intRequestable)
|
||||
if err != nil {
|
||||
const text = "invalid or missing actor property on embedded interaction request"
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
return nil, gtserror.NewErrorBadRequest(err, text)
|
||||
}
|
||||
actorURI := actors[0]
|
||||
|
||||
// Ensure we have an object URI, which
|
||||
// should point to the statusable being
|
||||
// interacted with, ie., the parent status.
|
||||
objects := ap.GetObjectIRIs(intRequestable)
|
||||
if len(objects) != 1 {
|
||||
parentURI, err := ap.GetOneObjectIRI(intRequestable)
|
||||
if err != nil {
|
||||
const text = "invalid or missing object property on embedded interaction request"
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
return nil, gtserror.NewErrorBadRequest(err, text)
|
||||
}
|
||||
parentURI := objects[0]
|
||||
|
||||
// Ensure we have instrument, which should
|
||||
// be or point to the activity/object that
|
||||
@@ -669,12 +667,11 @@ func (f *DB) parseAcceptInteractionRequestable(
|
||||
|
||||
// Ensure we have result URI, which should
|
||||
// point to an authorization for this interaction.
|
||||
results := ap.GetResultIRIs(accept)
|
||||
if len(results) != 1 {
|
||||
authURI, err := ap.GetOneResultIRI(accept)
|
||||
if err != nil {
|
||||
const text = "invalid or missing result property on embedded interaction request"
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
return nil, gtserror.NewErrorBadRequest(err, text)
|
||||
}
|
||||
authURI := results[0]
|
||||
|
||||
// Check if we have a gtsmodel interaction
|
||||
// request already stored for this interaction.
|
||||
|
||||
@@ -375,7 +375,11 @@ func (f *DB) createStatusable(
|
||||
forwarded bool,
|
||||
) error {
|
||||
// Check for spam / relevance.
|
||||
ok, err := f.statusableOK(ctx, receiver, requester, statusable)
|
||||
ok, err := f.statusableOK(ctx,
|
||||
receiver,
|
||||
requester,
|
||||
statusable,
|
||||
)
|
||||
if err != nil {
|
||||
// Error already
|
||||
// wrapped.
|
||||
|
||||
@@ -398,9 +398,8 @@ func (f *DB) ReplyRequest(ctx context.Context, replyReq vocab.GoToSocialReplyReq
|
||||
}
|
||||
|
||||
// Extract the attributed to (i.e. author) URI of status.
|
||||
attributedToURI, err := ap.ExtractAttributedToURI(statusable)
|
||||
attributedToURI, err := ap.GetOneAttributedTo(statusable)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("invalid status attributedTo value: %w", err)
|
||||
return gtserror.WrapWithCode(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ func (f *DB) undoFollow(
|
||||
// Make sure the Undo
|
||||
// actor owns the target.
|
||||
if !sameActor(
|
||||
undo.GetActivityStreamsActor(),
|
||||
asFollow.GetActivityStreamsActor(),
|
||||
undo,
|
||||
asFollow,
|
||||
) {
|
||||
// Ignore this Activity.
|
||||
return nil
|
||||
@@ -204,8 +204,8 @@ func (f *DB) undoLike(
|
||||
// Make sure the Undo
|
||||
// actor owns the target.
|
||||
if !sameActor(
|
||||
undo.GetActivityStreamsActor(),
|
||||
asLike.GetActivityStreamsActor(),
|
||||
undo,
|
||||
asLike,
|
||||
) {
|
||||
// Ignore this Activity.
|
||||
return nil
|
||||
@@ -304,8 +304,8 @@ func (f *DB) undoBlock(
|
||||
// Make sure the Undo
|
||||
// actor owns the target.
|
||||
if !sameActor(
|
||||
undo.GetActivityStreamsActor(),
|
||||
asBlock.GetActivityStreamsActor(),
|
||||
undo,
|
||||
asBlock,
|
||||
) {
|
||||
// Ignore this Activity.
|
||||
return nil
|
||||
@@ -374,8 +374,8 @@ func (f *DB) undoAnnounce(
|
||||
// Make sure the Undo actor owns the
|
||||
// Announce they're trying to undo.
|
||||
if !sameActor(
|
||||
undo.GetActivityStreamsActor(),
|
||||
asAnnounce.GetActivityStreamsActor(),
|
||||
undo,
|
||||
asAnnounce,
|
||||
) {
|
||||
// Ignore this Activity.
|
||||
return nil
|
||||
|
||||
@@ -52,40 +52,18 @@ func typeNames(objects []ap.TypeOrIRI) []string {
|
||||
|
||||
// isSender returns whether an object with AttributedTo property comes from the given requesting account.
|
||||
func isSender(with ap.WithAttributedTo, requester *gtsmodel.Account) bool {
|
||||
for _, uri := range ap.GetAttributedTo(with) {
|
||||
if uri.String() == requester.URI {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
uri, _ := ap.GetOneAttributedTo(with)
|
||||
return uri != nil && uri.String() == requester.URI
|
||||
}
|
||||
|
||||
func sameActor(actor1 vocab.ActivityStreamsActorProperty, actor2 vocab.ActivityStreamsActorProperty) bool {
|
||||
func sameActor(actor1 ap.WithActor, actor2 ap.WithActor) bool {
|
||||
if actor1 == nil || actor2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for a1Iter := actor1.Begin(); a1Iter != actor1.End(); a1Iter = a1Iter.Next() {
|
||||
a1IRI := a1Iter.GetIRI()
|
||||
if a1IRI == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
a1IRIStr := a1IRI.String()
|
||||
for a2Iter := actor2.Begin(); a2Iter != actor2.End(); a2Iter = a2Iter.Next() {
|
||||
a2IRI := a2Iter.GetIRI()
|
||||
if a2IRI == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
a2IRIStr := a2IRI.String()
|
||||
if a1IRIStr == a2IRIStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
actor1URI, _ := ap.GetOneActorIRI(actor1)
|
||||
actor2URI, _ := ap.GetOneActorIRI(actor2)
|
||||
return actor1URI != nil && actor2URI != nil &&
|
||||
actor1URI.String() == actor2URI.String()
|
||||
}
|
||||
|
||||
// NewID creates a new IRI id for the provided activity or object. The
|
||||
|
||||
@@ -62,26 +62,26 @@ import (
|
||||
// to the ResponseWriter as is expected that the caller to PostInbox will
|
||||
// do so when handling the error.
|
||||
func (f *Federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
|
||||
// Extract any other IRIs involved in this activity.
|
||||
otherIRIs := []*url.URL{}
|
||||
|
||||
// Extract any other IRIs
|
||||
// involved in this activity.
|
||||
var otherIRIs []*url.URL
|
||||
|
||||
// Get the ID of the Activity itslf.
|
||||
activityID, err := pub.GetId(activity)
|
||||
if err == nil {
|
||||
activityID, _ := pub.GetId(activity)
|
||||
if activityID != nil {
|
||||
otherIRIs = append(otherIRIs, activityID)
|
||||
}
|
||||
|
||||
// Check if the Activity has an 'inReplyTo'.
|
||||
if replyToable, ok := activity.(ap.ReplyToable); ok {
|
||||
if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil {
|
||||
otherIRIs = append(otherIRIs, inReplyToURI)
|
||||
}
|
||||
otherIRIs = append(otherIRIs, ap.GetInReplyTo(replyToable)...)
|
||||
}
|
||||
|
||||
// Check for TO and CC URIs on the Activity.
|
||||
if addressable, ok := activity.(ap.Addressable); ok {
|
||||
otherIRIs = append(otherIRIs, ap.ExtractToURIs(addressable)...)
|
||||
otherIRIs = append(otherIRIs, ap.ExtractCcURIs(addressable)...)
|
||||
otherIRIs = append(otherIRIs, ap.GetTo(addressable)...)
|
||||
otherIRIs = append(otherIRIs, ap.GetCc(addressable)...)
|
||||
}
|
||||
|
||||
// Now perform the same checks, but
|
||||
@@ -99,20 +99,18 @@ func (f *Federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Reques
|
||||
continue
|
||||
}
|
||||
|
||||
objectID, err := pub.GetId(t)
|
||||
if err == nil {
|
||||
objectID, _ := pub.GetId(t)
|
||||
if objectID != nil {
|
||||
otherIRIs = append(otherIRIs, objectID)
|
||||
}
|
||||
|
||||
if replyToable, ok := t.(ap.ReplyToable); ok {
|
||||
if inReplyToURI := ap.ExtractInReplyToURI(replyToable); inReplyToURI != nil {
|
||||
otherIRIs = append(otherIRIs, inReplyToURI)
|
||||
}
|
||||
otherIRIs = append(otherIRIs, ap.GetInReplyTo(replyToable)...)
|
||||
}
|
||||
|
||||
if addressable, ok := t.(ap.Addressable); ok {
|
||||
otherIRIs = append(otherIRIs, ap.ExtractToURIs(addressable)...)
|
||||
otherIRIs = append(otherIRIs, ap.ExtractCcURIs(addressable)...)
|
||||
otherIRIs = append(otherIRIs, ap.GetTo(addressable)...)
|
||||
otherIRIs = append(otherIRIs, ap.GetCc(addressable)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/config"
|
||||
"codeberg.org/gruf/go-bytesize"
|
||||
"codeberg.org/gruf/go-fastcopy"
|
||||
"codeberg.org/gruf/go-storage"
|
||||
"codeberg.org/gruf/go-storage/disk"
|
||||
@@ -107,11 +106,6 @@ func NewFileStorage() (*Driver, error) {
|
||||
// Load runtime configuration
|
||||
basePath := config.GetStorageLocalBasePath()
|
||||
|
||||
// Update fastcopy global buffer pool
|
||||
// to use our requested buffer size.
|
||||
const bufsize = 16 * bytesize.KiB
|
||||
fastcopy.Buffer(int(bufsize))
|
||||
|
||||
// Use default disk config with
|
||||
// increased write buffer size.
|
||||
diskCfg := disk.DefaultConfig()
|
||||
|
||||
@@ -381,7 +381,6 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
||||
// Account that created the status. Assume we have this
|
||||
// in the db by the time this function is called, else error.
|
||||
status.Account, err = c.getASAttributedToAccount(ctx,
|
||||
status.URI,
|
||||
statusable,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -495,12 +494,12 @@ func (c *Converter) ASFollowToFollowRequest(ctx context.Context, followable ap.F
|
||||
// Stringify uri obj.
|
||||
uri := uriObj.String()
|
||||
|
||||
origin, err := c.getASActorAccount(ctx, uri, followable)
|
||||
origin, err := c.getASActorAccount(ctx, followable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target, err := c.getASObjectAccount(ctx, uri, followable)
|
||||
target, err := c.getASObjectAccount(ctx, followable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -525,12 +524,12 @@ func (c *Converter) ASFollowToFollow(ctx context.Context, followable ap.Followab
|
||||
// Stringify uri obj.
|
||||
uri := uriObj.String()
|
||||
|
||||
origin, err := c.getASActorAccount(ctx, uri, followable)
|
||||
origin, err := c.getASActorAccount(ctx, followable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target, err := c.getASObjectAccount(ctx, uri, followable)
|
||||
target, err := c.getASObjectAccount(ctx, followable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -557,12 +556,12 @@ func (c *Converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gt
|
||||
// Stringify uri obj.
|
||||
uri := uriObj.String()
|
||||
|
||||
origin, err := c.getASActorAccount(ctx, uri, likeable)
|
||||
origin, err := c.getASActorAccount(ctx, likeable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target, err := c.getASObjectStatus(ctx, uri, likeable)
|
||||
target, err := c.getASObjectStatus(ctx, likeable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -593,12 +592,12 @@ func (c *Converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable)
|
||||
// Stringify uri obj.
|
||||
uri := uriObj.String()
|
||||
|
||||
origin, err := c.getASActorAccount(ctx, uri, blockable)
|
||||
origin, err := c.getASActorAccount(ctx, blockable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target, err := c.getASObjectAccount(ctx, uri, blockable)
|
||||
target, err := c.getASObjectAccount(ctx, blockable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -677,7 +676,6 @@ func (c *Converter) ASAnnounceToStatus(
|
||||
// Extract and load the boost actor account,
|
||||
// (this MUST already be in database by now).
|
||||
boost.Account, err = c.getASActorAccount(ctx,
|
||||
uri,
|
||||
announceable,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -727,7 +725,7 @@ func (c *Converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable)
|
||||
uri := uriObj.String()
|
||||
|
||||
// Extract the origin (actor) account for report.
|
||||
origin, err := c.getASActorAccount(ctx, uri, flaggable)
|
||||
origin, err := c.getASActorAccount(ctx, flaggable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -852,16 +850,14 @@ func (c *Converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) getASActorAccount(ctx context.Context, id string, with ap.WithActor) (*gtsmodel.Account, error) {
|
||||
// Get actor IRIs from type.
|
||||
actor := ap.GetActorIRIs(with)
|
||||
if len(actor) == 0 {
|
||||
err := gtserror.Newf("unusable actor property iri for %s", id)
|
||||
func (c *Converter) getASActorAccount(ctx context.Context, with ap.WithActor) (*gtsmodel.Account, error) {
|
||||
actorIRI, err := ap.GetOneActorIRI(with)
|
||||
if err != nil {
|
||||
return nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// Check for account in database with provided actor URI.
|
||||
account, err := c.state.DB.GetAccountByURI(ctx, actor[0].String())
|
||||
// Check for account in database with the extracted actor URI.
|
||||
account, err := c.state.DB.GetAccountByURI(ctx, actorIRI.String())
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error getting actor account from database: %w", err)
|
||||
}
|
||||
@@ -869,16 +865,14 @@ func (c *Converter) getASActorAccount(ctx context.Context, id string, with ap.Wi
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (c *Converter) getASAttributedToAccount(ctx context.Context, id string, with ap.WithAttributedTo) (*gtsmodel.Account, error) {
|
||||
// Get attribTo IRIs from type.
|
||||
attribTo := ap.GetAttributedTo(with)
|
||||
if len(attribTo) == 0 {
|
||||
err := gtserror.Newf("unusable attributedTo property iri for %s", id)
|
||||
func (c *Converter) getASAttributedToAccount(ctx context.Context, with ap.WithAttributedTo) (*gtsmodel.Account, error) {
|
||||
attribTo, err := ap.GetOneAttributedTo(with)
|
||||
if err != nil {
|
||||
return nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// Check for account in database with provided attributedTo URI.
|
||||
account, err := c.state.DB.GetAccountByURI(ctx, attribTo[0].String())
|
||||
// Check for account in database with extracted attributedTo URI.
|
||||
account, err := c.state.DB.GetAccountByURI(ctx, attribTo.String())
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error getting actor account from database: %w", err)
|
||||
}
|
||||
@@ -886,16 +880,14 @@ func (c *Converter) getASAttributedToAccount(ctx context.Context, id string, wit
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (c *Converter) getASObjectAccount(ctx context.Context, id string, with ap.WithObject) (*gtsmodel.Account, error) {
|
||||
// Get object IRIs from type.
|
||||
object := ap.GetObjectIRIs(with)
|
||||
if len(object) == 0 {
|
||||
err := gtserror.Newf("unusable object property iri for %s", id)
|
||||
func (c *Converter) getASObjectAccount(ctx context.Context, with ap.WithObject) (*gtsmodel.Account, error) {
|
||||
objectIRI, err := ap.GetOneObjectIRI(with)
|
||||
if err != nil {
|
||||
return nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// Check for account in database with provided object URI.
|
||||
account, err := c.state.DB.GetAccountByURI(ctx, object[0].String())
|
||||
// Check for account in database with the extracted object URI.
|
||||
account, err := c.state.DB.GetAccountByURI(ctx, objectIRI.String())
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error getting object account from database: %w", err)
|
||||
}
|
||||
@@ -903,16 +895,14 @@ func (c *Converter) getASObjectAccount(ctx context.Context, id string, with ap.W
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (c *Converter) getASObjectStatus(ctx context.Context, id string, with ap.WithObject) (*gtsmodel.Status, error) {
|
||||
// Get object IRIs from type.
|
||||
object := ap.GetObjectIRIs(with)
|
||||
if len(object) == 0 {
|
||||
err := gtserror.Newf("unusable object property iri for %s", id)
|
||||
func (c *Converter) getASObjectStatus(ctx context.Context, with ap.WithObject) (*gtsmodel.Status, error) {
|
||||
objectIRI, err := ap.GetOneObjectIRI(with)
|
||||
if err != nil {
|
||||
return nil, gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// Check for status in database with provided object URI.
|
||||
status, err := c.state.DB.GetStatusByURI(ctx, object[0].String())
|
||||
// Check for status in database with the extracted object URI.
|
||||
status, err := c.state.DB.GetStatusByURI(ctx, objectIRI.String())
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error getting object status from database: %w", err)
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# buffers
|
||||
|
||||
this provides global instances of configurable (2^n size), shared byte buffer memory pools.
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// 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 buffers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"codeberg.org/gruf/go-mempool"
|
||||
)
|
||||
|
||||
var (
|
||||
// global map of memory pool instances.
|
||||
buffers = make(map[uint32]*MemoryPool, 8)
|
||||
|
||||
// global map lock.
|
||||
mutex sync.Mutex
|
||||
)
|
||||
|
||||
// MemoryPool is a memory pool of
|
||||
// byte buffers, of a predefined size.
|
||||
//
|
||||
// This will be a shared global instance that
|
||||
// any callers of buffers.Pool($sz) can access.
|
||||
type MemoryPool struct {
|
||||
p mempool.UnsafePool
|
||||
s uint32
|
||||
}
|
||||
|
||||
// Pool returns a shared MemoryPool instance
|
||||
// of requested size (rounded to nearest 2^n).
|
||||
//
|
||||
// NOTE: this acquires a lock on a global mutex
|
||||
// instance and should generally only be called
|
||||
// on package init to store a global reference.
|
||||
func Pool(sz uint32) *MemoryPool {
|
||||
n := math.Log2(float64(sz))
|
||||
n = math.Round(n)
|
||||
n = math.Exp2(max(8, n))
|
||||
sz = uint32(n)
|
||||
mutex.Lock()
|
||||
p := buffers[sz]
|
||||
if p == nil {
|
||||
p = new(MemoryPool)
|
||||
p.s = sz
|
||||
buffers[sz] = p
|
||||
}
|
||||
mutex.Unlock()
|
||||
return p
|
||||
}
|
||||
|
||||
// Get returns a byteutil.Buffer{} instance from pool.
|
||||
func (p *MemoryPool) Get() *byteutil.Buffer {
|
||||
buf := (*byteutil.Buffer)(p.p.Get())
|
||||
if buf == nil {
|
||||
buf = new(byteutil.Buffer)
|
||||
buf.B = make([]byte, p.s)
|
||||
} else {
|
||||
clear(buf.B[0:cap(buf.B)])
|
||||
}
|
||||
buf.B = buf.B[:0]
|
||||
return buf
|
||||
}
|
||||
|
||||
// Put replaces byteutil.Buffer{} instance in pool.
|
||||
func (p *MemoryPool) Put(buf *byteutil.Buffer) {
|
||||
if buf == nil {
|
||||
return
|
||||
}
|
||||
if cap(buf.B) < int(p.s) || cap(buf.B) > 2*int(p.s) {
|
||||
return // drop buffers outside size range
|
||||
}
|
||||
p.p.Put(unsafe.Pointer(buf))
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// 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 buffers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"codeberg.org/gruf/go-fastcopy"
|
||||
)
|
||||
|
||||
// global fastcopy instance.
|
||||
var copypool = fastcopy.Global()
|
||||
|
||||
func init() {
|
||||
// Get new pool.
|
||||
p := Pool(16384)
|
||||
|
||||
// Set fastcopy.CopyPool{} getter and setter functions, ensuring buffer is set to max size.
|
||||
copypool.Get = func() *[]byte { buf := p.Get(); buf.B = buf.Full(); return toBytes(buf) }
|
||||
copypool.Put = func(b *[]byte) { p.Put(toBuffer(b)) }
|
||||
}
|
||||
|
||||
// Copy: see fastcopy.CopyPool{}.Copy().
|
||||
func Copy(dst io.Writer, src io.Reader) (int64, error) {
|
||||
return copypool.Copy(dst, src)
|
||||
}
|
||||
|
||||
// CopyN: see fastcopy.CopyPool{}.CopyN().
|
||||
func CopyN(dst io.Writer, src io.Reader, n int64) (int64, error) {
|
||||
return copypool.CopyN(dst, src, n)
|
||||
}
|
||||
|
||||
// CopyBuffer: see fastcopy.CopyBuffer().
|
||||
func CopyBuffer(dst io.Writer, src io.Reader, buf *byteutil.Buffer) (int64, error) {
|
||||
return fastcopy.CopyBuffer(dst, src, toBytes(buf))
|
||||
}
|
||||
|
||||
// toBytes casts buffer to bytes with compile-time assertion,
|
||||
// this allows us to use our byteutil.Buffer{} pool for []bytes too.
|
||||
// this is only possible due to the confirmed memory semantics below.
|
||||
func toBytes(buf *byteutil.Buffer) (b *[]byte) {
|
||||
if unsafe.Sizeof(buf) != unsafe.Sizeof(b) ||
|
||||
unsafe.Offsetof(buf.B) != 0 {
|
||||
panic("compile time assertion")
|
||||
}
|
||||
b = (*[]byte)(unsafe.Pointer(buf))
|
||||
return
|
||||
}
|
||||
|
||||
// toBuffer casts bytes to buffer with compile-time assertion,
|
||||
// this allows us to use our byteutil.Buffer{} pool for []bytes too.
|
||||
// this is only possible due to the confirmed memory semantics below.
|
||||
func toBuffer(b *[]byte) (buf *byteutil.Buffer) {
|
||||
if unsafe.Sizeof(buf) != unsafe.Sizeof(b) ||
|
||||
unsafe.Offsetof(buf.B) != 0 {
|
||||
panic("compile time assertion")
|
||||
}
|
||||
buf = (*byteutil.Buffer)(unsafe.Pointer(b))
|
||||
return
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# log
|
||||
|
||||
An opinionated logging package to be initialized once on server initialization, and with a `context.Context` almost always required.
|
||||
An opinionated logging package expecting to be configured **once** on server initialization, and with a `context.Context` almost always required.
|
||||
|
||||
Formatting with the `logfmt` function is handled by `codeberg.org/gruf/go-kv/v2/format`, which is significantly faster and more useful in map / struct formatting than the standard library pkg.
|
||||
Formatting with the `logfmt` function is handled by `codeberg.org/gruf/go-kv/v2/format`, which is significantly faster and more useful in map / struct formatting than the standard library pkg.
|
||||
|
||||
Generated
Vendored
+15
-23
@@ -18,32 +18,24 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"codeberg.org/gruf/go-mempool"
|
||||
"codeberg.org/gruf/go-kv/v2/format"
|
||||
)
|
||||
|
||||
// memory pool of log buffers.
|
||||
var bufpool mempool.UnsafePool
|
||||
// LogFormatted provides log formatting
|
||||
// of wrapped interface via String() method.
|
||||
type LogFormatted struct{ any }
|
||||
|
||||
// getBuf acquires a buffer from memory pool.
|
||||
func getBuf() *byteutil.Buffer {
|
||||
buf := (*byteutil.Buffer)(bufpool.Get())
|
||||
if buf == nil {
|
||||
buf = new(byteutil.Buffer)
|
||||
buf.B = make([]byte, 0, 512)
|
||||
}
|
||||
return buf
|
||||
// String: implements fmt.Stringer{}.
|
||||
func (f LogFormatted) String() string {
|
||||
buf := bufpool.Get()
|
||||
buf.B = format.Global.Append(buf.B, f.any, argArgs)
|
||||
str := string(buf.B)
|
||||
bufpool.Put(buf)
|
||||
return str
|
||||
}
|
||||
|
||||
// putBuf places (after resetting) buffer back in
|
||||
// memory pool, dropping if capacity too large.
|
||||
func putBuf(buf *byteutil.Buffer) {
|
||||
if cap(buf.B) > int(^uint16(0)) {
|
||||
return // drop large buffer
|
||||
}
|
||||
buf.B = buf.B[:0]
|
||||
ptr := unsafe.Pointer(buf)
|
||||
bufpool.Put(ptr)
|
||||
// Formatted wraps value in LogFormatted{}
|
||||
// for nicer formatting via String() method.
|
||||
func Formatted(v any) LogFormatted {
|
||||
return LogFormatted{v}
|
||||
}
|
||||
+6
-2
@@ -24,6 +24,7 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"code.superseriousbusiness.org/gopkg/buffers"
|
||||
"code.superseriousbusiness.org/gopkg/log/format"
|
||||
"code.superseriousbusiness.org/gopkg/log/level"
|
||||
"code.superseriousbusiness.org/gopkg/xslices"
|
||||
@@ -45,6 +46,9 @@ var state = struct {
|
||||
},
|
||||
}
|
||||
|
||||
// memory pool of log buffers.
|
||||
var bufpool = buffers.Pool(1024)
|
||||
|
||||
// Level returns the
|
||||
// currently set log.
|
||||
func Level() LEVEL {
|
||||
@@ -391,8 +395,8 @@ func logf(ctx context.Context, lvl LEVEL, fields []kv.Field, msg string, args ..
|
||||
_ = runtime.Callers(3, pcs)
|
||||
|
||||
// Acquire buffer.
|
||||
buf := getBuf()
|
||||
defer putBuf(buf)
|
||||
buf := bufpool.Get()
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
if ctx != nil && len(state.hooks) > 0 {
|
||||
// Ensure fields have space for our context hooks.
|
||||
|
||||
+13
@@ -66,6 +66,19 @@ func Name(pc uintptr) string {
|
||||
// Get will return calling func information for given PC value,
|
||||
// caching func names by their PC values to reduce calls to Caller().
|
||||
func Get(pc uintptr) string {
|
||||
ptr := callerCache.Load()
|
||||
if ptr != nil {
|
||||
name, ok := (*ptr)[pc]
|
||||
if ok {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return get_slow(pc)
|
||||
}
|
||||
|
||||
// get_slow is the outlined version of the slow
|
||||
// parts of Get() in order to inline the fast parts.
|
||||
func get_slow(pc uintptr) string {
|
||||
var cache map[uintptr]string
|
||||
for {
|
||||
// Load caller cache map.
|
||||
|
||||
+74
-52
@@ -3,106 +3,125 @@ package fastcopy
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
_ "unsafe" // link to io.errInvalidWrite.
|
||||
)
|
||||
|
||||
var (
|
||||
// global pool instance.
|
||||
pool = CopyPool{size: 4096}
|
||||
// global pool.
|
||||
global CopyPool
|
||||
|
||||
// errInvalidWrite means that a write returned an impossible count.
|
||||
errInvalidWrite = errors.New("invalid write result")
|
||||
)
|
||||
|
||||
// CopyPool provides a memory pool of byte
|
||||
// buffers for io copies from readers to writers.
|
||||
type CopyPool struct {
|
||||
size int
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// See CopyPool.Buffer().
|
||||
func Buffer(sz int) int {
|
||||
return pool.Buffer(sz)
|
||||
}
|
||||
// Global returns global CopyPool{} instance.
|
||||
func Global() *CopyPool { return &global }
|
||||
|
||||
// See CopyPool.CopyN().
|
||||
func CopyN(dst io.Writer, src io.Reader, n int64) (int64, error) {
|
||||
return pool.CopyN(dst, src, n)
|
||||
return global.CopyN(dst, src, n)
|
||||
}
|
||||
|
||||
// See CopyPool.Copy().
|
||||
func Copy(dst io.Writer, src io.Reader) (int64, error) {
|
||||
return pool.Copy(dst, src)
|
||||
return global.Copy(dst, src)
|
||||
}
|
||||
|
||||
// Buffer sets the pool buffer size to allocate. Returns current size.
|
||||
// Note this is NOT atomically safe, please call BEFORE other calls to CopyPool.
|
||||
func (cp *CopyPool) Buffer(sz int) int {
|
||||
if sz > 0 {
|
||||
// update size
|
||||
cp.size = sz
|
||||
} else if cp.size < 1 {
|
||||
// default size
|
||||
return 4096
|
||||
// CopyPool provides a memory pool of byte
|
||||
// buffers for io copies from read -> writer.
|
||||
type CopyPool struct {
|
||||
Get func() *[]byte
|
||||
Put func(*[]byte)
|
||||
}
|
||||
|
||||
// get attempts to fetch buffer from
|
||||
// currently-set CopyPool{}.Get() func.
|
||||
func (cp *CopyPool) get() *[]byte {
|
||||
if cp.Get != nil {
|
||||
return cp.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// put attempts to replace buffer into
|
||||
// currently-set CopyPool{}.Put() func.
|
||||
func (cp *CopyPool) put(buf *[]byte) {
|
||||
if cp.Put != nil {
|
||||
cp.Put(buf)
|
||||
}
|
||||
return cp.size
|
||||
}
|
||||
|
||||
// CopyN performs the same logic as io.CopyN(), with the difference
|
||||
// being that the byte buffer is acquired from a memory pool.
|
||||
// being that the byte buffer is acquired from a memory pool if required.
|
||||
func (cp *CopyPool) CopyN(dst io.Writer, src io.Reader, n int64) (int64, error) {
|
||||
written, err := cp.Copy(dst, io.LimitReader(src, n))
|
||||
if written == n {
|
||||
return n, nil
|
||||
}
|
||||
if written < n && err == nil {
|
||||
// src stopped early; must have been EOF.
|
||||
// src stopped early;
|
||||
// must have been EOF.
|
||||
err = io.EOF
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
|
||||
// Copy performs the same logic as io.Copy(), with the difference
|
||||
// being that the byte buffer is acquired from a memory pool.
|
||||
// being that the byte buffer is acquired from a memory pool if required.
|
||||
func (cp *CopyPool) Copy(dst io.Writer, src io.Reader) (int64, error) {
|
||||
// Prefer using io.WriterTo to do the copy (avoids alloc + copy)
|
||||
|
||||
// Prefer using io.WriterTo to do
|
||||
// the copy (avoids alloc + copy).
|
||||
if wt, ok := src.(io.WriterTo); ok {
|
||||
return wt.WriteTo(dst)
|
||||
}
|
||||
|
||||
// Prefer using io.ReaderFrom to do the copy.
|
||||
// Prefer using io.ReaderFrom to copy.
|
||||
if rt, ok := dst.(io.ReaderFrom); ok {
|
||||
return rt.ReadFrom(src)
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
// Acquire buf.
|
||||
buf := cp.get()
|
||||
|
||||
if b, ok := cp.pool.Get().(*[]byte); ok {
|
||||
// Acquired buf from pool
|
||||
buf = *b
|
||||
} else {
|
||||
// Allocate new buffer of size
|
||||
buf = make([]byte, cp.Buffer(0))
|
||||
// Perform copy operation with buf.
|
||||
n, err := CopyBuffer(dst, src, buf)
|
||||
|
||||
// Release.
|
||||
cp.put(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// CopyBuffer performs the same logic as io.Copy(), without any optimized
|
||||
// checks for io.WriterTo{} or io.ReaderFrom{}. Data will definitively be
|
||||
// copied from source to destination using the given byte buffer. This allows
|
||||
// callers to wrap this with flexible buffer sourcing, knowing it will be used.
|
||||
func CopyBuffer(dst io.Writer, src io.Reader, buf *[]byte) (int64, error) {
|
||||
switch {
|
||||
case dst == nil:
|
||||
panic("nil dst")
|
||||
case src == nil:
|
||||
panic("nil src")
|
||||
case buf == nil || len(*buf) == 0:
|
||||
new := make([]byte, 4096)
|
||||
buf = &new
|
||||
default:
|
||||
// Ensure full buf available.
|
||||
(*buf) = (*buf)[0:cap(*buf)]
|
||||
}
|
||||
|
||||
// Defer release to pool
|
||||
defer cp.pool.Put(&buf)
|
||||
|
||||
var n int64
|
||||
for {
|
||||
// Perform next read into buf
|
||||
nr, err := src.Read(buf)
|
||||
// Perform read into buf.
|
||||
nr, err := src.Read(*buf)
|
||||
if nr > 0 {
|
||||
|
||||
// We error check AFTER checking
|
||||
// no. read bytes so incomplete
|
||||
// read still gets written up to nr.
|
||||
|
||||
// Perform next write from buf
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
// Perform next write from buf.
|
||||
nw, ew := dst.Write((*buf)[0:nr])
|
||||
|
||||
// Check for valid write
|
||||
// Check for valid write.
|
||||
if nw < 0 || nr < nw {
|
||||
if ew == nil {
|
||||
ew = errInvalidWrite
|
||||
@@ -110,21 +129,24 @@ func (cp *CopyPool) Copy(dst io.Writer, src io.Reader) (int64, error) {
|
||||
return n, ew
|
||||
}
|
||||
|
||||
// Incr total count
|
||||
// Incr total.
|
||||
n += int64(nw)
|
||||
|
||||
// Check write error
|
||||
// Check for
|
||||
// write error
|
||||
if ew != nil {
|
||||
return n, ew
|
||||
}
|
||||
|
||||
// Check unequal read/writes
|
||||
// Check unequal
|
||||
// read / writes.
|
||||
if nr != nw {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
|
||||
// Return on err
|
||||
// Return on
|
||||
// any set error.
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil // expected
|
||||
|
||||
Vendored
+5
-4
@@ -213,8 +213,9 @@ code.superseriousbusiness.org/go-jpeg-image-structure/v2
|
||||
# code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0
|
||||
## explicit; go 1.12
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2
|
||||
# code.superseriousbusiness.org/gopkg v0.0.0-20260520114201-7a01c90db818
|
||||
## explicit; go 1.24.6
|
||||
# code.superseriousbusiness.org/gopkg v0.0.0-20260623123713-4ead213a2d22
|
||||
## explicit; go 1.25.0
|
||||
code.superseriousbusiness.org/gopkg/buffers
|
||||
code.superseriousbusiness.org/gopkg/log
|
||||
code.superseriousbusiness.org/gopkg/log/format
|
||||
code.superseriousbusiness.org/gopkg/log/level
|
||||
@@ -242,7 +243,7 @@ codeberg.org/gruf/go-byteutil
|
||||
codeberg.org/gruf/go-cache/v3
|
||||
codeberg.org/gruf/go-cache/v3/simple
|
||||
codeberg.org/gruf/go-cache/v3/ttl
|
||||
# codeberg.org/gruf/go-caller v0.0.0-20250806133437-db8d0b1f71cf
|
||||
# codeberg.org/gruf/go-caller v0.0.0-20260615202027-bc5a6ff3cb31
|
||||
## explicit; go 1.24.5
|
||||
codeberg.org/gruf/go-caller
|
||||
# codeberg.org/gruf/go-debug v1.3.0
|
||||
@@ -251,7 +252,7 @@ codeberg.org/gruf/go-debug
|
||||
# codeberg.org/gruf/go-errors/v2 v2.3.4
|
||||
## explicit; go 1.24.5
|
||||
codeberg.org/gruf/go-errors/v2
|
||||
# codeberg.org/gruf/go-fastcopy v1.1.3
|
||||
# codeberg.org/gruf/go-fastcopy v1.2.1
|
||||
## explicit; go 1.17
|
||||
codeberg.org/gruf/go-fastcopy
|
||||
# codeberg.org/gruf/go-fastpath/v2 v2.0.1
|
||||
|
||||
Reference in New Issue
Block a user