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