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
15 ErrNotMatch = errors.New("not match to the path pattern")
16
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
32
33 type Pattern struct {
34
35 ops []op
36
37 pool []string
38
39 vars []string
40
41 stacksize int
42
43 tailLen int
44
45 verb string
46 }
47
48
49
50
51
52
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
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
151
152
153
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
226
227
228
229
230
231 func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
232 return p.MatchAndEscape(components, verb, UnescapingModeDefault)
233 }
234
235
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
274
275
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
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
312
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
330 func unescape(s string, mode UnescapingMode, multisegment bool) (string, error) {
331
332 if mode == UnescapingModeLegacy {
333 return s, nil
334 }
335
336 if !multisegment {
337 mode = UnescapingModeAllCharacters
338 }
339
340
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 }
382
View as plain text