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 }