forked from zenazn/goji
/
string_pattern.go
133 lines (122 loc) · 2.78 KB
/
string_pattern.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package web
import (
"fmt"
"net/http"
"regexp"
"strings"
)
// stringPattern is a struct describing
type stringPattern struct {
raw string
pats []string
breaks []byte
literals []string
wildcard bool
}
func (s stringPattern) Prefix() string {
return s.literals[0]
}
func (s stringPattern) Match(r *http.Request, c *C) bool {
return s.match(r, c, true)
}
func (s stringPattern) Run(r *http.Request, c *C) {
s.match(r, c, false)
}
func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool {
path := r.URL.Path
var matches map[string]string
if !dryrun {
if s.wildcard {
matches = make(map[string]string, len(s.pats)+1)
} else if len(s.pats) != 0 {
matches = make(map[string]string, len(s.pats))
}
}
for i, pat := range s.pats {
sli := s.literals[i]
if !strings.HasPrefix(path, sli) {
return false
}
path = path[len(sli):]
m := 0
bc := s.breaks[i]
for ; m < len(path); m++ {
if path[m] == bc || path[m] == '/' {
break
}
}
if m == 0 {
// Empty strings are not matches, otherwise routes like
// "/:foo" would match the path "/"
return false
}
if !dryrun {
matches[pat] = path[:m]
}
path = path[m:]
}
// There's exactly one more literal than pat.
tail := s.literals[len(s.pats)]
if s.wildcard {
if !strings.HasPrefix(path, tail) {
return false
}
if !dryrun {
matches["*"] = path[len(tail)-1:]
}
} else if path != tail {
return false
}
if c == nil || dryrun {
return true
}
if c.URLParams == nil {
c.URLParams = matches
} else {
for k, v := range matches {
c.URLParams[k] = v
}
}
return true
}
func (s stringPattern) String() string {
return fmt.Sprintf("stringPattern(%q)", s.raw)
}
// "Break characters" are characters that can end patterns. They are not allowed
// to appear in pattern names. "/" was chosen because it is the standard path
// separator, and "." was chosen because it often delimits file extensions. ";"
// and "," were chosen because Section 3.3 of RFC 3986 suggests their use.
const bc = "/.;,"
var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`)
func parseStringPattern(s string) stringPattern {
raw := s
var wildcard bool
if strings.HasSuffix(s, "/*") {
s = s[:len(s)-1]
wildcard = true
}
matches := patternRe.FindAllStringSubmatchIndex(s, -1)
pats := make([]string, len(matches))
breaks := make([]byte, len(matches))
literals := make([]string, len(matches)+1)
n := 0
for i, match := range matches {
a, b := match[2], match[3]
literals[i] = s[n : a-1] // Need to leave off the colon
pats[i] = s[a:b]
if b == len(s) {
breaks[i] = '/'
} else {
breaks[i] = s[b]
}
n = b
}
literals[len(matches)] = s[n:]
return stringPattern{
raw: raw,
pats: pats,
breaks: breaks,
literals: literals,
wildcard: wildcard,
}
}