| package runtime |
| |
| import ( |
| "errors" |
| "fmt" |
| "strconv" |
| "strings" |
| |
| "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" |
| "google.golang.org/grpc/grpclog" |
| ) |
| |
| var ( |
| // ErrNotMatch indicates that the given HTTP request path does not match to the pattern. |
| ErrNotMatch = errors.New("not match to the path pattern") |
| // ErrInvalidPattern indicates that the given definition of Pattern is not valid. |
| ErrInvalidPattern = errors.New("invalid pattern") |
| ) |
| |
| type MalformedSequenceError string |
| |
| func (e MalformedSequenceError) Error() string { |
| return "malformed path escape " + strconv.Quote(string(e)) |
| } |
| |
| type op struct { |
| code utilities.OpCode |
| operand int |
| } |
| |
| // Pattern is a template pattern of http request paths defined in |
| // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto |
| type Pattern struct { |
| // ops is a list of operations |
| ops []op |
| // pool is a constant pool indexed by the operands or vars. |
| pool []string |
| // vars is a list of variables names to be bound by this pattern |
| vars []string |
| // stacksize is the max depth of the stack |
| stacksize int |
| // tailLen is the length of the fixed-size segments after a deep wildcard |
| tailLen int |
| // verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part. |
| verb string |
| } |
| |
| // NewPattern returns a new Pattern from the given definition values. |
| // "ops" is a sequence of op codes. "pool" is a constant pool. |
| // "verb" is the verb part of the pattern. It is empty if the pattern does not have the part. |
| // "version" must be 1 for now. |
| // It returns an error if the given definition is invalid. |
| func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) { |
| if version != 1 { |
| grpclog.Errorf("unsupported version: %d", version) |
| return Pattern{}, ErrInvalidPattern |
| } |
| |
| l := len(ops) |
| if l%2 != 0 { |
| grpclog.Errorf("odd number of ops codes: %d", l) |
| return Pattern{}, ErrInvalidPattern |
| } |
| |
| var ( |
| typedOps []op |
| stack, maxstack int |
| tailLen int |
| pushMSeen bool |
| vars []string |
| ) |
| for i := 0; i < l; i += 2 { |
| op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]} |
| switch op.code { |
| case utilities.OpNop: |
| continue |
| case utilities.OpPush: |
| if pushMSeen { |
| tailLen++ |
| } |
| stack++ |
| case utilities.OpPushM: |
| if pushMSeen { |
| grpclog.Error("pushM appears twice") |
| return Pattern{}, ErrInvalidPattern |
| } |
| pushMSeen = true |
| stack++ |
| case utilities.OpLitPush: |
| if op.operand < 0 || len(pool) <= op.operand { |
| grpclog.Errorf("negative literal index: %d", op.operand) |
| return Pattern{}, ErrInvalidPattern |
| } |
| if pushMSeen { |
| tailLen++ |
| } |
| stack++ |
| case utilities.OpConcatN: |
| if op.operand <= 0 { |
| grpclog.Errorf("negative concat size: %d", op.operand) |
| return Pattern{}, ErrInvalidPattern |
| } |
| stack -= op.operand |
| if stack < 0 { |
| grpclog.Error("stack underflow") |
| return Pattern{}, ErrInvalidPattern |
| } |
| stack++ |
| case utilities.OpCapture: |
| if op.operand < 0 || len(pool) <= op.operand { |
| grpclog.Errorf("variable name index out of bound: %d", op.operand) |
| return Pattern{}, ErrInvalidPattern |
| } |
| v := pool[op.operand] |
| op.operand = len(vars) |
| vars = append(vars, v) |
| stack-- |
| if stack < 0 { |
| grpclog.Error("stack underflow") |
| return Pattern{}, ErrInvalidPattern |
| } |
| default: |
| grpclog.Errorf("invalid opcode: %d", op.code) |
| return Pattern{}, ErrInvalidPattern |
| } |
| |
| if maxstack < stack { |
| maxstack = stack |
| } |
| typedOps = append(typedOps, op) |
| } |
| return Pattern{ |
| ops: typedOps, |
| pool: pool, |
| vars: vars, |
| stacksize: maxstack, |
| tailLen: tailLen, |
| verb: verb, |
| }, nil |
| } |
| |
| // MustPattern is a helper function which makes it easier to call NewPattern in variable initialization. |
| func MustPattern(p Pattern, err error) Pattern { |
| if err != nil { |
| grpclog.Fatalf("Pattern initialization failed: %v", err) |
| } |
| return p |
| } |
| |
| // MatchAndEscape examines components to determine if they match to a Pattern. |
| // MatchAndEscape will return an error if no Patterns matched or if a pattern |
| // matched but contained malformed escape sequences. If successful, the function |
| // returns a mapping from field paths to their captured values. |
| func (p Pattern) MatchAndEscape(components []string, verb string, unescapingMode UnescapingMode) (map[string]string, error) { |
| if p.verb != verb { |
| if p.verb != "" { |
| return nil, ErrNotMatch |
| } |
| if len(components) == 0 { |
| components = []string{":" + verb} |
| } else { |
| components = append([]string{}, components...) |
| components[len(components)-1] += ":" + verb |
| } |
| } |
| |
| var pos int |
| stack := make([]string, 0, p.stacksize) |
| captured := make([]string, len(p.vars)) |
| l := len(components) |
| for _, op := range p.ops { |
| var err error |
| |
| switch op.code { |
| case utilities.OpNop: |
| continue |
| case utilities.OpPush, utilities.OpLitPush: |
| if pos >= l { |
| return nil, ErrNotMatch |
| } |
| c := components[pos] |
| if op.code == utilities.OpLitPush { |
| if lit := p.pool[op.operand]; c != lit { |
| return nil, ErrNotMatch |
| } |
| } else if op.code == utilities.OpPush { |
| if c, err = unescape(c, unescapingMode, false); err != nil { |
| return nil, err |
| } |
| } |
| stack = append(stack, c) |
| pos++ |
| case utilities.OpPushM: |
| end := len(components) |
| if end < pos+p.tailLen { |
| return nil, ErrNotMatch |
| } |
| end -= p.tailLen |
| c := strings.Join(components[pos:end], "/") |
| if c, err = unescape(c, unescapingMode, true); err != nil { |
| return nil, err |
| } |
| stack = append(stack, c) |
| pos = end |
| case utilities.OpConcatN: |
| n := op.operand |
| l := len(stack) - n |
| stack = append(stack[:l], strings.Join(stack[l:], "/")) |
| case utilities.OpCapture: |
| n := len(stack) - 1 |
| captured[op.operand] = stack[n] |
| stack = stack[:n] |
| } |
| } |
| if pos < l { |
| return nil, ErrNotMatch |
| } |
| bindings := make(map[string]string) |
| for i, val := range captured { |
| bindings[p.vars[i]] = val |
| } |
| return bindings, nil |
| } |
| |
| // MatchAndEscape examines components to determine if they match to a Pattern. |
| // It will never perform per-component unescaping (see: UnescapingModeLegacy). |
| // MatchAndEscape will return an error if no Patterns matched. If successful, |
| // the function returns a mapping from field paths to their captured values. |
| // |
| // Deprecated: Use MatchAndEscape. |
| func (p Pattern) Match(components []string, verb string) (map[string]string, error) { |
| return p.MatchAndEscape(components, verb, UnescapingModeDefault) |
| } |
| |
| // Verb returns the verb part of the Pattern. |
| func (p Pattern) Verb() string { return p.verb } |
| |
| func (p Pattern) String() string { |
| var stack []string |
| for _, op := range p.ops { |
| switch op.code { |
| case utilities.OpNop: |
| continue |
| case utilities.OpPush: |
| stack = append(stack, "*") |
| case utilities.OpLitPush: |
| stack = append(stack, p.pool[op.operand]) |
| case utilities.OpPushM: |
| stack = append(stack, "**") |
| case utilities.OpConcatN: |
| n := op.operand |
| l := len(stack) - n |
| stack = append(stack[:l], strings.Join(stack[l:], "/")) |
| case utilities.OpCapture: |
| n := len(stack) - 1 |
| stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n]) |
| } |
| } |
| segs := strings.Join(stack, "/") |
| if p.verb != "" { |
| return fmt.Sprintf("/%s:%s", segs, p.verb) |
| } |
| return "/" + segs |
| } |
| |
| /* |
| * The following code is adopted and modified from Go's standard library |
| * and carries the attached license. |
| * |
| * Copyright 2009 The Go Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the LICENSE file. |
| */ |
| |
| // ishex returns whether or not the given byte is a valid hex character |
| func ishex(c byte) bool { |
| switch { |
| case '0' <= c && c <= '9': |
| return true |
| case 'a' <= c && c <= 'f': |
| return true |
| case 'A' <= c && c <= 'F': |
| return true |
| } |
| return false |
| } |
| |
| func isRFC6570Reserved(c byte) bool { |
| switch c { |
| case '!', '#', '$', '&', '\'', '(', ')', '*', |
| '+', ',', '/', ':', ';', '=', '?', '@', '[', ']': |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // unhex converts a hex point to the bit representation |
| func unhex(c byte) byte { |
| switch { |
| case '0' <= c && c <= '9': |
| return c - '0' |
| case 'a' <= c && c <= 'f': |
| return c - 'a' + 10 |
| case 'A' <= c && c <= 'F': |
| return c - 'A' + 10 |
| } |
| return 0 |
| } |
| |
| // shouldUnescapeWithMode returns true if the character is escapable with the |
| // given mode |
| func shouldUnescapeWithMode(c byte, mode UnescapingMode) bool { |
| switch mode { |
| case UnescapingModeAllExceptReserved: |
| if isRFC6570Reserved(c) { |
| return false |
| } |
| case UnescapingModeAllExceptSlash: |
| if c == '/' { |
| return false |
| } |
| case UnescapingModeAllCharacters: |
| return true |
| } |
| return true |
| } |
| |
| // unescape unescapes a path string using the provided mode |
| func unescape(s string, mode UnescapingMode, multisegment bool) (string, error) { |
| // TODO(v3): remove UnescapingModeLegacy |
| if mode == UnescapingModeLegacy { |
| return s, nil |
| } |
| |
| if !multisegment { |
| mode = UnescapingModeAllCharacters |
| } |
| |
| // Count %, check that they're well-formed. |
| n := 0 |
| for i := 0; i < len(s); { |
| if s[i] == '%' { |
| n++ |
| if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { |
| s = s[i:] |
| if len(s) > 3 { |
| s = s[:3] |
| } |
| |
| return "", MalformedSequenceError(s) |
| } |
| i += 3 |
| } else { |
| i++ |
| } |
| } |
| |
| if n == 0 { |
| return s, nil |
| } |
| |
| var t strings.Builder |
| t.Grow(len(s)) |
| for i := 0; i < len(s); i++ { |
| switch s[i] { |
| case '%': |
| c := unhex(s[i+1])<<4 | unhex(s[i+2]) |
| if shouldUnescapeWithMode(c, mode) { |
| t.WriteByte(c) |
| i += 2 |
| continue |
| } |
| fallthrough |
| default: |
| t.WriteByte(s[i]) |
| } |
| } |
| |
| return t.String(), nil |
| } |