diff options
Diffstat (limited to 'internal/api/client/statuses/statuscreate.go')
-rw-r--r-- | internal/api/client/statuses/statuscreate.go | 156 |
1 files changed, 37 insertions, 119 deletions
diff --git a/internal/api/client/statuses/statuscreate.go b/internal/api/client/statuses/statuscreate.go index 8198d5358..c83cdbad7 100644 --- a/internal/api/client/statuses/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -27,11 +27,9 @@ import ( "github.com/go-playground/form/v4" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" - "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/util" - "github.com/superseriousbusiness/gotosocial/internal/validate" ) // StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate @@ -272,9 +270,9 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { return } - form, err := parseStatusCreateForm(c) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + form, errWithCode := parseStatusCreateForm(c) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } @@ -287,11 +285,6 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { // } // form.Status += "\n\nsent from " + user + "'s iphone\n" - if errWithCode := validateStatusCreateForm(form); errWithCode != nil { - apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) - return - } - apiStatus, errWithCode := m.processor.Status().Create( c.Request.Context(), authed.Account, @@ -303,7 +296,7 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiStatus) + apiutil.JSON(c, http.StatusOK, apiStatus) } // intPolicyFormBinding satisfies gin's binding.Binding interface. @@ -328,108 +321,69 @@ func (intPolicyFormBinding) Bind(req *http.Request, obj any) error { return decoder.Decode(obj, req.Form) } -func parseStatusCreateForm(c *gin.Context) (*apimodel.StatusCreateRequest, error) { +func parseStatusCreateForm(c *gin.Context) (*apimodel.StatusCreateRequest, gtserror.WithCode) { form := new(apimodel.StatusCreateRequest) switch ct := c.ContentType(); ct { case binding.MIMEJSON: // Just bind with default json binding. if err := c.ShouldBindWith(form, binding.JSON); err != nil { - return nil, err + return nil, gtserror.NewErrorBadRequest( + err, + err.Error(), + ) } case binding.MIMEPOSTForm: // Bind with default form binding first. if err := c.ShouldBindWith(form, binding.FormPost); err != nil { - return nil, err + return nil, gtserror.NewErrorBadRequest( + err, + err.Error(), + ) } // Now do custom binding. intReqForm := new(apimodel.StatusInteractionPolicyForm) if err := c.ShouldBindWith(intReqForm, intPolicyFormBinding{}); err != nil { - return nil, err + return nil, gtserror.NewErrorBadRequest( + err, + err.Error(), + ) } + form.InteractionPolicy = intReqForm.InteractionPolicy case binding.MIMEMultipartPOSTForm: // Bind with default form binding first. if err := c.ShouldBindWith(form, binding.FormMultipart); err != nil { - return nil, err + return nil, gtserror.NewErrorBadRequest( + err, + err.Error(), + ) } // Now do custom binding. intReqForm := new(apimodel.StatusInteractionPolicyForm) if err := c.ShouldBindWith(intReqForm, intPolicyFormBinding{}); err != nil { - return nil, err + return nil, gtserror.NewErrorBadRequest( + err, + err.Error(), + ) } + form.InteractionPolicy = intReqForm.InteractionPolicy default: - err := fmt.Errorf( - "content-type %s not supported for this endpoint; supported content-types are %s, %s, %s", - ct, binding.MIMEJSON, binding.MIMEPOSTForm, binding.MIMEMultipartPOSTForm, - ) - return nil, err - } - - return form, nil -} - -// validateStatusCreateForm checks the form for disallowed -// combinations of attachments, overlength inputs, etc. -// -// Side effect: normalizes the post's language tag. -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 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) - } - - 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) - } - - 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 errWithCode := validateStatusPoll(form); errWithCode != nil { - return errWithCode - } + text := fmt.Sprintf("content-type %s not supported for this endpoint; supported content-types are %s, %s, %s", + ct, binding.MIMEJSON, binding.MIMEPOSTForm, binding.MIMEMultipartPOSTForm) + return nil, gtserror.NewErrorNotAcceptable(errors.New(text), text) } + // Check not scheduled status. 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 != "" { - lang, err := validate.Language(form.Language) - if err != nil { - return gtserror.NewErrorBadRequest(err, err.Error()) - } - form.Language = lang + return nil, gtserror.NewErrorNotImplemented(errors.New(text), text) } // Check if the deprecated "federated" field was @@ -438,42 +392,9 @@ func validateStatusCreateForm(form *apimodel.StatusCreateRequest) gtserror.WithC form.LocalOnly = util.Ptr(!*form.Federated) // nolint:staticcheck } - return nil -} - -func validateStatusPoll(form *apimodel.StatusCreateRequest) gtserror.WithCode { - var ( - maxPollOptions = config.GetStatusesPollMaxOptions() - pollOptions = len(form.Poll.Options) - maxPollOptionChars = config.GetStatusesPollOptionMaxChars() - ) + // Normalize poll expiry time if a poll was given. + if form.Poll != nil && form.Poll.ExpiresInI != nil { - 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 form.Poll.ExpiresInI != nil { // If we parsed this as JSON, expires_in // may be either a float64 or a string. expiresIn, err := apiutil.ParseDuration( @@ -481,13 +402,10 @@ func validateStatusPoll(form *apimodel.StatusCreateRequest) gtserror.WithCode { "expires_in", ) if err != nil { - return gtserror.NewErrorBadRequest(err, err.Error()) - } - - if expiresIn != nil { - form.Poll.ExpiresIn = *expiresIn + return nil, gtserror.NewErrorBadRequest(err, err.Error()) } + form.Poll.ExpiresIn = util.PtrOrZero(expiresIn) } - return nil + return form, nil } |