/* * * Copyright 2014 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ // Package metadata define the structure of the metadata supported by gRPC library. // Please refer to https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md // for more information about custom-metadata. package metadata // import "google.golang.org/grpc/metadata" import ( "context" "fmt" "sort" "strings" "google.golang.org/grpc/internal" ) func init() { internal.FromOutgoingContextRaw = fromOutgoingContextRaw } // DecodeKeyValue returns k, v, nil. // // Deprecated: use k and v directly instead. func DecodeKeyValue(k, v string) (string, string, error) { return k, v, nil } // MD is a mapping from metadata keys to values. Users should use the following // two convenience functions New and Pairs to generate MD. type MD map[string][]string // New creates an MD from a given key-value map. // // Only the following ASCII characters are allowed in keys: // - digits: 0-9 // - uppercase letters: A-Z (normalized to lower) // - lowercase letters: a-z // - special characters: -_. // // Uppercase letters are automatically converted to lowercase. // // Keys beginning with "grpc-" are reserved for grpc-internal use only and may // result in errors if set in metadata. func New(m map[string]string) MD { md := make(MD, len(m)) for k, val := range m { key := strings.ToLower(k) md[key] = append(md[key], val) } return md } // Pairs returns an MD formed by the mapping of key, value ... // Pairs panics if len(kv) is odd. // // Only the following ASCII characters are allowed in keys: // - digits: 0-9 // - uppercase letters: A-Z (normalized to lower) // - lowercase letters: a-z // - special characters: -_. // // Uppercase letters are automatically converted to lowercase. // // Keys beginning with "grpc-" are reserved for grpc-internal use only and may // result in errors if set in metadata. func Pairs(kv ...string) MD { if len(kv)%2 == 1 { panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv))) } md := make(MD, len(kv)/2) for i := 0; i < len(kv); i += 2 { key := strings.ToLower(kv[i]) md[key] = append(md[key], kv[i+1]) } return md } // loggableMetadataKeys is the set of metadata keys whose values are known not // to carry credentials or other sensitive data, and are therefore safe to print // verbatim in String. Values for any key not in this set are redacted. Keys are // in metadata's canonical lowercase form. // // The list is intentionally restricted to standardized gRPC and HTTP/2 protocol // headers. Everything else, including all application-defined keys, is // censored. // Note that this list might change as we add/remove support for // metadata types. var loggableMetadataKeys = map[string]bool{ "content-type": true, "te": true, "user-agent": true, "grpc-encoding": true, "grpc-accept-encoding": true, "grpc-timeout": true, "grpc-status": true, "grpc-message-type": true, "grpc-previous-rpc-attempts": true, "grpc-retry-pushback-ms": true, } // String implements fmt.Stringer to allow metadata to be printed when stored in // a context. // // To avoid accidentally leaking credentials or other sensitive data (for // example via log(md), or by logging a value that happens to contain metadata), // String reports only keys on an allowlist of standardized, non-sensitive // protocol headers (see loggableMetadataKeys). Every other key is omitted // entirely, along with its values, because a key name may itself be sensitive; // only the number of omitted keys is reported, as "". This is a // best-effort guard against accidents, not a security boundary: a caller that // genuinely wants the full contents can still print map[string][]string(md) // directly. // // Note that this only affects verbs that use the Stringer, such as %v and %s. // The %#v verb prints the underlying map with all values and is not redacted. // Users should not rely on the output of this method to be stable // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (md MD) String() string { keys := make([]string, 0, len(md)) for k := range md { if loggableMetadataKeys[strings.ToLower(k)] { keys = append(keys, k) } } sort.Strings(keys) var sb strings.Builder sb.WriteString("map[") for i, k := range keys { if i > 0 { sb.WriteByte(' ') } fmt.Fprintf(&sb, "%s:%v", k, md[k]) } if redacted := len(md) - len(keys); redacted > 0 { if len(keys) > 0 { sb.WriteByte(' ') } fmt.Fprintf(&sb, "<%d redacted>", redacted) } sb.WriteByte(']') return sb.String() } // Len returns the number of items in md. func (md MD) Len() int { return len(md) } // Copy returns a copy of md. func (md MD) Copy() MD { out := make(MD, len(md)) for k, v := range md { out[k] = copyOf(v) } return out } // Get obtains the values for a given key. // // k is converted to lowercase before searching in md. func (md MD) Get(k string) []string { k = strings.ToLower(k) return md[k] } // Set sets the value of a given key with a slice of values. // // k is converted to lowercase before storing in md. func (md MD) Set(k string, vals ...string) { if len(vals) == 0 { return } k = strings.ToLower(k) md[k] = vals } // Append adds the values to key k, not overwriting what was already stored at // that key. // // k is converted to lowercase before storing in md. func (md MD) Append(k string, vals ...string) { if len(vals) == 0 { return } k = strings.ToLower(k) md[k] = append(md[k], vals...) } // Delete removes the values for a given key k which is converted to lowercase // before removing it from md. func (md MD) Delete(k string) { k = strings.ToLower(k) delete(md, k) } // Join joins any number of mds into a single MD. // // The order of values for each key is determined by the order in which the mds // containing those values are presented to Join. func Join(mds ...MD) MD { out := MD{} for _, md := range mds { for k, v := range md { out[k] = append(out[k], v...) } } return out } type mdIncomingKey struct{} type mdOutgoingKey struct{} // NewIncomingContext creates a new context with incoming md attached. md must // not be modified after calling this function. func NewIncomingContext(ctx context.Context, md MD) context.Context { return context.WithValue(ctx, mdIncomingKey{}, md) } // NewOutgoingContext creates a new context with outgoing md attached. If used // in conjunction with AppendToOutgoingContext, NewOutgoingContext will // overwrite any previously-appended metadata. md must not be modified after // calling this function. func NewOutgoingContext(ctx context.Context, md MD) context.Context { return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md}) } // AppendToOutgoingContext returns a new context with the provided kv merged // with any existing metadata in the context. Please refer to the documentation // of Pairs for a description of kv. func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context { if len(kv)%2 == 1 { panic(fmt.Sprintf("metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d", len(kv))) } md, _ := ctx.Value(mdOutgoingKey{}).(rawMD) added := make([][]string, len(md.added)+1) copy(added, md.added) kvCopy := make([]string, 0, len(kv)) for i := 0; i < len(kv); i += 2 { kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1]) } added[len(added)-1] = kvCopy return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md.md, added: added}) } // FromIncomingContext returns the incoming metadata in ctx if it exists. // // All keys in the returned MD are lowercase. func FromIncomingContext(ctx context.Context) (MD, bool) { md, ok := ctx.Value(mdIncomingKey{}).(MD) if !ok { return nil, false } out := make(MD, len(md)) for k, v := range md { // We need to manually convert all keys to lower case, because MD is a // map, and there's no guarantee that the MD attached to the context is // created using our helper functions. key := strings.ToLower(k) out[key] = copyOf(v) } return out, true } // ValueFromIncomingContext returns the metadata value corresponding to the metadata // key from the incoming metadata if it exists. Keys are matched in a case insensitive // manner. func ValueFromIncomingContext(ctx context.Context, key string) []string { md, ok := ctx.Value(mdIncomingKey{}).(MD) if !ok { return nil } if v, ok := md[key]; ok { return copyOf(v) } for k, v := range md { // Case insensitive comparison: MD is a map, and there's no guarantee // that the MD attached to the context is created using our helper // functions. if strings.EqualFold(k, key) { return copyOf(v) } } return nil } func copyOf(v []string) []string { vals := make([]string, len(v)) copy(vals, v) return vals } // fromOutgoingContextRaw returns the un-merged, intermediary contents of rawMD. // // Remember to perform strings.ToLower on the keys, for both the returned MD (MD // is a map, there's no guarantee it's created using our helper functions) and // the extra kv pairs (AppendToOutgoingContext doesn't turn them into // lowercase). func fromOutgoingContextRaw(ctx context.Context) (MD, [][]string, bool) { raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) if !ok { return nil, nil, false } return raw.md, raw.added, true } // FromOutgoingContext returns the outgoing metadata in ctx if it exists. // // All keys in the returned MD are lowercase. func FromOutgoingContext(ctx context.Context) (MD, bool) { raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) if !ok { return nil, false } mdSize := len(raw.md) for i := range raw.added { mdSize += len(raw.added[i]) / 2 } out := make(MD, mdSize) for k, v := range raw.md { // We need to manually convert all keys to lower case, because MD is a // map, and there's no guarantee that the MD attached to the context is // created using our helper functions. key := strings.ToLower(k) out[key] = copyOf(v) } for _, added := range raw.added { if len(added)%2 == 1 { panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added))) } for i := 0; i < len(added); i += 2 { key := strings.ToLower(added[i]) out[key] = append(out[key], added[i+1]) } } return out, ok } // ValueFromOutgoingContext returns the metadata value corresponding to the // metadata key from the outgoing metadata if it exists. Keys are matched in a // case-insensitive manner. // // Unlike FromOutgoingContext, this does not copy the entire outgoing metadata. func ValueFromOutgoingContext(ctx context.Context, key string) []string { raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) if !ok { return nil } key = strings.ToLower(key) // Unlike ValueFromIncomingContext, this can't return as soon as raw.md is // checked: raw.added still needs to be walked and accumulated regardless. // The else below prevents that from overwriting an exact match with an // unrelated case-insensitive one. var matchedMD []string if v, ok := raw.md[key]; ok { matchedMD = v } else { for k, v := range raw.md { // Case insensitive comparison: MD is a map, and there's no // guarantee that the MD attached to the context is created using // our helper functions. if strings.EqualFold(k, key) { matchedMD = v break } } } // Defer allocating vals until raw.added actually has a match, so a match // found only in raw.md doesn't pay for both a copyOf and a growing // append. var vals []string for _, added := range raw.added { if len(added)%2 == 1 { panic(fmt.Sprintf("metadata: ValueFromOutgoingContext got an odd number of input pairs for metadata: %d", len(added))) } // Case insensitive, like FromOutgoingContext: added isn't guaranteed // lowercase, though AppendToOutgoingContext already lowercases it in // practice, so try the cheap exact match first. for i := 0; i < len(added); i += 2 { if added[i] == key || strings.EqualFold(added[i], key) { if vals == nil { vals = make([]string, 0, len(matchedMD)+1) vals = append(vals, matchedMD...) } vals = append(vals, added[i+1]) } } } if vals == nil && matchedMD != nil { return copyOf(matchedMD) } return vals } type rawMD struct { md MD added [][]string }