blob: fd4833d3fff8ca9f61875143ac2ae6efad9d55e3 [file] [log] [blame]
/*
*
* 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)
}