From 61eab4db0550b6c02988cdb2a3714049396cbf31 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 21 Sep 2022 08:08:11 -0400 Subject: [PATCH 1/2] chore: Add linter rule to prevent breaking of sse --- scripts/rules.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/scripts/rules.go b/scripts/rules.go index c34ae6ed3bff3..1c0385a655eaa 100644 --- a/scripts/rules.go +++ b/scripts/rules.go @@ -8,16 +8,20 @@ // - https://pkg.go.dev/github.com/quasilyte/go-ruleguard/dsl // // You run one of the following commands to execute your go rules only: -// golangci-lint run -// golangci-lint run --disable-all --enable=gocritic +// +// golangci-lint run +// golangci-lint run --disable-all --enable=gocritic +// // Note: don't forget to run `golangci-lint cache clean`! package gorules import ( "github.com/quasilyte/go-ruleguard/dsl" + "github.com/quasilyte/go-ruleguard/dsl/types" ) // Use xerrors everywhere! It provides additional stacktrace info! +// //nolint:unused,deadcode,varnamelen func xerrors(m dsl.Matcher) { m.Import("errors") @@ -35,6 +39,7 @@ func xerrors(m dsl.Matcher) { } // databaseImport enforces not importing any database types into /codersdk. +// //nolint:unused,deadcode,varnamelen func databaseImport(m dsl.Matcher) { m.Import("github.com/coder/coder/coderd/database") @@ -46,6 +51,7 @@ func databaseImport(m dsl.Matcher) { // doNotCallTFailNowInsideGoroutine enforces not calling t.FailNow or // functions that may themselves call t.FailNow in goroutines outside // the main test goroutine. See testing.go:834 for why. +// //nolint:unused,deadcode,varnamelen func doNotCallTFailNowInsideGoroutine(m dsl.Matcher) { m.Import("testing") @@ -84,6 +90,7 @@ func doNotCallTFailNowInsideGoroutine(m dsl.Matcher) { // useStandardTimeoutsAndDelaysInTests ensures all tests use common // constants for timeouts and delays in usual scenarios, this allows us // to tweak them based on platform (important to avoid CI flakes). +// //nolint:unused,deadcode,varnamelen func useStandardTimeoutsAndDelaysInTests(m dsl.Matcher) { m.Import("github.com/stretchr/testify/require") @@ -232,3 +239,30 @@ func ProperRBACReturn(m dsl.Matcher) { } `).Report("Must write to 'ResponseWriter' before returning'") } + +// FullResponseWriter ensures that any overridden response writer has full +// functionality. Mainly is hijackable and flushable. +func FullResponseWriter(m dsl.Matcher) { + m.Match(` + type $w struct { + $*_ + http.ResponseWriter + $*_ + } + `). + At(m["w"]). + Where(m["w"].Filter(notImplementsFullResponseWriter)). + Report("ResponseWriter \"$w\" must implement http.Flusher and http.Hijacker") +} + +// notImplementsFullResponseWriter returns false if the type does not implement +// http.Flusher, http.Hijacker, and http.ResponseWriter. +func notImplementsFullResponseWriter(ctx *dsl.VarFilterContext) bool { + flusher := ctx.GetInterface(`net/http.Flusher`) + hijacker := ctx.GetInterface(`net/http.Hijacker`) + writer := ctx.GetInterface(`net/http.ResponseWriter`) + p := types.NewPointer(ctx.Type) + return !(types.Implements(types.NewPointer(ctx.Type), writer) || types.Implements(ctx.Type, writer)) || + !(types.Implements(types.NewPointer(ctx.Type), flusher) || types.Implements(ctx.Type, flusher)) || + !(types.Implements(p, hijacker) || types.Implements(ctx.Type, hijacker)) +} From e6025e30a067ae36108c42f8357146a154ed6c01 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 21 Sep 2022 08:12:22 -0400 Subject: [PATCH 2/2] Simplify slightly, reuse pointer --- scripts/rules.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/rules.go b/scripts/rules.go index 1c0385a655eaa..38abb44e8107a 100644 --- a/scripts/rules.go +++ b/scripts/rules.go @@ -262,7 +262,7 @@ func notImplementsFullResponseWriter(ctx *dsl.VarFilterContext) bool { hijacker := ctx.GetInterface(`net/http.Hijacker`) writer := ctx.GetInterface(`net/http.ResponseWriter`) p := types.NewPointer(ctx.Type) - return !(types.Implements(types.NewPointer(ctx.Type), writer) || types.Implements(ctx.Type, writer)) || - !(types.Implements(types.NewPointer(ctx.Type), flusher) || types.Implements(ctx.Type, flusher)) || + return !(types.Implements(p, writer) || types.Implements(ctx.Type, writer)) || + !(types.Implements(p, flusher) || types.Implements(ctx.Type, flusher)) || !(types.Implements(p, hijacker) || types.Implements(ctx.Type, hijacker)) }