From 47bb9f9c09385c34c3aa2f42c1ac6935fb9802da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Wed, 18 Sep 2024 12:16:03 +0200 Subject: [PATCH 1/3] feature: header encoder filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- filters/builtin/builtin.go | 2 + filters/builtin/header_encode.go | 112 +++++++++++++++++++++++++++++++ filters/filters.go | 2 + 3 files changed, 116 insertions(+) create mode 100644 filters/builtin/header_encode.go diff --git a/filters/builtin/builtin.go b/filters/builtin/builtin.go index 1003756c60..d51974e7af 100644 --- a/filters/builtin/builtin.go +++ b/filters/builtin/builtin.go @@ -139,6 +139,8 @@ func Filters() []filters.Spec { NewSetPath(), NewModRequestHeader(), NewModResponseHeader(), + NewEncodeRequestHeader(), + NewEncodeResponseHeader(), NewDropQuery(), NewSetQuery(), NewHealthCheck(), diff --git a/filters/builtin/header_encode.go b/filters/builtin/header_encode.go new file mode 100644 index 0000000000..cd57f26ac1 --- /dev/null +++ b/filters/builtin/header_encode.go @@ -0,0 +1,112 @@ +package builtin + +import ( + log "github.com/sirupsen/logrus" + "github.com/zalando/skipper/filters" + xencoding "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" +) + +type encodeTyp int + +const ( + requestEncoder encodeTyp = iota + 1 + responseEncoder +) + +type encodeHeaderSpec struct { + typ encodeTyp +} + +type encodeHeader struct { + typ encodeTyp + header string + encoder *xencoding.Encoder +} + +func NewEncodeRequestHeader() *encodeHeaderSpec { + return &encodeHeaderSpec{ + typ: requestEncoder, + } +} +func NewEncodeResponseHeader() *encodeHeaderSpec { + return &encodeHeaderSpec{ + typ: responseEncoder, + } +} + +func (spec *encodeHeaderSpec) Name() string { + switch spec.typ { + case requestEncoder: + return filters.EncodeRequestHeaderName + case responseEncoder: + return filters.EncodeResponseHeaderName + } + return "unknown" +} + +func (spec *encodeHeaderSpec) CreateFilter(args []interface{}) (filters.Filter, error) { + if len(args) != 2 { + return nil, filters.ErrInvalidFilterParameters + } + + header, ok := args[0].(string) + if !ok { + return nil, filters.ErrInvalidFilterParameters + } + to, ok := args[1].(string) + if !ok { + return nil, filters.ErrInvalidFilterParameters + } + + var ( + encoder *xencoding.Encoder + ) + + switch to { + case "ISO8859_1": + encoder = charmap.ISO8859_1.NewEncoder() + case "Windows1252": + encoder = charmap.Windows1252.NewEncoder() + } + + return &encodeHeader{ + typ: spec.typ, + header: header, + encoder: encoder, + }, nil +} + +func (f *encodeHeader) Request(ctx filters.FilterContext) { + if f.typ != requestEncoder { + return + } + + s := ctx.Request().Header.Get(f.header) + if s == "" { + return + } + + sNew, err := f.encoder.String(s) + if err != nil { + log.Errorf("Failed to encode %q: %v", s, err) + } + ctx.Request().Header.Set(f.header, sNew) +} + +func (f *encodeHeader) Response(ctx filters.FilterContext) { + if f.typ != responseEncoder { + return + } + s := ctx.Response().Header.Get(f.header) + if s == "" { + return + } + + sNew, err := f.encoder.String(s) + if err != nil { + log.Errorf("Failed to encode %q: %v", s, err) + } + ctx.Response().Header.Set(f.header, sNew) + +} diff --git a/filters/filters.go b/filters/filters.go index 358e666b17..82cbeb77d9 100644 --- a/filters/filters.go +++ b/filters/filters.go @@ -230,6 +230,8 @@ const ( AppendContextResponseHeaderName = "appendContextResponseHeader" CopyRequestHeaderName = "copyRequestHeader" CopyResponseHeaderName = "copyResponseHeader" + EncodeRequestHeaderName = "encodeRequestHeader" + EncodeResponseHeaderName = "encodeResponseHeader" ModPathName = "modPath" SetPathName = "setPath" RedirectToName = "redirectTo" From e88489e483cf25d5ba3cf7d8d35924f78a19316e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Wed, 18 Sep 2024 12:23:29 +0200 Subject: [PATCH 2/3] add all encodings from https://pkg.go.dev/golang.org/x/text@v0.18.0/encoding/charmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- filters/builtin/header_encode.go | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/filters/builtin/header_encode.go b/filters/builtin/header_encode.go index cd57f26ac1..ba6780c1b7 100644 --- a/filters/builtin/header_encode.go +++ b/filters/builtin/header_encode.go @@ -66,8 +66,63 @@ func (spec *encodeHeaderSpec) CreateFilter(args []interface{}) (filters.Filter, switch to { case "ISO8859_1": encoder = charmap.ISO8859_1.NewEncoder() + case "ISO8859_10": + encoder = charmap.ISO8859_10.NewEncoder() + case "ISO8859_13": + encoder = charmap.ISO8859_13.NewEncoder() + case "ISO8859_14": + encoder = charmap.ISO8859_14.NewEncoder() + case "ISO8859_15": + encoder = charmap.ISO8859_15.NewEncoder() + case "ISO8859_16": + encoder = charmap.ISO8859_16.NewEncoder() + case "ISO8859_2": + encoder = charmap.ISO8859_2.NewEncoder() + case "ISO8859_3": + encoder = charmap.ISO8859_3.NewEncoder() + case "ISO8859_4": + encoder = charmap.ISO8859_4.NewEncoder() + case "ISO8859_5": + encoder = charmap.ISO8859_5.NewEncoder() + case "ISO8859_6": + encoder = charmap.ISO8859_6.NewEncoder() + case "ISO8859_7": + encoder = charmap.ISO8859_7.NewEncoder() + case "ISO8859_8": + encoder = charmap.ISO8859_8.NewEncoder() + case "ISO8859_9": + encoder = charmap.ISO8859_9.NewEncoder() + case "KOI8R": + encoder = charmap.KOI8R.NewEncoder() + case "KOI8U": + encoder = charmap.KOI8U.NewEncoder() + case "Macintosh": + encoder = charmap.Macintosh.NewEncoder() + case "MacintoshCyrillic": + encoder = charmap.MacintoshCyrillic.NewEncoder() + case "Windows1250": + encoder = charmap.Windows1250.NewEncoder() + case "Windows1251": + encoder = charmap.Windows1251.NewEncoder() case "Windows1252": encoder = charmap.Windows1252.NewEncoder() + case "Windows1253": + encoder = charmap.Windows1253.NewEncoder() + case "Windows1254": + encoder = charmap.Windows1254.NewEncoder() + case "Windows1255": + encoder = charmap.Windows1255.NewEncoder() + case "Windows1256": + encoder = charmap.Windows1256.NewEncoder() + case "Windows1257": + encoder = charmap.Windows1257.NewEncoder() + case "Windows1258": + encoder = charmap.Windows1258.NewEncoder() + case "Windows874": + encoder = charmap.Windows874.NewEncoder() + default: + log.Errorf("Unknown encoder for %q", to) + return nil, filters.ErrInvalidFilterParameters } return &encodeHeader{ From e6840ef3562d025d8a218a7668e0afedbec6f80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Wed, 18 Sep 2024 16:45:02 +0200 Subject: [PATCH 3/3] add test cases, request header test fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- filters/builtin/header_encode_test.go | 115 ++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 filters/builtin/header_encode_test.go diff --git a/filters/builtin/header_encode_test.go b/filters/builtin/header_encode_test.go new file mode 100644 index 0000000000..7accf71afc --- /dev/null +++ b/filters/builtin/header_encode_test.go @@ -0,0 +1,115 @@ +package builtin + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/zalando/skipper/eskip" + "github.com/zalando/skipper/filters" + "github.com/zalando/skipper/filters/diag" + "github.com/zalando/skipper/proxy/proxytest" + "github.com/zalando/skipper/routing" + "github.com/zalando/skipper/routing/testdataclient" +) + +func Test_encodeRequestHeader(t *testing.T) { + tests := []struct { + name string + doc string + data string + want []byte + }{ + { + name: "test request header Windows1252", + doc: `r: * -> encodeRequestHeader("X-Test", "Windows1252") -> logHeader("request")-> "%s";`, + data: `für`, + want: []byte{102, 252, 114}, //`f\xfcr`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Result", r.Header.Get("Result")) + w.WriteHeader(http.StatusOK) + })) + defer backend.Close() + + r := eskip.MustParse(fmt.Sprintf(tt.doc, backend.URL)) + fr := make(filters.Registry) + fr.Register(NewEncodeRequestHeader()) + fr.Register(diag.NewLogHeader()) + + dc := testdataclient.New(r) + defer dc.Close() + + proxy := proxytest.WithRoutingOptions(fr, routing.Options{ + DataClients: []routing.DataClient{dc}, + }) + defer proxy.Close() + + req, err := http.NewRequest("GET", proxy.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + req.Header.Set("Result", tt.data) + + rsp, err := proxy.Client().Do(req) + if err != nil { + t.Fatalf("Failed to do request: %v", err) + } + defer rsp.Body.Close() + if result := rsp.Header.Get("Result"); result != string(tt.want) { + t.Fatalf("Failed to get %q, got %q", tt.want, result) + } + }) + } +} + +func Test_encodeResponseHeader(t *testing.T) { + tests := []struct { + name string + doc string + data string + want []byte + }{ + { + name: "test response header Windows1252", + doc: `r: * -> encodeResponseHeader("Result", "Windows1252") -> setResponseHeader("Result", "%s") -> ;`, + data: `für`, + want: []byte{102, 252, 114}, //`f\xfcr`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := eskip.MustParse(fmt.Sprintf(tt.doc, tt.data)) + fr := make(filters.Registry) + fr.Register(NewEncodeResponseHeader()) + fr.Register(NewSetResponseHeader()) + + dc := testdataclient.New(r) + defer dc.Close() + + proxy := proxytest.WithRoutingOptions(fr, routing.Options{ + DataClients: []routing.DataClient{dc}, + }) + defer proxy.Close() + + req, err := http.NewRequest("GET", proxy.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + rsp, err := proxy.Client().Do(req) + if err != nil { + t.Fatalf("Failed to do request: %v", err) + } + defer rsp.Body.Close() + if result := rsp.Header.Get("Result"); result != string(tt.want) { + t.Fatalf("Failed to get %q, got %q", tt.want, result) + } + + }) + } +}