gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

pattern.go (9726B)


      1 package runtime
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"strconv"
      7 	"strings"
      8 
      9 	"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
     10 	"google.golang.org/grpc/grpclog"
     11 )
     12 
     13 var (
     14 	// ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
     15 	ErrNotMatch = errors.New("not match to the path pattern")
     16 	// ErrInvalidPattern indicates that the given definition of Pattern is not valid.
     17 	ErrInvalidPattern = errors.New("invalid pattern")
     18 	// ErrMalformedSequence indicates that an escape sequence was malformed.
     19 	ErrMalformedSequence = errors.New("malformed escape sequence")
     20 )
     21 
     22 type MalformedSequenceError string
     23 
     24 func (e MalformedSequenceError) Error() string {
     25 	return "malformed path escape " + strconv.Quote(string(e))
     26 }
     27 
     28 type op struct {
     29 	code    utilities.OpCode
     30 	operand int
     31 }
     32 
     33 // Pattern is a template pattern of http request paths defined in
     34 // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
     35 type Pattern struct {
     36 	// ops is a list of operations
     37 	ops []op
     38 	// pool is a constant pool indexed by the operands or vars.
     39 	pool []string
     40 	// vars is a list of variables names to be bound by this pattern
     41 	vars []string
     42 	// stacksize is the max depth of the stack
     43 	stacksize int
     44 	// tailLen is the length of the fixed-size segments after a deep wildcard
     45 	tailLen int
     46 	// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
     47 	verb string
     48 }
     49 
     50 // NewPattern returns a new Pattern from the given definition values.
     51 // "ops" is a sequence of op codes. "pool" is a constant pool.
     52 // "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
     53 // "version" must be 1 for now.
     54 // It returns an error if the given definition is invalid.
     55 func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
     56 	if version != 1 {
     57 		grpclog.Infof("unsupported version: %d", version)
     58 		return Pattern{}, ErrInvalidPattern
     59 	}
     60 
     61 	l := len(ops)
     62 	if l%2 != 0 {
     63 		grpclog.Infof("odd number of ops codes: %d", l)
     64 		return Pattern{}, ErrInvalidPattern
     65 	}
     66 
     67 	var (
     68 		typedOps        []op
     69 		stack, maxstack int
     70 		tailLen         int
     71 		pushMSeen       bool
     72 		vars            []string
     73 	)
     74 	for i := 0; i < l; i += 2 {
     75 		op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]}
     76 		switch op.code {
     77 		case utilities.OpNop:
     78 			continue
     79 		case utilities.OpPush:
     80 			if pushMSeen {
     81 				tailLen++
     82 			}
     83 			stack++
     84 		case utilities.OpPushM:
     85 			if pushMSeen {
     86 				grpclog.Infof("pushM appears twice")
     87 				return Pattern{}, ErrInvalidPattern
     88 			}
     89 			pushMSeen = true
     90 			stack++
     91 		case utilities.OpLitPush:
     92 			if op.operand < 0 || len(pool) <= op.operand {
     93 				grpclog.Infof("negative literal index: %d", op.operand)
     94 				return Pattern{}, ErrInvalidPattern
     95 			}
     96 			if pushMSeen {
     97 				tailLen++
     98 			}
     99 			stack++
    100 		case utilities.OpConcatN:
    101 			if op.operand <= 0 {
    102 				grpclog.Infof("negative concat size: %d", op.operand)
    103 				return Pattern{}, ErrInvalidPattern
    104 			}
    105 			stack -= op.operand
    106 			if stack < 0 {
    107 				grpclog.Info("stack underflow")
    108 				return Pattern{}, ErrInvalidPattern
    109 			}
    110 			stack++
    111 		case utilities.OpCapture:
    112 			if op.operand < 0 || len(pool) <= op.operand {
    113 				grpclog.Infof("variable name index out of bound: %d", op.operand)
    114 				return Pattern{}, ErrInvalidPattern
    115 			}
    116 			v := pool[op.operand]
    117 			op.operand = len(vars)
    118 			vars = append(vars, v)
    119 			stack--
    120 			if stack < 0 {
    121 				grpclog.Infof("stack underflow")
    122 				return Pattern{}, ErrInvalidPattern
    123 			}
    124 		default:
    125 			grpclog.Infof("invalid opcode: %d", op.code)
    126 			return Pattern{}, ErrInvalidPattern
    127 		}
    128 
    129 		if maxstack < stack {
    130 			maxstack = stack
    131 		}
    132 		typedOps = append(typedOps, op)
    133 	}
    134 	return Pattern{
    135 		ops:       typedOps,
    136 		pool:      pool,
    137 		vars:      vars,
    138 		stacksize: maxstack,
    139 		tailLen:   tailLen,
    140 		verb:      verb,
    141 	}, nil
    142 }
    143 
    144 // MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
    145 func MustPattern(p Pattern, err error) Pattern {
    146 	if err != nil {
    147 		grpclog.Fatalf("Pattern initialization failed: %v", err)
    148 	}
    149 	return p
    150 }
    151 
    152 // MatchAndEscape examines components to determine if they match to a Pattern.
    153 // MatchAndEscape will return an error if no Patterns matched or if a pattern
    154 // matched but contained malformed escape sequences. If successful, the function
    155 // returns a mapping from field paths to their captured values.
    156 func (p Pattern) MatchAndEscape(components []string, verb string, unescapingMode UnescapingMode) (map[string]string, error) {
    157 	if p.verb != verb {
    158 		if p.verb != "" {
    159 			return nil, ErrNotMatch
    160 		}
    161 		if len(components) == 0 {
    162 			components = []string{":" + verb}
    163 		} else {
    164 			components = append([]string{}, components...)
    165 			components[len(components)-1] += ":" + verb
    166 		}
    167 	}
    168 
    169 	var pos int
    170 	stack := make([]string, 0, p.stacksize)
    171 	captured := make([]string, len(p.vars))
    172 	l := len(components)
    173 	for _, op := range p.ops {
    174 		var err error
    175 
    176 		switch op.code {
    177 		case utilities.OpNop:
    178 			continue
    179 		case utilities.OpPush, utilities.OpLitPush:
    180 			if pos >= l {
    181 				return nil, ErrNotMatch
    182 			}
    183 			c := components[pos]
    184 			if op.code == utilities.OpLitPush {
    185 				if lit := p.pool[op.operand]; c != lit {
    186 					return nil, ErrNotMatch
    187 				}
    188 			} else if op.code == utilities.OpPush {
    189 				if c, err = unescape(c, unescapingMode, false); err != nil {
    190 					return nil, err
    191 				}
    192 			}
    193 			stack = append(stack, c)
    194 			pos++
    195 		case utilities.OpPushM:
    196 			end := len(components)
    197 			if end < pos+p.tailLen {
    198 				return nil, ErrNotMatch
    199 			}
    200 			end -= p.tailLen
    201 			c := strings.Join(components[pos:end], "/")
    202 			if c, err = unescape(c, unescapingMode, true); err != nil {
    203 				return nil, err
    204 			}
    205 			stack = append(stack, c)
    206 			pos = end
    207 		case utilities.OpConcatN:
    208 			n := op.operand
    209 			l := len(stack) - n
    210 			stack = append(stack[:l], strings.Join(stack[l:], "/"))
    211 		case utilities.OpCapture:
    212 			n := len(stack) - 1
    213 			captured[op.operand] = stack[n]
    214 			stack = stack[:n]
    215 		}
    216 	}
    217 	if pos < l {
    218 		return nil, ErrNotMatch
    219 	}
    220 	bindings := make(map[string]string)
    221 	for i, val := range captured {
    222 		bindings[p.vars[i]] = val
    223 	}
    224 	return bindings, nil
    225 }
    226 
    227 // MatchAndEscape examines components to determine if they match to a Pattern.
    228 // It will never perform per-component unescaping (see: UnescapingModeLegacy).
    229 // MatchAndEscape will return an error if no Patterns matched. If successful,
    230 // the function returns a mapping from field paths to their captured values.
    231 //
    232 // Deprecated: Use MatchAndEscape.
    233 func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
    234 	return p.MatchAndEscape(components, verb, UnescapingModeDefault)
    235 }
    236 
    237 // Verb returns the verb part of the Pattern.
    238 func (p Pattern) Verb() string { return p.verb }
    239 
    240 func (p Pattern) String() string {
    241 	var stack []string
    242 	for _, op := range p.ops {
    243 		switch op.code {
    244 		case utilities.OpNop:
    245 			continue
    246 		case utilities.OpPush:
    247 			stack = append(stack, "*")
    248 		case utilities.OpLitPush:
    249 			stack = append(stack, p.pool[op.operand])
    250 		case utilities.OpPushM:
    251 			stack = append(stack, "**")
    252 		case utilities.OpConcatN:
    253 			n := op.operand
    254 			l := len(stack) - n
    255 			stack = append(stack[:l], strings.Join(stack[l:], "/"))
    256 		case utilities.OpCapture:
    257 			n := len(stack) - 1
    258 			stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
    259 		}
    260 	}
    261 	segs := strings.Join(stack, "/")
    262 	if p.verb != "" {
    263 		return fmt.Sprintf("/%s:%s", segs, p.verb)
    264 	}
    265 	return "/" + segs
    266 }
    267 
    268 /*
    269  * The following code is adopted and modified from Go's standard library
    270  * and carries the attached license.
    271  *
    272  *     Copyright 2009 The Go Authors. All rights reserved.
    273  *     Use of this source code is governed by a BSD-style
    274  *     license that can be found in the LICENSE file.
    275  */
    276 
    277 // ishex returns whether or not the given byte is a valid hex character
    278 func ishex(c byte) bool {
    279 	switch {
    280 	case '0' <= c && c <= '9':
    281 		return true
    282 	case 'a' <= c && c <= 'f':
    283 		return true
    284 	case 'A' <= c && c <= 'F':
    285 		return true
    286 	}
    287 	return false
    288 }
    289 
    290 func isRFC6570Reserved(c byte) bool {
    291 	switch c {
    292 	case '!', '#', '$', '&', '\'', '(', ')', '*',
    293 		'+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
    294 		return true
    295 	default:
    296 		return false
    297 	}
    298 }
    299 
    300 // unhex converts a hex point to the bit representation
    301 func unhex(c byte) byte {
    302 	switch {
    303 	case '0' <= c && c <= '9':
    304 		return c - '0'
    305 	case 'a' <= c && c <= 'f':
    306 		return c - 'a' + 10
    307 	case 'A' <= c && c <= 'F':
    308 		return c - 'A' + 10
    309 	}
    310 	return 0
    311 }
    312 
    313 // shouldUnescapeWithMode returns true if the character is escapable with the
    314 // given mode
    315 func shouldUnescapeWithMode(c byte, mode UnescapingMode) bool {
    316 	switch mode {
    317 	case UnescapingModeAllExceptReserved:
    318 		if isRFC6570Reserved(c) {
    319 			return false
    320 		}
    321 	case UnescapingModeAllExceptSlash:
    322 		if c == '/' {
    323 			return false
    324 		}
    325 	case UnescapingModeAllCharacters:
    326 		return true
    327 	}
    328 	return true
    329 }
    330 
    331 // unescape unescapes a path string using the provided mode
    332 func unescape(s string, mode UnescapingMode, multisegment bool) (string, error) {
    333 	// TODO(v3): remove UnescapingModeLegacy
    334 	if mode == UnescapingModeLegacy {
    335 		return s, nil
    336 	}
    337 
    338 	if !multisegment {
    339 		mode = UnescapingModeAllCharacters
    340 	}
    341 
    342 	// Count %, check that they're well-formed.
    343 	n := 0
    344 	for i := 0; i < len(s); {
    345 		if s[i] == '%' {
    346 			n++
    347 			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
    348 				s = s[i:]
    349 				if len(s) > 3 {
    350 					s = s[:3]
    351 				}
    352 
    353 				return "", MalformedSequenceError(s)
    354 			}
    355 			i += 3
    356 		} else {
    357 			i++
    358 		}
    359 	}
    360 
    361 	if n == 0 {
    362 		return s, nil
    363 	}
    364 
    365 	var t strings.Builder
    366 	t.Grow(len(s))
    367 	for i := 0; i < len(s); i++ {
    368 		switch s[i] {
    369 		case '%':
    370 			c := unhex(s[i+1])<<4 | unhex(s[i+2])
    371 			if shouldUnescapeWithMode(c, mode) {
    372 				t.WriteByte(c)
    373 				i += 2
    374 				continue
    375 			}
    376 			fallthrough
    377 		default:
    378 			t.WriteByte(s[i])
    379 		}
    380 	}
    381 
    382 	return t.String(), nil
    383 }