Skip to content

Commit 0ccf90d

Browse files
evanjarvindbr8
authored andcommitted
metadata: Use strings.EqualFold for ValueFromIncomingContext (grpc#6743)
1 parent 9c42dca commit 0ccf90d

File tree

2 files changed

+67
-12
lines changed

2 files changed

+67
-12
lines changed

metadata/metadata.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,16 @@ func Join(mds ...MD) MD {
153153
type mdIncomingKey struct{}
154154
type mdOutgoingKey struct{}
155155

156-
// NewIncomingContext creates a new context with incoming md attached.
156+
// NewIncomingContext creates a new context with incoming md attached. md must
157+
// not be modified after calling this function.
157158
func NewIncomingContext(ctx context.Context, md MD) context.Context {
158159
return context.WithValue(ctx, mdIncomingKey{}, md)
159160
}
160161

161162
// NewOutgoingContext creates a new context with outgoing md attached. If used
162163
// in conjunction with AppendToOutgoingContext, NewOutgoingContext will
163-
// overwrite any previously-appended metadata.
164+
// overwrite any previously-appended metadata. md must not be modified after
165+
// calling this function.
164166
func NewOutgoingContext(ctx context.Context, md MD) context.Context {
165167
return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md})
166168
}
@@ -203,7 +205,8 @@ func FromIncomingContext(ctx context.Context) (MD, bool) {
203205
}
204206

205207
// ValueFromIncomingContext returns the metadata value corresponding to the metadata
206-
// key from the incoming metadata if it exists. Key must be lower-case.
208+
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
209+
// manner.
207210
//
208211
// # Experimental
209212
//
@@ -219,17 +222,16 @@ func ValueFromIncomingContext(ctx context.Context, key string) []string {
219222
return copyOf(v)
220223
}
221224
for k, v := range md {
222-
// We need to manually convert all keys to lower case, because MD is a
223-
// map, and there's no guarantee that the MD attached to the context is
224-
// created using our helper functions.
225-
if strings.ToLower(k) == key {
225+
// Case insenitive comparison: MD is a map, and there's no guarantee
226+
// that the MD attached to the context is created using our helper
227+
// functions.
228+
if strings.EqualFold(k, key) {
226229
return copyOf(v)
227230
}
228231
}
229232
return nil
230233
}
231234

232-
// the returned slice must not be modified in place
233235
func copyOf(v []string) []string {
234236
vals := make([]string, len(v))
235237
copy(vals, v)

metadata/metadata_test.go

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,48 @@ func (s) TestDelete(t *testing.T) {
198198
}
199199
}
200200

201+
func (s) TestFromIncomingContext(t *testing.T) {
202+
md := Pairs(
203+
"X-My-Header-1", "42",
204+
)
205+
// Verify that we lowercase if callers directly modify md
206+
md["X-INCORRECT-UPPERCASE"] = []string{"foo"}
207+
ctx := NewIncomingContext(context.Background(), md)
208+
209+
result, found := FromIncomingContext(ctx)
210+
if !found {
211+
t.Fatal("FromIncomingContext must return metadata")
212+
}
213+
expected := MD{
214+
"x-my-header-1": []string{"42"},
215+
"x-incorrect-uppercase": []string{"foo"},
216+
}
217+
if !reflect.DeepEqual(result, expected) {
218+
t.Errorf("FromIncomingContext returned %#v, expected %#v", result, expected)
219+
}
220+
221+
// ensure modifying result does not modify the value in the context
222+
result["new_key"] = []string{"foo"}
223+
result["x-my-header-1"][0] = "mutated"
224+
225+
result2, found := FromIncomingContext(ctx)
226+
if !found {
227+
t.Fatal("FromIncomingContext must return metadata")
228+
}
229+
if !reflect.DeepEqual(result2, expected) {
230+
t.Errorf("FromIncomingContext after modifications returned %#v, expected %#v", result2, expected)
231+
}
232+
}
233+
201234
func (s) TestValueFromIncomingContext(t *testing.T) {
202235
md := Pairs(
203236
"X-My-Header-1", "42",
204237
"X-My-Header-2", "43-1",
205238
"X-My-Header-2", "43-2",
206239
"x-my-header-3", "44",
207240
)
241+
// Verify that we lowercase if callers directly modify md
242+
md["X-INCORRECT-UPPERCASE"] = []string{"foo"}
208243
ctx := NewIncomingContext(context.Background(), md)
209244

210245
for _, test := range []struct {
@@ -227,6 +262,10 @@ func (s) TestValueFromIncomingContext(t *testing.T) {
227262
key: "x-unknown",
228263
want: nil,
229264
},
265+
{
266+
key: "x-incorrect-uppercase",
267+
want: []string{"foo"},
268+
},
230269
} {
231270
v := ValueFromIncomingContext(ctx, test.key)
232271
if !reflect.DeepEqual(v, test.want) {
@@ -348,8 +387,22 @@ func BenchmarkFromIncomingContext(b *testing.B) {
348387
func BenchmarkValueFromIncomingContext(b *testing.B) {
349388
md := Pairs("X-My-Header-1", "42")
350389
ctx := NewIncomingContext(context.Background(), md)
351-
b.ResetTimer()
352-
for n := 0; n < b.N; n++ {
353-
ValueFromIncomingContext(ctx, "x-my-header-1")
354-
}
390+
391+
b.Run("key-found", func(b *testing.B) {
392+
for n := 0; n < b.N; n++ {
393+
result := ValueFromIncomingContext(ctx, "x-my-header-1")
394+
if len(result) != 1 {
395+
b.Fatal("ensures not optimized away")
396+
}
397+
}
398+
})
399+
400+
b.Run("key-not-found", func(b *testing.B) {
401+
for n := 0; n < b.N; n++ {
402+
result := ValueFromIncomingContext(ctx, "key-not-found")
403+
if len(result) != 0 {
404+
b.Fatal("ensures not optimized away")
405+
}
406+
}
407+
})
355408
}

0 commit comments

Comments
 (0)