diff --git a/ftwhttp/header.go b/ftwhttp/header.go index cb593321..abe80cb4 100644 --- a/ftwhttp/header.go +++ b/ftwhttp/header.go @@ -6,6 +6,7 @@ package ftwhttp import ( "bytes" "io" + "net/textproto" "sort" "github.com/rs/zerolog/log" @@ -32,8 +33,8 @@ func (w stringWriter) WriteString(s string) (n int, err error) { return w.w.Write([]byte(s)) } -// Add adds the key, value pair to the header. -// It appends to any existing values associated with key. +// Add adds the (key, value) pair to the headers if it does not exist +// The key is case-insensitive func (h Header) Add(key, value string) { if h.Get(key) == "" { h.Set(key, value) @@ -42,36 +43,33 @@ func (h Header) Add(key, value string) { // Set sets the header entries associated with key to // the single element value. It replaces any existing -// values associated with key. +// values associated with a case-insensitive key. func (h Header) Set(key, value string) { + h.Del(key) h[key] = value } -// Get gets the first value associated with the given key. -// It is case insensitive; +// Get gets the value associated with the given key. // If there are no values associated with the key, Get returns "". +// The key is case-insensitive func (h Header) Get(key string) string { if h == nil { return "" } - v := h[key] + v := h[h.getKeyMatchingCanonicalKey(key)] return v } -// Value returns the value associated with the given key. -// It is case insensitive; +// Value is a wrapper to Get func (h Header) Value(key string) string { - if h == nil { - return "" - } - - return h[key] + return h.Get(key) } // Del deletes the value associated with key. +// The key is case-insensitive func (h Header) Del(key string) { - delete(h, key) + delete(h, h.getKeyMatchingCanonicalKey(key)) } // Write writes a header in wire format. @@ -138,3 +136,20 @@ func (h Header) getSortedHeadersByName() []string { return keys } + +// getKeyMatchingCanonicalKey finds a key matching with the given one, provided both are canonicalised +func (h Header) getKeyMatchingCanonicalKey(searchKey string) string { + searchKey = canonicalKey(searchKey) + for k := range h { + if searchKey == canonicalKey(k) { + return k + } + } + + return "" +} + +// canonicalKey transforms given to the canonical form +func canonicalKey(key string) string { + return textproto.CanonicalMIMEHeaderKey(key) +} diff --git a/ftwhttp/header_test.go b/ftwhttp/header_test.go index 780c1d4f..c9110717 100644 --- a/ftwhttp/header_test.go +++ b/ftwhttp/header_test.go @@ -104,7 +104,7 @@ func (s *headerTestSuite) TestHeaderWriteString() { } } -func (s *headerTestSuite) TestHeaderSetGet() { +func (s *headerTestSuite) TestHeaderAdd() { h := Header{ "Custom": "Value", } @@ -113,6 +113,45 @@ func (s *headerTestSuite) TestHeaderSetGet() { s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value") } +func (s *headerTestSuite) TestHeaderAdd_CaseInsensitiveKey() { + h := Header{} + + h.Add("camel-Header", "Value") + value := h.Get("Camel-Header") + s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value") + + // Header exists, ignore overwriting + h.Add("Camel-Header", "Value2") + value = h.Get("Camel-Header") + s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value") + + // Overwrite header + h.Set("Camel-Header", "Value3") + value = h.Get("Camel-Header") + s.Equalf("Value3", value, "got: %s, want: %s\n", value, "Value3") +} + +func (s *headerTestSuite) TestHeaderGet() { + h := Header{ + "Custom": "Value", + } + value := h.Get("Custom") + s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value") +} + +func (s *headerTestSuite) TestHeaderGet_CaseInsensitiveKey() { + h := Header{ + "Custom": "Value", + "Custom-Header": "Value2", + } + + value := h.Get("Custom") + s.Equalf("Value", value, "got: %s, want: %s\n", value, "Value") + + value = h.Get("custom-header") + s.Equalf("Value2", value, "got: %s, want: %s\n", value, "Value2") +} + func (s *headerTestSuite) TestHeaderDel() { for i, test := range headerWriteTests { // we clone it because we are modifying the original @@ -126,6 +165,14 @@ func (s *headerTestSuite) TestHeaderDel() { } } +func (s *headerTestSuite) TestHeaderDel_CaseInsensitiveKey() { + h := Header{} + h.Add("content-Type", "Value") + h.Del("Content-type") + value := h.Get("Content-Type") + s.Equalf("", value, "#case: got: %s, want: %s\n", value, "") +} + func (s *headerTestSuite) TestHeaderClone() { h := Header{ "Custom": "Value",