🐛 bug: fix route constraints problems (#2033)

* 🐛 bug: fix route constraints problems

* escape support for data

* exactLen -> len
pull/2050/head^2
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

@ -698,18 +698,21 @@ const (
// Route Constraints
const (
ConstraintInt = "int"
ConstraintBool = "bool"
ConstraintFloat = "float"
ConstraintAlpha = "alpha"
ConstraintGuid = "guid"
ConstraintMinLen = "minLen"
ConstraintMaxLen = "maxLen"
ConstraintExactLen = "exactLen"
ConstraintBetweenLen = "betweenLen"
ConstraintMin = "min"
ConstraintMax = "max"
ConstraintRange = "range"
ConstraintDatetime = "datetime"
ConstraintRegex = "regex"
ConstraintInt = "int"
ConstraintBool = "bool"
ConstraintFloat = "float"
ConstraintAlpha = "alpha"
ConstraintGuid = "guid"
ConstraintMinLen = "minLen"
ConstraintMaxLen = "maxLen"
ConstraintLen = "len"
ConstraintBetweenLen = "betweenLen"
ConstraintMinLenLower = "minlen"
ConstraintMaxLenLower = "maxlen"
ConstraintBetweenLenLower = "betweenlen"
ConstraintMin = "min"
ConstraintMax = "max"
ConstraintRange = "range"
ConstraintDatetime = "datetime"
ConstraintRegex = "regex"
)

46
path.go
View File

@ -7,6 +7,7 @@
package fiber
import (
"fmt"
"regexp"
"strconv"
"strings"
@ -79,7 +80,7 @@ const (
guidConstraint
minLenConstraint
maxLenConstraint
exactLenConstraint
lenConstraint
betweenLenConstraint
minConstraint
maxConstraint
@ -261,7 +262,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
if hasConstraint := (parameterConstraintStart != -1 && parameterConstraintEnd != -1); hasConstraint {
constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
userconstraints := strings.Split(constraintString, string(parameterConstraintSeparatorChars))
userconstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars))
constraints = make([]*Constraint, 0, len(userconstraints))
for _, c := range userconstraints {
@ -272,7 +273,15 @@ func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *r
if start != -1 && end != -1 {
constraint := &Constraint{
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
@ -384,6 +393,21 @@ func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
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
func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool {
var i, paramsIterator, partLen int
@ -526,13 +550,13 @@ func getParamConstraintType(constraintPart string) TypeConstraint {
return alphaConstraint
case ConstraintGuid:
return guidConstraint
case ConstraintMinLen:
case ConstraintMinLen, ConstraintMinLenLower:
return minLenConstraint
case ConstraintMaxLen:
case ConstraintMaxLen, ConstraintMaxLenLower:
return maxLenConstraint
case ConstraintExactLen:
return exactLenConstraint
case ConstraintBetweenLen:
case ConstraintLen:
return lenConstraint
case ConstraintBetweenLen, ConstraintBetweenLenLower:
return betweenLenConstraint
case ConstraintMin:
return minConstraint
@ -555,7 +579,7 @@ func (c *Constraint) CheckConstraint(param string) bool {
var num int
// 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}
for _, data := range needOneData {
@ -570,6 +594,8 @@ func (c *Constraint) CheckConstraint(param string) bool {
}
}
fmt.Print(c.Data)
// check constraints
switch c.ID {
case intConstraint:
@ -598,7 +624,7 @@ func (c *Constraint) CheckConstraint(param string) bool {
if len(param) > data {
return false
}
case exactLenConstraint:
case lenConstraint:
data, _ := strconv.Atoi(c.Data[0])
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/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/123", params: []string{"123"}, match: false},
{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/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) {
@ -681,7 +694,7 @@ func Benchmark_Path_matchParams(t *testing.B) {
{url: "/api/v1/123", params: []string{"123"}, 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/123", params: []string{"123"}, match: false},
{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/true", params: []string{"true"}, match: false},
})
}
func Test_Path_matchParams0(t *testing.T) {
t.Parallel()
type testparams struct {
url string
params []string
match bool
partialCheck bool
}
var ctxParams [maxParams]string
testCase := func(r string, cases []testparams) {
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},
benchCase("/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},
})
benchCase("/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},
})
}