From 7670a7d964dd01d4e1b61bbed400e16e4f2ec481 Mon Sep 17 00:00:00 2001 From: Vyr Cossont Date: Sat, 23 Nov 2024 13:43:00 -0800 Subject: [PATCH] Special-case literal null expires_in for v2 filter PUT --- internal/api/client/filters/v2/filterput.go | 39 ++++++++++++++++++--- internal/processing/filters/v2/update.go | 13 ++++--- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/internal/api/client/filters/v2/filterput.go b/internal/api/client/filters/v2/filterput.go index cde03360d..2b440d5ca 100644 --- a/internal/api/client/filters/v2/filterput.go +++ b/internal/api/client/filters/v2/filterput.go @@ -18,7 +18,10 @@ package v2 import ( + "bytes" + "encoding/json" "errors" + "io" "net/http" "github.com/gin-gonic/gin" @@ -180,12 +183,43 @@ func (m *Module) FilterPUTHandler(c *gin.Context) { return } + // If the request is JSON: + // Explicitly check whether `expires_in` is a null literal (vs. not being set at all). + hasNullExpiresIn := false + if c.ContentType() == gin.MIMEJSON { + // To do this, we need to read the request body twice, once here and once below for the form, so we buffer it. + // If a filter update request is bigger than a megabyte, somebody's messing with us. + bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 1024*1024)) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + return + } + c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + + // Partially parse the body as a JSON object. + requestJSONObject := map[string]json.RawMessage{} + if err := json.Unmarshal(bodyBytes, &requestJSONObject); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + return + } + + // Try to parse the `expires_in` field as a literal null. + if raw, found := requestJSONObject["expires_in"]; found { + hasNullExpiresIn = string(raw) == "null" + } + } + form := &apimodel.FilterUpdateRequestV2{} if err := c.ShouldBind(form); err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) return } + // Interpret a literal null `expires_in` as unsetting the expiration date. + if hasNullExpiresIn { + form.ExpiresIn = util.Ptr(0) + } + if err := validateNormalizeUpdateFilter(form); err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1) return @@ -283,11 +317,6 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error { } } - // Interpret zero as indefinite duration. - if form.ExpiresIn != nil && *form.ExpiresIn == 0 { - form.ExpiresIn = nil - } - // Normalize and validate updates. for i, formKeyword := range form.Keywords { if formKeyword.Keyword != nil { diff --git a/internal/processing/filters/v2/update.go b/internal/processing/filters/v2/update.go index 0d443d58e..d5b5cce01 100644 --- a/internal/processing/filters/v2/update.go +++ b/internal/processing/filters/v2/update.go @@ -21,8 +21,6 @@ "context" "errors" "fmt" - "time" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -30,6 +28,7 @@ "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/util" + "time" ) // Update an existing filter for the given account, using the provided parameters. @@ -68,10 +67,16 @@ func (p *Processor) Update( filterColumns = append(filterColumns, "action") filter.Action = typeutils.APIFilterActionToFilterAction(*form.FilterAction) } - // TODO: (Vyr) is it possible to unset a filter expiration with this API? if form.ExpiresIn != nil { + expiresIn := *form.ExpiresIn filterColumns = append(filterColumns, "expires_at") - filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn)) + if expiresIn == 0 { + // Unset the expiration date. + filter.ExpiresAt = time.Time{} + } else { + // Update the expiration date. + filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(expiresIn)) + } } if form.Context != nil { filterColumns = append(filterColumns,