Skip to content

Commit

Permalink
feat: make header getters case-insensitive (#331)
Browse files Browse the repository at this point in the history
* Make header getters case-insensitive
* Store one value per canonical key
  • Loading branch information
huberts90 authored Jul 16, 2024
1 parent 52092c5 commit 506d93d
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 15 deletions.
43 changes: 29 additions & 14 deletions ftwhttp/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package ftwhttp
import (
"bytes"
"io"
"net/textproto"
"sort"

"github.com/rs/zerolog/log"
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
49 changes: 48 additions & 1 deletion ftwhttp/header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (s *headerTestSuite) TestHeaderWriteString() {
}
}

func (s *headerTestSuite) TestHeaderSetGet() {
func (s *headerTestSuite) TestHeaderAdd() {
h := Header{
"Custom": "Value",
}
Expand All @@ -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
Expand All @@ -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",
Expand Down

0 comments on commit 506d93d

Please sign in to comment.