Source file
src/net/http/pattern.go
1
2
3
4
5
6
7 package http
8
9 import (
10 "errors"
11 "fmt"
12 "net/url"
13 "strings"
14 "unicode"
15 )
16
17
18
19 type pattern struct {
20 str string
21 method string
22 host string
23
24
25
26
27
28
29
30
31
32
33 segments []segment
34 loc string
35 }
36
37 func (p *pattern) String() string { return p.str }
38
39 func (p *pattern) lastSegment() segment {
40 return p.segments[len(p.segments)-1]
41 }
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 type segment struct {
62 s string
63 wild bool
64 multi bool
65 }
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 func parsePattern(s string) (_ *pattern, err error) {
85 if len(s) == 0 {
86 return nil, errors.New("empty pattern")
87 }
88 off := 0
89 defer func() {
90 if err != nil {
91 err = fmt.Errorf("at offset %d: %w", off, err)
92 }
93 }()
94
95 method, rest, found := strings.Cut(s, " ")
96 if !found {
97 rest = method
98 method = ""
99 }
100 if method != "" && !validMethod(method) {
101 return nil, fmt.Errorf("invalid method %q", method)
102 }
103 p := &pattern{str: s, method: method}
104
105 if found {
106 off = len(method) + 1
107 }
108 i := strings.IndexByte(rest, '/')
109 if i < 0 {
110 return nil, errors.New("host/path missing /")
111 }
112 p.host = rest[:i]
113 rest = rest[i:]
114 if j := strings.IndexByte(p.host, '{'); j >= 0 {
115 off += j
116 return nil, errors.New("host contains '{' (missing initial '/'?)")
117 }
118
119 off += i
120
121
122
123 if method != "" && method != "CONNECT" && rest != cleanPath(rest) {
124 return nil, errors.New("non-CONNECT pattern with unclean path can never match")
125 }
126
127 seenNames := map[string]bool{}
128 for len(rest) > 0 {
129
130 rest = rest[1:]
131 off = len(s) - len(rest)
132 if len(rest) == 0 {
133
134 p.segments = append(p.segments, segment{wild: true, multi: true})
135 break
136 }
137 i := strings.IndexByte(rest, '/')
138 if i < 0 {
139 i = len(rest)
140 }
141 var seg string
142 seg, rest = rest[:i], rest[i:]
143 if i := strings.IndexByte(seg, '{'); i < 0 {
144
145 seg = pathUnescape(seg)
146 p.segments = append(p.segments, segment{s: seg})
147 } else {
148
149 if i != 0 {
150 return nil, errors.New("bad wildcard segment (must start with '{')")
151 }
152 if seg[len(seg)-1] != '}' {
153 return nil, errors.New("bad wildcard segment (must end with '}')")
154 }
155 name := seg[1 : len(seg)-1]
156 if name == "$" {
157 if len(rest) != 0 {
158 return nil, errors.New("{$} not at end")
159 }
160 p.segments = append(p.segments, segment{s: "/"})
161 break
162 }
163 name, multi := strings.CutSuffix(name, "...")
164 if multi && len(rest) != 0 {
165 return nil, errors.New("{...} wildcard not at end")
166 }
167 if name == "" {
168 return nil, errors.New("empty wildcard")
169 }
170 if !isValidWildcardName(name) {
171 return nil, fmt.Errorf("bad wildcard name %q", name)
172 }
173 if seenNames[name] {
174 return nil, fmt.Errorf("duplicate wildcard name %q", name)
175 }
176 seenNames[name] = true
177 p.segments = append(p.segments, segment{s: name, wild: true, multi: multi})
178 }
179 }
180 return p, nil
181 }
182
183 func isValidWildcardName(s string) bool {
184 if s == "" {
185 return false
186 }
187
188 for i, c := range s {
189 if !unicode.IsLetter(c) && c != '_' && (i == 0 || !unicode.IsDigit(c)) {
190 return false
191 }
192 }
193 return true
194 }
195
196 func pathUnescape(path string) string {
197 u, err := url.PathUnescape(path)
198 if err != nil {
199
200 return path
201 }
202 return u
203 }
204
205
206 type relationship string
207
208 const (
209 equivalent relationship = "equivalent"
210 moreGeneral relationship = "moreGeneral"
211 moreSpecific relationship = "moreSpecific"
212 disjoint relationship = "disjoint"
213 overlaps relationship = "overlaps"
214 )
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 func (p1 *pattern) conflictsWith(p2 *pattern) bool {
230 if p1.host != p2.host {
231
232
233
234 return false
235 }
236 rel := p1.comparePathsAndMethods(p2)
237 return rel == equivalent || rel == overlaps
238 }
239
240 func (p1 *pattern) comparePathsAndMethods(p2 *pattern) relationship {
241 mrel := p1.compareMethods(p2)
242
243 if mrel == disjoint {
244 return disjoint
245 }
246 prel := p1.comparePaths(p2)
247 return combineRelationships(mrel, prel)
248 }
249
250
251
252
253
254
255
256
257 func (p1 *pattern) compareMethods(p2 *pattern) relationship {
258 if p1.method == p2.method {
259 return equivalent
260 }
261 if p1.method == "" {
262
263 return moreGeneral
264 }
265 if p2.method == "" {
266 return moreSpecific
267 }
268 if p1.method == "GET" && p2.method == "HEAD" {
269
270 return moreGeneral
271 }
272 if p2.method == "GET" && p1.method == "HEAD" {
273 return moreSpecific
274 }
275 return disjoint
276 }
277
278
279
280 func (p1 *pattern) comparePaths(p2 *pattern) relationship {
281
282
283 if len(p1.segments) != len(p2.segments) && !p1.lastSegment().multi && !p2.lastSegment().multi {
284 return disjoint
285 }
286
287
288 var segs1, segs2 []segment
289 rel := equivalent
290 for segs1, segs2 = p1.segments, p2.segments; len(segs1) > 0 && len(segs2) > 0; segs1, segs2 = segs1[1:], segs2[1:] {
291 rel = combineRelationships(rel, compareSegments(segs1[0], segs2[0]))
292 if rel == disjoint {
293 return rel
294 }
295 }
296
297
298
299 if len(segs1) == 0 && len(segs2) == 0 {
300 return rel
301 }
302
303
304
305 if len(segs1) < len(segs2) && p1.lastSegment().multi {
306 return combineRelationships(rel, moreGeneral)
307 }
308 if len(segs2) < len(segs1) && p2.lastSegment().multi {
309 return combineRelationships(rel, moreSpecific)
310 }
311 return disjoint
312 }
313
314
315 func compareSegments(s1, s2 segment) relationship {
316 if s1.multi && s2.multi {
317 return equivalent
318 }
319 if s1.multi {
320 return moreGeneral
321 }
322 if s2.multi {
323 return moreSpecific
324 }
325 if s1.wild && s2.wild {
326 return equivalent
327 }
328 if s1.wild {
329 if s2.s == "/" {
330
331 return disjoint
332 }
333 return moreGeneral
334 }
335 if s2.wild {
336 if s1.s == "/" {
337 return disjoint
338 }
339 return moreSpecific
340 }
341
342 if s1.s == s2.s {
343 return equivalent
344 }
345 return disjoint
346 }
347
348
349
350
351
352
353
354
355
356 func combineRelationships(r1, r2 relationship) relationship {
357 switch r1 {
358 case equivalent:
359 return r2
360 case disjoint:
361 return disjoint
362 case overlaps:
363 if r2 == disjoint {
364 return disjoint
365 }
366 return overlaps
367 case moreGeneral, moreSpecific:
368 switch r2 {
369 case equivalent:
370 return r1
371 case inverseRelationship(r1):
372 return overlaps
373 default:
374 return r2
375 }
376 default:
377 panic(fmt.Sprintf("unknown relationship %q", r1))
378 }
379 }
380
381
382
383 func inverseRelationship(r relationship) relationship {
384 switch r {
385 case moreSpecific:
386 return moreGeneral
387 case moreGeneral:
388 return moreSpecific
389 default:
390 return r
391 }
392 }
393
394
395 func isLitOrSingle(seg segment) bool {
396 if seg.wild {
397 return !seg.multi
398 }
399 return seg.s != "/"
400 }
401
402
403 func describeConflict(p1, p2 *pattern) string {
404 mrel := p1.compareMethods(p2)
405 prel := p1.comparePaths(p2)
406 rel := combineRelationships(mrel, prel)
407 if rel == equivalent {
408 return fmt.Sprintf("%s matches the same requests as %s", p1, p2)
409 }
410 if rel != overlaps {
411 panic("describeConflict called with non-conflicting patterns")
412 }
413 if prel == overlaps {
414 return fmt.Sprintf(`%[1]s and %[2]s both match some paths, like %[3]q.
415 But neither is more specific than the other.
416 %[1]s matches %[4]q, but %[2]s doesn't.
417 %[2]s matches %[5]q, but %[1]s doesn't.`,
418 p1, p2, commonPath(p1, p2), differencePath(p1, p2), differencePath(p2, p1))
419 }
420 if mrel == moreGeneral && prel == moreSpecific {
421 return fmt.Sprintf("%s matches more methods than %s, but has a more specific path pattern", p1, p2)
422 }
423 if mrel == moreSpecific && prel == moreGeneral {
424 return fmt.Sprintf("%s matches fewer methods than %s, but has a more general path pattern", p1, p2)
425 }
426 return fmt.Sprintf("bug: unexpected way for two patterns %s and %s to conflict: methods %s, paths %s", p1, p2, mrel, prel)
427 }
428
429
430 func writeMatchingPath(b *strings.Builder, segs []segment) {
431 for _, s := range segs {
432 writeSegment(b, s)
433 }
434 }
435
436 func writeSegment(b *strings.Builder, s segment) {
437 b.WriteByte('/')
438 if !s.multi && s.s != "/" {
439 b.WriteString(s.s)
440 }
441 }
442
443
444
445 func commonPath(p1, p2 *pattern) string {
446 var b strings.Builder
447 var segs1, segs2 []segment
448 for segs1, segs2 = p1.segments, p2.segments; len(segs1) > 0 && len(segs2) > 0; segs1, segs2 = segs1[1:], segs2[1:] {
449 if s1 := segs1[0]; s1.wild {
450 writeSegment(&b, segs2[0])
451 } else {
452 writeSegment(&b, s1)
453 }
454 }
455 if len(segs1) > 0 {
456 writeMatchingPath(&b, segs1)
457 } else if len(segs2) > 0 {
458 writeMatchingPath(&b, segs2)
459 }
460 return b.String()
461 }
462
463
464
465 func differencePath(p1, p2 *pattern) string {
466 var b strings.Builder
467
468 var segs1, segs2 []segment
469 for segs1, segs2 = p1.segments, p2.segments; len(segs1) > 0 && len(segs2) > 0; segs1, segs2 = segs1[1:], segs2[1:] {
470 s1 := segs1[0]
471 s2 := segs2[0]
472 if s1.multi && s2.multi {
473
474 b.WriteByte('/')
475 return b.String()
476
477 }
478 if s1.multi && !s2.multi {
479
480
481
482
483 b.WriteByte('/')
484 if s2.s == "/" {
485 if s1.s != "" {
486 b.WriteString(s1.s)
487 } else {
488 b.WriteString("x")
489 }
490 }
491 return b.String()
492 }
493 if !s1.multi && s2.multi {
494 writeSegment(&b, s1)
495 } else if s1.wild && s2.wild {
496
497
498 writeSegment(&b, s1)
499 } else if s1.wild && !s2.wild {
500
501
502
503
504 if s1.s != s2.s {
505 writeSegment(&b, s1)
506 } else {
507 b.WriteByte('/')
508 b.WriteString(s2.s + "x")
509 }
510 } else if !s1.wild && s2.wild {
511 writeSegment(&b, s1)
512 } else {
513
514
515 if s1.s != s2.s {
516 panic(fmt.Sprintf("literals differ: %q and %q", s1.s, s2.s))
517 }
518 writeSegment(&b, s1)
519 }
520 }
521 if len(segs1) > 0 {
522
523
524 writeMatchingPath(&b, segs1)
525 } else if len(segs2) > 0 {
526 writeMatchingPath(&b, segs2)
527 }
528 return b.String()
529 }
530
View as plain text