mirror of
https://github.com/go-gost/x.git
synced 2026-10-19 16:16:32 +00:00
feat(forwarder): delete JSON body fields via json-: rewrite match
Add a json-: match prefix to http.rewriteRequestBody/rewriteResponseBody that deletes the matched JSON field (sjson.DeleteBytes) instead of replacing it, keeping the existing value-regex conditional semantics.
This commit is contained in:
@@ -48,8 +48,16 @@ func parseBodyRewrites(vs []config.HTTPBodyRewriteConfig, log logger.Logger) []c
|
||||
var pattern *regexp.Regexp
|
||||
var rewriteType string
|
||||
|
||||
if js, ok := strings.CutPrefix(v.Match, "json:"); ok {
|
||||
// json:<path> or json:<path>=<value-regex>
|
||||
js, ok := strings.CutPrefix(v.Match, "json:")
|
||||
del := false
|
||||
if !ok {
|
||||
// json-:<path> deletes the matched JSON field instead of replacing it.
|
||||
if js, ok = strings.CutPrefix(v.Match, "json-:"); ok {
|
||||
del = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
// json:<path>[=<value-regex>] or json-:<path>[=<value-regex>]
|
||||
path, valRegex, _ := strings.Cut(js, "=")
|
||||
if valRegex == "" {
|
||||
valRegex = ".*"
|
||||
@@ -60,7 +68,11 @@ func parseBodyRewrites(vs []config.HTTPBodyRewriteConfig, log logger.Logger) []c
|
||||
log.Warnf("invalid JSON value regex %q for path %q: %v", valRegex, path, err)
|
||||
continue
|
||||
}
|
||||
rewriteType = "json:" + path
|
||||
if del {
|
||||
rewriteType = "json-:" + path
|
||||
} else {
|
||||
rewriteType = "json:" + path
|
||||
}
|
||||
} else {
|
||||
if v.Match != "" {
|
||||
pattern, _ = regexp.Compile(v.Match)
|
||||
|
||||
@@ -497,10 +497,22 @@ func (b *rewriteBody) apply(body []byte, opts ...rewriter.RewriteOption) ([]byte
|
||||
body = rewritten
|
||||
}
|
||||
} else if rw.Pattern != nil {
|
||||
if strings.HasPrefix(rw.Type, "json:") {
|
||||
if strings.HasPrefix(rw.Type, "json:") || strings.HasPrefix(rw.Type, "json-:") {
|
||||
del := strings.HasPrefix(rw.Type, "json-:")
|
||||
path := rw.Type[5:]
|
||||
if del {
|
||||
path = rw.Type[6:]
|
||||
}
|
||||
if rw.Pattern.MatchString(gjson.GetBytes(body, path).String()) {
|
||||
replaced, err := sjson.SetBytes(body, path, string(rw.Replacement))
|
||||
var (
|
||||
replaced []byte
|
||||
err error
|
||||
)
|
||||
if del {
|
||||
replaced, err = sjson.DeleteBytes(body, path)
|
||||
} else {
|
||||
replaced, err = sjson.SetBytes(body, path, string(rw.Replacement))
|
||||
}
|
||||
if err == nil {
|
||||
body = replaced
|
||||
}
|
||||
@@ -514,9 +526,9 @@ func (b *rewriteBody) apply(body []byte, opts ...rewriter.RewriteOption) ([]byte
|
||||
}
|
||||
|
||||
// shouldApply reports whether a rewrite rule's configured type matches the
|
||||
// actual request Content-Type. A type prefix "json:" matches only
|
||||
// "application/json" content. An empty type defaults to "text/html", or "*"
|
||||
// when a Rewriter is set.
|
||||
// actual request Content-Type. JSON rules ("json:" or "json-:" Type prefixes)
|
||||
// match only "application/json" content. An empty type defaults to
|
||||
// "text/html", or "*" when a Rewriter is set.
|
||||
func shouldApply(configuredType, actualContentType string, hasRewriter bool) bool {
|
||||
t := configuredType
|
||||
if t == "" {
|
||||
@@ -526,7 +538,7 @@ func shouldApply(configuredType, actualContentType string, hasRewriter bool) boo
|
||||
t = "text/html"
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(t, "json:") {
|
||||
if strings.HasPrefix(t, "json:") || strings.HasPrefix(t, "json-:") {
|
||||
return actualContentType == "application/json"
|
||||
}
|
||||
return t == "*" || strings.Contains(t, actualContentType)
|
||||
|
||||
@@ -1960,6 +1960,89 @@ func TestNewRewriteBody(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewriteJSONDelete(t *testing.T) {
|
||||
makeRewrite := func(rewriteType, match, replace string) chain.HTTPBodyRewriteSettings {
|
||||
return chain.HTTPBodyRewriteSettings{
|
||||
Pattern: regexp.MustCompile(match),
|
||||
Type: rewriteType,
|
||||
Replacement: []byte(replace),
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete matched field", func(t *testing.T) {
|
||||
src := io.NopCloser(strings.NewReader(`{"user":{"id":123},"keep":1}`))
|
||||
body, err := newRewriteBody(ctx, src, []chain.HTTPBodyRewriteSettings{
|
||||
makeRewrite("json-:user.id", "123", ""),
|
||||
}, "application/json", "", -1, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body == nil {
|
||||
t.Fatal("expected non-nil body")
|
||||
}
|
||||
got, _ := io.ReadAll(body)
|
||||
body.Close()
|
||||
if string(got) != `{"user":{},"keep":1}` {
|
||||
t.Errorf("body = %q, want %q", string(got), `{"user":{},"keep":1}`)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no match leaves body unchanged", func(t *testing.T) {
|
||||
src := io.NopCloser(strings.NewReader(`{"user":{"id":123},"keep":1}`))
|
||||
body, err := newRewriteBody(ctx, src, []chain.HTTPBodyRewriteSettings{
|
||||
makeRewrite("json-:user.id", "^9$", ""),
|
||||
}, "application/json", "", -1, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body == nil {
|
||||
t.Fatal("expected non-nil body")
|
||||
}
|
||||
got, _ := io.ReadAll(body)
|
||||
body.Close()
|
||||
if string(got) != `{"user":{"id":123},"keep":1}` {
|
||||
t.Errorf("body = %q, want %q (unchanged)", string(got), `{"user":{"id":123},"keep":1}`)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-json content type filtered out", func(t *testing.T) {
|
||||
src := io.NopCloser(strings.NewReader(`{"user":{"id":123},"keep":1}`))
|
||||
body, err := newRewriteBody(ctx, src, []chain.HTTPBodyRewriteSettings{
|
||||
makeRewrite("json-:user.id", "123", ""),
|
||||
}, "text/plain", "", -1, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body == nil {
|
||||
t.Fatal("expected non-nil body")
|
||||
}
|
||||
got, _ := io.ReadAll(body)
|
||||
body.Close()
|
||||
if string(got) != `{"user":{"id":123},"keep":1}` {
|
||||
t.Errorf("body = %q, want %q (unchanged)", string(got), `{"user":{"id":123},"keep":1}`)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("json: prefix still replaces", func(t *testing.T) {
|
||||
src := io.NopCloser(strings.NewReader(`{"user":{"id":123},"keep":1}`))
|
||||
body, err := newRewriteBody(ctx, src, []chain.HTTPBodyRewriteSettings{
|
||||
makeRewrite("json:user.id", "123", "999"),
|
||||
}, "application/json", "", -1, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body == nil {
|
||||
t.Fatal("expected non-nil body")
|
||||
}
|
||||
got, _ := io.ReadAll(body)
|
||||
body.Close()
|
||||
if string(got) != `{"user":{"id":"999"},"keep":1}` {
|
||||
t.Errorf("body = %q, want %q", string(got), `{"user":{"id":"999"},"keep":1}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewriteRespBody_CompressedEncodings(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user