🐛 bug: fix route constraints problems (#2033)

* 🐛 bug: fix route constraints problems

* escape support for data

* exactLen -> len
This commit is contained in:
M. Efe Çetin 2022-08-26 15:16:06 +03:00 committed by GitHub
parent ffb2d4cb1a
commit aef7ea53b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 49 deletions

View File

@ -705,8 +705,11 @@ const (
ConstraintGuid = "guid" ConstraintGuid = "guid"
ConstraintMinLen = "minLen" ConstraintMinLen = "minLen"
ConstraintMaxLen = "maxLen" ConstraintMaxLen = "maxLen"
ConstraintExactLen = "exactLen" ConstraintLen = "len"
ConstraintBetweenLen = "betweenLen" ConstraintBetweenLen = "betweenLen"
ConstraintMinLenLower = "minlen"
ConstraintMaxLenLower = "maxlen"
ConstraintBetweenLenLower = "betweenlen"
ConstraintMin = "min" ConstraintMin = "min"
ConstraintMax = "max" ConstraintMax = "max"
ConstraintRange = "range" ConstraintRange = "range"

46
path.go
View File

@ -7,6 +7,7 @@
package fiber package fiber
import ( import (
"fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -79,7 +80,7 @@ const (
guidConstraint guidConstraint
minLenConstraint minLenConstraint
maxLenConstraint maxLenConstraint
exactLenConstraint lenConstraint
betweenLenConstraint betweenLenConstraint
minConstraint minConstraint
maxConstraint maxConstraint
@ -261,7 +262,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint { if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint {
constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd] constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
userconstraints := strings.Split(constraintString, string(parameterConstraintSeparatorChars)) userconstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars))
constraints = make([]*Constraint, 0, len(userconstraints)) constraints = make([]*Constraint, 0, len(userconstraints))
for _, c := range userconstraints { for _, c := range userconstraints {
@ -272,7 +273,15 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
if start != -1 && end != -1 { if start != -1 && end != -1 {
constraint := &Constraint{ constraint := &Constraint{
ID: getParamConstraintType(c[:start]), ID: getParamConstraintType(c[:start]),
Data: strings.Split(RemoveEscapeChar(c[start+1:end]), string(parameterConstraintDataSeparatorChars)), Data: splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars)),
}
// remove escapes from data
if len(constraint.Data) == 1 {
constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
} else if len(constraint.Data) == 2 {
constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
constraint.Data[1] = RemoveEscapeChar(constraint.Data[1])
} }
// Precompile regex if has regex constraint // Precompile regex if has regex constraint
@ -384,6 +393,21 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
return pos return pos
} }
// splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators
// This function also takes a care of escape char when splitting.
func splitNonEscaped(s, sep string) []string {
var result []string
i := findNextNonEscapedCharsetPosition(s, []byte(sep))
for i > -1 {
result = append(result, s[:i])
s = s[i+len(sep):]
i = findNextNonEscapedCharsetPosition(s, []byte(sep))
}
return append(result, s)
}
// getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool {
var i, paramsIterator, partLen int var i, paramsIterator, partLen int
@ -526,13 +550,13 @@ func getParamConstraintType(constraintPart string) TypeConstraint {
return alphaConstraint return alphaConstraint
case ConstraintGuid: case ConstraintGuid:
return guidConstraint return guidConstraint
case ConstraintMinLen: case ConstraintMinLen, ConstraintMinLenLower:
return minLenConstraint return minLenConstraint
case ConstraintMaxLen: case ConstraintMaxLen, ConstraintMaxLenLower:
return maxLenConstraint return maxLenConstraint
case ConstraintExactLen: case ConstraintLen:
return exactLenConstraint return lenConstraint
case ConstraintBetweenLen: case ConstraintBetweenLen, ConstraintBetweenLenLower:
return betweenLenConstraint return betweenLenConstraint
case ConstraintMin: case ConstraintMin:
return minConstraint return minConstraint
@ -555,7 +579,7 @@ func (c *Constraint) CheckConstraint(param string) bool {
var num int var num int
// check data exists // check data exists
needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, exactLenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint} needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint} needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint}
for _, data := range needOneData { for _, data := range needOneData {
@ -570,6 +594,8 @@ func (c *Constraint) CheckConstraint(param string) bool {
} }
} }
fmt.Print(c.Data)
// check constraints // check constraints
switch c.ID { switch c.ID {
case intConstraint: case intConstraint:
@ -598,7 +624,7 @@ func (c *Constraint) CheckConstraint(param string) bool {
if len(param) > data { if len(param) > data {
return false return false
} }
case exactLenConstraint: case lenConstraint:
data, _ := strconv.Atoi(c.Data[0]) data, _ := strconv.Atoi(c.Data[0])
if len(param) != data { if len(param) != data {

View File

@ -475,7 +475,7 @@ func Test_Path_matchParams(t *testing.T) {
{url: "/api/v1/123", params: []string{"123"}, match: true}, {url: "/api/v1/123", params: []string{"123"}, match: true},
{url: "/api/v1/12345", params: []string{"12345"}, match: true}, {url: "/api/v1/12345", params: []string{"12345"}, match: true},
}) })
testCase("/api/v1/:param<exactLen(5)>", []testparams{ testCase("/api/v1/:param<len(5)>", []testparams{
{url: "/api/v1/ent", params: []string{"ent"}, match: false}, {url: "/api/v1/ent", params: []string{"ent"}, match: false},
{url: "/api/v1/123", params: []string{"123"}, match: false}, {url: "/api/v1/123", params: []string{"123"}, match: false},
{url: "/api/v1/12345", params: []string{"12345"}, match: true}, {url: "/api/v1/12345", params: []string{"12345"}, match: true},
@ -549,6 +549,19 @@ func Test_Path_matchParams(t *testing.T) {
{url: "/api/v1/25", params: []string{"25"}, match: true}, {url: "/api/v1/25", params: []string{"25"}, match: true},
{url: "/api/v1/true", params: []string{"true"}, match: false}, {url: "/api/v1/true", params: []string{"true"}, match: false},
}) })
testCase("/api/v1/:param<int\\;range(10,30)>", []testparams{
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
{url: "/api/v1/87283827683", params: []string{"87283827683"}, match: true},
{url: "/api/v1/25", params: []string{"25"}, match: true},
{url: "/api/v1/true", params: []string{"true"}, match: true},
})
testCase("/api/v1/:param<range(10\\,30,1500)>", []testparams{
{url: "/api/v1/entity", params: []string{"entity"}, match: false},
{url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
{url: "/api/v1/25", params: []string{"25"}, match: true},
{url: "/api/v1/1200", params: []string{"1200"}, match: true},
{url: "/api/v1/true", params: []string{"true"}, match: false},
})
} }
func Test_Utils_GetTrimmedParam(t *testing.T) { func Test_Utils_GetTrimmedParam(t *testing.T) {
@ -681,7 +694,7 @@ func Benchmark_Path_matchParams(t *testing.B) {
{url: "/api/v1/123", params: []string{"123"}, match: true}, {url: "/api/v1/123", params: []string{"123"}, match: true},
{url: "/api/v1/12345", params: []string{"12345"}, match: true}, {url: "/api/v1/12345", params: []string{"12345"}, match: true},
}) })
benchCase("/api/v1/:param<exactLen(5)>", []testparams{ benchCase("/api/v1/:param<len(5)>", []testparams{
{url: "/api/v1/ent", params: []string{"ent"}, match: false}, {url: "/api/v1/ent", params: []string{"ent"}, match: false},
{url: "/api/v1/123", params: []string{"123"}, match: false}, {url: "/api/v1/123", params: []string{"123"}, match: false},
{url: "/api/v1/12345", params: []string{"12345"}, match: true}, {url: "/api/v1/12345", params: []string{"12345"}, match: true},
@ -755,28 +768,17 @@ func Benchmark_Path_matchParams(t *testing.B) {
{url: "/api/v1/25", params: []string{"25"}, match: true}, {url: "/api/v1/25", params: []string{"25"}, match: true},
{url: "/api/v1/true", params: []string{"true"}, match: false}, {url: "/api/v1/true", params: []string{"true"}, match: false},
}) })
} benchCase("/api/v1/:param<int\\;range(10,30)>", []testparams{
{url: "/api/v1/entity", params: []string{"entity"}, match: true},
func Test_Path_matchParams0(t *testing.T) { {url: "/api/v1/87283827683", params: []string{"87283827683"}, match: true},
t.Parallel() {url: "/api/v1/25", params: []string{"25"}, match: true},
type testparams struct { {url: "/api/v1/true", params: []string{"true"}, match: true},
url string })
params []string benchCase("/api/v1/:param<range(10\\,30,1500)>", []testparams{
match bool {url: "/api/v1/entity", params: []string{"entity"}, match: false},
partialCheck bool {url: "/api/v1/87283827683", params: []string{"8728382"}, match: false},
} {url: "/api/v1/25", params: []string{"25"}, match: true},
var ctxParams [maxParams]string {url: "/api/v1/1200", params: []string{"1200"}, match: true},
testCase := func(r string, cases []testparams) { {url: "/api/v1/true", params: []string{"true"}, match: false},
parser := parseRoute(r)
for _, c := range cases {
match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck)
utils.AssertEqual(t, c.match, match, fmt.Sprintf("route: '%s', url: '%s'", r, c.url))
if match && len(c.params) > 0 {
utils.AssertEqual(t, c.params[0:len(c.params)], ctxParams[0:len(c.params)], fmt.Sprintf("route: '%s', url: '%s'", r, c.url))
}
}
}
testCase("/api/v1/:param<datetime(2006-01-02)>", []testparams{
{url: "/api/v1/2005-11-01", params: []string{"2005-11-01"}, match: true},
}) })
} }