Skip to content

Commit b610ffd

Browse files
authored
Never encode binary metadata within the metadata map (#1188)
This change ensures consistency for the user when accessing metadata values: they are never encoded except when sent on the wire. Previously, they would appear encoded to client code, but not to server code. As such, this represents a behavior change, but one unlikely to affect user code, as it's unusual to inspect the metadata after setting it.
1 parent 277e90a commit b610ffd

File tree

8 files changed

+113
-127
lines changed

8 files changed

+113
-127
lines changed

metadata/metadata.go

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,79 +36,47 @@
3636
package metadata // import "google.golang.org/grpc/metadata"
3737

3838
import (
39-
"encoding/base64"
4039
"fmt"
4140
"strings"
4241

4342
"golang.org/x/net/context"
4443
)
4544

46-
const (
47-
binHdrSuffix = "-bin"
48-
)
49-
50-
// encodeKeyValue encodes key and value qualified for transmission via gRPC.
51-
// Transmitting binary headers violates HTTP/2 spec.
52-
// TODO(zhaoq): Maybe check if k is ASCII also.
53-
func encodeKeyValue(k, v string) (string, string) {
54-
k = strings.ToLower(k)
55-
if strings.HasSuffix(k, binHdrSuffix) {
56-
val := base64.StdEncoding.EncodeToString([]byte(v))
57-
v = string(val)
58-
}
59-
return k, v
60-
}
61-
62-
// DecodeKeyValue returns the original key and value corresponding to the
63-
// encoded data in k, v.
64-
// If k is a binary header and v contains comma, v is split on comma before decoded,
65-
// and the decoded v will be joined with comma before returned.
45+
// DecodeKeyValue returns k, v, nil. It is deprecated and should not be used.
6646
func DecodeKeyValue(k, v string) (string, string, error) {
67-
if !strings.HasSuffix(k, binHdrSuffix) {
68-
return k, v, nil
69-
}
70-
vvs := strings.Split(v, ",")
71-
for i, vv := range vvs {
72-
val, err := base64.StdEncoding.DecodeString(vv)
73-
if err != nil {
74-
return "", "", err
75-
}
76-
vvs[i] = string(val)
77-
}
78-
return k, strings.Join(vvs, ","), nil
47+
return k, v, nil
7948
}
8049

8150
// MD is a mapping from metadata keys to values. Users should use the following
8251
// two convenience functions New and Pairs to generate MD.
8352
type MD map[string][]string
8453

8554
// New creates a MD from given key-value map.
86-
// Keys are automatically converted to lowercase. And for keys having "-bin" as suffix, their values will be applied Base64 encoding.
55+
// Keys are automatically converted to lowercase.
8756
func New(m map[string]string) MD {
8857
md := MD{}
89-
for k, v := range m {
90-
key, val := encodeKeyValue(k, v)
58+
for k, val := range m {
59+
key := strings.ToLower(k)
9160
md[key] = append(md[key], val)
9261
}
9362
return md
9463
}
9564

9665
// Pairs returns an MD formed by the mapping of key, value ...
9766
// Pairs panics if len(kv) is odd.
98-
// Keys are automatically converted to lowercase. And for keys having "-bin" as suffix, their values will be appplied Base64 encoding.
67+
// Keys are automatically converted to lowercase.
9968
func Pairs(kv ...string) MD {
10069
if len(kv)%2 == 1 {
10170
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
10271
}
10372
md := MD{}
104-
var k string
73+
var key string
10574
for i, s := range kv {
10675
if i%2 == 0 {
107-
k = s
76+
key = strings.ToLower(s)
10877
continue
10978
}
110-
key, val := encodeKeyValue(k, s)
111-
md[key] = append(md[key], val)
79+
md[key] = append(md[key], s)
11280
}
11381
return md
11482
}

metadata/metadata_test.go

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -38,73 +38,20 @@ import (
3838
"testing"
3939
)
4040

41-
const binaryValue = string(128)
42-
43-
func TestEncodeKeyValue(t *testing.T) {
44-
for _, test := range []struct {
45-
// input
46-
kin string
47-
vin string
48-
// output
49-
kout string
50-
vout string
51-
}{
52-
{"key", "abc", "key", "abc"},
53-
{"KEY", "abc", "key", "abc"},
54-
{"key-bin", "abc", "key-bin", "YWJj"},
55-
{"key-bin", binaryValue, "key-bin", "woA="},
56-
} {
57-
k, v := encodeKeyValue(test.kin, test.vin)
58-
if k != test.kout || !reflect.DeepEqual(v, test.vout) {
59-
t.Fatalf("encodeKeyValue(%q, %q) = %q, %q, want %q, %q", test.kin, test.vin, k, v, test.kout, test.vout)
60-
}
61-
}
62-
}
63-
64-
func TestDecodeKeyValue(t *testing.T) {
65-
for _, test := range []struct {
66-
// input
67-
kin string
68-
vin string
69-
// output
70-
kout string
71-
vout string
72-
err error
73-
}{
74-
{"a", "abc", "a", "abc", nil},
75-
{"key-bin", "Zm9vAGJhcg==", "key-bin", "foo\x00bar", nil},
76-
{"key-bin", "woA=", "key-bin", binaryValue, nil},
77-
{"a", "abc,efg", "a", "abc,efg", nil},
78-
{"key-bin", "Zm9vAGJhcg==,Zm9vAGJhcg==", "key-bin", "foo\x00bar,foo\x00bar", nil},
79-
} {
80-
k, v, err := DecodeKeyValue(test.kin, test.vin)
81-
if k != test.kout || !reflect.DeepEqual(v, test.vout) || !reflect.DeepEqual(err, test.err) {
82-
t.Fatalf("DecodeKeyValue(%q, %q) = %q, %q, %v, want %q, %q, %v", test.kin, test.vin, k, v, err, test.kout, test.vout, test.err)
83-
}
84-
}
85-
}
86-
8741
func TestPairsMD(t *testing.T) {
8842
for _, test := range []struct {
8943
// input
9044
kv []string
9145
// output
92-
md MD
93-
size int
46+
md MD
9447
}{
95-
{[]string{}, MD{}, 0},
96-
{[]string{"k1", "v1", "k2-bin", binaryValue}, New(map[string]string{
97-
"k1": "v1",
98-
"k2-bin": binaryValue,
99-
}), 2},
48+
{[]string{}, MD{}},
49+
{[]string{"k1", "v1", "k1", "v2"}, MD{"k1": []string{"v1", "v2"}}},
10050
} {
10151
md := Pairs(test.kv...)
10252
if !reflect.DeepEqual(md, test.md) {
10353
t.Fatalf("Pairs(%v) = %v, want %v", test.kv, md, test.md)
10454
}
105-
if md.Len() != test.size {
106-
t.Fatalf("Pairs(%v) generates md of size %d, want %d", test.kv, md.Len(), test.size)
107-
}
10855
}
10956
}
11057

test/end2end_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,19 @@ import (
7575
var (
7676
// For headers:
7777
testMetadata = metadata.MD{
78-
"key1": []string{"value1"},
79-
"key2": []string{"value2"},
78+
"key1": []string{"value1"},
79+
"key2": []string{"value2"},
80+
"key3-bin": []string{"binvalue1", string([]byte{1, 2, 3})},
8081
}
8182
testMetadata2 = metadata.MD{
8283
"key1": []string{"value12"},
8384
"key2": []string{"value22"},
8485
}
8586
// For trailers:
8687
testTrailerMetadata = metadata.MD{
87-
"tkey1": []string{"trailerValue1"},
88-
"tkey2": []string{"trailerValue2"},
88+
"tkey1": []string{"trailerValue1"},
89+
"tkey2": []string{"trailerValue2"},
90+
"tkey3-bin": []string{"trailerbinvalue1", string([]byte{3, 2, 1})},
8991
}
9092
testTrailerMetadata2 = metadata.MD{
9193
"tkey1": []string{"trailerValue12"},

transport/handler_server.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request) (ServerTr
111111
v = v[:i]
112112
}
113113
}
114+
v, err := decodeMetadataHeader(k, v)
115+
if err != nil {
116+
return nil, streamErrorf(codes.InvalidArgument, "malformed binary metadata: %v", err)
117+
}
114118
metakv = append(metakv, k, v)
115119
}
116120
}
@@ -207,10 +211,9 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
207211
continue
208212
}
209213
for _, v := range vv {
210-
// http2 ResponseWriter mechanism to
211-
// send undeclared Trailers after the
212-
// headers have possibly been written.
213-
h.Add(http2.TrailerPrefix+k, v)
214+
// http2 ResponseWriter mechanism to send undeclared Trailers after
215+
// the headers have possibly been written.
216+
h.Add(http2.TrailerPrefix+k, encodeMetadataHeader(k, v))
214217
}
215218
}
216219
}
@@ -265,6 +268,7 @@ func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error {
265268
continue
266269
}
267270
for _, v := range vv {
271+
v = encodeMetadataHeader(k, v)
268272
h.Add(k, v)
269273
}
270274
}

transport/http2_client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -437,23 +437,23 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
437437
)
438438
if md, ok := metadata.FromOutgoingContext(ctx); ok {
439439
hasMD = true
440-
for k, v := range md {
440+
for k, vv := range md {
441441
// HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set.
442442
if isReservedHeader(k) {
443443
continue
444444
}
445-
for _, entry := range v {
446-
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry})
445+
for _, v := range vv {
446+
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
447447
}
448448
}
449449
}
450450
if md, ok := t.md.(*metadata.MD); ok {
451-
for k, v := range *md {
451+
for k, vv := range *md {
452452
if isReservedHeader(k) {
453453
continue
454454
}
455-
for _, entry := range v {
456-
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry})
455+
for _, v := range vv {
456+
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
457457
}
458458
}
459459
}

transport/http2_server.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -644,13 +644,13 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error {
644644
if s.sendCompress != "" {
645645
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress})
646646
}
647-
for k, v := range md {
647+
for k, vv := range md {
648648
if isReservedHeader(k) {
649649
// Clients don't tolerate reading restricted headers after some non restricted ones were sent.
650650
continue
651651
}
652-
for _, entry := range v {
653-
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry})
652+
for _, v := range vv {
653+
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
654654
}
655655
}
656656
bufLen := t.hBuf.Len()
@@ -713,21 +713,17 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
713713
panic(err)
714714
}
715715

716-
for k, v := range metadata.New(map[string]string{"grpc-status-details-bin": (string)(stBytes)}) {
717-
for _, entry := range v {
718-
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry})
719-
}
720-
}
716+
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-status-details-bin", Value: encodeBinHeader(stBytes)})
721717
}
722718

723719
// Attach the trailer metadata.
724-
for k, v := range s.trailer {
720+
for k, vv := range s.trailer {
725721
// Clients don't tolerate reading restricted headers after some non restricted ones were sent.
726722
if isReservedHeader(k) {
727723
continue
728724
}
729-
for _, entry := range v {
730-
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry})
725+
for _, v := range vv {
726+
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
731727
}
732728
}
733729
bufLen := t.hBuf.Len()

transport/http_util.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ package transport
3636
import (
3737
"bufio"
3838
"bytes"
39+
"encoding/base64"
3940
"fmt"
4041
"io"
4142
"net"
@@ -50,7 +51,6 @@ import (
5051
spb "google.golang.org/genproto/googleapis/rpc/status"
5152
"google.golang.org/grpc/codes"
5253
"google.golang.org/grpc/grpclog"
53-
"google.golang.org/grpc/metadata"
5454
"google.golang.org/grpc/status"
5555
)
5656

@@ -164,6 +164,31 @@ func (d *decodeState) status() *status.Status {
164164
return d.statusGen
165165
}
166166

167+
const binHdrSuffix = "-bin"
168+
169+
func encodeBinHeader(v []byte) string {
170+
return base64.StdEncoding.EncodeToString(v)
171+
}
172+
173+
func decodeBinHeader(v string) ([]byte, error) {
174+
return base64.StdEncoding.DecodeString(v)
175+
}
176+
177+
func encodeMetadataHeader(k, v string) string {
178+
if strings.HasSuffix(k, binHdrSuffix) {
179+
return encodeBinHeader(([]byte)(v))
180+
}
181+
return v
182+
}
183+
184+
func decodeMetadataHeader(k, v string) (string, error) {
185+
if strings.HasSuffix(k, binHdrSuffix) {
186+
b, err := decodeBinHeader(v)
187+
return string(b), err
188+
}
189+
return v, nil
190+
}
191+
167192
func (d *decodeState) processHeaderField(f hpack.HeaderField) error {
168193
switch f.Name {
169194
case "content-type":
@@ -181,12 +206,12 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) error {
181206
case "grpc-message":
182207
d.rawStatusMsg = decodeGrpcMessage(f.Value)
183208
case "grpc-status-details-bin":
184-
_, v, err := metadata.DecodeKeyValue("grpc-status-details-bin", f.Value)
209+
v, err := decodeBinHeader(f.Value)
185210
if err != nil {
186211
return streamErrorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err)
187212
}
188213
s := &spb.Status{}
189-
if err := proto.Unmarshal([]byte(v), s); err != nil {
214+
if err := proto.Unmarshal(v, s); err != nil {
190215
return streamErrorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err)
191216
}
192217
d.statusGen = status.FromProto(s)
@@ -203,12 +228,12 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) error {
203228
if d.mdata == nil {
204229
d.mdata = make(map[string][]string)
205230
}
206-
k, v, err := metadata.DecodeKeyValue(f.Name, f.Value)
231+
v, err := decodeMetadataHeader(f.Name, f.Value)
207232
if err != nil {
208233
grpclog.Printf("Failed to decode (%q, %q): %v", f.Name, f.Value, err)
209234
return nil
210235
}
211-
d.mdata[k] = append(d.mdata[k], v)
236+
d.mdata[f.Name] = append(d.mdata[f.Name], v)
212237
}
213238
}
214239
return nil

0 commit comments

Comments
 (0)