path.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
  2. // 📄 Github Repository: https://github.com/gofiber/fiber
  3. // 📌 API Documentation: https://docs.gofiber.io
  4. // ⚠️ This path parser was inspired by ucarion/urlpath (MIT License).
  5. // 💖 Maintained and modified for Fiber by @renewerner87
  6. package fiber
  7. import (
  8. "fmt"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "unicode"
  14. "github.com/google/uuid"
  15. "github.com/gofiber/fiber/v2/utils"
  16. )
  17. // routeParser holds the path segments and param names
  18. type routeParser struct {
  19. segs []*routeSegment // the parsed segments of the route
  20. params []string // that parameter names the parsed route
  21. wildCardCount int // number of wildcard parameters, used internally to give the wildcard parameter its number
  22. plusCount int // number of plus parameters, used internally to give the plus parameter its number
  23. }
  24. // paramsSeg holds the segment metadata
  25. type routeSegment struct {
  26. // const information
  27. Const string // constant part of the route
  28. // parameter information
  29. IsParam bool // Truth value that indicates whether it is a parameter or a constant part
  30. ParamName string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added
  31. ComparePart string // search part to find the end of the parameter
  32. PartCount int // how often is the search part contained in the non-param segments? -> necessary for greedy search
  33. IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus
  34. IsOptional bool // indicates whether the parameter is optional or not
  35. // common information
  36. IsLast bool // shows if the segment is the last one for the route
  37. HasOptionalSlash bool // segment has the possibility of an optional slash
  38. Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
  39. Length int // length of the parameter for segment, when its 0 then the length is undetermined
  40. // future TODO: add support for optional groups "/abc(/def)?"
  41. }
  42. // different special routing signs
  43. const (
  44. wildcardParam byte = '*' // indicates an optional greedy parameter
  45. plusParam byte = '+' // indicates a required greedy parameter
  46. optionalParam byte = '?' // concludes a parameter by name and makes it optional
  47. paramStarterChar byte = ':' // start character for a parameter with name
  48. slashDelimiter byte = '/' // separator for the route, unlike the other delimiters this character at the end can be optional
  49. escapeChar byte = '\\' // escape character
  50. paramConstraintStart byte = '<' // start of type constraint for a parameter
  51. paramConstraintEnd byte = '>' // end of type constraint for a parameter
  52. paramConstraintSeparator byte = ';' // separator of type constraints for a parameter
  53. paramConstraintDataStart byte = '(' // start of data of type constraint for a parameter
  54. paramConstraintDataEnd byte = ')' // end of data of type constraint for a parameter
  55. paramConstraintDataSeparator byte = ',' // separator of datas of type constraint for a parameter
  56. )
  57. // TypeConstraint parameter constraint types
  58. type TypeConstraint int16
  59. type Constraint struct {
  60. ID TypeConstraint
  61. RegexCompiler *regexp.Regexp
  62. Data []string
  63. }
  64. const (
  65. noConstraint TypeConstraint = iota + 1
  66. intConstraint
  67. boolConstraint
  68. floatConstraint
  69. alphaConstraint
  70. datetimeConstraint
  71. guidConstraint
  72. minLenConstraint
  73. maxLenConstraint
  74. lenConstraint
  75. betweenLenConstraint
  76. minConstraint
  77. maxConstraint
  78. rangeConstraint
  79. regexConstraint
  80. )
  81. // list of possible parameter and segment delimiter
  82. var (
  83. // slash has a special role, unlike the other parameters it must not be interpreted as a parameter
  84. routeDelimiter = []byte{slashDelimiter, '-', '.'}
  85. // list of greedy parameters
  86. greedyParameters = []byte{wildcardParam, plusParam}
  87. // list of chars for the parameter recognizing
  88. parameterStartChars = []byte{wildcardParam, plusParam, paramStarterChar}
  89. // list of chars of delimiters and the starting parameter name char
  90. parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...)
  91. // list of chars to find the end of a parameter
  92. parameterEndChars = append([]byte{optionalParam}, parameterDelimiterChars...)
  93. // list of parameter constraint start
  94. parameterConstraintStartChars = []byte{paramConstraintStart}
  95. // list of parameter constraint end
  96. parameterConstraintEndChars = []byte{paramConstraintEnd}
  97. // list of parameter separator
  98. parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
  99. // list of parameter constraint data start
  100. parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
  101. // list of parameter constraint data end
  102. parameterConstraintDataEndChars = []byte{paramConstraintDataEnd}
  103. // list of parameter constraint data separator
  104. parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
  105. )
  106. // RoutePatternMatch reports whether the given request path would match the
  107. // provided Fiber route pattern using the same rules as the router. This can be
  108. // handy in tests or tooling where you need to verify patterns without
  109. // registering them on an App instance.
  110. //
  111. // An optional Config may be passed to control matching behavior such as
  112. // case-sensitivity or strict routing. When no configuration is supplied the
  113. // default Config is used.
  114. //
  115. // Example:
  116. //
  117. // match := fiber.RoutePatternMatch("/api/v1/users", "/api/:version/*")
  118. // // match == true
  119. func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
  120. // See logic in (*Route).match and (*App).register
  121. var ctxParams [maxParams]string
  122. config := Config{}
  123. if len(cfg) > 0 {
  124. config = cfg[0]
  125. }
  126. if path == "" {
  127. path = "/"
  128. }
  129. // Cannot have an empty pattern
  130. if pattern == "" {
  131. pattern = "/"
  132. }
  133. // Pattern always start with a '/'
  134. if pattern[0] != '/' {
  135. pattern = "/" + pattern
  136. }
  137. patternPretty := pattern
  138. // Case-sensitive routing, all to lowercase
  139. if !config.CaseSensitive {
  140. patternPretty = utils.ToLower(patternPretty)
  141. path = utils.ToLower(path)
  142. }
  143. // Strict routing, remove trailing slashes
  144. if !config.StrictRouting && len(patternPretty) > 1 {
  145. patternPretty = utils.TrimRight(patternPretty, '/')
  146. }
  147. parser := parseRoute(patternPretty)
  148. if patternPretty == "/" && path == "/" {
  149. return true
  150. // '*' wildcard matches any path
  151. } else if patternPretty == "/*" {
  152. return true
  153. }
  154. // Does this route have parameters
  155. if len(parser.params) > 0 {
  156. if match := parser.getMatch(path, path, &ctxParams, false); match {
  157. return true
  158. }
  159. }
  160. // Check for a simple match
  161. patternPretty = RemoveEscapeChar(patternPretty)
  162. if len(patternPretty) == len(path) && patternPretty == path {
  163. return true
  164. }
  165. // No match
  166. return false
  167. }
  168. // parseRoute analyzes the route and divides it into segments for constant areas and parameters,
  169. // this information is needed later when assigning the requests to the declared routes
  170. func parseRoute(pattern string) routeParser {
  171. parser := routeParser{}
  172. originalPattern := pattern
  173. part := ""
  174. for len(pattern) > 0 {
  175. nextParamPosition := findNextParamPosition(pattern)
  176. // handle the parameter part
  177. if nextParamPosition == 0 {
  178. processedPart, seg := parser.analyseParameterPart(pattern)
  179. parser.params, parser.segs, part = append(parser.params, seg.ParamName), append(parser.segs, seg), processedPart
  180. } else {
  181. processedPart, seg := parser.analyseConstantPart(pattern, nextParamPosition)
  182. parser.segs, part = append(parser.segs, seg), processedPart
  183. }
  184. // reduce the pattern by the processed parts
  185. if len(part) == len(pattern) {
  186. break
  187. }
  188. pattern = pattern[len(part):]
  189. }
  190. // mark last segment
  191. if len(parser.segs) > 0 {
  192. parser.segs[len(parser.segs)-1].IsLast = true
  193. }
  194. parser.segs = addParameterMetaInfo(parser.segs)
  195. // Check if the route has too many parameters
  196. if len(parser.params) > maxParams {
  197. panic(fmt.Sprintf("Route '%s' has %d parameters, which exceeds the maximum of %d",
  198. originalPattern, len(parser.params), maxParams))
  199. }
  200. return parser
  201. }
  202. // addParameterMetaInfo add important meta information to the parameter segments
  203. // to simplify the search for the end of the parameter
  204. func addParameterMetaInfo(segs []*routeSegment) []*routeSegment {
  205. var comparePart string
  206. segLen := len(segs)
  207. // loop from end to begin
  208. for i := segLen - 1; i >= 0; i-- {
  209. // set the compare part for the parameter
  210. if segs[i].IsParam {
  211. // important for finding the end of the parameter
  212. segs[i].ComparePart = RemoveEscapeChar(comparePart)
  213. } else {
  214. comparePart = segs[i].Const
  215. if len(comparePart) > 1 {
  216. comparePart = utils.TrimRight(comparePart, slashDelimiter)
  217. }
  218. }
  219. }
  220. // loop from begin to end
  221. for i := 0; i < segLen; i++ {
  222. // check how often the compare part is in the following const parts
  223. if segs[i].IsParam {
  224. // check if parameter segments are directly after each other and if one of them is greedy
  225. // in case the next parameter or the current parameter is not a wildcard it's not greedy, we only want one character
  226. if segLen > i+1 && !segs[i].IsGreedy && segs[i+1].IsParam && !segs[i+1].IsGreedy {
  227. segs[i].Length = 1
  228. }
  229. if segs[i].ComparePart == "" {
  230. continue
  231. }
  232. for j := i + 1; j <= len(segs)-1; j++ {
  233. if !segs[j].IsParam {
  234. // count is important for the greedy match
  235. segs[i].PartCount += strings.Count(segs[j].Const, segs[i].ComparePart)
  236. }
  237. }
  238. // check if the end of the segment is a optional slash and then if the segement is optional or the last one
  239. } else if segs[i].Const[len(segs[i].Const)-1] == slashDelimiter && (segs[i].IsLast || (segLen > i+1 && segs[i+1].IsOptional)) {
  240. segs[i].HasOptionalSlash = true
  241. }
  242. }
  243. return segs
  244. }
  245. // findNextParamPosition search for the next possible parameter start position
  246. func findNextParamPosition(pattern string) int {
  247. nextParamPosition := findNextNonEscapedCharsetPosition(pattern, parameterStartChars)
  248. if nextParamPosition != -1 && len(pattern) > nextParamPosition && pattern[nextParamPosition] != wildcardParam {
  249. // search for parameter characters for the found parameter start,
  250. // if there are more, move the parameter start to the last parameter char
  251. for found := findNextNonEscapedCharsetPosition(pattern[nextParamPosition+1:], parameterStartChars); found == 0; {
  252. nextParamPosition++
  253. if len(pattern) > nextParamPosition {
  254. break
  255. }
  256. }
  257. }
  258. return nextParamPosition
  259. }
  260. // analyseConstantPart find the end of the constant part and create the route segment
  261. func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) {
  262. // handle the constant part
  263. processedPart := pattern
  264. if nextParamPosition != -1 {
  265. // remove the constant part until the parameter
  266. processedPart = pattern[:nextParamPosition]
  267. }
  268. constPart := RemoveEscapeChar(processedPart)
  269. return processedPart, &routeSegment{
  270. Const: constPart,
  271. Length: len(constPart),
  272. }
  273. }
  274. // analyseParameterPart find the parameter end and create the route segment
  275. func (routeParser *routeParser) analyseParameterPart(pattern string) (string, *routeSegment) {
  276. isWildCard := pattern[0] == wildcardParam
  277. isPlusParam := pattern[0] == plusParam
  278. var parameterEndPosition int
  279. if strings.ContainsRune(pattern, rune(paramConstraintStart)) && strings.ContainsRune(pattern, rune(paramConstraintEnd)) {
  280. parameterEndPosition = findNextCharsetPositionConstraint(pattern[1:], parameterEndChars)
  281. } else {
  282. parameterEndPosition = findNextNonEscapedCharsetPosition(pattern[1:], parameterEndChars)
  283. }
  284. parameterConstraintStart := -1
  285. parameterConstraintEnd := -1
  286. // handle wildcard end
  287. switch {
  288. case isWildCard, isPlusParam:
  289. parameterEndPosition = 0
  290. case parameterEndPosition == -1:
  291. parameterEndPosition = len(pattern) - 1
  292. case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars):
  293. parameterEndPosition++
  294. }
  295. // find constraint part if exists in the parameter part and remove it
  296. if parameterEndPosition > 0 {
  297. parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
  298. parameterConstraintEnd = findLastCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars)
  299. }
  300. // cut params part
  301. processedPart := pattern[0 : parameterEndPosition+1]
  302. paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
  303. // Check has constraint
  304. var constraints []*Constraint
  305. if hasConstraint := parameterConstraintStart != -1 && parameterConstraintEnd != -1; hasConstraint {
  306. constraintString := pattern[parameterConstraintStart+1 : parameterConstraintEnd]
  307. userConstraints := splitNonEscaped(constraintString, string(parameterConstraintSeparatorChars))
  308. constraints = make([]*Constraint, 0, len(userConstraints))
  309. for _, c := range userConstraints {
  310. start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
  311. end := findLastCharsetPosition(c, parameterConstraintDataEndChars)
  312. // Assign constraint
  313. if start != -1 && end != -1 {
  314. constraint := &Constraint{
  315. ID: getParamConstraintType(c[:start]),
  316. }
  317. // remove escapes from data
  318. if constraint.ID != regexConstraint {
  319. constraint.Data = splitNonEscaped(c[start+1:end], string(parameterConstraintDataSeparatorChars))
  320. if len(constraint.Data) == 1 {
  321. constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
  322. } else if len(constraint.Data) == 2 { // This is fine, we simply expect two parts
  323. constraint.Data[0] = RemoveEscapeChar(constraint.Data[0])
  324. constraint.Data[1] = RemoveEscapeChar(constraint.Data[1])
  325. }
  326. }
  327. // Precompile regex if has regex constraint
  328. if constraint.ID == regexConstraint {
  329. constraint.Data = []string{c[start+1 : end]}
  330. constraint.RegexCompiler = regexp.MustCompile(constraint.Data[0])
  331. }
  332. constraints = append(constraints, constraint)
  333. } else {
  334. constraints = append(constraints, &Constraint{
  335. ID: getParamConstraintType(c),
  336. Data: []string{},
  337. })
  338. }
  339. }
  340. paramName = RemoveEscapeChar(GetTrimmedParam(pattern[0:parameterConstraintStart]))
  341. }
  342. // add access iterator to wildcard and plus
  343. if isWildCard {
  344. routeParser.wildCardCount++
  345. paramName += strconv.Itoa(routeParser.wildCardCount)
  346. } else if isPlusParam {
  347. routeParser.plusCount++
  348. paramName += strconv.Itoa(routeParser.plusCount)
  349. }
  350. segment := &routeSegment{
  351. ParamName: paramName,
  352. IsParam: true,
  353. IsOptional: isWildCard || pattern[parameterEndPosition] == optionalParam,
  354. IsGreedy: isWildCard || isPlusParam,
  355. }
  356. if len(constraints) > 0 {
  357. segment.Constraints = constraints
  358. }
  359. return processedPart, segment
  360. }
  361. // isInCharset check is the given character in the charset list
  362. func isInCharset(searchChar byte, charset []byte) bool {
  363. for _, char := range charset {
  364. if char == searchChar {
  365. return true
  366. }
  367. }
  368. return false
  369. }
  370. // findNextCharsetPosition search the next char position from the charset
  371. func findNextCharsetPosition(search string, charset []byte) int {
  372. nextPosition := -1
  373. for _, char := range charset {
  374. if pos := strings.IndexByte(search, char); pos != -1 && (pos < nextPosition || nextPosition == -1) {
  375. nextPosition = pos
  376. }
  377. }
  378. return nextPosition
  379. }
  380. // findNextCharsetPosition search the last char position from the charset
  381. func findLastCharsetPosition(search string, charset []byte) int {
  382. lastPosition := -1
  383. for _, char := range charset {
  384. if pos := strings.LastIndexByte(search, char); pos != -1 && (pos < lastPosition || lastPosition == -1) {
  385. lastPosition = pos
  386. }
  387. }
  388. return lastPosition
  389. }
  390. // findNextCharsetPositionConstraint search the next char position from the charset
  391. // unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
  392. func findNextCharsetPositionConstraint(search string, charset []byte) int {
  393. constraintStart := findNextNonEscapedCharsetPosition(search, parameterConstraintStartChars)
  394. constraintEnd := findNextNonEscapedCharsetPosition(search, parameterConstraintEndChars)
  395. nextPosition := -1
  396. for _, char := range charset {
  397. pos := strings.IndexByte(search, char)
  398. if pos != -1 && (pos < nextPosition || nextPosition == -1) {
  399. if (pos > constraintStart && pos > constraintEnd) || (pos < constraintStart && pos < constraintEnd) {
  400. nextPosition = pos
  401. }
  402. }
  403. }
  404. return nextPosition
  405. }
  406. // findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters
  407. func findNextNonEscapedCharsetPosition(search string, charset []byte) int {
  408. pos := findNextCharsetPosition(search, charset)
  409. for pos > 0 && search[pos-1] == escapeChar {
  410. if len(search) == pos+1 {
  411. // escaped character is at the end
  412. return -1
  413. }
  414. nextPossiblePos := findNextCharsetPosition(search[pos+1:], charset)
  415. if nextPossiblePos == -1 {
  416. return -1
  417. }
  418. // the previous character is taken into consideration
  419. pos = nextPossiblePos + pos + 1
  420. }
  421. return pos
  422. }
  423. // splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators
  424. // This function also takes a care of escape char when splitting.
  425. func splitNonEscaped(s, sep string) []string {
  426. var result []string
  427. i := findNextNonEscapedCharsetPosition(s, []byte(sep))
  428. for i > -1 {
  429. result = append(result, s[:i])
  430. s = s[i+len(sep):]
  431. i = findNextNonEscapedCharsetPosition(s, []byte(sep))
  432. }
  433. return append(result, s)
  434. }
  435. // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
  436. func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here
  437. var i, paramsIterator, partLen int
  438. for _, segment := range routeParser.segs {
  439. partLen = len(detectionPath)
  440. // check const segment
  441. if !segment.IsParam {
  442. i = segment.Length
  443. // is optional part or the const part must match with the given string
  444. // check if the end of the segment is an optional slash
  445. if segment.HasOptionalSlash && partLen == i-1 && detectionPath == segment.Const[:i-1] {
  446. i--
  447. } else if !(i <= partLen && detectionPath[:i] == segment.Const) {
  448. return false
  449. }
  450. } else {
  451. // determine parameter length
  452. i = findParamLen(detectionPath, segment)
  453. if !segment.IsOptional && i == 0 {
  454. return false
  455. }
  456. // take over the params positions
  457. params[paramsIterator] = path[:i]
  458. if !(segment.IsOptional && i == 0) {
  459. // check constraint
  460. for _, c := range segment.Constraints {
  461. if matched := c.CheckConstraint(params[paramsIterator]); !matched {
  462. return false
  463. }
  464. }
  465. }
  466. paramsIterator++
  467. }
  468. // reduce founded part from the string
  469. if partLen > 0 {
  470. detectionPath, path = detectionPath[i:], path[i:]
  471. }
  472. }
  473. if detectionPath != "" && !partialCheck {
  474. return false
  475. }
  476. return true
  477. }
  478. // findParamLen for the expressjs wildcard behavior (right to left greedy)
  479. // look at the other segments and take what is left for the wildcard from right to left
  480. func findParamLen(s string, segment *routeSegment) int {
  481. if segment.IsLast {
  482. return findParamLenForLastSegment(s, segment)
  483. }
  484. if segment.Length != 0 && len(s) >= segment.Length {
  485. return segment.Length
  486. } else if segment.IsGreedy {
  487. // Search the parameters until the next constant part
  488. // special logic for greedy params
  489. searchCount := strings.Count(s, segment.ComparePart)
  490. if searchCount > 1 {
  491. return findGreedyParamLen(s, searchCount, segment)
  492. }
  493. }
  494. if len(segment.ComparePart) == 1 {
  495. if constPosition := strings.IndexByte(s, segment.ComparePart[0]); constPosition != -1 {
  496. return constPosition
  497. }
  498. } else if constPosition := strings.Index(s, segment.ComparePart); constPosition != -1 {
  499. // if the compare part was found, but contains a slash although this part is not greedy, then it must not match
  500. // example: /api/:param/fixedEnd -> path: /api/123/456/fixedEnd = no match , /api/123/fixedEnd = match
  501. if !segment.IsGreedy && strings.IndexByte(s[:constPosition], slashDelimiter) != -1 {
  502. return 0
  503. }
  504. return constPosition
  505. }
  506. return len(s)
  507. }
  508. // findParamLenForLastSegment get the length of the parameter if it is the last segment
  509. func findParamLenForLastSegment(s string, seg *routeSegment) int {
  510. if !seg.IsGreedy {
  511. if i := strings.IndexByte(s, slashDelimiter); i != -1 {
  512. return i
  513. }
  514. }
  515. return len(s)
  516. }
  517. // findGreedyParamLen get the length of the parameter for greedy segments from right to left
  518. func findGreedyParamLen(s string, searchCount int, segment *routeSegment) int {
  519. // check all from right to left segments
  520. for i := segment.PartCount; i > 0 && searchCount > 0; i-- {
  521. searchCount--
  522. if constPosition := strings.LastIndex(s, segment.ComparePart); constPosition != -1 {
  523. s = s[:constPosition]
  524. } else {
  525. break
  526. }
  527. }
  528. return len(s)
  529. }
  530. // GetTrimmedParam trims the ':' & '?' from a string
  531. func GetTrimmedParam(param string) string {
  532. start := 0
  533. end := len(param)
  534. if end == 0 || param[start] != paramStarterChar { // is not a param
  535. return param
  536. }
  537. start++
  538. if param[end-1] == optionalParam { // is ?
  539. end--
  540. }
  541. return param[start:end]
  542. }
  543. // RemoveEscapeChar remove escape characters
  544. func RemoveEscapeChar(word string) string {
  545. if strings.IndexByte(word, escapeChar) != -1 {
  546. return strings.ReplaceAll(word, string(escapeChar), "")
  547. }
  548. return word
  549. }
  550. func getParamConstraintType(constraintPart string) TypeConstraint {
  551. switch constraintPart {
  552. case ConstraintInt:
  553. return intConstraint
  554. case ConstraintBool:
  555. return boolConstraint
  556. case ConstraintFloat:
  557. return floatConstraint
  558. case ConstraintAlpha:
  559. return alphaConstraint
  560. case ConstraintGuid:
  561. return guidConstraint
  562. case ConstraintMinLen, ConstraintMinLenLower:
  563. return minLenConstraint
  564. case ConstraintMaxLen, ConstraintMaxLenLower:
  565. return maxLenConstraint
  566. case ConstraintLen:
  567. return lenConstraint
  568. case ConstraintBetweenLen, ConstraintBetweenLenLower:
  569. return betweenLenConstraint
  570. case ConstraintMin:
  571. return minConstraint
  572. case ConstraintMax:
  573. return maxConstraint
  574. case ConstraintRange:
  575. return rangeConstraint
  576. case ConstraintDatetime:
  577. return datetimeConstraint
  578. case ConstraintRegex:
  579. return regexConstraint
  580. default:
  581. return noConstraint
  582. }
  583. }
  584. //nolint:errcheck // TODO: Properly check _all_ errors in here, log them & immediately return
  585. func (c *Constraint) CheckConstraint(param string) bool {
  586. var err error
  587. var num int
  588. // check data exists
  589. needOneData := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
  590. needTwoData := []TypeConstraint{betweenLenConstraint, rangeConstraint}
  591. for _, data := range needOneData {
  592. if c.ID == data && len(c.Data) == 0 {
  593. return false
  594. }
  595. }
  596. for _, data := range needTwoData {
  597. if c.ID == data && len(c.Data) < 2 {
  598. return false
  599. }
  600. }
  601. // check constraints
  602. switch c.ID {
  603. case noConstraint:
  604. // Nothing to check
  605. case intConstraint:
  606. _, err = strconv.Atoi(param)
  607. case boolConstraint:
  608. _, err = strconv.ParseBool(param)
  609. case floatConstraint:
  610. _, err = strconv.ParseFloat(param, 32)
  611. case alphaConstraint:
  612. for _, r := range param {
  613. if !unicode.IsLetter(r) {
  614. return false
  615. }
  616. }
  617. case guidConstraint:
  618. _, err = uuid.Parse(param)
  619. case minLenConstraint:
  620. data, _ := strconv.Atoi(c.Data[0])
  621. if len(param) < data {
  622. return false
  623. }
  624. case maxLenConstraint:
  625. data, _ := strconv.Atoi(c.Data[0])
  626. if len(param) > data {
  627. return false
  628. }
  629. case lenConstraint:
  630. data, _ := strconv.Atoi(c.Data[0])
  631. if len(param) != data {
  632. return false
  633. }
  634. case betweenLenConstraint:
  635. data, _ := strconv.Atoi(c.Data[0])
  636. data2, _ := strconv.Atoi(c.Data[1])
  637. length := len(param)
  638. if length < data || length > data2 {
  639. return false
  640. }
  641. case minConstraint:
  642. data, _ := strconv.Atoi(c.Data[0])
  643. num, err = strconv.Atoi(param)
  644. if num < data {
  645. return false
  646. }
  647. case maxConstraint:
  648. data, _ := strconv.Atoi(c.Data[0])
  649. num, err = strconv.Atoi(param)
  650. if num > data {
  651. return false
  652. }
  653. case rangeConstraint:
  654. data, _ := strconv.Atoi(c.Data[0])
  655. data2, _ := strconv.Atoi(c.Data[1])
  656. num, err = strconv.Atoi(param)
  657. if num < data || num > data2 {
  658. return false
  659. }
  660. case datetimeConstraint:
  661. _, err = time.Parse(c.Data[0], param)
  662. case regexConstraint:
  663. if match := c.RegexCompiler.MatchString(param); !match {
  664. return false
  665. }
  666. }
  667. return err == nil
  668. }