summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2024-06-10 18:42:41 +0000
committerLibravatar GitHub <noreply@github.com>2024-06-10 19:42:41 +0100
commitfd6637df4aeed721442bff6dfbce9bdd1b5ac7b8 (patch)
tree8d4ddffdd8742b3cd7aa0be5e26ea235e76b127d
parent[chore] Roll back use of `(created)` pseudo-header pending #2991 (#2992) (diff)
downloadgotosocial-fd6637df4aeed721442bff6dfbce9bdd1b5ac7b8.tar.xz
[bugfix] boost and account recursion (#2982)
* fix possible infinite recursion if moved accounts are self-referential * adds a defensive check for a boost being a boost of a boost wrapper * add checks on input for a boost of a boost * remove unnecessary check * add protections on account move to prevent move recursion loops * separate status conversion without boost logic into separate function to remove risk of recursion * move boost check to boost function itself * formatting * use error 422 instead of 500 * use gtserror not standard errors package for error creation
-rw-r--r--internal/db/account.go3
-rw-r--r--internal/db/bundb/account.go21
-rw-r--r--internal/federation/dereferencing/announce.go27
-rw-r--r--internal/processing/account/move.go149
-rw-r--r--internal/processing/account/move_test.go2
-rw-r--r--internal/processing/status/boost.go9
-rw-r--r--internal/typeutils/internaltofrontend.go160
7 files changed, 244 insertions, 127 deletions
diff --git a/internal/db/account.go b/internal/db/account.go
index dec36d2ac..4f02a4d29 100644
--- a/internal/db/account.go
+++ b/internal/db/account.go
@@ -57,6 +57,9 @@ type Account interface {
// GetAccountByFollowersURI returns one account with the given followers_uri, or an error if something goes wrong.
GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, error)
+ // GetAccountByMovedToURI returns any accounts with given moved_to_uri set.
+ GetAccountsByMovedToURI(ctx context.Context, uri string) ([]*gtsmodel.Account, error)
+
// GetAccounts returns accounts
// with the given parameters.
GetAccounts(
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
index 4e969e0ef..eb5385c70 100644
--- a/internal/db/bundb/account.go
+++ b/internal/db/bundb/account.go
@@ -252,6 +252,27 @@ func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gts
return a.GetAccountByUsernameDomain(ctx, username, domain)
}
+func (a *accountDB) GetAccountsByMovedToURI(ctx context.Context, uri string) ([]*gtsmodel.Account, error) {
+ var accountIDs []string
+
+ // Find all account IDs with
+ // given moved_to_uri column.
+ if err := a.db.NewSelect().
+ Table("accounts").
+ Column("id").
+ Where("? = ?", bun.Ident("moved_to_uri"), uri).
+ Scan(ctx, &accountIDs); err != nil {
+ return nil, err
+ }
+
+ if len(accountIDs) == 0 {
+ return nil, nil
+ }
+
+ // Return account models for all found IDs.
+ return a.GetAccountsByIDs(ctx, accountIDs)
+}
+
// GetAccounts selects accounts using the given parameters.
// Unlike with other functions, the paging for GetAccounts
// is done not by ID, but by a concatenation of `[domain]/@[username]`,
diff --git a/internal/federation/dereferencing/announce.go b/internal/federation/dereferencing/announce.go
index 02b1d5e5c..6516bdced 100644
--- a/internal/federation/dereferencing/announce.go
+++ b/internal/federation/dereferencing/announce.go
@@ -22,7 +22,6 @@ import (
"errors"
"net/url"
- "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@@ -56,25 +55,17 @@ func (d *Dereferencer) EnrichAnnounce(
)
}
- // Fetch/deref status being boosted.
- var target *gtsmodel.Status
-
- if targetURIObj.Host == config.GetHost() {
- // This is a local status, fetch from the database
- target, err = d.state.DB.GetStatusByURI(ctx, targetURI)
- } else {
- // This is a remote status, we need to dereference it.
- //
- // d.GetStatusByURI will handle domain block checking for us,
- // so we don't try to deref an announce target on a blocked host.
- target, _, err = d.GetStatusByURI(ctx, requestUser, targetURIObj)
+ // Fetch and dereference status being boosted, noting that
+ // d.GetStatusByURI handles domain blocks and local statuses.
+ target, _, err := d.GetStatusByURI(ctx, requestUser, targetURIObj)
+ if err != nil {
+ return nil, gtserror.Newf("error fetching boost target %s: %w", targetURI, err)
}
- if err != nil {
- return nil, gtserror.Newf(
- "error getting boost target status %s: %w",
- targetURI, err,
- )
+ if target.BoostOfID != "" {
+ // Ensure that the target is not a boost (should not be possible).
+ err := gtserror.Newf("target status %s is a boost", targetURI)
+ return nil, err
}
// Generate an ID for the boost wrapper status.
diff --git a/internal/processing/account/move.go b/internal/processing/account/move.go
index 21e4f887b..44f8da268 100644
--- a/internal/processing/account/move.go
+++ b/internal/processing/account/move.go
@@ -27,7 +27,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
@@ -44,8 +46,8 @@ func (p *Processor) MoveSelf(
) gtserror.WithCode {
// Ensure valid MovedToURI.
if form.MovedToURI == "" {
- err := errors.New("no moved_to_uri provided in account Move request")
- return gtserror.NewErrorBadRequest(err, err.Error())
+ const text = "no moved_to_uri provided in Move request"
+ return gtserror.NewErrorBadRequest(errors.New(text), text)
}
targetAcctURIStr := form.MovedToURI
@@ -56,29 +58,30 @@ func (p *Processor) MoveSelf(
}
if targetAcctURI.Scheme != "https" && targetAcctURI.Scheme != "http" {
- err := errors.New("invalid moved_to_uri provided in account Move request: uri scheme must be http or https")
- return gtserror.NewErrorBadRequest(err, err.Error())
+ const text = "invalid move_to_uri in Move request: scheme must be http(s)"
+ return gtserror.NewErrorBadRequest(errors.New(text), text)
}
- // Self account Move requires password to ensure it's for real.
+ // Self account Move requires
+ // password to ensure it's for real.
if form.Password == "" {
- err := errors.New("no password provided in account Move request")
- return gtserror.NewErrorBadRequest(err, err.Error())
+ const text = "no password provided in Move request"
+ return gtserror.NewErrorBadRequest(errors.New(text), text)
}
if err := bcrypt.CompareHashAndPassword(
[]byte(authed.User.EncryptedPassword),
[]byte(form.Password),
); err != nil {
- err := errors.New("invalid password provided in account Move request")
- return gtserror.NewErrorBadRequest(err, err.Error())
+ const text = "invalid password provided in Move request"
+ return gtserror.NewErrorBadRequest(errors.New(text), text)
}
// We can't/won't validate Move activities
// to domains we have blocked, so check this.
targetDomainBlocked, err := p.state.DB.IsDomainBlocked(ctx, targetAcctURI.Host)
if err != nil {
- err := fmt.Errorf(
+ err := gtserror.Newf(
"db error checking if target domain %s blocked: %w",
targetAcctURI.Host, err,
)
@@ -86,12 +89,12 @@ func (p *Processor) MoveSelf(
}
if targetDomainBlocked {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"domain of %s is blocked from this instance; "+
"you will not be able to Move to that account",
targetAcctURIStr,
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
var (
@@ -123,22 +126,24 @@ func (p *Processor) MoveSelf(
targetAcctURI,
)
if err != nil {
- err := fmt.Errorf("error dereferencing moved_to_uri account: %w", err)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ const text = "error dereferencing moved_to_uri"
+ err := gtserror.Newf("error dereferencing move_to_uri: %w", err)
+ return gtserror.NewErrorUnprocessableEntity(err, text)
}
if !targetAcct.SuspendedAt.IsZero() {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"target account %s is suspended from this instance; "+
"you will not be able to Move to that account",
targetAcct.URI,
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
- if targetAcct.IsRemote() {
- // Force refresh Move target account
- // to ensure we have up-to-date version.
+ if targetAcctable == nil {
+ // Target account was not dereferenced, now
+ // force refresh Move target account to ensure we
+ // have most up-to-date version (non remote = no-op).
targetAcct, _, err = p.federator.RefreshAccount(ctx,
originAcct.Username,
targetAcct,
@@ -146,11 +151,9 @@ func (p *Processor) MoveSelf(
dereferencing.Freshest,
)
if err != nil {
- err := fmt.Errorf(
- "error refreshing target account %s: %w",
- targetAcctURIStr, err,
- )
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ const text = "error dereferencing moved_to_uri"
+ err := gtserror.Newf("error dereferencing move_to_uri: %w", err)
+ return gtserror.NewErrorUnprocessableEntity(err, text)
}
}
@@ -158,33 +161,41 @@ func (p *Processor) MoveSelf(
// this move reattempt is to the same account.
if originAcct.IsMoving() &&
originAcct.MovedToURI != targetAcct.URI {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"your account is already Moving or has Moved to %s; you cannot also Move to %s",
originAcct.MovedToURI, targetAcct.URI,
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
// Target account MUST be aliased to this
// account for this to be a valid Move.
if !slices.Contains(targetAcct.AlsoKnownAsURIs, originAcct.URI) {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"target account %s is not aliased to this account via alsoKnownAs; "+
"if you just changed it, please wait a few minutes and try the Move again",
targetAcct.URI,
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
// Target account cannot itself have
// already Moved somewhere else.
if targetAcct.MovedToURI != "" {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"target account %s has already Moved somewhere else (%s); "+
"you will not be able to Move to that account",
targetAcct.URI, targetAcct.MovedToURI,
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
+ }
+
+ // Check this isn't a recursive loop of moves.
+ if errWithCode := p.checkMoveRecursion(ctx,
+ originAcct,
+ targetAcct,
+ ); errWithCode != nil {
+ return errWithCode
}
// If a Move has been *attempted* within last 5m,
@@ -194,7 +205,7 @@ func (p *Processor) MoveSelf(
ctx, originAcct.URI, targetAcct.URI,
)
if err != nil {
- err := fmt.Errorf(
+ err := gtserror.Newf(
"error checking latest Move attempt involving origin %s and target %s: %w",
originAcct.URI, targetAcct.URI, err,
)
@@ -203,12 +214,12 @@ func (p *Processor) MoveSelf(
if !latestMoveAttempt.IsZero() &&
time.Since(latestMoveAttempt) < 5*time.Minute {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"your account or target account have been involved in a Move attempt within "+
"the last 5 minutes, will not process Move; please try again after %s",
latestMoveAttempt.Add(5*time.Minute),
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
// If a Move has *succeeded* within the last week
@@ -218,7 +229,7 @@ func (p *Processor) MoveSelf(
ctx, originAcct.URI, targetAcct.URI,
)
if err != nil {
- err := fmt.Errorf(
+ err := gtserror.Newf(
"error checking latest Move success involving origin %s and target %s: %w",
originAcct.URI, targetAcct.URI, err,
)
@@ -227,12 +238,12 @@ func (p *Processor) MoveSelf(
if !latestMoveSuccess.IsZero() &&
time.Since(latestMoveSuccess) < 168*time.Hour {
- err := fmt.Errorf(
+ text := fmt.Sprintf(
"your account or target account have been involved in a successful Move within "+
"the last 7 days, will not process Move; please try again after %s",
latestMoveSuccess.Add(168*time.Hour),
)
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
// See if we have a Move stored already
@@ -246,21 +257,21 @@ func (p *Processor) MoveSelf(
move = originAcct.Move
if move == nil {
// This shouldn't happen...
- err := fmt.Errorf("nil move for id %s", originAcct.MoveID)
+ err := gtserror.Newf("error fetching move %s (was nil)", originAcct.MovedToURI)
return gtserror.NewErrorInternalError(err)
}
if move.OriginURI != originAcct.URI ||
move.TargetURI != targetAcct.URI {
// This is also weird...
- err := errors.New("a Move is already stored for your account but contains invalid fields")
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ const text = "existing stored Move contains invalid fields"
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
if originAcct.MovedToURI != move.TargetURI {
// Huh... I'll be damned.
- err := errors.New("stored Move target URI does not equal your moved_to_uri value")
- return gtserror.NewErrorUnprocessableEntity(err, err.Error())
+ const text = "existing stored Move target URI != moved_to_uri"
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
}
} else {
// Move not stored yet, create it.
@@ -295,7 +306,7 @@ func (p *Processor) MoveSelf(
URI: moveURIStr,
}
if err := p.state.DB.PutMove(ctx, move); err != nil {
- err := fmt.Errorf("db error storing move %s: %w", moveURIStr, err)
+ err := gtserror.Newf("db error storing move %s: %w", moveURIStr, err)
return gtserror.NewErrorInternalError(err)
}
@@ -311,7 +322,7 @@ func (p *Processor) MoveSelf(
"move_id",
"moved_to_uri",
); err != nil {
- err := fmt.Errorf("db error updating account: %w", err)
+ err := gtserror.Newf("db error updating account: %w", err)
return gtserror.NewErrorInternalError(err)
}
}
@@ -327,3 +338,55 @@ func (p *Processor) MoveSelf(
return nil
}
+
+// checkMoveRecursion checks that a move from origin to target would
+// not cause a loop of account moved_from_uris pointing in a loop.
+func (p *Processor) checkMoveRecursion(
+ ctx context.Context,
+ origin *gtsmodel.Account,
+ target *gtsmodel.Account,
+) gtserror.WithCode {
+ // We only ever need barebones models.
+ ctx = gtscontext.SetBarebones(ctx)
+
+ // Stack based account move following loop.
+ stack := []*gtsmodel.Account{origin}
+ checked := make(map[string]struct{})
+ for len(stack) > 0 {
+
+ // Pop account from stack.
+ next := stack[len(stack)-1]
+ stack = stack[:len(stack)-1]
+
+ // Add account URI to checked.
+ checked[next.URI] = struct{}{}
+
+ // Fetch any accounts that list 'next' as their 'moved_to_uri'.
+ movedFrom, err := p.state.DB.GetAccountsByMovedToURI(ctx, next.URI)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("error fetching accounts by moved_to_uri: %w", err)
+ return gtserror.NewErrorInternalError(err)
+ }
+
+ for _, account := range movedFrom {
+ if _, ok := checked[account.URI]; ok {
+ // Account with URI has
+ // already been checked.
+ continue
+ }
+
+ // Check movedFrom accounts to ensure
+ // none of them actually come from target,
+ // which would cause a recursion loop.
+ if account.URI == target.URI {
+ text := fmt.Sprintf("move %s -> %s would cause move recursion due to %s", origin.URI, target.URI, account.URI)
+ return gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
+ }
+
+ // Append 'from' account to stack.
+ stack = append(stack, account)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/processing/account/move_test.go b/internal/processing/account/move_test.go
index c1a931252..9d06829ca 100644
--- a/internal/processing/account/move_test.go
+++ b/internal/processing/account/move_test.go
@@ -161,7 +161,7 @@ func (suite *MoveTestSuite) TestMoveAccountBadPassword() {
MovedToURI: targetAcct.URI,
},
)
- suite.EqualError(err, "invalid password provided in account Move request")
+ suite.EqualError(err, "invalid password provided in Move request")
}
func TestMoveTestSuite(t *testing.T) {
diff --git a/internal/processing/status/boost.go b/internal/processing/status/boost.go
index 1c1da4ca7..1b410bb0a 100644
--- a/internal/processing/status/boost.go
+++ b/internal/processing/status/boost.go
@@ -49,6 +49,7 @@ func (p *Processor) BoostCreate(
return nil, errWithCode
}
+ // Unwrap target in case it is a boost.
target, errWithCode = p.c.UnwrapIfBoost(
ctx,
requester,
@@ -58,7 +59,13 @@ func (p *Processor) BoostCreate(
return nil, errWithCode
}
- // Ensure valid boost target.
+ // Check is viable target.
+ if target.BoostOfID != "" {
+ err := gtserror.Newf("target status %s is boost wrapper", target.URI)
+ return nil, gtserror.NewErrorUnprocessableEntity(err)
+ }
+
+ // Ensure valid boost target for requester.
boostable, err := p.filter.StatusBoostable(ctx,
requester,
target,
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 80f083ef1..a8f9b7f8f 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -147,6 +147,24 @@ func (c *Converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode
// if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields.
// In other words, this is the public record that the server has of an account.
func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
+ account, err := c.accountToAPIAccountPublic(ctx, a)
+ if err != nil {
+ return nil, err
+ }
+
+ if a.MovedTo != nil {
+ account.Moved, err = c.accountToAPIAccountPublic(ctx, a.MovedTo)
+ if err != nil {
+ log.Errorf(ctx, "error converting account movedTo: %v", err)
+ }
+ }
+
+ return account, nil
+}
+
+// accountToAPIAccountPublic provides all the logic for AccountToAPIAccount, MINUS fetching moved account, to prevent possible recursion.
+func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
+
// Populate account struct fields.
err := c.state.DB.PopulateAccount(ctx, a)
@@ -154,7 +172,7 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
case err == nil:
// No problem.
- case err != nil && a.Stats != nil:
+ case a.Stats != nil:
// We have stats so that's
// *maybe* OK, try to continue.
log.Errorf(ctx, "error(s) populating account, will continue: %s", err)
@@ -266,37 +284,10 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
acct = a.Username // omit domain
}
- // Populate moved.
- var moved *apimodel.Account
- if a.MovedTo != nil {
- moved, err = c.AccountToAPIAccountPublic(ctx, a.MovedTo)
- if err != nil {
- log.Errorf(ctx, "error converting account movedTo: %v", err)
- }
- }
-
- // Bool ptrs should be set, but warn
- // and use a default if they're not.
- var boolPtrDef = func(
- pName string,
- p *bool,
- d bool,
- ) bool {
- if p != nil {
- return *p
- }
-
- log.Warnf(ctx,
- "%s ptr was nil, using default %t",
- pName, d,
- )
- return d
- }
-
var (
- locked = boolPtrDef("locked", a.Locked, true)
- discoverable = boolPtrDef("discoverable", a.Discoverable, false)
- bot = boolPtrDef("bot", a.Bot, false)
+ locked = util.PtrValueOr(a.Locked, true)
+ discoverable = util.PtrValueOr(a.Discoverable, false)
+ bot = util.PtrValueOr(a.Bot, false)
)
// Remaining properties are simple and
@@ -329,7 +320,6 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
EnableRSS: enableRSS,
HideCollections: hideCollections,
Role: role,
- Moved: moved,
}
// Bodge default avatar + header in,
@@ -350,7 +340,8 @@ func (c *Converter) fieldsToAPIFields(f []*gtsmodel.Field) []apimodel.Field {
}
if !field.VerifiedAt.IsZero() {
- mField.VerifiedAt = func() *string { s := util.FormatISO8601(field.VerifiedAt); return &s }()
+ verified := util.FormatISO8601(field.VerifiedAt)
+ mField.VerifiedAt = util.Ptr(verified)
}
fields[i] = mField
@@ -755,6 +746,10 @@ func (c *Converter) StatusToAPIStatus(
var aside string
aside, apiStatus.MediaAttachments = placeholdUnknownAttachments(apiStatus.MediaAttachments)
apiStatus.Content += aside
+ if apiStatus.Reblog != nil {
+ aside, apiStatus.Reblog.MediaAttachments = placeholdUnknownAttachments(apiStatus.Reblog.MediaAttachments)
+ apiStatus.Reblog.Content += aside
+ }
return apiStatus, nil
}
@@ -1051,27 +1046,81 @@ func (c *Converter) StatusToAPIStatusSource(ctx context.Context, s *gtsmodel.Sta
// Requesting account can be nil.
func (c *Converter) statusToFrontend(
ctx context.Context,
+ status *gtsmodel.Status,
+ requestingAccount *gtsmodel.Account,
+ filterContext statusfilter.FilterContext,
+ filters []*gtsmodel.Filter,
+ mutes *usermute.CompiledUserMuteList,
+) (
+ *apimodel.Status,
+ error,
+) {
+ apiStatus, err := c.baseStatusToFrontend(ctx,
+ status,
+ requestingAccount,
+ filterContext,
+ filters,
+ mutes,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ if status.BoostOf != nil {
+ reblog, err := c.baseStatusToFrontend(ctx,
+ status.BoostOf,
+ requestingAccount,
+ filterContext,
+ filters,
+ mutes,
+ )
+ if errors.Is(err, statusfilter.ErrHideStatus) {
+ // If we'd hide the original status, hide the boost.
+ return nil, err
+ } else if err != nil {
+ return nil, gtserror.Newf("error converting boosted status: %w", err)
+ }
+
+ // Set boosted status and set interactions from original.
+ apiStatus.Reblog = &apimodel.StatusReblogged{reblog}
+ apiStatus.Favourited = apiStatus.Reblog.Favourited
+ apiStatus.Bookmarked = apiStatus.Reblog.Bookmarked
+ apiStatus.Muted = apiStatus.Reblog.Muted
+ apiStatus.Reblogged = apiStatus.Reblog.Reblogged
+ apiStatus.Pinned = apiStatus.Reblog.Pinned
+ }
+
+ return apiStatus, nil
+}
+
+// baseStatusToFrontend performs the main logic
+// of statusToFrontend() without handling of boost
+// logic, to prevent *possible* recursion issues.
+func (c *Converter) baseStatusToFrontend(
+ ctx context.Context,
s *gtsmodel.Status,
requestingAccount *gtsmodel.Account,
filterContext statusfilter.FilterContext,
filters []*gtsmodel.Filter,
mutes *usermute.CompiledUserMuteList,
-) (*apimodel.Status, error) {
+) (
+ *apimodel.Status,
+ error,
+) {
// Try to populate status struct pointer fields.
// We can continue in many cases of partial failure,
// but there are some fields we actually need.
if err := c.state.DB.PopulateStatus(ctx, s); err != nil {
- if s.Account == nil {
- err = gtserror.Newf("error(s) populating status, cannot continue (status.Account not set): %w", err)
- return nil, err
- }
+ switch {
+ case s.Account == nil:
+ return nil, gtserror.Newf("error(s) populating status, required account not set: %w", err)
- if s.BoostOfID != "" && s.BoostOf == nil {
- err = gtserror.Newf("error(s) populating status, cannot continue (status.BoostOfID set, but status.Boost not set): %w", err)
- return nil, err
- }
+ case s.BoostOfID != "" && s.BoostOf == nil:
+ return nil, gtserror.Newf("error(s) populating status, required boost not set: %w", err)
- log.Errorf(ctx, "error(s) populating status, will continue: %v", err)
+ default:
+ log.Errorf(ctx, "error(s) populating status, will continue: %v", err)
+ }
}
apiAuthorAccount, err := c.AccountToAPIAccountPublic(ctx, s.Account)
@@ -1153,19 +1202,6 @@ func (c *Converter) statusToFrontend(
apiStatus.Language = util.Ptr(s.Language)
}
- if s.BoostOf != nil {
- reblog, err := c.StatusToAPIStatus(ctx, s.BoostOf, requestingAccount, filterContext, filters, mutes)
- if errors.Is(err, statusfilter.ErrHideStatus) {
- // If we'd hide the original status, hide the boost.
- return nil, err
- }
- if err != nil {
- return nil, gtserror.Newf("error converting boosted status: %w", err)
- }
-
- apiStatus.Reblog = &apimodel.StatusReblogged{reblog}
- }
-
if app := s.CreatedWithApplication; app != nil {
apiStatus.Application, err = c.AppToAPIAppPublic(ctx, app)
if err != nil {
@@ -1190,14 +1226,9 @@ func (c *Converter) statusToFrontend(
// Status interactions.
//
- // Take from boosted status if set,
- // otherwise take from status itself.
- if apiStatus.Reblog != nil {
- apiStatus.Favourited = apiStatus.Reblog.Favourited
- apiStatus.Bookmarked = apiStatus.Reblog.Bookmarked
- apiStatus.Muted = apiStatus.Reblog.Muted
- apiStatus.Reblogged = apiStatus.Reblog.Reblogged
- apiStatus.Pinned = apiStatus.Reblog.Pinned
+ if s.BoostOf != nil { //nolint
+ // populated *outside* this
+ // function to prevent recursion.
} else {
interacts, err := c.interactionsWithStatusForAccount(ctx, s, requestingAccount)
if err != nil {
@@ -1230,6 +1261,7 @@ func (c *Converter) statusToFrontend(
}
return nil, fmt.Errorf("error applying filters: %w", err)
}
+
apiStatus.Filtered = filterResults
return apiStatus, nil