mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 19:52:24 -05:00 
			
		
		
		
	[bugfix] unwrap boosts when checking in-reply-to status (#2702)
* add stronger checks on status being replied to * update error code test is expecting
This commit is contained in:
		
					parent
					
						
							
								c2a691fd83
							
						
					
				
			
			
				commit
				
					
						fcecd0c952
					
				
			
		
					 2 changed files with 46 additions and 41 deletions
				
			
		|  | @ -284,13 +284,13 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { | ||||||
| 
 | 
 | ||||||
| 	// check response | 	// check response | ||||||
| 
 | 
 | ||||||
| 	suite.EqualValues(http.StatusBadRequest, recorder.Code) | 	suite.EqualValues(http.StatusNotFound, recorder.Code) | ||||||
| 
 | 
 | ||||||
| 	result := recorder.Result() | 	result := recorder.Result() | ||||||
| 	defer result.Body.Close() | 	defer result.Body.Close() | ||||||
| 	b, err := ioutil.ReadAll(result.Body) | 	b, err := ioutil.ReadAll(result.Body) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(`{"error":"Bad Request: cannot reply to status that does not exist"}`, string(b)) | 	suite.Equal(`{"error":"Not Found: target status not found"}`, string(b)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Post a reply to the status of a local user that allows replies. | // Post a reply to the status of a local user that allows replies. | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/messages" | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
|  | @ -40,12 +41,20 @@ import ( | ||||||
| // Create processes the given form to create a new status, returning the api model representation of that status if it's OK. | // Create processes the given form to create a new status, returning the api model representation of that status if it's OK. | ||||||
| // | // | ||||||
| // Precondition: the form's fields should have already been validated and normalized by the caller. | // Precondition: the form's fields should have already been validated and normalized by the caller. | ||||||
| func (p *Processor) Create(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) { | func (p *Processor) Create( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	requester *gtsmodel.Account, | ||||||
|  | 	application *gtsmodel.Application, | ||||||
|  | 	form *apimodel.AdvancedStatusCreateForm, | ||||||
|  | ) ( | ||||||
|  | 	*apimodel.Status, | ||||||
|  | 	gtserror.WithCode, | ||||||
|  | ) { | ||||||
| 	// Generate new ID for status. | 	// Generate new ID for status. | ||||||
| 	statusID := id.NewULID() | 	statusID := id.NewULID() | ||||||
| 
 | 
 | ||||||
| 	// Generate necessary URIs for username, to build status URIs. | 	// Generate necessary URIs for username, to build status URIs. | ||||||
| 	accountURIs := uris.GenerateURIsForAccount(requestingAccount.Username) | 	accountURIs := uris.GenerateURIsForAccount(requester.Username) | ||||||
| 
 | 
 | ||||||
| 	// Get current time. | 	// Get current time. | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
|  | @ -57,9 +66,9 @@ func (p *Processor) Create(ctx context.Context, requestingAccount *gtsmodel.Acco | ||||||
| 		CreatedAt:                now, | 		CreatedAt:                now, | ||||||
| 		UpdatedAt:                now, | 		UpdatedAt:                now, | ||||||
| 		Local:                    util.Ptr(true), | 		Local:                    util.Ptr(true), | ||||||
| 		Account:                  requestingAccount, | 		Account:                  requester, | ||||||
| 		AccountID:                requestingAccount.ID, | 		AccountID:                requester.ID, | ||||||
| 		AccountURI:               requestingAccount.URI, | 		AccountURI:               requester.URI, | ||||||
| 		ActivityStreamsType:      ap.ObjectNote, | 		ActivityStreamsType:      ap.ObjectNote, | ||||||
| 		Sensitive:                &form.Sensitive, | 		Sensitive:                &form.Sensitive, | ||||||
| 		CreatedWithApplicationID: application.ID, | 		CreatedWithApplicationID: application.ID, | ||||||
|  | @ -86,7 +95,12 @@ func (p *Processor) Create(ctx context.Context, requestingAccount *gtsmodel.Acco | ||||||
| 		status.PollID = status.Poll.ID | 		status.PollID = status.Poll.ID | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if errWithCode := p.processReplyToID(ctx, form, requestingAccount.ID, status); errWithCode != nil { | 	// Check + attach in-reply-to status. | ||||||
|  | 	if errWithCode := p.processInReplyTo(ctx, | ||||||
|  | 		requester, | ||||||
|  | 		status, | ||||||
|  | 		form.InReplyToID, | ||||||
|  | 	); errWithCode != nil { | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -94,15 +108,15 @@ func (p *Processor) Create(ctx context.Context, requestingAccount *gtsmodel.Acco | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if errWithCode := p.processMediaIDs(ctx, form, requestingAccount.ID, status); errWithCode != nil { | 	if errWithCode := p.processMediaIDs(ctx, form, requester.ID, status); errWithCode != nil { | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := processVisibility(form, requestingAccount.Privacy, status); err != nil { | 	if err := processVisibility(form, requester.Privacy, status); err != nil { | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := processLanguage(form, requestingAccount.Language, status); err != nil { | 	if err := processLanguage(form, requester.Language, status); err != nil { | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -128,58 +142,49 @@ func (p *Processor) Create(ctx context.Context, requestingAccount *gtsmodel.Acco | ||||||
| 		APObjectType:   ap.ObjectNote, | 		APObjectType:   ap.ObjectNote, | ||||||
| 		APActivityType: ap.ActivityCreate, | 		APActivityType: ap.ActivityCreate, | ||||||
| 		GTSModel:       status, | 		GTSModel:       status, | ||||||
| 		OriginAccount:  requestingAccount, | 		OriginAccount:  requester, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if status.Poll != nil { | 	if status.Poll != nil { | ||||||
| 		// Now that the status is inserted, and side effects queued, | 		// Now that the status is inserted, and side effects queued, | ||||||
| 		// attempt to schedule an expiry handler for the status poll. | 		// attempt to schedule an expiry handler for the status poll. | ||||||
| 		if err := p.polls.ScheduleExpiry(ctx, status.Poll); err != nil { | 		if err := p.polls.ScheduleExpiry(ctx, status.Poll); err != nil { | ||||||
| 			err := gtserror.Newf("error scheduling poll expiry: %w", err) | 			log.Errorf(ctx, "error scheduling poll expiry: %v", err) | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return p.c.GetAPIStatus(ctx, requestingAccount, status) | 	return p.c.GetAPIStatus(ctx, requester, status) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *Processor) processReplyToID(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) gtserror.WithCode { | func (p *Processor) processInReplyTo(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status, inReplyToID string) gtserror.WithCode { | ||||||
| 	if form.InReplyToID == "" { | 	if inReplyToID == "" { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// If this status is a reply to another status, we need to do a bit of work to establish whether or not this status can be posted: | 	// Fetch target in-reply-to status (checking visibility). | ||||||
| 	// | 	inReplyTo, errWithCode := p.c.GetVisibleTargetStatus(ctx, | ||||||
| 	// 1. Does the replied status exist in the database? | 		requester, | ||||||
| 	// 2. Is the replied status marked as replyable? | 		inReplyToID, | ||||||
| 	// 3. Does a block exist between either the current account or the account that posted the status it's replying to? | 		nil, | ||||||
| 	// | 	) | ||||||
| 	// If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing. | 	if errWithCode != nil { | ||||||
| 
 | 		return errWithCode | ||||||
| 	inReplyTo, err := p.state.DB.GetStatusByID(ctx, form.InReplyToID) |  | ||||||
| 	if err != nil && !errors.Is(err, db.ErrNoEntries) { |  | ||||||
| 		err := gtserror.Newf("error fetching status %s from db: %w", form.InReplyToID, err) |  | ||||||
| 		return gtserror.NewErrorInternalError(err) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if inReplyTo == nil { | 	// If this is a boost, unwrap it to get source status. | ||||||
| 		const text = "cannot reply to status that does not exist" | 	inReplyTo, errWithCode = p.c.UnwrapIfBoost(ctx, | ||||||
| 		return gtserror.NewErrorBadRequest(errors.New(text), text) | 		requester, | ||||||
|  | 		inReplyTo, | ||||||
|  | 	) | ||||||
|  | 	if errWithCode != nil { | ||||||
|  | 		return errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !*inReplyTo.Replyable { | 	if !*inReplyTo.Replyable { | ||||||
| 		text := fmt.Sprintf("status %s is marked as not replyable", form.InReplyToID) | 		const text = "in-reply-to status marked as not replyable" | ||||||
| 		return gtserror.NewErrorForbidden(errors.New(text), text) | 		return gtserror.NewErrorForbidden(errors.New(text), text) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if blocked, err := p.state.DB.IsEitherBlocked(ctx, thisAccountID, inReplyTo.AccountID); err != nil { |  | ||||||
| 		err := gtserror.Newf("error checking block in db: %w", err) |  | ||||||
| 		return gtserror.NewErrorInternalError(err) |  | ||||||
| 	} else if blocked { |  | ||||||
| 		text := fmt.Sprintf("status %s is not replyable", form.InReplyToID) |  | ||||||
| 		return gtserror.NewErrorNotFound(errors.New(text), text) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Set status fields from inReplyTo. | 	// Set status fields from inReplyTo. | ||||||
| 	status.InReplyToID = inReplyTo.ID | 	status.InReplyToID = inReplyTo.ID | ||||||
| 	status.InReplyTo = inReplyTo | 	status.InReplyTo = inReplyTo | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue