From d11caa02b8c8947b68abccfd65f95ff53f9d20e8 Mon Sep 17 00:00:00 2001 From: Daniel Bae <157205701+MrBeldum@users.noreply.github.com> Date: Mon, 5 Oct 2026 14:37:18 -0700 Subject: [PATCH] fix: call BlackWordsFn list func per request instead of once at setup --- blackwords.go | 17 +++++++++++------ blackwords_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/blackwords.go b/blackwords.go index 86ad9d7..5a6b6e2 100644 --- a/blackwords.go +++ b/blackwords.go @@ -9,6 +9,16 @@ import ( // BlackWords middleware doesn't allow some words in the request body func BlackWords(words ...string) func(http.Handler) http.Handler { + return blackWords(func() []string { return words }) +} + +// BlackWordsFn middleware uses func to get the list and doesn't allow some words in the request body. +// The func is called for every request, so the list can be changed at runtime. +func BlackWordsFn(fn func() []string) func(http.Handler) http.Handler { + return blackWords(fn) +} + +func blackWords(wordsFn func() []string) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { @@ -23,7 +33,7 @@ func BlackWords(words ...string) func(http.Handler) http.Handler { body := strings.ToLower(string(content)) if body != "" { - for _, word := range words { + for _, word := range wordsFn() { if strings.Contains(body, strings.ToLower(word)) { _ = EncodeJSON(w, http.StatusForbidden, JSON{"error": "one of blacklisted words detected"}) return @@ -35,8 +45,3 @@ func BlackWords(words ...string) func(http.Handler) http.Handler { return http.HandlerFunc(fn) } } - -// BlackWordsFn middleware uses func to get the list and doesn't allow some words in the request body -func BlackWordsFn(fn func() []string) func(http.Handler) http.Handler { - return BlackWords(fn()...) -} diff --git a/blackwords_test.go b/blackwords_test.go index ab6ee21..d70edb6 100644 --- a/blackwords_test.go +++ b/blackwords_test.go @@ -6,10 +6,12 @@ import ( "fmt" "net/http" "net/http/httptest" + "sync" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBlackwords(t *testing.T) { @@ -124,3 +126,36 @@ func TestBlackwordsUnreadableBody(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) assert.False(t, called, "handler must not run on a body that can't be inspected") } + +func TestBlackwordsFnDynamic(t *testing.T) { + var mu sync.Mutex + words := []string{"bad1"} + bwMiddleware := BlackWordsFn(func() []string { + mu.Lock() + defer mu.Unlock() + return words + }) + + ts := httptest.NewServer(bwMiddleware(getTestHandlerBlah())) + defer ts.Close() + + send := func(body string) int { + req, err := http.NewRequest("POST", ts.URL+"/something", bytes.NewBufferString(body)) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + return resp.StatusCode + } + + assert.Equal(t, http.StatusForbidden, send("has bad1 in it")) + assert.Equal(t, http.StatusOK, send("has bad2 in it")) + + // the list is changed after the middleware was created, it should be picked up by the next request + mu.Lock() + words = []string{"bad2"} + mu.Unlock() + + assert.Equal(t, http.StatusOK, send("has bad1 in it")) + assert.Equal(t, http.StatusForbidden, send("has bad2 in it")) +}