From 6934ae378ab5743da80a5995fc74d167502187b1 Mon Sep 17 00:00:00 2001 From: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com> Date: Tue, 12 Jul 2022 08:32:20 +0100 Subject: [PATCH] [chore] improved router logging, recovery and error handling (#705) * move panic recovery to logging middleware, improve logging + panic recovery logic Signed-off-by: kim * remove dead code Signed-off-by: kim * remove skip paths code Signed-off-by: kim * re-enable log quoting Signed-off-by: kim * use human-friendly bytesize in logging body size Signed-off-by: kim * only disable quoting in debug builds Signed-off-by: kim * use logrus level instead of debug.DEBUG() to enable/disable quoting Signed-off-by: kim * shutup linter Signed-off-by: kim * fix instance tests Signed-off-by: kim * fix gin test contexts created with missing engine HTML renderer Signed-off-by: kim * add note regarding not logging query parameters Signed-off-by: kim * better explain 'DisableQuoting' logic Signed-off-by: kim * add license text Signed-off-by: kim --- go.mod | 1 + go.sum | 2 + internal/api/client/account/account_test.go | 2 +- internal/api/client/account/block_test.go | 3 +- internal/api/client/account/follow_test.go | 3 +- internal/api/client/admin/admin_test.go | 2 +- internal/api/client/auth/auth_test.go | 2 +- .../api/client/fileserver/servefile_test.go | 4 +- .../followrequest/followrequest_test.go | 2 +- internal/api/client/instance/instance_test.go | 28 +- .../api/client/instance/instancepatch_test.go | 14 +- .../client/instance/instancepeersget_test.go | 33 +- internal/api/client/media/mediacreate_test.go | 7 +- internal/api/client/media/mediaupdate_test.go | 4 +- .../api/client/status/statusboost_test.go | 12 +- .../api/client/status/statuscreate_test.go | 19 +- internal/api/client/status/statusfave_test.go | 7 +- .../api/client/status/statusfavedby_test.go | 3 +- .../api/client/status/statusunfave_test.go | 7 +- .../api/client/user/passwordchange_test.go | 10 +- internal/api/errorhandling.go | 37 +-- internal/api/s2s/user/inboxpost_test.go | 8 +- internal/api/s2s/user/outboxget_test.go | 6 +- internal/api/s2s/user/repliesget_test.go | 6 +- internal/api/s2s/user/statusget_test.go | 4 +- internal/api/s2s/user/userget_test.go | 4 +- .../api/s2s/webfinger/webfingerget_test.go | 9 +- internal/log/log.go | 18 +- internal/router/logger.go | 93 ++++-- internal/router/router.go | 6 +- internal/router/template.go | 2 +- testrig/gin.go | 37 +++ vendor/codeberg.org/gruf/go-bytesize/LICENSE | 9 + .../codeberg.org/gruf/go-bytesize/README.md | 3 + .../codeberg.org/gruf/go-bytesize/bytesize.go | 306 ++++++++++++++++++ vendor/modules.txt | 3 + 36 files changed, 526 insertions(+), 190 deletions(-) create mode 100644 testrig/gin.go create mode 100644 vendor/codeberg.org/gruf/go-bytesize/LICENSE create mode 100644 vendor/codeberg.org/gruf/go-bytesize/README.md create mode 100644 vendor/codeberg.org/gruf/go-bytesize/bytesize.go diff --git a/go.mod b/go.mod index 420facc6f..e01678a25 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/superseriousbusiness/gotosocial go 1.18 require ( + codeberg.org/gruf/go-bytesize v0.2.1 codeberg.org/gruf/go-byteutil v1.0.2 codeberg.org/gruf/go-cache/v2 v2.1.1 codeberg.org/gruf/go-debug v1.2.0 diff --git a/go.sum b/go.sum index eaa543601..c6ab9451e 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ codeberg.org/gruf/go-bitutil v1.0.1/go.mod h1:3ezHnADoiRJs9jgn65AEZ3HY7dsabAYLmm codeberg.org/gruf/go-bytes v1.0.0/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg= codeberg.org/gruf/go-bytes v1.0.2 h1:malqE42Ni+h1nnYWBUAJaDDtEzF4aeN4uPN8DfMNNvo= codeberg.org/gruf/go-bytes v1.0.2/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg= +codeberg.org/gruf/go-bytesize v0.2.1 h1:nbAta3FCYe3Q18osqg8Ylk/naOopdqEKiKMpo6KTpAA= +codeberg.org/gruf/go-bytesize v0.2.1/go.mod h1:n/GU8HzL9f3UNp/mUKyr1qVmTlj7+xacpp0OHfkvLPs= codeberg.org/gruf/go-byteutil v1.0.0/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU= codeberg.org/gruf/go-byteutil v1.0.2 h1:OesVyK5VKWeWdeDR00zRJ+Oy8hjXx1pBhn7WVvcZWVE= codeberg.org/gruf/go-byteutil v1.0.2/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU= diff --git a/internal/api/client/account/account_test.go b/internal/api/client/account/account_test.go index 39d942ee3..ceb15c7a2 100644 --- a/internal/api/client/account/account_test.go +++ b/internal/api/client/account/account_test.go @@ -82,7 +82,7 @@ func (suite *AccountStandardTestSuite) TearDownTest() { } func (suite *AccountStandardTestSuite) newContext(recorder *httptest.ResponseRecorder, requestMethod string, requestBody []byte, requestPath string, bodyContentType string) *gin.Context { - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) diff --git a/internal/api/client/account/block_test.go b/internal/api/client/account/block_test.go index fcbe1ec39..9c75330aa 100644 --- a/internal/api/client/account/block_test.go +++ b/internal/api/client/account/block_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/account" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" ) type BlockTestSuite struct { @@ -40,7 +41,7 @@ type BlockTestSuite struct { func (suite *BlockTestSuite) TestBlockSelf() { testAcct := suite.testAccounts["local_account_1"] recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedAccount, testAcct) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) diff --git a/internal/api/client/account/follow_test.go b/internal/api/client/account/follow_test.go index 478330c83..fad67b185 100644 --- a/internal/api/client/account/follow_test.go +++ b/internal/api/client/account/follow_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/account" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" ) type FollowTestSuite struct { @@ -40,7 +41,7 @@ type FollowTestSuite struct { func (suite *FollowTestSuite) TestFollowSelf() { testAcct := suite.testAccounts["local_account_1"] recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedAccount, testAcct) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) diff --git a/internal/api/client/admin/admin_test.go b/internal/api/client/admin/admin_test.go index c5035c798..5017afd05 100644 --- a/internal/api/client/admin/admin_test.go +++ b/internal/api/client/admin/admin_test.go @@ -100,7 +100,7 @@ func (suite *AdminStandardTestSuite) TearDownTest() { } func (suite *AdminStandardTestSuite) newContext(recorder *httptest.ResponseRecorder, requestMethod string, requestBody []byte, requestPath string, bodyContentType string) *gin.Context { - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["admin_account"]) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["admin_account"])) diff --git a/internal/api/client/auth/auth_test.go b/internal/api/client/auth/auth_test.go index 188c40b3a..d84db058a 100644 --- a/internal/api/client/auth/auth_test.go +++ b/internal/api/client/auth/auth_test.go @@ -111,7 +111,7 @@ func (suite *AuthStandardTestSuite) TearDownTest() { func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath string, requestBody []byte, bodyContentType string) (*gin.Context, *httptest.ResponseRecorder) { // create the recorder and gin test context recorder := httptest.NewRecorder() - ctx, engine := gin.CreateTestContext(recorder) + ctx, engine := testrig.CreateGinTestContext(recorder, nil) // load templates into the engine testrig.ConfigureTemplatesWithGin(engine, "../../../../web/template") diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/client/fileserver/servefile_test.go index a36a79a58..e918a9026 100644 --- a/internal/api/client/fileserver/servefile_test.go +++ b/internal/api/client/fileserver/servefile_test.go @@ -126,7 +126,7 @@ func (suite *ServeFileTestSuite) TestServeOriginalFileSuccessful() { suite.NotNil(targetAttachment) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAttachment.URL, nil) ctx.Request.Header.Set("accept", "*/*") @@ -172,7 +172,7 @@ func (suite *ServeFileTestSuite) TestServeSmallFileSuccessful() { suite.NotNil(targetAttachment) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAttachment.Thumbnail.URL, nil) ctx.Request.Header.Set("accept", "*/*") diff --git a/internal/api/client/followrequest/followrequest_test.go b/internal/api/client/followrequest/followrequest_test.go index e98d75c77..f9a14d261 100644 --- a/internal/api/client/followrequest/followrequest_test.go +++ b/internal/api/client/followrequest/followrequest_test.go @@ -96,7 +96,7 @@ func (suite *FollowRequestStandardTestSuite) TearDownTest() { } func (suite *FollowRequestStandardTestSuite) newContext(recorder *httptest.ResponseRecorder, requestMethod string, requestBody []byte, requestPath string, bodyContentType string) *gin.Context { - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) diff --git a/internal/api/client/instance/instance_test.go b/internal/api/client/instance/instance_test.go index 645e70474..efdf86523 100644 --- a/internal/api/client/instance/instance_test.go +++ b/internal/api/client/instance/instance_test.go @@ -98,27 +98,29 @@ func (suite *InstanceStandardTestSuite) TearDownTest() { testrig.StandardStorageTeardown(suite.storage) } -func (suite *InstanceStandardTestSuite) newContext(recorder *httptest.ResponseRecorder, requestMethod string, requestBody []byte, requestPath string, bodyContentType string) *gin.Context { - ctx, _ := gin.CreateTestContext(recorder) - - ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["admin_account"]) - ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["admin_account"])) - ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["admin_account"]) - ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"]) - +func (suite *InstanceStandardTestSuite) newContext(recorder *httptest.ResponseRecorder, method string, path string, body []byte, contentType string, auth bool) *gin.Context { protocol := config.GetProtocol() host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) - requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) + requestURI := fmt.Sprintf("%s/%s", baseURI, path) - ctx.Request = httptest.NewRequest(requestMethod, requestURI, bytes.NewReader(requestBody)) // the endpoint we're hitting + req := httptest.NewRequest(method, requestURI, bytes.NewReader(body)) // the endpoint we're hitting - if bodyContentType != "" { - ctx.Request.Header.Set("Content-Type", bodyContentType) + if contentType != "" { + req.Header.Set("Content-Type", contentType) } - ctx.Request.Header.Set("accept", "application/json") + req.Header.Set("accept", "application/json") + + ctx, _ := testrig.CreateGinTestContext(recorder, req) + + if auth { + ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["admin_account"]) + ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["admin_account"])) + ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["admin_account"]) + ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"]) + } return ctx } diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index 1aec421e1..864eb9965 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -49,7 +49,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) // call the handler suite.instanceModule.InstanceUpdatePATCHHandler(ctx) @@ -79,7 +79,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) // call the handler suite.instanceModule.InstanceUpdatePATCHHandler(ctx) @@ -109,7 +109,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) // call the handler suite.instanceModule.InstanceUpdatePATCHHandler(ctx) @@ -137,7 +137,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch4() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) // call the handler suite.instanceModule.InstanceUpdatePATCHHandler(ctx) @@ -166,7 +166,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch5() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) @@ -200,7 +200,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) // call the handler suite.instanceModule.InstanceUpdatePATCHHandler(ctx) @@ -230,7 +230,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch7() { // set up the request recorder := httptest.NewRecorder() - ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType()) + ctx := suite.newContext(recorder, http.MethodPatch, instance.InstanceInformationPath, bodyBytes, w.FormDataContentType(), true) // call the handler suite.instanceModule.InstanceUpdatePATCHHandler(ctx) diff --git a/internal/api/client/instance/instancepeersget_test.go b/internal/api/client/instance/instancepeersget_test.go index cb35a9e50..48fb42a73 100644 --- a/internal/api/client/instance/instancepeersget_test.go +++ b/internal/api/client/instance/instancepeersget_test.go @@ -26,7 +26,7 @@ import ( "net/http/httptest" "testing" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/render" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/instance" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -40,7 +40,8 @@ type InstancePeersGetTestSuite struct { func (suite *InstancePeersGetTestSuite) TestInstancePeersGetNoParams() { recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, r := testrig.CreateGinTestContext(recorder, nil) + r.HTMLRender = render.HTMLDebug{} baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s", baseURI, instance.InstancePeersPath) @@ -63,11 +64,9 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetNoParamsUnauthorized config.SetInstanceExposePeers(false) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s", baseURI, instance.InstancePeersPath) - ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", false) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -88,7 +87,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetNoParamsAuthorized() recorder := httptest.NewRecorder() baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s", baseURI, instance.InstancePeersPath) - ctx := suite.newContext(recorder, http.MethodGet, []byte{}, requestURI, "") + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", true) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -105,11 +104,9 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetNoParamsAuthorized() func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspended() { recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s?filter=suspended", baseURI, instance.InstancePeersPath) - ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", false) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -128,11 +125,9 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspendedUnautho config.SetInstanceExposeSuspended(false) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s?filter=suspended", baseURI, instance.InstancePeersPath) - ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", false) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -153,7 +148,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspendedAuthori recorder := httptest.NewRecorder() baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s?filter=suspended", baseURI, instance.InstancePeersPath) - ctx := suite.newContext(recorder, http.MethodGet, []byte{}, requestURI, "") + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", true) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -170,11 +165,9 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspendedAuthori func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAll() { recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s?filter=suspended,open", baseURI, instance.InstancePeersPath) - ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", false) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -202,11 +195,9 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAllWithObfuscated() suite.NoError(err) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s?filter=suspended,open", baseURI, instance.InstancePeersPath) - ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", false) suite.instanceModule.InstancePeersGETHandler(ctx) @@ -223,11 +214,9 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAllWithObfuscated() func (suite *InstancePeersGetTestSuite) TestInstancePeersGetFunkyParams() { recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - baseURI := fmt.Sprintf("%s://%s", config.GetProtocol(), config.GetHost()) requestURI := fmt.Sprintf("%s/%s?filter=aaaaaaaaaaaaaaaaa,open", baseURI, instance.InstancePeersPath) - ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) + ctx := suite.newContext(recorder, http.MethodGet, requestURI, nil, "", true) suite.instanceModule.InstancePeersGETHandler(ctx) diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 153cde90f..935fc2970 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -30,7 +30,6 @@ import ( "net/http/httptest" "testing" - "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" @@ -130,7 +129,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -218,7 +217,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() { t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -265,7 +264,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() { t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) diff --git a/internal/api/client/media/mediaupdate_test.go b/internal/api/client/media/mediaupdate_test.go index 4457426de..deb93d83c 100644 --- a/internal/api/client/media/mediaupdate_test.go +++ b/internal/api/client/media/mediaupdate_test.go @@ -130,7 +130,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -195,7 +195,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() { t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) diff --git a/internal/api/client/status/statusboost_test.go b/internal/api/client/status/statusboost_test.go index c8d33f0f8..5b4b1b3cd 100644 --- a/internal/api/client/status/statusboost_test.go +++ b/internal/api/client/status/statusboost_test.go @@ -31,6 +31,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusBoostTestSuite struct { @@ -38,7 +39,6 @@ type StatusBoostTestSuite struct { } func (suite *StatusBoostTestSuite) TestPostBoost() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) @@ -46,7 +46,7 @@ func (suite *StatusBoostTestSuite) TestPostBoost() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -112,7 +112,7 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() { testUser := suite.testUsers["local_account_1"] recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, testUser) @@ -170,7 +170,6 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() { // try to boost a status that's not boostable func (suite *StatusBoostTestSuite) TestPostUnboostable() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) @@ -178,7 +177,7 @@ func (suite *StatusBoostTestSuite) TestPostUnboostable() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -209,7 +208,6 @@ func (suite *StatusBoostTestSuite) TestPostUnboostable() { // try to boost a status that's not visible to the user func (suite *StatusBoostTestSuite) TestPostNotVisible() { - // stop local_account_2 following zork err := suite.db.DeleteByID(context.Background(), suite.testFollows["local_account_2_local_account_1"].ID, >smodel.Follow{}) suite.NoError(err) @@ -221,7 +219,7 @@ func (suite *StatusBoostTestSuite) TestPostNotVisible() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"]) diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/status/statuscreate_test.go index 206dc5cfc..c6c9b4dab 100644 --- a/internal/api/client/status/statuscreate_test.go +++ b/internal/api/client/status/statuscreate_test.go @@ -28,7 +28,6 @@ import ( "net/url" "testing" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -52,13 +51,12 @@ https://docs.gotosocial.org/en/latest/user_guide/posts/#links // Post a new status with some custom visibility settings func (suite *StatusCreateTestSuite) TestPostNewStatus() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -108,7 +106,6 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { // mention an account that is not yet known to the instance -- it should be looked up and put in the db func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() { - // first remove remote account 1 from the database so it gets looked up again remoteAccount := suite.testAccounts["remote_account_1"] if err := suite.db.DeleteByID(context.Background(), remoteAccount.ID, >smodel.Account{}); err != nil { @@ -120,7 +117,7 @@ func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -150,13 +147,12 @@ func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() { } func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -186,13 +182,12 @@ func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { } func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -234,7 +229,7 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -266,7 +261,7 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -309,7 +304,7 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) diff --git a/internal/api/client/status/statusfave_test.go b/internal/api/client/status/statusfave_test.go index 5b10ba9b3..da5d2a48a 100644 --- a/internal/api/client/status/statusfave_test.go +++ b/internal/api/client/status/statusfave_test.go @@ -33,6 +33,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusFaveTestSuite struct { @@ -41,7 +42,6 @@ type StatusFaveTestSuite struct { // fave a status func (suite *StatusFaveTestSuite) TestPostFave() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) @@ -49,7 +49,7 @@ func (suite *StatusFaveTestSuite) TestPostFave() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -90,7 +90,6 @@ func (suite *StatusFaveTestSuite) TestPostFave() { // try to fave a status that's not faveable func (suite *StatusFaveTestSuite) TestPostUnfaveable() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) @@ -98,7 +97,7 @@ func (suite *StatusFaveTestSuite) TestPostUnfaveable() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) diff --git a/internal/api/client/status/statusfavedby_test.go b/internal/api/client/status/statusfavedby_test.go index 08749275b..e704fa724 100644 --- a/internal/api/client/status/statusfavedby_test.go +++ b/internal/api/client/status/statusfavedby_test.go @@ -33,6 +33,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusFavedByTestSuite struct { @@ -47,7 +48,7 @@ func (suite *StatusFavedByTestSuite) TestGetFavedBy() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_2"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"]) diff --git a/internal/api/client/status/statusunfave_test.go b/internal/api/client/status/statusunfave_test.go index 641668391..b8448d657 100644 --- a/internal/api/client/status/statusunfave_test.go +++ b/internal/api/client/status/statusunfave_test.go @@ -33,6 +33,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/client/status" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" ) type StatusUnfaveTestSuite struct { @@ -41,7 +42,6 @@ type StatusUnfaveTestSuite struct { // unfave a status func (suite *StatusUnfaveTestSuite) TestPostUnfave() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) @@ -50,7 +50,7 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -91,7 +91,6 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() { // try to unfave a status that's already not faved func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() { - t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) @@ -100,7 +99,7 @@ func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() { // setup recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) diff --git a/internal/api/client/user/passwordchange_test.go b/internal/api/client/user/passwordchange_test.go index 11df161e2..31b59b2ce 100644 --- a/internal/api/client/user/passwordchange_test.go +++ b/internal/api/client/user/passwordchange_test.go @@ -27,11 +27,11 @@ import ( "net/url" "testing" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/user" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/testrig" "golang.org/x/crypto/bcrypt" ) @@ -44,7 +44,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordChangePOST() { oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -78,7 +78,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordMissingOldPassword() { oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -105,7 +105,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordIncorrectOldPassword() { oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) @@ -133,7 +133,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() { oauthToken := oauth.DBTokenToToken(t) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedToken, oauthToken) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) diff --git a/internal/api/errorhandling.go b/internal/api/errorhandling.go index 834f49ee8..32699fe36 100644 --- a/internal/api/errorhandling.go +++ b/internal/api/errorhandling.go @@ -22,7 +22,6 @@ import ( "context" "net/http" - "codeberg.org/gruf/go-errors/v2" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -86,47 +85,13 @@ func genericErrorHandler(c *gin.Context, instanceGet func(ctx context.Context, d // if something goes wrong during the function, it will recover and just try to serve // an appropriate application/json content-type error. func ErrorHandler(c *gin.Context, errWithCode gtserror.WithCode, instanceGet func(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode)) { - path := c.Request.URL.Path - if raw := c.Request.URL.RawQuery; raw != "" { - path = path + "?" + raw - } - - l := logrus.WithFields(logrus.Fields{ - "path": path, - "error": errWithCode.Error(), - }) - - statusCode := errWithCode.Code() - - if statusCode == http.StatusInternalServerError { - l.Error("Internal Server Error") - } else { - l.Debug("handling error") - } - - // if we panic for any reason during error handling, - // we should still try to return a basic code - defer func() { - if p := recover(); p != nil { - // Fetch stacktrace up to this point - callers := errors.GetCallers(3, 10) - - // Log this panic to the standard log - l = l.WithField("stacktrace", callers) - l.Errorf("recovered from panic: %v", p) - - // Respond with determined error code - c.JSON(statusCode, gin.H{"error": errWithCode.Safe()}) - } - }() - // discover if we're allowed to serve a nice html error page, // or if we should just use a json. Normally we would want to // check for a returned error, but if an error occurs here we // can just fall back to default behavior (serve json error). accept, _ := NegotiateAccept(c, HTMLOrJSONAcceptHeaders...) - if statusCode == http.StatusNotFound { + if errWithCode.Code() == http.StatusNotFound { // use our special not found handler with useful status text NotFoundHandler(c, instanceGet, accept) } else { diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/s2s/user/inboxpost_test.go index 3a6ddbcfa..b8fe73909 100644 --- a/internal/api/s2s/user/inboxpost_test.go +++ b/internal/api/s2s/user/inboxpost_test.go @@ -96,7 +96,7 @@ func (suite *InboxPostTestSuite) TestPostBlock() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodPost, targetURI.String(), body) // the endpoint we're hitting ctx.Request.Header.Set("Signature", signature) ctx.Request.Header.Set("Date", dateHeader) @@ -199,7 +199,7 @@ func (suite *InboxPostTestSuite) TestPostUnblock() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodPost, targetURI.String(), body) // the endpoint we're hitting ctx.Request.Header.Set("Signature", signature) ctx.Request.Header.Set("Date", dateHeader) @@ -292,7 +292,7 @@ func (suite *InboxPostTestSuite) TestPostUpdate() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodPost, targetURI.String(), body) // the endpoint we're hitting ctx.Request.Header.Set("Signature", signature) ctx.Request.Header.Set("Date", dateHeader) @@ -416,7 +416,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodPost, targetURI.String(), body) // the endpoint we're hitting ctx.Request.Header.Set("Signature", signature) ctx.Request.Header.Set("Date", dateHeader) diff --git a/internal/api/s2s/user/outboxget_test.go b/internal/api/s2s/user/outboxget_test.go index c5fb9e6ed..58b57954c 100644 --- a/internal/api/s2s/user/outboxget_test.go +++ b/internal/api/s2s/user/outboxget_test.go @@ -57,7 +57,7 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.OutboxURI, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) @@ -115,7 +115,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.OutboxURI+"?page=true", nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) @@ -173,7 +173,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.OutboxURI+"?page=true&max_id=01F8MHAMCHF6Y650WCRSCP4WMY", nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) diff --git a/internal/api/s2s/user/repliesget_test.go b/internal/api/s2s/user/repliesget_test.go index 6c2913057..07a3f2788 100644 --- a/internal/api/s2s/user/repliesget_test.go +++ b/internal/api/s2s/user/repliesget_test.go @@ -60,7 +60,7 @@ func (suite *RepliesGetTestSuite) TestGetReplies() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetStatus.URI+"/replies", nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) @@ -124,7 +124,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetStatus.URI+"/replies?only_other_accounts=false&page=true", nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) @@ -191,7 +191,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetStatus.URI+"/replies?only_other_accounts=false&page=true&min_id=01FF25D5Q0DH7CHD57CTRS6WK0", nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) diff --git a/internal/api/s2s/user/statusget_test.go b/internal/api/s2s/user/statusget_test.go index 29f93fd71..26fb71818 100644 --- a/internal/api/s2s/user/statusget_test.go +++ b/internal/api/s2s/user/statusget_test.go @@ -59,7 +59,7 @@ func (suite *StatusGetTestSuite) TestGetStatus() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetStatus.URI, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) @@ -127,7 +127,7 @@ func (suite *StatusGetTestSuite) TestGetStatusLowercase() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, strings.ToLower(targetStatus.URI), nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) diff --git a/internal/api/s2s/user/userget_test.go b/internal/api/s2s/user/userget_test.go index f5b15a390..0b242432c 100644 --- a/internal/api/s2s/user/userget_test.go +++ b/internal/api/s2s/user/userget_test.go @@ -60,7 +60,7 @@ func (suite *UserGetTestSuite) TestGetUser() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.URI, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) @@ -141,7 +141,7 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() { // setup request recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.PublicKeyURI, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/activity+json") ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader) diff --git a/internal/api/s2s/webfinger/webfingerget_test.go b/internal/api/s2s/webfinger/webfingerget_test.go index 781fad027..3e91b8f6a 100644 --- a/internal/api/s2s/webfinger/webfingerget_test.go +++ b/internal/api/s2s/webfinger/webfingerget_test.go @@ -26,7 +26,6 @@ import ( "net/http/httptest" "testing" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" @@ -49,7 +48,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUser() { requestPath := fmt.Sprintf("/%s?resource=acct:%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, host) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, requestPath, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") @@ -86,7 +85,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHo requestPath := fmt.Sprintf("/%s?resource=acct:%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, host) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, requestPath, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") @@ -123,7 +122,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByAc requestPath := fmt.Sprintf("/%s?resource=acct:%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, accountDomain) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, requestPath, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") @@ -149,7 +148,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithoutAcct() { requestPath := fmt.Sprintf("/%s?resource=%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, host) recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ := testrig.CreateGinTestContext(recorder, nil) ctx.Request = httptest.NewRequest(http.MethodGet, requestPath, nil) // the endpoint we're hitting ctx.Request.Header.Set("accept", "application/json") diff --git a/internal/log/log.go b/internal/log/log.go index 7ffa31c99..67dd03606 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -41,12 +41,6 @@ func Initialize() error { out := SplitErrOutputs(os.Stdout, os.Stderr) logrus.SetOutput(out) - logrus.SetFormatter(&logrus.TextFormatter{ - DisableColors: true, - DisableQuote: true, - FullTimestamp: true, - }) - // check if a desired log level has been set if lvl := config.GetLogLevel(); lvl != "" { level, err := logrus.ParseLevel(lvl) @@ -60,6 +54,18 @@ func Initialize() error { } } + // set our custom formatter options + logrus.SetFormatter(&logrus.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + + // By default, quoting is enabled to help differentiate key-value + // fields in log lines. But when debug (or higher, e.g. trace) logging + // is enabled, we disable this. This allows easier copy-pasting of + // entry fields without worrying about escaped quotes. + DisableQuote: logrus.GetLevel() >= logrus.DebugLevel, + }) + // check if syslog has been enabled, and configure it if so if config.GetSyslogEnabled() { protocol := config.GetSyslogProtocol() diff --git a/internal/router/logger.go b/internal/router/logger.go index 7a92da8f1..692a616a4 100644 --- a/internal/router/logger.go +++ b/internal/router/logger.go @@ -19,55 +19,78 @@ package router import ( + "fmt" "net/http" + "os" "time" + "codeberg.org/gruf/go-bytesize" + "codeberg.org/gruf/go-errors/v2" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) -var skipPaths = map[string]interface{}{ - "/api/v1/streaming": nil, -} +// loggingMiddleware provides a request logging and panic recovery gin handler. +func loggingMiddleware(c *gin.Context) { + // Initialize the logging fields + fields := make(logrus.Fields, 7) -func loggingMiddleware() gin.HandlerFunc { - logHandler := func(c *gin.Context) { - start := time.Now() + // Determine pre-handler time + before := time.Now() + + defer func() { + code := c.Writer.Status() path := c.Request.URL.Path - raw := c.Request.URL.RawQuery - // Process request - c.Next() - - // Log only when path is not being skipped - if _, ok := skipPaths[path]; !ok { - latency := time.Since(start) - clientIP := c.ClientIP() - userAgent := c.Request.UserAgent() - method := c.Request.Method - statusCode := c.Writer.Status() - errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String() - bodySize := c.Writer.Size() - if raw != "" { - path = path + "?" + raw + if r := recover(); r != nil { + if c.Writer.Status() == 0 { + // No response was written, send a generic Internal Error + c.Writer.WriteHeader(http.StatusInternalServerError) } - l := logrus.WithFields(logrus.Fields{ - "latency": latency, - "clientIP": clientIP, - "userAgent": userAgent, - "method": method, - "statusCode": statusCode, - "path": path, - }) + // Append panic information to the request ctx + _ = c.Error(fmt.Errorf("recovered panic: %v", r)) - if errorMessage == "" { - l.Infof("[%s] %s: wrote %d bytes", latency, http.StatusText(statusCode), bodySize) - } else { - l.Errorf("[%s] %s: %s", latency, http.StatusText(statusCode), errorMessage) + // Dump a stacktrace to stderr + callers := errors.GetCallers(3, 10) + fmt.Fprintf(os.Stderr, "recovered panic: %v\n%s", r, callers) + } + + // NOTE: + // It is very important here that we are ONLY logging + // the request path, and none of the query parameters. + // Query parameters can contain sensitive information + // and could lead to storing plaintext API keys in logs + + // Set request logging fields + fields["latency"] = time.Since(before) + fields["clientIP"] = c.ClientIP() + fields["userAgent"] = c.Request.UserAgent() + fields["method"] = c.Request.Method + fields["statusCode"] = code + fields["path"] = path + + // Create a log entry with fields + l := logrus.WithFields(fields) + l.Level = logrus.InfoLevel + + if code >= 500 { + // This is a server error + l.Level = logrus.ErrorLevel + + if len(c.Errors) > 0 { + // Add an error string log field + fields["error"] = c.Errors.String() } } - } - return logHandler + // Generate a nicer looking bytecount + size := bytesize.Size(c.Writer.Size()) + + // Finally, write log entry with status text body size + l.Logf(l.Level, "%s: wrote %s", http.StatusText(code), size) + }() + + // Process request + c.Next() } diff --git a/internal/router/router.go b/internal/router/router.go index ee7024cd1..3917de314 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -143,9 +143,7 @@ func New(ctx context.Context, db db.DB) (Router, error) { // create the actual engine here -- this is the core request routing handler for gts engine := gin.New() - - engine.Use(gin.RecoveryWithWriter(logrus.StandardLogger().Writer())) - engine.Use(loggingMiddleware()) + engine.Use(loggingMiddleware) // 8 MiB engine.MaxMultipartMemory = 8 << 20 @@ -175,7 +173,7 @@ func New(ctx context.Context, db db.DB) (Router, error) { LoadTemplateFunctions(engine) // load templates onto the engine - if err := loadTemplates(engine); err != nil { + if err := LoadTemplates(engine); err != nil { return nil, err } diff --git a/internal/router/template.go b/internal/router/template.go index c033c8f07..ebd8629e8 100644 --- a/internal/router/template.go +++ b/internal/router/template.go @@ -31,7 +31,7 @@ import ( ) // LoadTemplates loads html templates for use by the given engine -func loadTemplates(engine *gin.Engine) error { +func LoadTemplates(engine *gin.Engine) error { templateBaseDir := config.GetWebTemplateBaseDir() if templateBaseDir == "" { return fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.WebTemplateBaseDirFlag()) diff --git a/testrig/gin.go b/testrig/gin.go new file mode 100644 index 000000000..853dfdd06 --- /dev/null +++ b/testrig/gin.go @@ -0,0 +1,37 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 . +*/ + +package testrig + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/superseriousbusiness/gotosocial/internal/router" +) + +// CreateGinTextContext creates a new gin.Context suitable for a test, with an instantiated gin.Engine. +func CreateGinTestContext(rw http.ResponseWriter, r *http.Request) (*gin.Context, *gin.Engine) { + ctx, eng := gin.CreateTestContext(rw) + router.LoadTemplateFunctions(eng) + if err := router.LoadTemplates(eng); err != nil { + panic(err) + } + ctx.Request = r + return ctx, eng +} diff --git a/vendor/codeberg.org/gruf/go-bytesize/LICENSE b/vendor/codeberg.org/gruf/go-bytesize/LICENSE new file mode 100644 index 000000000..b7c4417ac --- /dev/null +++ b/vendor/codeberg.org/gruf/go-bytesize/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2021 gruf + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/codeberg.org/gruf/go-bytesize/README.md b/vendor/codeberg.org/gruf/go-bytesize/README.md new file mode 100644 index 000000000..46a0457ca --- /dev/null +++ b/vendor/codeberg.org/gruf/go-bytesize/README.md @@ -0,0 +1,3 @@ +Byte size formatting and parsing. + +todo: more robust tests \ No newline at end of file diff --git a/vendor/codeberg.org/gruf/go-bytesize/bytesize.go b/vendor/codeberg.org/gruf/go-bytesize/bytesize.go new file mode 100644 index 000000000..c2fedc8d7 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-bytesize/bytesize.go @@ -0,0 +1,306 @@ +package bytesize + +import ( + "errors" + "math/bits" + "unsafe" +) + +var ( + // ErrInvalidUnit is returned when an invalid IEC/SI is provided. + ErrInvalidUnit = errors.New("bytesize: invalid unit") + + // ErrInvalidFormat is returned when an invalid size value is provided. + ErrInvalidFormat = errors.New("bytesize: invalid format") + + // bunits are the binary unit chars. + units = `kMGTPE` + + // iecpows is a precomputed table of 1024^n. + iecpows = [...]float64{ + float64(1024), // KiB + float64(1024 * 1024), // MiB + float64(1024 * 1024 * 1024), // GiB + float64(1024 * 1024 * 1024 * 1024), // TiB + float64(1024 * 1024 * 1024 * 1024 * 1024), // PiB + float64(1024 * 1024 * 1024 * 1024 * 1024 * 1024), // EiB + } + + // sipows is a precomputed table of 1000^n. + sipows = [...]float64{ + float64(1e3), // KB + float64(1e6), // MB + float64(1e9), // GB + float64(1e12), // TB + float64(1e15), // PB + float64(1e18), // EB + } + + // bvals is a precomputed table of IEC unit values. + iecvals = [...]uint64{ + 'k': 1024, + 'K': 1024, + 'M': 1024 * 1024, + 'G': 1024 * 1024 * 1024, + 'T': 1024 * 1024 * 1024 * 1024, + 'P': 1024 * 1024 * 1024 * 1024 * 1024, + 'E': 1024 * 1024 * 1024 * 1024 * 1024 * 1024, + } + + // sivals is a precomputed table of SI unit values. + sivals = [...]uint64{ + // ASCII numbers _aren't_ valid SI unit values, + // BUT if the space containing a possible unit + // char is checked with this table -- it is valid + // to provide no unit char so unit=1 works. + '0': 1, + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + '6': 1, + '7': 1, + '8': 1, + '9': 1, + + 'k': 1e3, + 'M': 1e6, + 'G': 1e9, + 'T': 1e12, + 'P': 1e15, + 'E': 1e18, + } +) + +// Size is a casting for uint64 types that provides formatting +// methods for byte sizes in both IEC and SI units. +type Size uint64 + +// ParseSize will parse a valid Size from given string. Both IEC and SI units are supported. +func ParseSize(s string) (Size, error) { + // Parse units from string + unit, l, err := parseUnit(s) + if err != nil { + return 0, err + } + + // Parse remaining string as float + f, n, err := atof64(s[:l]) + if err != nil || n != l { + return 0, ErrInvalidFormat + } + + return Size(uint64(f) * unit), nil +} + +// AppendFormat defaults to using Size.AppendFormatIEC(). +func (sz Size) AppendFormat(dst []byte) []byte { + return sz.AppendFormatIEC(dst) // default +} + +// AppendFormatSI will append SI formatted size to 'dst'. +func (sz Size) AppendFormatSI(dst []byte) []byte { + if uint64(sz) < 1000 { + dst = itoa(dst, uint64(sz)) + dst = append(dst, 'B') + return dst + } // above is fast-path, .appendFormat() is outlined + return sz.appendFormat(dst, 1000, &sipows, "B") +} + +// AppendFormatIEC will append IEC formatted size to 'dst'. +func (sz Size) AppendFormatIEC(dst []byte) []byte { + if uint64(sz) < 1024 { + dst = itoa(dst, uint64(sz)) + dst = append(dst, 'B') + return dst + } // above is fast-path, .appendFormat() is outlined + return sz.appendFormat(dst, 1024, &iecpows, "iB") +} + +// appendFormat will append formatted Size to 'dst', depending on base, powers table and single unit suffix. +func (sz Size) appendFormat(dst []byte, base uint64, pows *[6]float64, sunit string) []byte { + const min = 0.75 + + // Larger number: get value of + // i / unit size. We have a 'min' + // threshold after which we prefer + // using the unit 1 down + n := bits.Len64(uint64(sz)) / 10 + f := float64(sz) / pows[n-1] + if f < min { + f *= float64(base) + n-- + } + + // Append formatted float with units + dst = ftoa(dst, f) + dst = append(dst, units[n-1]) + dst = append(dst, sunit...) + return dst +} + +// StringSI returns an SI unit string format of Size. +func (sz Size) StringSI() string { + b := sz.AppendFormatSI(make([]byte, 0, 6)) + return *(*string)(unsafe.Pointer(&b)) +} + +// StringIEC returns an IEC unit string format of Size. +func (sz Size) StringIEC() string { + b := sz.AppendFormatIEC(make([]byte, 0, 7)) + return *(*string)(unsafe.Pointer(&b)) +} + +// String returns a string format of Size, defaults to IEC unit format. +func (sz Size) String() string { + return sz.StringIEC() +} + +// parseUnit will parse the byte size unit from string 's'. +func parseUnit(s string) (uint64, int, error) { + var isIEC bool + + // Check for string + if len(s) < 1 { + return 0, 0, ErrInvalidFormat + } + + // Strip 'byte' unit suffix + if l := len(s) - 1; s[l] == 'B' { + s = s[:l] + + // Check str remains + if len(s) < 1 { + return 0, 0, ErrInvalidFormat + } + } + + // Strip IEC binary unit suffix + if l := len(s) - 1; s[l] == 'i' { + s = s[:l] + isIEC = true + + // Check str remains + if len(s) < 1 { + return 0, 0, ErrInvalidFormat + } + } + + // Location of unit char. + l := len(s) - 1 + + var unit uint64 + switch c := int(s[l]); { + // Determine IEC unit in use + case isIEC && c < len(iecvals): + unit = iecvals[c] + if unit == 0 { + return 0, 0, ErrInvalidUnit + } + + // Determine SI unit in use + case c < len(sivals): + unit = sivals[c] + switch unit { + case 0: + return 0, 0, ErrInvalidUnit + case 1: + l++ + } + } + + return unit, l, nil +} + +// ftoa appends string formatted 'f' to 'dst', assumed < ~800. +func ftoa(dst []byte, f float64) []byte { + switch i := uint64(f); { + // Append with 2 d.p. + case i < 10: + f *= 10 + + // Calculate next dec. value + d1 := uint8(uint64(f) % 10) + + f *= 10 + + // Calculate next dec. value + d2 := uint8(uint64(f) % 10) + + // Round the final value + if uint64(f*10)%10 > 4 { + d2++ + + // Overflow, incr 'd1' + if d2 == 10 { + d2 = 0 + d1++ + + // Overflow, incr 'i' + if d1 == 10 { + d1 = 0 + i++ + } + } + } + + // Append decimal value + dst = itoa(dst, i) + dst = append(dst, + '.', + '0'+d1, + '0'+d2, + ) + + // Append with 1 d.p. + case i < 100: + f *= 10 + + // Calculate next dec. value + d1 := uint8(uint64(f) % 10) + + // Round the final value + if uint64(f*10)%10 > 4 { + d1++ + + // Overflow, incr 'i' + if d1 == 10 { + d1 = 0 + i++ + } + } + + // Append decimal value + dst = itoa(dst, i) + dst = append(dst, '.', '0'+d1) + + // No decimal places + default: + dst = itoa(dst, i) + } + + return dst +} + +// itoa appends string formatted 'i' to 'dst'. +func itoa(dst []byte, i uint64) []byte { + // Assemble int in reverse order. + var b [4]byte + bp := len(b) - 1 + + // Append integer + for i >= 10 { + q := i / 10 + b[bp] = byte('0' + i - q*10) + bp-- + i = q + } // i < 10 + b[bp] = byte('0' + i) + + return append(dst, b[bp:]...) +} + +//go:linkname atof64 strconv.atof64 +func atof64(string) (float64, int, error) diff --git a/vendor/modules.txt b/vendor/modules.txt index 99a3b0d8a..f1e143df8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,6 +7,9 @@ codeberg.org/gruf/go-bitutil # codeberg.org/gruf/go-bytes v1.0.2 ## explicit; go 1.14 codeberg.org/gruf/go-bytes +# codeberg.org/gruf/go-bytesize v0.2.1 +## explicit; go 1.17 +codeberg.org/gruf/go-bytesize # codeberg.org/gruf/go-byteutil v1.0.2 ## explicit; go 1.16 codeberg.org/gruf/go-byteutil