Compare commits

...

2 commits

Author SHA1 Message Date
Vyr Cossont 7670a7d964 Special-case literal null expires_in for v2 filter PUT 2024-11-23 13:43:00 -08:00
Vyr Cossont 0e910d733d Additional v2 regression test 2024-11-23 13:42:30 -08:00
3 changed files with 60 additions and 9 deletions

View file

@ -18,7 +18,10 @@
package v2 package v2
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"io"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -180,12 +183,43 @@ func (m *Module) FilterPUTHandler(c *gin.Context) {
return 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{} form := &apimodel.FilterUpdateRequestV2{}
if err := c.ShouldBind(form); err != nil { if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return 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 { if err := validateNormalizeUpdateFilter(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1) apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
return 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. // Normalize and validate updates.
for i, formKeyword := range form.Keywords { for i, formKeyword := range form.Keywords {
if formKeyword.Keyword != nil { if formKeyword.Keyword != nil {

View file

@ -394,3 +394,20 @@ func (suite *FiltersTestSuite) TestPutFilterUnsetExpirationDateNullJSON() {
filter = suite.setFilterExpiration(id, nil, nil, &requestJson) filter = suite.setFilterExpiration(id, nil, nil, &requestJson)
suite.Nil(filter.ExpiresAt) suite.Nil(filter.ExpiresAt)
} }
// Regression test related to https://github.com/superseriousbusiness/gotosocial/issues/3497
func (suite *FiltersTestSuite) TestPutFilterUnalteredExpirationDateJSON() {
id := suite.testFilters["local_account_1_filter_4"].ID
// Setup: set an expiration date for the filter.
expiresIn := 86400
filter := suite.setFilterExpiration(id, &expiresIn, nil, nil)
if !suite.NotNil(filter.ExpiresAt) {
suite.FailNow("Test precondition failed")
}
// Update nothing. There should still be an expiration date.
requestJson := `{}`
filter = suite.setFilterExpiration(id, nil, nil, &requestJson)
suite.NotNil(filter.ExpiresAt)
}

View file

@ -21,8 +21,6 @@
"context" "context"
"errors" "errors"
"fmt" "fmt"
"time"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
@ -30,6 +28,7 @@
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"time"
) )
// Update an existing filter for the given account, using the provided parameters. // Update an existing filter for the given account, using the provided parameters.
@ -68,10 +67,16 @@ func (p *Processor) Update(
filterColumns = append(filterColumns, "action") filterColumns = append(filterColumns, "action")
filter.Action = typeutils.APIFilterActionToFilterAction(*form.FilterAction) filter.Action = typeutils.APIFilterActionToFilterAction(*form.FilterAction)
} }
// TODO: (Vyr) is it possible to unset a filter expiration with this API?
if form.ExpiresIn != nil { if form.ExpiresIn != nil {
expiresIn := *form.ExpiresIn
filterColumns = append(filterColumns, "expires_at") 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 { if form.Context != nil {
filterColumns = append(filterColumns, filterColumns = append(filterColumns,