diff options
author | 2023-05-12 11:17:31 +0200 | |
---|---|---|
committer | 2023-05-12 11:17:31 +0200 | |
commit | 8eda0051eced29fe039e9fc1c50e4011b6c9545b (patch) | |
tree | 8f3f6b523c4edfe01a6c9768f255d81aec43ed50 /internal/api/client/accounts/accountupdate.go | |
parent | [feature] status refetch support (#1690) (diff) | |
download | gotosocial-8eda0051eced29fe039e9fc1c50e4011b6c9545b.tar.xz |
[bugfix] Ensure account fields can be set by JSON (#1762)
Diffstat (limited to 'internal/api/client/accounts/accountupdate.go')
-rw-r--r-- | internal/api/client/accounts/accountupdate.go | 99 |
1 files changed, 64 insertions, 35 deletions
diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go index 6d0da95e3..26af29118 100644 --- a/internal/api/client/accounts/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -24,11 +24,13 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" "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/gtserror" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "golang.org/x/exp/slices" ) // AccountUpdateCredentialsPATCHHandler swagger:operation PATCH /api/v1/accounts/update_credentials accountUpdate @@ -41,6 +43,7 @@ import ( // // consumes: // - multipart/form-data +// - application/json // // produces: // - application/json @@ -169,26 +172,26 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) { c.JSON(http.StatusOK, acctSensitive) } -type fieldAttributesBinding struct{} +// fieldsAttributesFormBinding satisfies gin's binding.Binding interface. +// Should only be used specifically for multipart/form-data MIME type. +type fieldsAttributesFormBinding struct{} -func (fieldAttributesBinding) Name() string { - return "FieldAttributes" +func (fieldsAttributesFormBinding) Name() string { + return "FieldsAttributes" } -func (fieldAttributesBinding) Bind(req *http.Request, obj any) error { +func (fieldsAttributesFormBinding) Bind(req *http.Request, obj any) error { if err := req.ParseForm(); err != nil { return err } + // Change default namespace prefix and suffix to + // allow correct parsing of the field attributes. decoder := form.NewDecoder() - // change default namespace prefix and suffix to allow correct parsing of the field attributes decoder.SetNamespacePrefix("[") decoder.SetNamespaceSuffix("]") - if err := decoder.Decode(obj, req.Form); err != nil { - return err - } - return nil + return decoder.Decode(obj, req.Form) } func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, error) { @@ -196,36 +199,34 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, Source: &apimodel.UpdateSource{}, } - if err := c.ShouldBind(&form); err != nil { - return nil, fmt.Errorf("could not parse form from request: %s", err) - } - - // use custom form binding to support field attributes in the form data - if err := c.ShouldBindWith(&form, fieldAttributesBinding{}); err != nil { - return nil, fmt.Errorf("could not parse form from request: %s", err) - } - - // parse source field-by-field - sourceMap := c.PostFormMap("source") - - if privacy, ok := sourceMap["privacy"]; ok { - form.Source.Privacy = &privacy - } + switch ct := c.ContentType(); ct { + case binding.MIMEJSON: + // Bind with default json binding first. + if err := c.ShouldBindWith(form, binding.JSON); err != nil { + return nil, err + } - if sensitive, ok := sourceMap["sensitive"]; ok { - sensitiveBool, err := strconv.ParseBool(sensitive) + // Now use custom form binding for + // field attributes in the json data. + var err error + form.FieldsAttributes, err = parseFieldsAttributesFromJSON(form.JSONFieldsAttributes) if err != nil { - return nil, fmt.Errorf("error parsing form source[sensitive]: %s", err) + return nil, fmt.Errorf("custom json binding failed: %w", err) + } + case binding.MIMEMultipartPOSTForm: + // Bind with default form binding first. + if err := c.ShouldBindWith(form, binding.FormMultipart); err != nil { + return nil, err } - form.Source.Sensitive = &sensitiveBool - } - - if language, ok := sourceMap["language"]; ok { - form.Source.Language = &language - } - if statusContentType, ok := sourceMap["status_content_type"]; ok { - form.Source.StatusContentType = &statusContentType + // Now use custom form binding for + // field attributes in the form data. + if err := c.ShouldBindWith(form, fieldsAttributesFormBinding{}); err != nil { + return nil, fmt.Errorf("custom form binding failed: %w", err) + } + default: + err := fmt.Errorf("content-type %s not supported for this endpoint; supported content-types are %s, %s", ct, binding.MIMEJSON, binding.MIMEMultipartPOSTForm) + return nil, err } if form == nil || @@ -248,3 +249,31 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, return form, nil } + +func parseFieldsAttributesFromJSON(jsonFieldsAttributes *map[string]apimodel.UpdateField) (*[]apimodel.UpdateField, error) { + if jsonFieldsAttributes == nil { + // Nothing set, nothing to do. + return nil, nil + } + + fieldsAttributes := make([]apimodel.UpdateField, 0, len(*jsonFieldsAttributes)) + for keyStr, updateField := range *jsonFieldsAttributes { + key, err := strconv.Atoi(keyStr) + if err != nil { + return nil, fmt.Errorf("couldn't parse fieldAttributes key %s to int: %w", keyStr, err) + } + + fieldsAttributes = append(fieldsAttributes, apimodel.UpdateField{ + Key: key, + Name: updateField.Name, + Value: updateField.Value, + }) + } + + // Sort slice by the key each field was submitted with. + slices.SortFunc(fieldsAttributes, func(a, b apimodel.UpdateField) bool { + return a.Key < b.Key + }) + + return &fieldsAttributes, nil +} |