diff options
Diffstat (limited to 'internal')
104 files changed, 526 insertions, 267 deletions
diff --git a/internal/api/activitypub/emoji/emojiget.go b/internal/api/activitypub/emoji/emojiget.go index c291a500a..5a9f0db72 100644 --- a/internal/api/activitypub/emoji/emojiget.go +++ b/internal/api/activitypub/emoji/emojiget.go @@ -18,7 +18,6 @@  package emoji  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -36,7 +35,7 @@ func (m *Module) EmojiGetHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return @@ -48,11 +47,12 @@ func (m *Module) EmojiGetHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	// Encode JSON HTTP response. +	apiutil.EncodeJSONResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		contentType, +		resp, +	)  } diff --git a/internal/api/activitypub/publickey/publickeyget.go b/internal/api/activitypub/publickey/publickeyget.go index a7de4efad..083a31961 100644 --- a/internal/api/activitypub/publickey/publickeyget.go +++ b/internal/api/activitypub/publickey/publickeyget.go @@ -18,7 +18,6 @@  package publickey  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -42,13 +41,13 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return @@ -60,11 +59,12 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	// Encode JSON HTTP response. +	apiutil.EncodeJSONResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		contentType, +		resp, +	)  } diff --git a/internal/api/activitypub/users/featured.go b/internal/api/activitypub/users/featured.go index 7a2b73a6f..f256c1e75 100644 --- a/internal/api/activitypub/users/featured.go +++ b/internal/api/activitypub/users/featured.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -67,13 +66,13 @@ func (m *Module) FeaturedCollectionGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// This isn't an ActivityPub request;  		// redirect to the user's profile.  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -86,11 +85,5 @@ func (m *Module) FeaturedCollectionGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/activitypub/users/followers.go b/internal/api/activitypub/users/followers.go index e93ef8d4d..956cdc71e 100644 --- a/internal/api/activitypub/users/followers.go +++ b/internal/api/activitypub/users/followers.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -39,13 +38,13 @@ func (m *Module) FollowersGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// This isn't an ActivityPub request;  		// redirect to the user's profile.  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -68,11 +67,5 @@ func (m *Module) FollowersGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/activitypub/users/following.go b/internal/api/activitypub/users/following.go index 54fb3b676..d01b55b57 100644 --- a/internal/api/activitypub/users/following.go +++ b/internal/api/activitypub/users/following.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -39,13 +38,13 @@ func (m *Module) FollowingGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// This isn't an ActivityPub request;  		// redirect to the user's profile.  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -68,11 +67,5 @@ func (m *Module) FollowingGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/activitypub/users/inboxpost.go b/internal/api/activitypub/users/inboxpost.go index c2d3d79c4..03ba5c5a6 100644 --- a/internal/api/activitypub/users/inboxpost.go +++ b/internal/api/activitypub/users/inboxpost.go @@ -47,6 +47,5 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) {  		return  	} -	// Inbox POST body was Accepted for processing. -	c.JSON(http.StatusAccepted, gin.H{"status": http.StatusText(http.StatusAccepted)}) +	apiutil.Data(c, http.StatusAccepted, apiutil.AppJSON, apiutil.StatusAcceptedJSON)  } diff --git a/internal/api/activitypub/users/outboxget.go b/internal/api/activitypub/users/outboxget.go index e4617ba90..7dcc354ac 100644 --- a/internal/api/activitypub/users/outboxget.go +++ b/internal/api/activitypub/users/outboxget.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"fmt"  	"net/http" @@ -93,13 +92,13 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// This isn't an ActivityPub request;  		// redirect to the user's profile.  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -135,11 +134,5 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/activitypub/users/outboxget_test.go b/internal/api/activitypub/users/outboxget_test.go index 4829a8946..31b7e8e9b 100644 --- a/internal/api/activitypub/users/outboxget_test.go +++ b/internal/api/activitypub/users/outboxget_test.go @@ -209,7 +209,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {  	suite.NoError(err)  	suite.Equal(`{    "@context": "https://www.w3.org/ns/activitystreams", -  "id": "http://localhost:8080/users/the_mighty_zork/outbox?page=true\u0026maxID=01F8MHAMCHF6Y650WCRSCP4WMY", +  "id": "http://localhost:8080/users/the_mighty_zork/outbox?page=true&maxID=01F8MHAMCHF6Y650WCRSCP4WMY",    "orderedItems": [],    "partOf": "http://localhost:8080/users/the_mighty_zork/outbox",    "type": "OrderedCollectionPage" diff --git a/internal/api/activitypub/users/repliesget.go b/internal/api/activitypub/users/repliesget.go index 3ac4ccbbb..2d3472f35 100644 --- a/internal/api/activitypub/users/repliesget.go +++ b/internal/api/activitypub/users/repliesget.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -107,13 +106,13 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// redirect to the status  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID)  		return @@ -161,12 +160,5 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		errWithCode := gtserror.NewErrorInternalError(err) -		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/activitypub/users/repliesget_test.go b/internal/api/activitypub/users/repliesget_test.go index ac25f3617..d20d8c6c0 100644 --- a/internal/api/activitypub/users/repliesget_test.go +++ b/internal/api/activitypub/users/repliesget_test.go @@ -266,11 +266,16 @@ func toJSON(a any) string {  		}  		a = m  	} -	b, err := json.MarshalIndent(a, "", "  ") +	var dst bytes.Buffer +	enc := json.NewEncoder(&dst) +	enc.SetIndent("", "  ") +	enc.SetEscapeHTML(false) +	err := enc.Encode(a)  	if err != nil {  		panic(err)  	} -	return string(b) +	dst.Truncate(dst.Len() - 1) // drop new-line +	return dst.String()  }  // indentJSON will return indented JSON from raw provided JSON. diff --git a/internal/api/activitypub/users/statusget.go b/internal/api/activitypub/users/statusget.go index f7b5ccfd1..27af9c6b4 100644 --- a/internal/api/activitypub/users/statusget.go +++ b/internal/api/activitypub/users/statusget.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -46,13 +45,13 @@ func (m *Module) StatusGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// redirect to the status  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID)  		return @@ -64,11 +63,5 @@ func (m *Module) StatusGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/activitypub/users/userget.go b/internal/api/activitypub/users/userget.go index 61be69836..2deca0fa4 100644 --- a/internal/api/activitypub/users/userget.go +++ b/internal/api/activitypub/users/userget.go @@ -18,7 +18,6 @@  package users  import ( -	"encoding/json"  	"errors"  	"net/http"  	"strings" @@ -46,13 +45,13 @@ func (m *Module) UsersGETHandler(c *gin.Context) {  		return  	} -	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) +	contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return  	} -	if format == string(apiutil.TextHTML) { +	if contentType == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return @@ -64,11 +63,5 @@ func (m *Module) UsersGETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, format, b) +	apiutil.JSONType(c, http.StatusOK, contentType, resp)  } diff --git a/internal/api/auth/token.go b/internal/api/auth/token.go index 9787e9efc..cab9352fa 100644 --- a/internal/api/auth/token.go +++ b/internal/api/auth/token.go @@ -110,5 +110,11 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) {  	c.Header("Cache-Control", "no-store")  	c.Header("Pragma", "no-cache") -	c.JSON(http.StatusOK, token) +	apiutil.EncodeJSONResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		apiutil.AppJSON, +		token, +	)  } diff --git a/internal/api/client/accounts/accountcreate.go b/internal/api/client/accounts/accountcreate.go index 473000f6d..061c66b57 100644 --- a/internal/api/client/accounts/accountcreate.go +++ b/internal/api/client/accounts/accountcreate.go @@ -107,7 +107,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, ti) +	apiutil.JSON(c, http.StatusOK, ti)  }  // validateNormalizeCreateAccount checks through all the necessary prerequisites for creating a new account, diff --git a/internal/api/client/accounts/accountdelete.go b/internal/api/client/accounts/accountdelete.go index 242902cab..947634f70 100644 --- a/internal/api/client/accounts/accountdelete.go +++ b/internal/api/client/accounts/accountdelete.go @@ -96,5 +96,7 @@ func (m *Module) AccountDeletePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusAccepted, gin.H{"message": "accepted"}) +	apiutil.JSON(c, http.StatusAccepted, map[string]string{ +		"message": "accepted", +	})  } diff --git a/internal/api/client/accounts/accountget.go b/internal/api/client/accounts/accountget.go index 300efe46e..4c1b66a20 100644 --- a/internal/api/client/accounts/accountget.go +++ b/internal/api/client/accounts/accountget.go @@ -90,5 +90,5 @@ func (m *Module) AccountGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, acctInfo) +	apiutil.JSON(c, http.StatusOK, acctInfo)  } diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go index 9c51f5924..ab731bd7e 100644 --- a/internal/api/client/accounts/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -170,7 +170,7 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, acctSensitive) +	apiutil.JSON(c, http.StatusOK, acctSensitive)  }  // fieldsAttributesFormBinding satisfies gin's binding.Binding interface. diff --git a/internal/api/client/accounts/accountverify.go b/internal/api/client/accounts/accountverify.go index 97a21e0d8..1799089ab 100644 --- a/internal/api/client/accounts/accountverify.go +++ b/internal/api/client/accounts/accountverify.go @@ -73,5 +73,5 @@ func (m *Module) AccountVerifyGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, acctSensitive) +	apiutil.JSON(c, http.StatusOK, acctSensitive)  } diff --git a/internal/api/client/accounts/block.go b/internal/api/client/accounts/block.go index ccf781849..24ff099a7 100644 --- a/internal/api/client/accounts/block.go +++ b/internal/api/client/accounts/block.go @@ -90,5 +90,5 @@ func (m *Module) AccountBlockPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship)  } diff --git a/internal/api/client/accounts/follow.go b/internal/api/client/accounts/follow.go index 260f647cc..2e6e79964 100644 --- a/internal/api/client/accounts/follow.go +++ b/internal/api/client/accounts/follow.go @@ -122,5 +122,5 @@ func (m *Module) AccountFollowPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship)  } diff --git a/internal/api/client/accounts/followers.go b/internal/api/client/accounts/followers.go index 2448bc50a..bedbcef24 100644 --- a/internal/api/client/accounts/followers.go +++ b/internal/api/client/accounts/followers.go @@ -151,5 +151,5 @@ func (m *Module) AccountFollowersGETHandler(c *gin.Context) {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/accounts/following.go b/internal/api/client/accounts/following.go index d106d6ea6..9a8e488b2 100644 --- a/internal/api/client/accounts/following.go +++ b/internal/api/client/accounts/following.go @@ -151,5 +151,5 @@ func (m *Module) AccountFollowingGETHandler(c *gin.Context) {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/accounts/lists.go b/internal/api/client/accounts/lists.go index 4ce1bf729..d42fdd3f9 100644 --- a/internal/api/client/accounts/lists.go +++ b/internal/api/client/accounts/lists.go @@ -93,5 +93,5 @@ func (m *Module) AccountListsGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, lists) +	apiutil.JSON(c, http.StatusOK, lists)  } diff --git a/internal/api/client/accounts/lookup.go b/internal/api/client/accounts/lookup.go index 4b31ea6cc..f6bd97657 100644 --- a/internal/api/client/accounts/lookup.go +++ b/internal/api/client/accounts/lookup.go @@ -89,5 +89,5 @@ func (m *Module) AccountLookupGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, account) +	apiutil.JSON(c, http.StatusOK, account)  } diff --git a/internal/api/client/accounts/note.go b/internal/api/client/accounts/note.go index 9a0667875..29ea01c9a 100644 --- a/internal/api/client/accounts/note.go +++ b/internal/api/client/accounts/note.go @@ -104,5 +104,5 @@ func (m *Module) AccountNotePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship)  } diff --git a/internal/api/client/accounts/relationships.go b/internal/api/client/accounts/relationships.go index 591ae7684..dfe8c1721 100644 --- a/internal/api/client/accounts/relationships.go +++ b/internal/api/client/accounts/relationships.go @@ -106,5 +106,5 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {  		relationships = append(relationships, *r)  	} -	c.JSON(http.StatusOK, relationships) +	apiutil.JSON(c, http.StatusOK, relationships)  } diff --git a/internal/api/client/accounts/search.go b/internal/api/client/accounts/search.go index c10fb2960..183fc1347 100644 --- a/internal/api/client/accounts/search.go +++ b/internal/api/client/accounts/search.go @@ -162,5 +162,5 @@ func (m *Module) AccountSearchGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, results) +	apiutil.JSON(c, http.StatusOK, results)  } diff --git a/internal/api/client/accounts/statuses.go b/internal/api/client/accounts/statuses.go index 867788501..870a96891 100644 --- a/internal/api/client/accounts/statuses.go +++ b/internal/api/client/accounts/statuses.go @@ -241,5 +241,6 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/accounts/unblock.go b/internal/api/client/accounts/unblock.go index 882a3b2f1..e8144711e 100644 --- a/internal/api/client/accounts/unblock.go +++ b/internal/api/client/accounts/unblock.go @@ -91,5 +91,6 @@ func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship) +  } diff --git a/internal/api/client/accounts/unfollow.go b/internal/api/client/accounts/unfollow.go index 4edc9ccea..9eb66aed3 100644 --- a/internal/api/client/accounts/unfollow.go +++ b/internal/api/client/accounts/unfollow.go @@ -91,5 +91,5 @@ func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship)  } diff --git a/internal/api/client/admin/accountaction.go b/internal/api/client/admin/accountaction.go index 91186ae73..89bcf644e 100644 --- a/internal/api/client/admin/accountaction.go +++ b/internal/api/client/admin/accountaction.go @@ -124,5 +124,7 @@ func (m *Module) AccountActionPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, gin.H{"message": "OK"}) +	apiutil.JSON(c, http.StatusOK, map[string]string{ +		"message": "OK", +	})  } diff --git a/internal/api/client/admin/domainkeysexpire.go b/internal/api/client/admin/domainkeysexpire.go index 73a811dd4..10a7597a4 100644 --- a/internal/api/client/admin/domainkeysexpire.go +++ b/internal/api/client/admin/domainkeysexpire.go @@ -132,7 +132,9 @@ func (m *Module) DomainKeysExpirePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusAccepted, &apimodel.AdminActionResponse{ActionID: actionID}) +	apiutil.JSON(c, http.StatusOK, &apimodel.AdminActionResponse{ +		ActionID: actionID, +	})  }  func validateDomainKeysExpire(form *apimodel.DomainKeysExpireRequest) error { diff --git a/internal/api/client/admin/domainpermission.go b/internal/api/client/admin/domainpermission.go index 203eddc8b..05319086f 100644 --- a/internal/api/client/admin/domainpermission.go +++ b/internal/api/client/admin/domainpermission.go @@ -122,7 +122,7 @@ func (m *Module) createDomainPermissions(  			return  		} -		c.JSON(http.StatusOK, domainBlock) +		apiutil.JSON(c, http.StatusOK, domainBlock)  		return  	} @@ -158,7 +158,7 @@ func (m *Module) createDomainPermissions(  		domainPerms = append(domainPerms, entry.Resource)  	} -	c.JSON(http.StatusOK, domainPerms) +	apiutil.JSON(c, http.StatusOK, domainPerms)  }  // deleteDomainPermission deletes a single domain permission (block or allow). @@ -200,7 +200,7 @@ func (m *Module) deleteDomainPermission(  		return  	} -	c.JSON(http.StatusOK, domainPerm) +	apiutil.JSON(c, http.StatusOK, domainPerm)  }  // getDomainPermission gets a single domain permission (block or allow). @@ -248,7 +248,7 @@ func (m *Module) getDomainPermission(  		return  	} -	c.JSON(http.StatusOK, domainPerm) +	apiutil.JSON(c, http.StatusOK, domainPerm)  }  // getDomainPermissions gets all domain permissions of the given type (block, allow). @@ -290,5 +290,5 @@ func (m *Module) getDomainPermissions(  		return  	} -	c.JSON(http.StatusOK, domainPerm) +	apiutil.JSON(c, http.StatusOK, domainPerm)  } diff --git a/internal/api/client/admin/emailtest.go b/internal/api/client/admin/emailtest.go index 5c5330679..8f274e226 100644 --- a/internal/api/client/admin/emailtest.go +++ b/internal/api/client/admin/emailtest.go @@ -116,5 +116,7 @@ func (m *Module) EmailTestPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusAccepted, gin.H{"status": "test email sent"}) +	apiutil.JSON(c, http.StatusAccepted, map[string]string{ +		"status": "test email sent", +	})  } diff --git a/internal/api/client/admin/emojicategoriesget.go b/internal/api/client/admin/emojicategoriesget.go index 9597f789c..2c097c6df 100644 --- a/internal/api/client/admin/emojicategoriesget.go +++ b/internal/api/client/admin/emojicategoriesget.go @@ -89,5 +89,5 @@ func (m *Module) EmojiCategoriesGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, categories) +	apiutil.JSON(c, http.StatusOK, categories)  } diff --git a/internal/api/client/admin/emojicreate.go b/internal/api/client/admin/emojicreate.go index e98ef754e..d916a76c1 100644 --- a/internal/api/client/admin/emojicreate.go +++ b/internal/api/client/admin/emojicreate.go @@ -131,7 +131,7 @@ func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiEmoji) +	apiutil.JSON(c, http.StatusOK, apiEmoji)  }  func validateCreateEmoji(form *apimodel.EmojiCreateRequest) error { diff --git a/internal/api/client/admin/emojidelete.go b/internal/api/client/admin/emojidelete.go index 8d388a409..b5cf72daf 100644 --- a/internal/api/client/admin/emojidelete.go +++ b/internal/api/client/admin/emojidelete.go @@ -105,5 +105,5 @@ func (m *Module) EmojiDELETEHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, emoji) +	apiutil.JSON(c, http.StatusOK, emoji)  } diff --git a/internal/api/client/admin/emojiget.go b/internal/api/client/admin/emojiget.go index 349747b6b..710094551 100644 --- a/internal/api/client/admin/emojiget.go +++ b/internal/api/client/admin/emojiget.go @@ -95,5 +95,5 @@ func (m *Module) EmojiGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, emoji) +	apiutil.JSON(c, http.StatusOK, emoji)  } diff --git a/internal/api/client/admin/emojisget.go b/internal/api/client/admin/emojisget.go index c0ae90004..212401117 100644 --- a/internal/api/client/admin/emojisget.go +++ b/internal/api/client/admin/emojisget.go @@ -206,5 +206,6 @@ func (m *Module) EmojisGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/admin/emojiupdate.go b/internal/api/client/admin/emojiupdate.go index 49f8c3414..f531f36f9 100644 --- a/internal/api/client/admin/emojiupdate.go +++ b/internal/api/client/admin/emojiupdate.go @@ -161,7 +161,7 @@ func (m *Module) EmojiPATCHHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, emoji) +	apiutil.JSON(c, http.StatusOK, emoji)  }  // do a first pass on the form here diff --git a/internal/api/client/admin/mediacleanup.go b/internal/api/client/admin/mediacleanup.go index 3ef3c1441..7a0ee4bd6 100644 --- a/internal/api/client/admin/mediacleanup.go +++ b/internal/api/client/admin/mediacleanup.go @@ -102,5 +102,5 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, remoteCacheDays) +	apiutil.JSON(c, http.StatusOK, remoteCacheDays)  } diff --git a/internal/api/client/admin/mediarefetch.go b/internal/api/client/admin/mediarefetch.go index 2c57d3b95..1c0da6dea 100644 --- a/internal/api/client/admin/mediarefetch.go +++ b/internal/api/client/admin/mediarefetch.go @@ -88,5 +88,5 @@ func (m *Module) MediaRefetchPOSTHandler(c *gin.Context) {  		return  	} -	c.Status(http.StatusAccepted) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.StatusAcceptedJSON)  } diff --git a/internal/api/client/admin/reportget.go b/internal/api/client/admin/reportget.go index 55425f53a..f70ae8b54 100644 --- a/internal/api/client/admin/reportget.go +++ b/internal/api/client/admin/reportget.go @@ -98,5 +98,5 @@ func (m *Module) ReportGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, report) +	apiutil.JSON(c, http.StatusOK, report)  } diff --git a/internal/api/client/admin/reportresolve.go b/internal/api/client/admin/reportresolve.go index fdd4f8449..9c1c32afe 100644 --- a/internal/api/client/admin/reportresolve.go +++ b/internal/api/client/admin/reportresolve.go @@ -120,5 +120,5 @@ func (m *Module) ReportResolvePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, report) +	apiutil.JSON(c, http.StatusOK, report)  } diff --git a/internal/api/client/admin/reportsget.go b/internal/api/client/admin/reportsget.go index cffa578f4..394962f6b 100644 --- a/internal/api/client/admin/reportsget.go +++ b/internal/api/client/admin/reportsget.go @@ -176,5 +176,6 @@ func (m *Module) ReportsGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/admin/rulecreate.go b/internal/api/client/admin/rulecreate.go index 7792233f6..e838bff1e 100644 --- a/internal/api/client/admin/rulecreate.go +++ b/internal/api/client/admin/rulecreate.go @@ -108,7 +108,7 @@ func (m *Module) RulePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiRule) +	apiutil.JSON(c, http.StatusOK, apiRule)  }  func validateCreateRule(form *apimodel.InstanceRuleCreateRequest) error { diff --git a/internal/api/client/admin/ruledelete.go b/internal/api/client/admin/ruledelete.go index 7281ed62e..dfa84615f 100644 --- a/internal/api/client/admin/ruledelete.go +++ b/internal/api/client/admin/ruledelete.go @@ -103,5 +103,5 @@ func (m *Module) RuleDELETEHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiRule) +	apiutil.JSON(c, http.StatusOK, apiRule)  } diff --git a/internal/api/client/admin/ruleget.go b/internal/api/client/admin/ruleget.go index 444820a3f..8281092fb 100644 --- a/internal/api/client/admin/ruleget.go +++ b/internal/api/client/admin/ruleget.go @@ -98,5 +98,5 @@ func (m *Module) RuleGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, rule) +	apiutil.JSON(c, http.StatusOK, rule)  } diff --git a/internal/api/client/admin/rulesget.go b/internal/api/client/admin/rulesget.go index 56f83866f..2cc9e0158 100644 --- a/internal/api/client/admin/rulesget.go +++ b/internal/api/client/admin/rulesget.go @@ -87,5 +87,5 @@ func (m *Module) RulesGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, resp) +	apiutil.JSON(c, http.StatusOK, resp)  } diff --git a/internal/api/client/admin/ruleupdate.go b/internal/api/client/admin/ruleupdate.go index 82ed41190..eafa3af34 100644 --- a/internal/api/client/admin/ruleupdate.go +++ b/internal/api/client/admin/ruleupdate.go @@ -123,5 +123,5 @@ func (m *Module) RulePATCHHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiRule) +	apiutil.JSON(c, http.StatusOK, apiRule)  } diff --git a/internal/api/client/apps/appcreate.go b/internal/api/client/apps/appcreate.go index ebbc462cd..8aa87c3b3 100644 --- a/internal/api/client/apps/appcreate.go +++ b/internal/api/client/apps/appcreate.go @@ -121,5 +121,5 @@ func (m *Module) AppsPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiApp) +	apiutil.JSON(c, http.StatusOK, apiApp)  } diff --git a/internal/api/client/blocks/blocksget.go b/internal/api/client/blocks/blocksget.go index 0761160bc..fe5104c61 100644 --- a/internal/api/client/blocks/blocksget.go +++ b/internal/api/client/blocks/blocksget.go @@ -142,5 +142,5 @@ func (m *Module) BlocksGETHandler(c *gin.Context) {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/bookmarks/bookmarksget.go b/internal/api/client/bookmarks/bookmarksget.go index 06aaaf578..17808642c 100644 --- a/internal/api/client/bookmarks/bookmarksget.go +++ b/internal/api/client/bookmarks/bookmarksget.go @@ -120,5 +120,6 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/customemojis/customemojisget.go b/internal/api/client/customemojis/customemojisget.go index f9a529114..be595afd7 100644 --- a/internal/api/client/customemojis/customemojisget.go +++ b/internal/api/client/customemojis/customemojisget.go @@ -71,5 +71,5 @@ func (m *Module) CustomEmojisGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, emojis) +	apiutil.JSON(c, http.StatusOK, emojis)  } diff --git a/internal/api/client/favourites/favouritesget.go b/internal/api/client/favourites/favouritesget.go index 112bbd856..3ba2f9fcf 100644 --- a/internal/api/client/favourites/favouritesget.go +++ b/internal/api/client/favourites/favouritesget.go @@ -137,5 +137,6 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/featuredtags/get.go b/internal/api/client/featuredtags/get.go index f4ba00a39..c1ee7ca2c 100644 --- a/internal/api/client/featuredtags/get.go +++ b/internal/api/client/featuredtags/get.go @@ -71,5 +71,5 @@ func (m *Module) FeaturedTagsGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, []interface{}{}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONArray)  } diff --git a/internal/api/client/filters/filtersget.go b/internal/api/client/filters/filtersget.go index 58ae23c32..38dd330a7 100644 --- a/internal/api/client/filters/filtersget.go +++ b/internal/api/client/filters/filtersget.go @@ -38,5 +38,5 @@ func (m *Module) FiltersGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, []string{}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONArray)  } diff --git a/internal/api/client/followrequests/authorize.go b/internal/api/client/followrequests/authorize.go index 707d3db26..406b54179 100644 --- a/internal/api/client/followrequests/authorize.go +++ b/internal/api/client/followrequests/authorize.go @@ -93,5 +93,5 @@ func (m *Module) FollowRequestAuthorizePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship)  } diff --git a/internal/api/client/followrequests/get.go b/internal/api/client/followrequests/get.go index af2f3741c..40cdceaea 100644 --- a/internal/api/client/followrequests/get.go +++ b/internal/api/client/followrequests/get.go @@ -139,5 +139,5 @@ func (m *Module) FollowRequestGETHandler(c *gin.Context) {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/followrequests/reject.go b/internal/api/client/followrequests/reject.go index 6514a615e..a8189b78a 100644 --- a/internal/api/client/followrequests/reject.go +++ b/internal/api/client/followrequests/reject.go @@ -91,5 +91,5 @@ func (m *Module) FollowRequestRejectPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, relationship) +	apiutil.JSON(c, http.StatusOK, relationship)  } diff --git a/internal/api/client/instance/instanceget.go b/internal/api/client/instance/instanceget.go index 57d47b902..6690e7e98 100644 --- a/internal/api/client/instance/instanceget.go +++ b/internal/api/client/instance/instanceget.go @@ -58,7 +58,7 @@ func (m *Module) InstanceInformationGETHandlerV1(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, instance) +	apiutil.JSON(c, http.StatusOK, instance)  }  // InstanceInformationGETHandlerV2 swagger:operation GET /api/v2/instance instanceGetV2 @@ -93,5 +93,5 @@ func (m *Module) InstanceInformationGETHandlerV2(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, instance) +	apiutil.JSON(c, http.StatusOK, instance)  } diff --git a/internal/api/client/instance/instancepatch.go b/internal/api/client/instance/instancepatch.go index 885ad19c8..484579cf2 100644 --- a/internal/api/client/instance/instancepatch.go +++ b/internal/api/client/instance/instancepatch.go @@ -161,7 +161,7 @@ func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, i) +	apiutil.JSON(c, http.StatusOK, i)  }  func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error { diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index 1f8b691be..0c270de21 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -78,8 +78,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {    "uri": "http://localhost:8080",    "account_domain": "localhost:8080",    "title": "Example Instance", -  "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", -  "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", +  "description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>", +  "short_description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",    "email": "someone@example.org",    "version": "0.0.0-testrig",    "languages": [ @@ -195,8 +195,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {    "uri": "http://localhost:8080",    "account_domain": "localhost:8080",    "title": "Geoff's Instance", -  "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", -  "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", +  "description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>", +  "short_description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",    "email": "admin@example.org",    "version": "0.0.0-testrig",    "languages": [ @@ -312,8 +312,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {    "uri": "http://localhost:8080",    "account_domain": "localhost:8080",    "title": "GoToSocial Testrig Instance", -  "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", -  "short_description": "\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e", +  "description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>", +  "short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>",    "email": "admin@example.org",    "version": "0.0.0-testrig",    "languages": [ @@ -480,8 +480,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {    "uri": "http://localhost:8080",    "account_domain": "localhost:8080",    "title": "GoToSocial Testrig Instance", -  "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", -  "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", +  "description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>", +  "short_description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",    "email": "",    "version": "0.0.0-testrig",    "languages": [ @@ -619,8 +619,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {    "uri": "http://localhost:8080",    "account_domain": "localhost:8080",    "title": "GoToSocial Testrig Instance", -  "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", -  "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", +  "description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>", +  "short_description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",    "email": "admin@example.org",    "version": "0.0.0-testrig",    "languages": [ @@ -773,8 +773,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {    "uri": "http://localhost:8080",    "account_domain": "localhost:8080",    "title": "GoToSocial Testrig Instance", -  "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", -  "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", +  "description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>", +  "short_description": "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",    "email": "admin@example.org",    "version": "0.0.0-testrig",    "languages": [ diff --git a/internal/api/client/instance/instancepeersget.go b/internal/api/client/instance/instancepeersget.go index 05085bc0f..c278c0674 100644 --- a/internal/api/client/instance/instancepeersget.go +++ b/internal/api/client/instance/instancepeersget.go @@ -156,5 +156,5 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, data) +	apiutil.JSON(c, http.StatusOK, data)  } diff --git a/internal/api/client/instance/instancerulesget.go b/internal/api/client/instance/instancerulesget.go index 5cc99ba41..9df1b8fbe 100644 --- a/internal/api/client/instance/instancerulesget.go +++ b/internal/api/client/instance/instancerulesget.go @@ -67,5 +67,5 @@ func (m *Module) InstanceRulesGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, resp) +	apiutil.JSON(c, http.StatusOK, resp)  } diff --git a/internal/api/client/lists/listaccounts.go b/internal/api/client/lists/listaccounts.go index 6feffb1e8..e1d340ebb 100644 --- a/internal/api/client/lists/listaccounts.go +++ b/internal/api/client/lists/listaccounts.go @@ -174,5 +174,6 @@ func (m *Module) ListAccountsGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/lists/listaccountsadd.go b/internal/api/client/lists/listaccountsadd.go index a2a74e475..6fb5eab3c 100644 --- a/internal/api/client/lists/listaccountsadd.go +++ b/internal/api/client/lists/listaccountsadd.go @@ -116,5 +116,5 @@ func (m *Module) ListAccountsPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, gin.H{}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject)  } diff --git a/internal/api/client/lists/listaccountsremove.go b/internal/api/client/lists/listaccountsremove.go index 2a89cb960..50e53a3a3 100644 --- a/internal/api/client/lists/listaccountsremove.go +++ b/internal/api/client/lists/listaccountsremove.go @@ -126,5 +126,5 @@ func (m *Module) ListAccountsDELETEHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, gin.H{}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject)  } diff --git a/internal/api/client/lists/listcreate.go b/internal/api/client/lists/listcreate.go index 1405aedd2..4228e5fff 100644 --- a/internal/api/client/lists/listcreate.go +++ b/internal/api/client/lists/listcreate.go @@ -102,5 +102,5 @@ func (m *Module) ListCreatePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiList) +	apiutil.JSON(c, http.StatusOK, apiList)  } diff --git a/internal/api/client/lists/listdelete.go b/internal/api/client/lists/listdelete.go index e0139b574..b03f21e5a 100644 --- a/internal/api/client/lists/listdelete.go +++ b/internal/api/client/lists/listdelete.go @@ -87,5 +87,5 @@ func (m *Module) ListDELETEHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, gin.H{}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject)  } diff --git a/internal/api/client/lists/listget.go b/internal/api/client/lists/listget.go index f8dc54eb1..34b21d28b 100644 --- a/internal/api/client/lists/listget.go +++ b/internal/api/client/lists/listget.go @@ -91,5 +91,5 @@ func (m *Module) ListGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, resp) +	apiutil.JSON(c, http.StatusOK, resp)  } diff --git a/internal/api/client/lists/listsget.go b/internal/api/client/lists/listsget.go index f16152a9d..6bfc3c883 100644 --- a/internal/api/client/lists/listsget.go +++ b/internal/api/client/lists/listsget.go @@ -77,5 +77,5 @@ func (m *Module) ListsGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, lists) +	apiutil.JSON(c, http.StatusOK, lists)  } diff --git a/internal/api/client/lists/listupdate.go b/internal/api/client/lists/listupdate.go index 97c0cc636..58a4cf1c4 100644 --- a/internal/api/client/lists/listupdate.go +++ b/internal/api/client/lists/listupdate.go @@ -148,5 +148,5 @@ func (m *Module) ListUpdatePUTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiList) +	apiutil.JSON(c, http.StatusOK, apiList)  } diff --git a/internal/api/client/markers/markersget.go b/internal/api/client/markers/markersget.go index eb403dcc6..9f4fc4270 100644 --- a/internal/api/client/markers/markersget.go +++ b/internal/api/client/markers/markersget.go @@ -84,7 +84,7 @@ func (m *Module) MarkersGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, marker) +	apiutil.JSON(c, http.StatusOK, marker)  }  // parseMarkerNames turns a list of strings into a set of valid marker timeline names, or returns an error. diff --git a/internal/api/client/markers/markerspost.go b/internal/api/client/markers/markerspost.go index 3167becac..8fe40c798 100644 --- a/internal/api/client/markers/markerspost.go +++ b/internal/api/client/markers/markerspost.go @@ -106,5 +106,5 @@ func (m *Module) MarkersPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, marker) +	apiutil.JSON(c, http.StatusOK, marker)  } diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index d2264bb0d..daa2e5bb7 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -139,7 +139,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {  		apiAttachment.URL = nil  	} -	c.JSON(http.StatusOK, apiAttachment) +	apiutil.JSON(c, http.StatusOK, apiAttachment)  }  func validateCreateMedia(form *apimodel.AttachmentRequest) error { diff --git a/internal/api/client/media/mediaget.go b/internal/api/client/media/mediaget.go index 431f73d65..8456f85d8 100644 --- a/internal/api/client/media/mediaget.go +++ b/internal/api/client/media/mediaget.go @@ -98,5 +98,5 @@ func (m *Module) MediaGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, attachment) +	apiutil.JSON(c, http.StatusOK, attachment)  } diff --git a/internal/api/client/media/mediaupdate.go b/internal/api/client/media/mediaupdate.go index 032cfd705..8378502e8 100644 --- a/internal/api/client/media/mediaupdate.go +++ b/internal/api/client/media/mediaupdate.go @@ -141,7 +141,7 @@ func (m *Module) MediaPUTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, attachment) +	apiutil.JSON(c, http.StatusOK, attachment)  }  func validateUpdateMedia(form *apimodel.AttachmentUpdateRequest) error { diff --git a/internal/api/client/notifications/notificationget.go b/internal/api/client/notifications/notificationget.go index 98e32498b..551eeca39 100644 --- a/internal/api/client/notifications/notificationget.go +++ b/internal/api/client/notifications/notificationget.go @@ -83,5 +83,5 @@ func (m *Module) NotificationGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, resp) +	apiutil.JSON(c, http.StatusOK, resp)  } diff --git a/internal/api/client/notifications/notificationsclear.go b/internal/api/client/notifications/notificationsclear.go index 4b63db283..2d7da3c6b 100644 --- a/internal/api/client/notifications/notificationsclear.go +++ b/internal/api/client/notifications/notificationsclear.go @@ -75,5 +75,5 @@ func (m *Module) NotificationsClearPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, struct{}{}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject)  } diff --git a/internal/api/client/notifications/notificationsget.go b/internal/api/client/notifications/notificationsget.go index fd175a115..da43cffec 100644 --- a/internal/api/client/notifications/notificationsget.go +++ b/internal/api/client/notifications/notificationsget.go @@ -155,5 +155,6 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/polls/polls_get.go b/internal/api/client/polls/polls_get.go index 0b15c0ed1..fc89255e9 100644 --- a/internal/api/client/polls/polls_get.go +++ b/internal/api/client/polls/polls_get.go @@ -96,5 +96,5 @@ func (m *Module) PollGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, poll) +	apiutil.JSON(c, http.StatusOK, poll)  } diff --git a/internal/api/client/polls/polls_vote.go b/internal/api/client/polls/polls_vote.go index e5281b3fc..0ab5ac20c 100644 --- a/internal/api/client/polls/polls_vote.go +++ b/internal/api/client/polls/polls_vote.go @@ -117,7 +117,7 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, poll) +	apiutil.JSON(c, http.StatusOK, poll)  }  func bindChoices(c *gin.Context) ([]int, error) { diff --git a/internal/api/client/preferences/preferencesget.go b/internal/api/client/preferences/preferencesget.go index 2834134de..4a6cb4b55 100644 --- a/internal/api/client/preferences/preferencesget.go +++ b/internal/api/client/preferences/preferencesget.go @@ -87,5 +87,6 @@ func (m *Module) PreferencesGETHandler(c *gin.Context) {  		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)  		return  	} -	c.JSON(http.StatusOK, resp) + +	apiutil.JSON(c, http.StatusOK, resp)  } diff --git a/internal/api/client/reports/reportcreate.go b/internal/api/client/reports/reportcreate.go index a4fa01148..a34b8d52e 100644 --- a/internal/api/client/reports/reportcreate.go +++ b/internal/api/client/reports/reportcreate.go @@ -107,5 +107,5 @@ func (m *Module) ReportPOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, apiReport) +	apiutil.JSON(c, http.StatusOK, apiReport)  } diff --git a/internal/api/client/reports/reportget.go b/internal/api/client/reports/reportget.go index 0d5d16da2..4a9b06664 100644 --- a/internal/api/client/reports/reportget.go +++ b/internal/api/client/reports/reportget.go @@ -90,5 +90,5 @@ func (m *Module) ReportGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, report) +	apiutil.JSON(c, http.StatusOK, report)  } diff --git a/internal/api/client/reports/reportsget.go b/internal/api/client/reports/reportsget.go index e290608eb..ba47f1b8b 100644 --- a/internal/api/client/reports/reportsget.go +++ b/internal/api/client/reports/reportsget.go @@ -168,5 +168,6 @@ func (m *Module) ReportsGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/search/searchget.go b/internal/api/client/search/searchget.go index 2759feb5b..909c14f24 100644 --- a/internal/api/client/search/searchget.go +++ b/internal/api/client/search/searchget.go @@ -235,5 +235,5 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, results) +	apiutil.JSON(c, http.StatusOK, results)  } diff --git a/internal/api/client/timelines/home.go b/internal/api/client/timelines/home.go index 963096f59..a7e7717da 100644 --- a/internal/api/client/timelines/home.go +++ b/internal/api/client/timelines/home.go @@ -147,5 +147,6 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/timelines/list.go b/internal/api/client/timelines/list.go index 2e13e32cd..dc5f21424 100644 --- a/internal/api/client/timelines/list.go +++ b/internal/api/client/timelines/list.go @@ -145,5 +145,6 @@ func (m *Module) ListTimelineGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/timelines/public.go b/internal/api/client/timelines/public.go index 7b8acf1ca..8eb34edc7 100644 --- a/internal/api/client/timelines/public.go +++ b/internal/api/client/timelines/public.go @@ -158,5 +158,6 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/timelines/tag.go b/internal/api/client/timelines/tag.go index 58754705b..0d95a6c58 100644 --- a/internal/api/client/timelines/tag.go +++ b/internal/api/client/timelines/tag.go @@ -142,5 +142,6 @@ func (m *Module) TagTimelineGETHandler(c *gin.Context) {  	if resp.LinkHeader != "" {  		c.Header("Link", resp.LinkHeader)  	} -	c.JSON(http.StatusOK, resp.Items) + +	apiutil.JSON(c, http.StatusOK, resp.Items)  } diff --git a/internal/api/client/user/passwordchange.go b/internal/api/client/user/passwordchange.go index 8a1487ac0..c2928e9e5 100644 --- a/internal/api/client/user/passwordchange.go +++ b/internal/api/client/user/passwordchange.go @@ -99,5 +99,5 @@ func (m *Module) PasswordChangePOSTHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, gin.H{"status": "OK"}) +	apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.StatusOKJSON)  } diff --git a/internal/api/fileserver/servefile.go b/internal/api/fileserver/servefile.go index 12d24ca76..5fe79f1d7 100644 --- a/internal/api/fileserver/servefile.go +++ b/internal/api/fileserver/servefile.go @@ -91,9 +91,8 @@ func (m *Module) ServeFile(c *gin.Context) {  	}  	if content.URL != nil { -		// This is a non-local, non-proxied S3 file we're redirecting to. -		// Derive the max-age value from how long the link has left until -		// it expires. +		// This is a non-local, non-proxied S3 file we're redirecting to. Derive +		// the max-age value from how long the link has left until it expires.  		maxAge := int(time.Until(content.URL.Expiry).Seconds())  		c.Header("Cache-Control", "private, max-age="+strconv.Itoa(maxAge)+", immutable")  		c.Redirect(http.StatusFound, content.URL.String()) @@ -110,7 +109,7 @@ func (m *Module) ServeFile(c *gin.Context) {  	// TODO: if the requester only accepts text/html we should try to serve them *something*.  	// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will  	// attempt to look up the content to provide a preview of the link, and they ask for text/html. -	format, err := apiutil.NegotiateAccept(c, apiutil.MIME(content.ContentType)) +	contentType, err := apiutil.NegotiateAccept(c, content.ContentType)  	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)  		return @@ -118,7 +117,7 @@ func (m *Module) ServeFile(c *gin.Context) {  	// if this is a head request, just return info + throw the reader away  	if c.Request.Method == http.MethodHead { -		c.Header("Content-Type", format) +		c.Header("Content-Type", contentType)  		c.Header("Content-Length", strconv.FormatInt(content.ContentLength, 10))  		c.Status(http.StatusOK)  		return @@ -128,12 +127,12 @@ func (m *Module) ServeFile(c *gin.Context) {  	rng := c.GetHeader("Range")  	if rng == "" {  		// This is a simple query for the whole file, so do a read from whole reader. -		c.DataFromReader(http.StatusOK, content.ContentLength, format, content.Content, nil) +		c.DataFromReader(http.StatusOK, content.ContentLength, contentType, content.Content, nil)  		return  	}  	// Set known content-type and serve range. -	c.Header("Content-Type", format) +	c.Header("Content-Type", contentType)  	serveFileRange(  		c.Writer,  		c.Request, diff --git a/internal/api/nodeinfo/nodeinfoget.go b/internal/api/nodeinfo/nodeinfoget.go index 3158eeb9a..368a5503d 100644 --- a/internal/api/nodeinfo/nodeinfoget.go +++ b/internal/api/nodeinfo/nodeinfoget.go @@ -18,7 +18,6 @@  package nodeinfo  import ( -	"encoding/json"  	"net/http"  	"github.com/gin-gonic/gin" @@ -55,11 +54,12 @@ func (m *Module) NodeInfo2GETHandler(c *gin.Context) {  		return  	} -	b, err := json.Marshal(nodeInfo) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, NodeInfo2ContentType, b) +	// Encode JSON HTTP response. +	apiutil.EncodeJSONResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		NodeInfo2ContentType, +		nodeInfo, +	)  } diff --git a/internal/api/util/errorhandling.go b/internal/api/util/errorhandling.go index 4fa544ffd..8bb251040 100644 --- a/internal/api/util/errorhandling.go +++ b/internal/api/util/errorhandling.go @@ -55,7 +55,9 @@ func NotFoundHandler(c *gin.Context, instanceGet func(ctx context.Context) (*api  			"requestID": gtscontext.RequestID(ctx),  		})  	default: -		c.JSON(http.StatusNotFound, gin.H{"error": errWithCode.Safe()}) +		JSON(c, http.StatusNotFound, map[string]string{ +			"error": errWithCode.Safe(), +		})  	}  } @@ -78,7 +80,9 @@ func genericErrorHandler(c *gin.Context, instanceGet func(ctx context.Context) (  			"requestID": gtscontext.RequestID(ctx),  		})  	default: -		c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) +		JSON(c, errWithCode.Code(), map[string]string{ +			"error": errWithCode.Safe(), +		})  	}  } @@ -102,7 +106,7 @@ func ErrorHandler(  	c *gin.Context,  	errWithCode gtserror.WithCode,  	instanceGet func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode), -	offers ...MIME, +	offers ...string,  ) {  	if ctxErr := c.Request.Context().Err(); ctxErr != nil {  		// Context error means either client has left already, @@ -175,7 +179,7 @@ func OAuthErrorHandler(c *gin.Context, errWithCode gtserror.WithCode) {  		l.Debug("handling OAuth error")  	} -	c.JSON(statusCode, gin.H{ +	JSON(c, statusCode, map[string]string{  		"error":             errWithCode.Error(),  		"error_description": errWithCode.Safe(),  	}) diff --git a/internal/api/util/mime.go b/internal/api/util/mime.go index edd0dcecf..ad1b405cd 100644 --- a/internal/api/util/mime.go +++ b/internal/api/util/mime.go @@ -17,20 +17,18 @@  package util -// MIME represents a mime-type. -type MIME string -  const ( -	AppJSON           MIME = `application/json` -	AppXML            MIME = `application/xml` -	AppXMLXRD         MIME = `application/xrd+xml` -	AppRSSXML         MIME = `application/rss+xml` -	AppActivityJSON   MIME = `application/activity+json` -	AppActivityLDJSON MIME = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` -	AppJRDJSON        MIME = `application/jrd+json` // https://www.rfc-editor.org/rfc/rfc7033#section-10.2 -	AppForm           MIME = `application/x-www-form-urlencoded` -	MultipartForm     MIME = `multipart/form-data` -	TextXML           MIME = `text/xml` -	TextHTML          MIME = `text/html` -	TextCSS           MIME = `text/css` +	// Possible GoToSocial mimetypes. +	AppJSON           = `application/json` +	AppXML            = `application/xml` +	AppXMLXRD         = `application/xrd+xml` +	AppRSSXML         = `application/rss+xml` +	AppActivityJSON   = `application/activity+json` +	AppActivityLDJSON = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` +	AppJRDJSON        = `application/jrd+json` // https://www.rfc-editor.org/rfc/rfc7033#section-10.2 +	AppForm           = `application/x-www-form-urlencoded` +	MultipartForm     = `multipart/form-data` +	TextXML           = `text/xml` +	TextHTML          = `text/html` +	TextCSS           = `text/css`  ) diff --git a/internal/api/util/negotiate.go b/internal/api/util/negotiate.go index f4268511b..5b4f54bc6 100644 --- a/internal/api/util/negotiate.go +++ b/internal/api/util/negotiate.go @@ -26,7 +26,7 @@ import (  )  // JSONAcceptHeaders is a slice of offers that just contains application/json types. -var JSONAcceptHeaders = []MIME{ +var JSONAcceptHeaders = []string{  	AppJSON,  } @@ -34,7 +34,7 @@ var JSONAcceptHeaders = []MIME{  // jrd+json content type, but will be chill and fall back to app/json.  // This is to be used specifically for webfinger responses.  // See https://www.rfc-editor.org/rfc/rfc7033#section-10.2 -var WebfingerJSONAcceptHeaders = []MIME{ +var WebfingerJSONAcceptHeaders = []string{  	AppJRDJSON,  	AppJSON,  } @@ -42,13 +42,13 @@ var WebfingerJSONAcceptHeaders = []MIME{  // JSONOrHTMLAcceptHeaders is a slice of offers that prefers AppJSON and will  // fall back to HTML if necessary. This is useful for error handling, since it can  // be used to serve a nice HTML page if the caller accepts that, or just JSON if not. -var JSONOrHTMLAcceptHeaders = []MIME{ +var JSONOrHTMLAcceptHeaders = []string{  	AppJSON,  	TextHTML,  }  // HTMLAcceptHeaders is a slice of offers that just contains text/html types. -var HTMLAcceptHeaders = []MIME{ +var HTMLAcceptHeaders = []string{  	TextHTML,  } @@ -57,7 +57,7 @@ var HTMLAcceptHeaders = []MIME{  // but which should also be able to serve ActivityPub as a fallback.  //  // https://www.w3.org/TR/activitypub/#retrieving-objects -var HTMLOrActivityPubHeaders = []MIME{ +var HTMLOrActivityPubHeaders = []string{  	TextHTML,  	AppActivityLDJSON,  	AppActivityJSON, @@ -68,7 +68,7 @@ var HTMLOrActivityPubHeaders = []MIME{  // which a user might also go to in their browser sometimes.  //  // https://www.w3.org/TR/activitypub/#retrieving-objects -var ActivityPubOrHTMLHeaders = []MIME{ +var ActivityPubOrHTMLHeaders = []string{  	AppActivityLDJSON,  	AppActivityJSON,  	TextHTML, @@ -78,12 +78,12 @@ var ActivityPubOrHTMLHeaders = []MIME{  // This is useful for URLs should only serve ActivityPub.  //  // https://www.w3.org/TR/activitypub/#retrieving-objects -var ActivityPubHeaders = []MIME{ +var ActivityPubHeaders = []string{  	AppActivityLDJSON,  	AppActivityJSON,  } -var HostMetaHeaders = []MIME{ +var HostMetaHeaders = []string{  	AppXMLXRD,  	AppXML,  } @@ -109,7 +109,7 @@ var HostMetaHeaders = []MIME{  // often-used Accept types.  //  // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation#server-driven_content_negotiation -func NegotiateAccept(c *gin.Context, offers ...MIME) (string, error) { +func NegotiateAccept(c *gin.Context, offers ...string) (string, error) {  	if len(offers) == 0 {  		return "", errors.New("no format offered")  	} diff --git a/internal/api/util/negotiate_test.go b/internal/api/util/negotiate_test.go index a8b28b55f..d1b08695f 100644 --- a/internal/api/util/negotiate_test.go +++ b/internal/api/util/negotiate_test.go @@ -9,7 +9,7 @@ import (  	"github.com/gin-gonic/gin"  ) -type testMIMES []MIME +type testMIMES []string  func (tm testMIMES) String(t *testing.T) string {  	t.Helper() diff --git a/internal/api/util/response.go b/internal/api/util/response.go new file mode 100644 index 000000000..e22bac545 --- /dev/null +++ b/internal/api/util/response.go @@ -0,0 +1,282 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. + +package util + +import ( +	"encoding/json" +	"encoding/xml" +	"io" +	"net/http" +	"strconv" +	"sync" + +	"codeberg.org/gruf/go-byteutil" +	"codeberg.org/gruf/go-fastcopy" +	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/log" +) + +var ( +	// Pre-preared response body data. +	StatusOKJSON = mustJSON(map[string]string{ +		"status": http.StatusText(http.StatusOK), +	}) +	StatusAcceptedJSON = mustJSON(map[string]string{ +		"status": http.StatusText(http.StatusAccepted), +	}) +	StatusInternalServerErrorJSON = mustJSON(map[string]string{ +		"status": http.StatusText(http.StatusInternalServerError), +	}) +	EmptyJSONObject = mustJSON("{}") +	EmptyJSONArray  = mustJSON("[]") + +	// write buffer pool. +	bufPool sync.Pool +) + +// JSON calls EncodeJSONResponse() using gin.Context{}, with content-type = AppJSON, +// This function handles the case of JSON unmarshal errors and pools read buffers. +func JSON(c *gin.Context, code int, data any) { +	EncodeJSONResponse(c.Writer, c.Request, code, AppJSON, data) +} + +// JSON calls EncodeJSONResponse() using gin.Context{}, with given content-type. +// This function handles the case of JSON unmarshal errors and pools read buffers. +func JSONType(c *gin.Context, code int, contentType string, data any) { +	EncodeJSONResponse(c.Writer, c.Request, code, contentType, data) +} + +// Data calls WriteResponseBytes() using gin.Context{}, with given content-type. +func Data(c *gin.Context, code int, contentType string, data []byte) { +	WriteResponseBytes(c.Writer, c.Request, code, contentType, data) +} + +// WriteResponse buffered streams 'data' as HTTP response +// to ResponseWriter with given status code content-type. +func WriteResponse( +	rw http.ResponseWriter, +	r *http.Request, +	statusCode int, +	contentType string, +	data io.Reader, +	length int64, +) { +	if length < 0 { +		// The worst-case scenario, length is not known so we need to +		// read the entire thing into memory to know length & respond. +		writeResponseUnknownLength(rw, r, statusCode, contentType, data) +		return +	} + +	// The best-case scenario, stream content of known length. +	rw.Header().Set("Content-Type", contentType) +	rw.Header().Set("Content-Length", strconv.FormatInt(length, 10)) +	rw.WriteHeader(statusCode) +	if _, err := fastcopy.Copy(rw, data); err != nil { +		log.Errorf(r.Context(), "error streaming: %v", err) +	} +} + +// WriteResponseBytes is functionally similar to +// WriteResponse except that it takes prepared bytes. +func WriteResponseBytes( +	rw http.ResponseWriter, +	r *http.Request, +	statusCode int, +	contentType string, +	data []byte, +) { +	rw.Header().Set("Content-Type", contentType) +	rw.Header().Set("Content-Length", strconv.Itoa(len(data))) +	rw.WriteHeader(statusCode) +	if _, err := rw.Write(data); err != nil && err != io.EOF { +		log.Errorf(r.Context(), "error writing: %v", err) +	} +} + +// EncodeJSONResponse encodes 'data' as JSON HTTP response +// to ResponseWriter with given status code, content-type. +func EncodeJSONResponse( +	rw http.ResponseWriter, +	r *http.Request, +	statusCode int, +	contentType string, +	data any, +) { +	// Acquire buffer. +	buf := getBuf() + +	// Wrap buffer in JSON encoder. +	enc := json.NewEncoder(buf) +	enc.SetEscapeHTML(false) + +	// Encode JSON data into byte buffer. +	if err := enc.Encode(data); err == nil { + +		// Drop new-line added by encoder. +		if buf.B[len(buf.B)-1] == '\n' { +			buf.B = buf.B[:len(buf.B)-1] +		} + +		// Respond with the now-known +		// size byte slice within buf. +		WriteResponseBytes(rw, r, +			statusCode, +			contentType, +			buf.B, +		) +	} else { +		// This will always be a JSON error, we +		// can't really add any more useful context. +		log.Error(r.Context(), err) + +		// Any error returned here is unrecoverable, +		// set Internal Server Error JSON response. +		WriteResponseBytes(rw, r, +			http.StatusInternalServerError, +			AppJSON, +			StatusInternalServerErrorJSON, +		) +	} + +	// Release. +	putBuf(buf) +} + +// EncodeJSONResponse encodes 'data' as XML HTTP response +// to ResponseWriter with given status code, content-type. +func EncodeXMLResponse( +	rw http.ResponseWriter, +	r *http.Request, +	statusCode int, +	contentType string, +	data any, +) { +	// Acquire buffer. +	buf := getBuf() + +	// Write XML header string to buf. +	buf.B = append(buf.B, xml.Header...) + +	// Wrap buffer in XML encoder. +	enc := xml.NewEncoder(buf) + +	// Encode JSON data into byte buffer. +	if err := enc.Encode(data); err == nil { + +		// Respond with the now-known +		// size byte slice within buf. +		WriteResponseBytes(rw, r, +			statusCode, +			contentType, +			buf.B, +		) +	} else { +		// This will always be an XML error, we +		// can't really add any more useful context. +		log.Error(r.Context(), err) + +		// Any error returned here is unrecoverable, +		// set Internal Server Error JSON response. +		WriteResponseBytes(rw, r, +			http.StatusInternalServerError, +			AppJSON, +			StatusInternalServerErrorJSON, +		) +	} + +	// Release. +	putBuf(buf) +} + +// writeResponseUnknownLength handles reading data of unknown legnth +// efficiently into memory, and passing on to WriteResponseBytes(). +func writeResponseUnknownLength( +	rw http.ResponseWriter, +	r *http.Request, +	statusCode int, +	contentType string, +	data io.Reader, +) { +	// Acquire buffer. +	buf := getBuf() + +	// Read content into buffer. +	_, err := buf.ReadFrom(data) + +	if err == nil { + +		// Respond with the now-known +		// size byte slice within buf. +		WriteResponseBytes(rw, r, +			statusCode, +			contentType, +			buf.B, +		) +	} else { +		// This will always be a reader error (non EOF), +		// but that doesn't mean the writer is closed yet! +		log.Errorf(r.Context(), "error reading: %v", err) + +		// Any error returned here is unrecoverable, +		// set Internal Server Error JSON response. +		WriteResponseBytes(rw, r, +			http.StatusInternalServerError, +			AppJSON, +			StatusInternalServerErrorJSON, +		) +	} + +	// Release. +	putBuf(buf) +} + +func getBuf() *byteutil.Buffer { +	// acquire buffer from pool. +	buf, _ := bufPool.Get().(*byteutil.Buffer) + +	if buf == nil { +		// alloc new buf if needed. +		buf = new(byteutil.Buffer) +		buf.B = make([]byte, 0, 4096) +	} + +	return buf +} + +func putBuf(buf *byteutil.Buffer) { +	if cap(buf.B) >= int(^uint16(0)) { +		// drop buffers of large size. +		return +	} + +	// ensure empty. +	buf.Reset() + +	// release to pool. +	bufPool.Put(buf) +} + +// mustJSON converts data to JSON, else panicking. +func mustJSON(data any) []byte { +	b, err := json.Marshal(data) +	if err != nil { +		panic(err) +	} +	return b +} diff --git a/internal/api/wellknown/hostmeta/hostmetaget.go b/internal/api/wellknown/hostmeta/hostmetaget.go index c74a2e246..131e2ac58 100644 --- a/internal/api/wellknown/hostmeta/hostmetaget.go +++ b/internal/api/wellknown/hostmeta/hostmetaget.go @@ -18,8 +18,6 @@  package hostmeta  import ( -	"bytes" -	"encoding/xml"  	"net/http"  	"github.com/gin-gonic/gin" @@ -52,21 +50,12 @@ func (m *Module) HostMetaGETHandler(c *gin.Context) {  	hostMeta := m.processor.Fedi().HostMetaGet() -	// this setup with a separate buffer we encode into is used because -	// xml.Marshal does not emit xml.Header by itself -	var buf bytes.Buffer - -	// Preallocate buffer of reasonable length. -	buf.Grow(len(xml.Header) + 64) - -	// No need to check for error on write to buffer. -	_, _ = buf.WriteString(xml.Header) - -	// Encode host-meta as XML to in-memory buffer. -	if err := xml.NewEncoder(&buf).Encode(hostMeta); err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	c.Data(http.StatusOK, HostMetaContentType, buf.Bytes()) +	// Encode XML HTTP response. +	apiutil.EncodeXMLResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		HostMetaContentType, +		hostMeta, +	)  } diff --git a/internal/api/wellknown/nodeinfo/nodeinfoget.go b/internal/api/wellknown/nodeinfo/nodeinfoget.go index 3cbb29650..c458f131e 100644 --- a/internal/api/wellknown/nodeinfo/nodeinfoget.go +++ b/internal/api/wellknown/nodeinfo/nodeinfoget.go @@ -55,5 +55,12 @@ func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) {  		return  	} -	c.JSON(http.StatusOK, resp) +	// Encode JSON HTTP response. +	apiutil.EncodeJSONResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		apiutil.AppJSON, +		resp, +	)  } diff --git a/internal/api/wellknown/webfinger/webfingerget.go b/internal/api/wellknown/webfinger/webfingerget.go index 02f366ea6..74cb8fefe 100644 --- a/internal/api/wellknown/webfinger/webfingerget.go +++ b/internal/api/wellknown/webfinger/webfingerget.go @@ -18,7 +18,6 @@  package webfinger  import ( -	"encoding/json"  	"errors"  	"fmt"  	"net/http" @@ -87,13 +86,12 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) {  		return  	} -	b, err := json.Marshal(resp) -	if err != nil { -		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) -		return -	} - -	// Always return "application/jrd+json" regardless of negotiated -	// format. See https://www.rfc-editor.org/rfc/rfc7033#section-10.2 -	c.Data(http.StatusOK, string(apiutil.AppJRDJSON), b) +	// Encode JSON HTTP response. +	apiutil.EncodeJSONResponse( +		c.Writer, +		c.Request, +		http.StatusOK, +		apiutil.AppJRDJSON, +		resp, +	)  } diff --git a/internal/web/tag.go b/internal/web/tag.go index d52de81d8..69591f114 100644 --- a/internal/web/tag.go +++ b/internal/web/tag.go @@ -45,7 +45,7 @@ func (m *Module) tagGETHandler(c *gin.Context) {  	}  	// We only serve text/html at this endpoint. -	if _, err := apiutil.NegotiateAccept(c, []apiutil.MIME{apiutil.TextHTML}...); err != nil { +	if _, err := apiutil.NegotiateAccept(c, apiutil.TextHTML); err != nil {  		apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), instanceGet)  		return  	}  | 
