Skip to content

Commit 343a34f

Browse files
committed
Encode using identity function by default, and remove
1 parent a924161 commit 343a34f

File tree

5 files changed

+75
-137
lines changed

5 files changed

+75
-137
lines changed

README.md

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ import pathToRegexp "github.com/soongo/path-to-regexp"
5353
- **Delimiter** The default delimiter for segments. (default: `'/'`)
5454
- **EndsWith** Optional character, or list of characters, to treat as "end" characters.
5555
- **Whitelist** List of characters to consider delimiters when parsing. (default: `nil`, any character)
56-
- **Encode** How to encode uri. (default: `pathToRegexp.EncodeURIComponent`)
57-
- **Decode** How to decode uri. (default: `pathToRegexp.DecodeURIComponent`)
56+
- **Encode** How to encode uri. (default: `func (uri string, token interface{}) string { return uri }`)
57+
- **Decode** How to decode uri. (default: `func (uri string, token interface{}) string { return uri }`)
5858

5959
```go
6060
var tokens []pathToRegexp.Token
@@ -221,27 +221,19 @@ fmt.Println(match)
221221
The `match` function will return a function for transforming paths into parameters:
222222

223223
```go
224-
match := pathToRegexp.MustMatch("/user/:id")
224+
match := pathToRegexp.MustMatch("/user/:id", &pathToRegexp.Options{Decode: func(str string, token interface{}) string {
225+
return pathToRegexp.DecodeURIComponent(str)
226+
}})
225227

226-
fmt.Printf("%#v\n", match("/user/123"))
228+
match("/user/123")
227229
//=> &pathtoregexp.MatchResult{Path:"/user/123", Index:0, Params:map[interface {}]interface {}{"id":"123"}}
228230

229231
match("/invalid") //=> nil
230-
```
231-
232-
### Normalize Pathname
233232

234-
The `NormalizePathname` function will return a normalized string for matching with `PathToRegexp`.
235-
236-
```js
237-
re := pathToRegexp.Must(pathToRegexp.PathToRegexp("/caf\u00E9", nil, nil))
238-
input := pathToRegexp.EncodeURI("/caf\u00E9");
239-
re.MatchString(input) //=> false, nil
240-
re.MatchString(pathToRegexp.NormalizePathname(input)); //=> true, nil
233+
match("/user/caf%C3%A9")
234+
//=> &pathtoregexp.MatchResult{Path:"/user/caf%C3%A9", Index:0, Params:map[interface {}]interface {}{"id":"café"}}
241235
```
242236

243-
**Note:** It may be preferable to implement something in your own library that normalizes the pathname for matching. E.g. [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) automatically URI encodes paths for you, which would result in a consistent match.
244-
245237
### Parse
246238

247239
The `Parse` function will return a list of strings and tokens from a path string:
@@ -266,32 +258,35 @@ fmt.Printf("%#v\n", tokens[2])
266258
The `Compile` function will return a function for transforming parameters into a valid path:
267259

268260
```go
269-
falseValue := false
270-
toPath := pathToRegexp.MustCompile("/user/:id", nil)
261+
toPath := pathToRegexp.MustCompile("/user/:id", &pathToRegexp.Options{Encode: func(str string, token interface{}) string {
262+
return pathToRegexp.EncodeURIComponent(str)
263+
}})
264+
265+
toPath(map[string]int{"id": 123}) //=> "/user/123"
266+
toPath(map[string]string{"id": "café"}) //=> "/user/caf%C3%A9"
267+
toPath(map[string]string{"id": "/"}) //=> "/user/%2F"
271268

272-
toPath(map[string]int{"id": 123}, nil) //=> "/user/123"
273-
toPath(map[string]string{"id": "café"}, nil) //=> "/user/caf%C3%A9"
274-
toPath(map[string]string{"id": "/"}, nil) //=> "/user/%2F"
269+
toPath(map[string]string{"id": ":/"}) //=> "/user/%3A%2F"
275270

276-
toPath(map[string]string{"id": ":/"}, nil) //=> "/user/%3A%2F"
277-
toPath(map[string]string{"id": ":/"}, &Options{
278-
Encode: func(value string, token interface{}) string {
279-
return value
280-
},
281-
Validate: &falseValue,
282-
}) //=> "/user/:/"
271+
// Without `encode`, you need to make sure inputs are encoded correctly.
272+
falseValue := false
273+
toPathRaw := pathToRegexp.MustCompile("/user/:id", &pathToRegexp.Options{Validate: &falseValue})
274+
toPathRaw(map[string]string{"id": "%3A%2F"}); //=> "/user/%3A%2F"
275+
toPathRaw(map[string]string{"id": ":/"}); //=> "/user/:/"
283276

284277
toPathRepeated := pathToRegexp.MustCompile("/:segment+", nil)
285278

286-
toPathRepeated(map[string]string{"segment": "foo"}, nil) //=> "/foo"
287-
toPathRepeated(map[string][]string{"segment": {"a", "b", "c"}}, nil) //=> "/a/b/c"
279+
toPathRepeated(map[string]string{"segment": "foo"}) //=> "/foo"
280+
toPathRepeated(map[string][]string{"segment": {"a", "b", "c"}}) //=> "/a/b/c"
281+
282+
toPathRegexp := pathToRegexp.MustCompile("/user/:id(\\d+)", &pathToRegexp.Options{Validate: &falseValue})
288283

289-
toPathRegexp := pathToRegexp.MustCompile("/user/:id(\\d+)", nil)
284+
toPathRegexp(map[string]int{"id": 123}) //=> "/user/123"
285+
toPathRegexp(map[string]string{"id": "123"}) //=> "/user/123"
286+
toPathRegexp(map[string]string{"id": "abc"}) //=> "/user/abc"
290287

291-
toPathRegexp(map[string]int{"id": 123}, nil) //=> "/user/123"
292-
toPathRegexp(map[string]string{"id": "123"}, nil) //=> "/user/123"
293-
toPathRegexp(map[string]string{"id": "abc"}, nil) //=> panic
294-
toPathRegexp(map[string]string{"id": "abc"}, &Options{Validate: &falseValue}) //=> "/user/abc"
288+
toPathRegexp = pathToRegexp.MustCompile("/user/:id(\\d+)", nil)
289+
toPathRegexp(map[string]string{"id": "abc"}) //=> panic
295290
```
296291

297292
**Note:** The generated function will panic on invalid input. It will do all necessary checks to ensure the generated path is valid. This method only works with strings.

go.mod

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,4 @@ module github.com/soongo/path-to-regexp
22

33
go 1.13
44

5-
require (
6-
github.com/dlclark/regexp2 v1.2.0
7-
golang.org/x/text v0.3.2
8-
)
5+
require github.com/dlclark/regexp2 v1.2.0

go.sum

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
22
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
3-
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
4-
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
5-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

path_to_regexp.go

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,9 @@ import (
1111
"reflect"
1212
"strconv"
1313
"strings"
14-
"unicode"
1514
"unsafe"
1615

17-
"golang.org/x/text/runes"
18-
1916
"github.com/dlclark/regexp2"
20-
"golang.org/x/text/transform"
21-
"golang.org/x/text/unicode/norm"
2217
)
2318

2419
// Token is parsed from path. For example, using `/user/:id`, `tokens` will
@@ -93,6 +88,10 @@ const defaultDelimiter = "/"
9388
var escapeRegexp = regexp2.MustCompile("([.+*?=^!:${}()[\\]|/\\\\])", regexp2.None)
9489
var tokenRegexp = regexp2.MustCompile("\\((?!\\?)", regexp2.None)
9590

91+
func identity(uri string, token interface{}) string {
92+
return uri
93+
}
94+
9695
// EncodeURIComponent encodes a text string as a valid component of a Uniform
9796
// Resource Identifier (URI).
9897
func EncodeURIComponent(str string) string {
@@ -150,33 +149,6 @@ func decodeURI(str string) string {
150149
return str
151150
}
152151

153-
// Returns the String value result of normalizing the string into the normalization form
154-
// named by form as specified in Unicode Standard Annex #15, Unicode Normalization Forms.
155-
// param form Applicable values: "NFC", "NFD", "NFKC", or "NFKD", If not specified default
156-
// is "NFC"
157-
func normalize(str string, form ...norm.Form) string {
158-
f := norm.NFC
159-
if len(form) > 0 {
160-
f = form[0]
161-
}
162-
t := transform.Chain(f, runes.Remove(runes.In(unicode.Mn)), f)
163-
normStr, _, _ := transform.String(t, str)
164-
return normStr
165-
}
166-
167-
// NormalizePathname normalizes a pathname for matching, replaces multiple slashes
168-
// with a single slash and normalizes unicode characters to "NFC". When using this method,
169-
// `decode` should be an identity function so you don't decode strings twice.
170-
func NormalizePathname(pathname string) string {
171-
pathname = decodeURI(pathname)
172-
r := regexp2.MustCompile("\\/+", regexp2.None)
173-
pathname, err := r.Replace(pathname, "/", -1, -1)
174-
if err != nil {
175-
panic(err)
176-
}
177-
return pathname
178-
}
179-
180152
// Balanced bracket helper function.
181153
func balanced(open string, close string, str string, index int) int {
182154
count, i, arr := 0, index, strings.Split(str, "")
@@ -431,7 +403,7 @@ func tokensToFunction(tokens []interface{}, o *Options) (
431403
o = &Options{}
432404
}
433405
reFlags := flags(o)
434-
encode, validate := encodeURIComponent, true
406+
encode, validate := identity, true
435407
if o.Encode != nil {
436408
encode = o.Encode
437409
}
@@ -690,13 +662,16 @@ func tokensToRegExp(rawTokens []interface{}, tokens *[]Token, o *Options) (*rege
690662
o = &Options{}
691663
}
692664

693-
strict, start, end, route := o.Strict, true, true, ""
665+
strict, start, end, route, encode := o.Strict, true, true, "", identity
694666
if o.Start != nil {
695667
start = *o.Start
696668
}
697669
if o.End != nil {
698670
end = *o.End
699671
}
672+
if o.Encode != nil {
673+
encode = o.Encode
674+
}
700675

701676
var ends []string
702677
if o.EndsWith != nil {
@@ -731,7 +706,7 @@ func tokensToRegExp(rawTokens []interface{}, tokens *[]Token, o *Options) (*rege
731706
// Iterate over the tokens and create our regexp string.
732707
for _, token := range rawTokens {
733708
if str, ok := token.(string); ok {
734-
route += escapeString(str)
709+
route += escapeString(encode(str, nil))
735710
} else if token, ok := token.(Token); ok {
736711
capture := token.Pattern
737712
if token.Repeat {

path_to_regexp_test.go

Lines changed: 34 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,7 @@ var tests = []a{
217217
a{
218218
a{m{}, nil},
219219
a{m{"test": "abc"}, "/abc"},
220-
a{
221-
m{"test": "a+b"},
222-
"/a+b",
223-
&Options{
224-
Encode: func(uri string, token interface{}) string {
225-
return uri
226-
},
227-
},
228-
},
220+
a{m{"test": "a+b"}, "/a+b"},
229221
a{
230222
m{"test": "a+b"},
231223
"/test",
@@ -238,7 +230,7 @@ var tests = []a{
238230
},
239231
},
240232
},
241-
a{m{"test": "a+b"}, "/a%2Bb"},
233+
a{m{"test": "a+b"}, "/a%2Bb", &Options{Encode: encodeURIComponent}},
242234
},
243235
},
244236
{
@@ -346,15 +338,7 @@ var tests = []a{
346338
a{
347339
a{m{}, nil},
348340
a{m{"test": "abc"}, "/abc"},
349-
a{
350-
m{"test": "a+b"},
351-
"/a+b",
352-
&Options{
353-
Encode: func(uri string, token interface{}) string {
354-
return uri
355-
},
356-
},
357-
},
341+
a{m{"test": "a+b"}, "/a+b"},
358342
a{
359343
m{"test": "a+b"},
360344
"/test",
@@ -367,7 +351,7 @@ var tests = []a{
367351
},
368352
},
369353
},
370-
a{m{"test": "a+b"}, "/a%2Bb"},
354+
a{m{"test": "a+b"}, "/a%2Bb", &Options{Encode: encodeURIComponent}},
371355
},
372356
},
373357
{
@@ -690,8 +674,16 @@ var tests = []a{
690674
},
691675
a{
692676
a{m{"test": "route"}, "/route"},
693-
a{m{"test": "something/else"}, "/something%2Felse"},
694-
a{m{"test": "something/else/more"}, "/something%2Felse%2Fmore"},
677+
a{
678+
m{"test": "something/else"},
679+
"/something%2Felse",
680+
&Options{Encode: encodeURIComponent},
681+
},
682+
a{
683+
m{"test": "something/else/more"},
684+
"/something%2Felse%2Fmore",
685+
&Options{Encode: encodeURIComponent},
686+
},
695687
},
696688
},
697689
{
@@ -1175,8 +1167,8 @@ var tests = []a{
11751167
a{
11761168
a{m{"test": ""}, "/"},
11771169
a{m{"test": "abc"}, "/abc"},
1178-
a{m{"test": "abc/123"}, "/abc%2F123"},
1179-
a{m{"test": "abc/123/456"}, "/abc%2F123%2F456"},
1170+
a{m{"test": "abc/123"}, "/abc%2F123", &Options{Encode: encodeURIComponent}},
1171+
a{m{"test": "abc/123/456"}, "/abc%2F123%2F456", &Options{Encode: encodeURIComponent}},
11801172
},
11811173
},
11821174
{
@@ -2240,7 +2232,7 @@ var tests = []a{
22402232
},
22412233
a{
22422234
a{m{"foo": "foo"}, "/foobaz"},
2243-
a{m{"foo": "foo/bar"}, "/foo%2Fbarbaz"},
2235+
a{m{"foo": "foo/bar"}, "/foo%2Fbarbaz", &Options{Encode: encodeURIComponent}},
22442236
a{m{"foo": a{"foo", "bar"}}, "/foo/barbaz"},
22452237
},
22462238
},
@@ -2420,7 +2412,8 @@ var tests = []a{
24202412
a{"/café", a{"/café", "café"}},
24212413
},
24222414
a{
2423-
a{m{"foo": "café"}, "/caf%C3%A9"},
2415+
a{m{"foo": "café"}, "/café"},
2416+
a{m{"foo": "café"}, "/caf%C3%A9", &Options{Encode: encodeURIComponent}},
24242417
},
24252418
},
24262419
{
@@ -2436,6 +2429,21 @@ var tests = []a{
24362429
a{nil, "/café"},
24372430
},
24382431
},
2432+
{
2433+
"/café",
2434+
&Options{Encode: func(uri string, token interface{}) string {
2435+
return encodeURI(uri)
2436+
}},
2437+
a{
2438+
"/café",
2439+
},
2440+
a{
2441+
a{"/caf%C3%A9", a{"/caf%C3%A9"}},
2442+
},
2443+
a{
2444+
a{nil, "/café"},
2445+
},
2446+
},
24392447
{
24402448
"packages/",
24412449
nil,
@@ -3163,40 +3171,6 @@ func TestPathToRegexp(t *testing.T) {
31633171
})
31643172
})
31653173

3166-
t.Run("normalize pathname", func(t *testing.T) {
3167-
t.Run("should match normalized pathnames", func(t *testing.T) {
3168-
re := Must(PathToRegexp("/caf\u00E9", nil, nil))
3169-
input := encodeURI("/cafe\u0301")
3170-
3171-
result := exec(re, input)
3172-
if result != nil {
3173-
t.Errorf("got %v want %v", result, nil)
3174-
}
3175-
3176-
want := []string{"/caf\u00E9"}
3177-
result = exec(re, normalize(NormalizePathname(input)))
3178-
if !reflect.DeepEqual(result, want) {
3179-
t.Errorf("got %v want %v", result, want)
3180-
}
3181-
})
3182-
3183-
t.Run("should not normalize encoded slash", func(t *testing.T) {
3184-
input, want := "/test/route%2F", "/test/route%2F"
3185-
result := NormalizePathname(input)
3186-
if result != want {
3187-
t.Errorf("got %s want %s", result, want)
3188-
}
3189-
})
3190-
3191-
t.Run("should fix repeated slashes", func(t *testing.T) {
3192-
input, want := encodeURI("/test///route"), "/test/route"
3193-
result := NormalizePathname(input)
3194-
if result != want {
3195-
t.Errorf("got %s want %s", result, want)
3196-
}
3197-
})
3198-
})
3199-
32003174
t.Run("path should be string, or strings, or a regular expression", func(t *testing.T) {
32013175
_, err := PathToRegexp(123, nil, nil)
32023176
if err == nil {

0 commit comments

Comments
 (0)