/* * * Copyright 2020 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 matcher import ( "fmt" "regexp" "strconv" "strings" "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/metadata" ) // HeaderMatcher is an interface for header matchers. These are // documented in (EnvoyProxy link here?). These matchers will match on different // aspects of HTTP header name/value pairs. type HeaderMatcher interface { Match(metadata.MD) bool String() string } // mdValuesFromOutgoingCtx retrieves metadata from context. If there are // multiple values, the values are concatenated with "," (comma and no space). // // All header matchers only match against the comma-concatenated string. func mdValuesFromOutgoingCtx(md metadata.MD, key string) (string, bool) { vs, ok := md[key] if !ok { return "", false } return strings.Join(vs, ","), true } // HeaderExactMatcher matches on an exact match of the value of the header. type HeaderExactMatcher struct { key string exact string invert bool } // NewHeaderExactMatcher returns a new HeaderExactMatcher. func NewHeaderExactMatcher(key, exact string, invert bool) *HeaderExactMatcher { return &HeaderExactMatcher{key: key, exact: exact, invert: invert} } // Match returns whether the passed in HTTP Headers match according to the // HeaderExactMatcher. func (hem *HeaderExactMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hem.key) if !ok { return false } return (v == hem.exact) != hem.invert } func (hem *HeaderExactMatcher) String() string { return fmt.Sprintf("headerExact:%v:%v", hem.key, hem.exact) } // HeaderRegexMatcher matches on whether the entire request header value matches // the regex. type HeaderRegexMatcher struct { key string re *regexp.Regexp invert bool } // NewHeaderRegexMatcher returns a new HeaderRegexMatcher. func NewHeaderRegexMatcher(key string, re *regexp.Regexp, invert bool) *HeaderRegexMatcher { return &HeaderRegexMatcher{key: key, re: re, invert: invert} } // Match returns whether the passed in HTTP Headers match according to the // HeaderRegexMatcher. func (hrm *HeaderRegexMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hrm.key) if !ok { return false } return grpcutil.FullMatchWithRegex(hrm.re, v) != hrm.invert } func (hrm *HeaderRegexMatcher) String() string { return fmt.Sprintf("headerRegex:%v:%v", hrm.key, hrm.re.String()) } // HeaderRangeMatcher matches on whether the request header value is within the // range. The header value must be an integer in base 10 notation. type HeaderRangeMatcher struct { key string start, end int64 // represents [start, end). invert bool } // NewHeaderRangeMatcher returns a new HeaderRangeMatcher. func NewHeaderRangeMatcher(key string, start, end int64, invert bool) *HeaderRangeMatcher { return &HeaderRangeMatcher{key: key, start: start, end: end, invert: invert} } // Match returns whether the passed in HTTP Headers match according to the // HeaderRangeMatcher. func (hrm *HeaderRangeMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hrm.key) if !ok { return false } if i, err := strconv.ParseInt(v, 10, 64); err == nil && i >= hrm.start && i < hrm.end { return !hrm.invert } return hrm.invert } func (hrm *HeaderRangeMatcher) String() string { return fmt.Sprintf("headerRange:%v:[%d,%d)", hrm.key, hrm.start, hrm.end) } // HeaderPresentMatcher will match based on whether the header is present in the // whole request. type HeaderPresentMatcher struct { key string present bool } // NewHeaderPresentMatcher returns a new HeaderPresentMatcher. func NewHeaderPresentMatcher(key string, present bool, invert bool) *HeaderPresentMatcher { if invert { present = !present } return &HeaderPresentMatcher{key: key, present: present} } // Match returns whether the passed in HTTP Headers match according to the // HeaderPresentMatcher. func (hpm *HeaderPresentMatcher) Match(md metadata.MD) bool { vs, ok := mdValuesFromOutgoingCtx(md, hpm.key) present := ok && len(vs) > 0 // TODO: Are we sure we need this len(vs) > 0? return present == hpm.present } func (hpm *HeaderPresentMatcher) String() string { return fmt.Sprintf("headerPresent:%v:%v", hpm.key, hpm.present) } // HeaderPrefixMatcher matches on whether the prefix of the header value matches // the prefix passed into this struct. type HeaderPrefixMatcher struct { key string prefix string invert bool } // NewHeaderPrefixMatcher returns a new HeaderPrefixMatcher. func NewHeaderPrefixMatcher(key string, prefix string, invert bool) *HeaderPrefixMatcher { return &HeaderPrefixMatcher{key: key, prefix: prefix, invert: invert} } // Match returns whether the passed in HTTP Headers match according to the // HeaderPrefixMatcher. func (hpm *HeaderPrefixMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hpm.key) if !ok { return false } return strings.HasPrefix(v, hpm.prefix) != hpm.invert } func (hpm *HeaderPrefixMatcher) String() string { return fmt.Sprintf("headerPrefix:%v:%v", hpm.key, hpm.prefix) } // HeaderSuffixMatcher matches on whether the suffix of the header value matches // the suffix passed into this struct. type HeaderSuffixMatcher struct { key string suffix string invert bool } // NewHeaderSuffixMatcher returns a new HeaderSuffixMatcher. func NewHeaderSuffixMatcher(key string, suffix string, invert bool) *HeaderSuffixMatcher { return &HeaderSuffixMatcher{key: key, suffix: suffix, invert: invert} } // Match returns whether the passed in HTTP Headers match according to the // HeaderSuffixMatcher. func (hsm *HeaderSuffixMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hsm.key) if !ok { return false } return strings.HasSuffix(v, hsm.suffix) != hsm.invert } func (hsm *HeaderSuffixMatcher) String() string { return fmt.Sprintf("headerSuffix:%v:%v", hsm.key, hsm.suffix) } // HeaderContainsMatcher matches on whether the header value contains the // value passed into this struct. type HeaderContainsMatcher struct { key string contains string invert bool } // NewHeaderContainsMatcher returns a new HeaderContainsMatcher. key is the HTTP // Header key to match on, and contains is the value that the header should // should contain for a successful match. An empty contains string does not // work, use HeaderPresentMatcher in that case. func NewHeaderContainsMatcher(key string, contains string, invert bool) *HeaderContainsMatcher { return &HeaderContainsMatcher{key: key, contains: contains, invert: invert} } // Match returns whether the passed in HTTP Headers match according to the // HeaderContainsMatcher. func (hcm *HeaderContainsMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hcm.key) if !ok { return false } return strings.Contains(v, hcm.contains) != hcm.invert } func (hcm *HeaderContainsMatcher) String() string { return fmt.Sprintf("headerContains:%v%v", hcm.key, hcm.contains) } // HeaderStringMatcher matches on whether the header value matches against the // StringMatcher specified. type HeaderStringMatcher struct { key string stringMatcher StringMatcher invert bool } // NewHeaderStringMatcher returns a new HeaderStringMatcher. func NewHeaderStringMatcher(key string, sm StringMatcher, invert bool) *HeaderStringMatcher { return &HeaderStringMatcher{ key: key, stringMatcher: sm, invert: invert, } } // Match returns whether the passed in HTTP Headers match according to the // specified StringMatcher. func (hsm *HeaderStringMatcher) Match(md metadata.MD) bool { v, ok := mdValuesFromOutgoingCtx(md, hsm.key) if !ok { return false } return hsm.stringMatcher.Match(v) != hsm.invert } func (hsm *HeaderStringMatcher) String() string { return fmt.Sprintf("headerString:%v:%v", hsm.key, hsm.stringMatcher) }