| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 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.Errorf("unsupported version: %d", version) |
| 56 | return Pattern{}, ErrInvalidPattern |
| 57 | } |
| 58 | |
| 59 | l := len(ops) |
| 60 | if l%2 != 0 { |
| 61 | grpclog.Errorf("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.Error("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.Errorf("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.Errorf("negative concat size: %d", op.operand) |
| 101 | return Pattern{}, ErrInvalidPattern |
| 102 | } |
| 103 | stack -= op.operand |
| 104 | if stack < 0 { |
| 105 | grpclog.Error("stack underflow") |
| 106 | return Pattern{}, ErrInvalidPattern |
| 107 | } |
| 108 | stack++ |
| 109 | case utilities.OpCapture: |
| 110 | if op.operand < 0 || len(pool) <= op.operand { |
| 111 | grpclog.Errorf("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.Error("stack underflow") |
| 120 | return Pattern{}, ErrInvalidPattern |
| 121 | } |
| 122 | default: |
| 123 | grpclog.Errorf("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 | } |