diff options
author | 2024-10-05 19:14:53 +0200 | |
---|---|---|
committer | 2024-10-05 19:14:53 +0200 | |
commit | 18e2f69e85bf7101b9d26c72e7676e4cb0dac285 (patch) | |
tree | c4f82494a63ee57108547b214ecbcbb3db002691 /internal/api | |
parent | [chore] Change order of error checking after PostInbox (#3394) (diff) | |
download | gotosocial-18e2f69e85bf7101b9d26c72e7676e4cb0dac285.tar.xz |
[bugfix] Return 501 (not implemented) if user tries to schedule post (#3395)
Diffstat (limited to 'internal/api')
-rw-r--r-- | internal/api/client/statuses/statuscreate.go | 124 | ||||
-rw-r--r-- | internal/api/client/statuses/statuscreate_test.go | 19 |
2 files changed, 97 insertions, 46 deletions
diff --git a/internal/api/client/statuses/statuscreate.go b/internal/api/client/statuses/statuscreate.go index 996f7605b..48d11f363 100644 --- a/internal/api/client/statuses/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -181,7 +181,7 @@ import ( // Providing this parameter will cause ScheduledStatus to be returned instead of Status. // Must be at least 5 minutes in the future. // -// This feature isn't implemented yet. +// This feature isn't implemented yet; attemping to set it will return 501 Not Implemented. // type: string // in: formData // - @@ -254,6 +254,8 @@ import ( // description: not acceptable // '500': // description: internal server error +// '501': +// description: scheduled_at was set, but this feature is not yet implemented func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) if err != nil { @@ -286,8 +288,8 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { // } // form.Status += "\n\nsent from " + user + "'s iphone\n" - if err := validateNormalizeCreateStatus(form); err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + if errWithCode := validateStatusCreateForm(form); errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } @@ -374,46 +376,61 @@ func parseStatusCreateForm(c *gin.Context) (*apimodel.StatusCreateRequest, error return form, nil } -// validateNormalizeCreateStatus checks the form -// for disallowed combinations of attachments and -// overlength inputs. +// validateStatusCreateForm checks the form for disallowed +// combinations of attachments, overlength inputs, etc. // // Side effect: normalizes the post's language tag. -func validateNormalizeCreateStatus(form *apimodel.StatusCreateRequest) error { - hasStatus := form.Status != "" - hasMedia := len(form.MediaIDs) != 0 - hasPoll := form.Poll != nil - - if !hasStatus && !hasMedia && !hasPoll { - return errors.New("no status, media, or poll provided") - } +func validateStatusCreateForm(form *apimodel.StatusCreateRequest) gtserror.WithCode { + var ( + chars = len([]rune(form.Status)) + len([]rune(form.SpoilerText)) + maxChars = config.GetStatusesMaxChars() + mediaFiles = len(form.MediaIDs) + maxMediaFiles = config.GetStatusesMediaMaxFiles() + hasMedia = mediaFiles != 0 + hasPoll = form.Poll != nil + ) - if hasMedia && hasPoll { - return errors.New("can't post media + poll in same status") + if chars == 0 && !hasMedia && !hasPoll { + // Status must contain *some* kind of content. + const text = "no status content, content warning, media, or poll provided" + return gtserror.NewErrorBadRequest(errors.New(text), text) } - maxChars := config.GetStatusesMaxChars() - if length := len([]rune(form.Status)) + len([]rune(form.SpoilerText)); length > maxChars { - return fmt.Errorf("status too long, %d characters provided (including spoiler/content warning) but limit is %d", length, maxChars) + if chars > maxChars { + text := fmt.Sprintf( + "status too long, %d characters provided (including content warning) but limit is %d", + chars, maxChars, + ) + return gtserror.NewErrorBadRequest(errors.New(text), text) } - maxMediaFiles := config.GetStatusesMediaMaxFiles() - if len(form.MediaIDs) > maxMediaFiles { - return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), maxMediaFiles) + if mediaFiles > maxMediaFiles { + text := fmt.Sprintf( + "too many media files attached to status, %d attached but limit is %d", + mediaFiles, maxMediaFiles, + ) + return gtserror.NewErrorBadRequest(errors.New(text), text) } if form.Poll != nil { - if err := validateNormalizeCreatePoll(form); err != nil { - return err + if errWithCode := validateStatusPoll(form); errWithCode != nil { + return errWithCode } } + if form.ScheduledAt != "" { + const text = "scheduled_at is not yet implemented" + return gtserror.NewErrorNotImplemented(errors.New(text), text) + } + + // Validate + normalize + // language tag if provided. if form.Language != "" { - language, err := validate.Language(form.Language) + lang, err := validate.Language(form.Language) if err != nil { - return err + return gtserror.NewErrorBadRequest(err, err.Error()) } - form.Language = language + form.Language = lang } // Check if the deprecated "federated" field was @@ -425,9 +442,36 @@ func validateNormalizeCreateStatus(form *apimodel.StatusCreateRequest) error { return nil } -func validateNormalizeCreatePoll(form *apimodel.StatusCreateRequest) error { - maxPollOptions := config.GetStatusesPollMaxOptions() - maxPollChars := config.GetStatusesPollOptionMaxChars() +func validateStatusPoll(form *apimodel.StatusCreateRequest) gtserror.WithCode { + var ( + maxPollOptions = config.GetStatusesPollMaxOptions() + pollOptions = len(form.Poll.Options) + maxPollOptionChars = config.GetStatusesPollOptionMaxChars() + ) + + if pollOptions == 0 { + const text = "poll with no options" + return gtserror.NewErrorBadRequest(errors.New(text), text) + } + + if pollOptions > maxPollOptions { + text := fmt.Sprintf( + "too many poll options provided, %d provided but limit is %d", + pollOptions, maxPollOptions, + ) + return gtserror.NewErrorBadRequest(errors.New(text), text) + } + + for _, option := range form.Poll.Options { + optionChars := len([]rune(option)) + if optionChars > maxPollOptionChars { + text := fmt.Sprintf( + "poll option too long, %d characters provided but limit is %d", + optionChars, maxPollOptionChars, + ) + return gtserror.NewErrorBadRequest(errors.New(text), text) + } + } // Normalize poll expiry if necessary. // If we parsed this as JSON, expires_in @@ -440,27 +484,15 @@ func validateNormalizeCreatePoll(form *apimodel.StatusCreateRequest) error { case string: expiresIn, err := strconv.Atoi(e) if err != nil { - return fmt.Errorf("could not parse expires_in value %s as integer: %w", e, err) + text := fmt.Sprintf("could not parse expires_in value %s as integer: %v", e, err) + return gtserror.NewErrorBadRequest(errors.New(text), text) } form.Poll.ExpiresIn = expiresIn default: - return fmt.Errorf("could not parse expires_in type %T as integer", ei) - } - } - - if len(form.Poll.Options) == 0 { - return errors.New("poll with no options") - } - - if len(form.Poll.Options) > maxPollOptions { - return fmt.Errorf("too many poll options provided, %d provided but limit is %d", len(form.Poll.Options), maxPollOptions) - } - - for _, p := range form.Poll.Options { - if length := len([]rune(p)); length > maxPollChars { - return fmt.Errorf("poll option too long, %d characters provided but limit is %d", length, maxPollChars) + text := fmt.Sprintf("could not parse expires_in type %T as integer", ei) + return gtserror.NewErrorBadRequest(errors.New(text), text) } } diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index 8598b5ef0..5f5386dd5 100644 --- a/internal/api/client/statuses/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -365,6 +365,25 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMessedUpIntPolicy() { }`, out) } +func (suite *StatusCreateTestSuite) TestPostNewScheduledStatus() { + out, recorder := suite.postStatus(map[string][]string{ + "status": {"this is a brand new status! #helloworld"}, + "spoiler_text": {"hello hello"}, + "sensitive": {"true"}, + "visibility": {string(apimodel.VisibilityMutualsOnly)}, + "scheduled_at": {"2080-10-04T15:32:02.018Z"}, + }, "") + + // We should have 501 from + // our call to the function. + suite.Equal(http.StatusNotImplemented, recorder.Code) + + // We should have a helpful error message. + suite.Equal(`{ + "error": "Not Implemented: scheduled_at is not yet implemented" +}`, out) +} + func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() { out, recorder := suite.postStatus(map[string][]string{ "status": {statusMarkdown}, |