path.go 24 KB

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