helpers.go 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  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. package fiber
  5. import (
  6. "bytes"
  7. "crypto/tls"
  8. "fmt"
  9. "hash/crc32"
  10. "io"
  11. "net"
  12. "os"
  13. "path/filepath"
  14. "reflect"
  15. "strings"
  16. "time"
  17. "unsafe"
  18. "github.com/gofiber/fiber/v2/log"
  19. "github.com/gofiber/fiber/v2/utils"
  20. "github.com/valyala/bytebufferpool"
  21. "github.com/valyala/fasthttp"
  22. )
  23. // acceptType is a struct that holds the parsed value of an Accept header
  24. // along with quality, specificity, parameters, and order.
  25. // Used for sorting accept headers.
  26. type acceptedType struct {
  27. spec string
  28. quality float64
  29. specificity int
  30. order int
  31. params string
  32. }
  33. // getTLSConfig returns a net listener's tls config
  34. func getTLSConfig(ln net.Listener) *tls.Config {
  35. // Get listener type
  36. pointer := reflect.ValueOf(ln)
  37. // Is it a tls.listener?
  38. if pointer.String() == "<*tls.listener Value>" {
  39. // Copy value from pointer
  40. if val := reflect.Indirect(pointer); val.Type() != nil {
  41. // Get private field from value
  42. if field := val.FieldByName("config"); field.Type() != nil {
  43. // Copy value from pointer field (unsafe)
  44. newval := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())) //nolint:gosec // Probably the only way to extract the *tls.Config from a net.Listener. TODO: Verify there really is no easier way without using unsafe.
  45. if newval.Type() != nil {
  46. // Get element from pointer
  47. if elem := newval.Elem(); elem.Type() != nil {
  48. // Cast value to *tls.Config
  49. c, ok := elem.Interface().(*tls.Config)
  50. if !ok {
  51. panic(fmt.Errorf("failed to type-assert to *tls.Config"))
  52. }
  53. return c
  54. }
  55. }
  56. }
  57. }
  58. }
  59. return nil
  60. }
  61. // readContent opens a named file and read content from it
  62. func readContent(rf io.ReaderFrom, name string) (int64, error) {
  63. // Read file
  64. f, err := os.Open(filepath.Clean(name))
  65. if err != nil {
  66. return 0, fmt.Errorf("failed to open: %w", err)
  67. }
  68. defer func() {
  69. if err = f.Close(); err != nil {
  70. log.Errorf("Error closing file: %s", err)
  71. }
  72. }()
  73. if n, err := rf.ReadFrom(f); err != nil {
  74. return n, fmt.Errorf("failed to read: %w", err)
  75. }
  76. return 0, nil
  77. }
  78. // quoteString escape special characters in a given string
  79. func (app *App) quoteString(raw string) string {
  80. bb := bytebufferpool.Get()
  81. // quoted := string(fasthttp.AppendQuotedArg(bb.B, getBytes(raw)))
  82. quoted := app.getString(fasthttp.AppendQuotedArg(bb.B, app.getBytes(raw)))
  83. bytebufferpool.Put(bb)
  84. return quoted
  85. }
  86. // Scan stack if other methods match the request
  87. func (app *App) methodExist(ctx *Ctx) bool {
  88. var exists bool
  89. methods := app.config.RequestMethods
  90. for i := 0; i < len(methods); i++ {
  91. // Skip original method
  92. if ctx.methodINT == i {
  93. continue
  94. }
  95. // Reset stack index
  96. indexRoute := -1
  97. tree, ok := ctx.app.treeStack[i][ctx.treePath]
  98. if !ok {
  99. tree = ctx.app.treeStack[i][""]
  100. }
  101. // Get stack length
  102. lenr := len(tree) - 1
  103. // Loop over the route stack starting from previous index
  104. for indexRoute < lenr {
  105. // Increment route index
  106. indexRoute++
  107. // Get *Route
  108. route := tree[indexRoute]
  109. // Skip use routes
  110. if route.use {
  111. continue
  112. }
  113. // Check if it matches the request path
  114. match := route.match(ctx.detectionPath, ctx.path, &ctx.values)
  115. // No match, next route
  116. if match {
  117. // We matched
  118. exists = true
  119. // Add method to Allow header
  120. ctx.Append(HeaderAllow, methods[i])
  121. // Break stack loop
  122. break
  123. }
  124. }
  125. }
  126. return exists
  127. }
  128. // uniqueRouteStack drop all not unique routes from the slice
  129. func uniqueRouteStack(stack []*Route) []*Route {
  130. var unique []*Route
  131. m := make(map[*Route]int)
  132. for _, v := range stack {
  133. if _, ok := m[v]; !ok {
  134. // Unique key found. Record position and collect
  135. // in result.
  136. m[v] = len(unique)
  137. unique = append(unique, v)
  138. }
  139. }
  140. return unique
  141. }
  142. // defaultString returns the value or a default value if it is set
  143. func defaultString(value string, defaultValue []string) string {
  144. if len(value) == 0 && len(defaultValue) > 0 {
  145. return defaultValue[0]
  146. }
  147. return value
  148. }
  149. const normalizedHeaderETag = "Etag"
  150. // Generate and set ETag header to response
  151. func setETag(c *Ctx, weak bool) { //nolint: revive // Accepting a bool param is fine here
  152. // Don't generate ETags for invalid responses
  153. if c.fasthttp.Response.StatusCode() != StatusOK {
  154. return
  155. }
  156. body := c.fasthttp.Response.Body()
  157. // Skips ETag if no response body is present
  158. if len(body) == 0 {
  159. return
  160. }
  161. // Get ETag header from request
  162. clientEtag := c.Get(HeaderIfNoneMatch)
  163. // Generate ETag for response
  164. const pol = 0xD5828281
  165. crc32q := crc32.MakeTable(pol)
  166. etag := fmt.Sprintf("\"%d-%v\"", len(body), crc32.Checksum(body, crc32q))
  167. // Enable weak tag
  168. if weak {
  169. etag = "W/" + etag
  170. }
  171. // Check if client's ETag is weak
  172. if strings.HasPrefix(clientEtag, "W/") {
  173. // Check if server's ETag is weak
  174. if clientEtag[2:] == etag || clientEtag[2:] == etag[2:] {
  175. // W/1 == 1 || W/1 == W/1
  176. if err := c.SendStatus(StatusNotModified); err != nil {
  177. log.Errorf("setETag: failed to SendStatus: %v", err)
  178. }
  179. c.fasthttp.ResetBody()
  180. return
  181. }
  182. // W/1 != W/2 || W/1 != 2
  183. c.setCanonical(normalizedHeaderETag, etag)
  184. return
  185. }
  186. if strings.Contains(clientEtag, etag) {
  187. // 1 == 1
  188. if err := c.SendStatus(StatusNotModified); err != nil {
  189. log.Errorf("setETag: failed to SendStatus: %v", err)
  190. }
  191. c.fasthttp.ResetBody()
  192. return
  193. }
  194. // 1 != 2
  195. c.setCanonical(normalizedHeaderETag, etag)
  196. }
  197. func getGroupPath(prefix, path string) string {
  198. if len(path) == 0 {
  199. return prefix
  200. }
  201. if path[0] != '/' {
  202. path = "/" + path
  203. }
  204. return utils.TrimRight(prefix, '/') + path
  205. }
  206. // acceptsOffer This function determines if an offer matches a given specification.
  207. // It checks if the specification ends with a '*' or if the offer has the prefix of the specification.
  208. // Returns true if the offer matches the specification, false otherwise.
  209. func acceptsOffer(spec, offer, _ string) bool {
  210. if len(spec) >= 1 && spec[len(spec)-1] == '*' {
  211. return true
  212. } else if strings.HasPrefix(spec, offer) {
  213. return true
  214. }
  215. return false
  216. }
  217. // acceptsOfferType This function determines if an offer type matches a given specification.
  218. // It checks if the specification is equal to */* (i.e., all types are accepted).
  219. // It gets the MIME type of the offer (either from the offer itself or by its file extension).
  220. // It checks if the offer MIME type matches the specification MIME type or if the specification is of the form <MIME_type>/* and the offer MIME type has the same MIME type.
  221. // It checks if the offer contains every parameter present in the specification.
  222. // Returns true if the offer type matches the specification, false otherwise.
  223. func acceptsOfferType(spec, offerType, specParams string) bool {
  224. var offerMime, offerParams string
  225. if i := strings.IndexByte(offerType, ';'); i == -1 {
  226. offerMime = offerType
  227. } else {
  228. offerMime = offerType[:i]
  229. offerParams = offerType[i:]
  230. }
  231. // Accept: */*
  232. if spec == "*/*" {
  233. return paramsMatch(specParams, offerParams)
  234. }
  235. var mimetype string
  236. if strings.IndexByte(offerMime, '/') != -1 {
  237. mimetype = offerMime // MIME type
  238. } else {
  239. mimetype = utils.GetMIME(offerMime) // extension
  240. }
  241. if spec == mimetype {
  242. // Accept: <MIME_type>/<MIME_subtype>
  243. return paramsMatch(specParams, offerParams)
  244. }
  245. s := strings.IndexByte(mimetype, '/')
  246. // Accept: <MIME_type>/*
  247. if strings.HasPrefix(spec, mimetype[:s]) && (spec[s:] == "/*" || mimetype[s:] == "/*") {
  248. return paramsMatch(specParams, offerParams)
  249. }
  250. return false
  251. }
  252. // paramsMatch returns whether offerParams contains all parameters present in specParams.
  253. // Matching is case insensitive, and surrounding quotes are stripped.
  254. // To align with the behavior of res.format from Express, the order of parameters is
  255. // ignored, and if a parameter is specified twice in the incoming Accept, the last
  256. // provided value is given precedence.
  257. // In the case of quoted values, RFC 9110 says that we must treat any character escaped
  258. // by a backslash as equivalent to the character itself (e.g., "a\aa" is equivalent to "aaa").
  259. // For the sake of simplicity, we forgo this and compare the value as-is. Besides, it would
  260. // be highly unusual for a client to escape something other than a double quote or backslash.
  261. // See https://www.rfc-editor.org/rfc/rfc9110#name-parameters
  262. func paramsMatch(specParamStr, offerParams string) bool {
  263. if specParamStr == "" {
  264. return true
  265. }
  266. // Preprocess the spec params to more easily test
  267. // for out-of-order parameters
  268. specParams := make([][2]string, 0, 2)
  269. forEachParameter(specParamStr, func(s1, s2 string) bool {
  270. if s1 == "q" || s1 == "Q" {
  271. return false
  272. }
  273. for i := range specParams {
  274. if utils.EqualFold(s1, specParams[i][0]) {
  275. specParams[i][1] = s2
  276. return false
  277. }
  278. }
  279. specParams = append(specParams, [2]string{s1, s2})
  280. return true
  281. })
  282. allSpecParamsMatch := true
  283. for i := range specParams {
  284. foundParam := false
  285. forEachParameter(offerParams, func(offerParam, offerVal string) bool {
  286. if utils.EqualFold(specParams[i][0], offerParam) {
  287. foundParam = true
  288. allSpecParamsMatch = utils.EqualFold(specParams[i][1], offerVal)
  289. return false
  290. }
  291. return true
  292. })
  293. if !foundParam || !allSpecParamsMatch {
  294. return false
  295. }
  296. }
  297. return allSpecParamsMatch
  298. }
  299. // getSplicedStrList function takes a string and a string slice as an argument, divides the string into different
  300. // elements divided by ',' and stores these elements in the string slice.
  301. // It returns the populated string slice as an output.
  302. //
  303. // If the given slice hasn't enough space, it will allocate more and return.
  304. func getSplicedStrList(headerValue string, dst []string) []string {
  305. if headerValue == "" {
  306. return nil
  307. }
  308. var (
  309. index int
  310. character rune
  311. lastElementEndsAt uint8
  312. insertIndex int
  313. )
  314. for index, character = range headerValue + "$" {
  315. if character == ',' || index == len(headerValue) {
  316. if insertIndex >= len(dst) {
  317. oldSlice := dst
  318. dst = make([]string, len(dst)+(len(dst)>>1)+2)
  319. copy(dst, oldSlice)
  320. }
  321. dst[insertIndex] = utils.TrimLeft(headerValue[lastElementEndsAt:index], ' ')
  322. lastElementEndsAt = uint8(index + 1)
  323. insertIndex++
  324. }
  325. }
  326. if len(dst) > insertIndex {
  327. dst = dst[:insertIndex]
  328. }
  329. return dst
  330. }
  331. // forEachMediaRange parses an Accept or Content-Type header, calling functor
  332. // on each media range.
  333. // See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
  334. func forEachMediaRange(header string, functor func(string)) {
  335. hasDQuote := strings.IndexByte(header, '"') != -1
  336. for len(header) > 0 {
  337. n := 0
  338. header = utils.TrimLeft(header, ' ')
  339. quotes := 0
  340. escaping := false
  341. if hasDQuote {
  342. // Complex case. We need to keep track of quotes and quoted-pairs (i.e., characters escaped with \ )
  343. loop:
  344. for n < len(header) {
  345. switch header[n] {
  346. case ',':
  347. if quotes%2 == 0 {
  348. break loop
  349. }
  350. case '"':
  351. if !escaping {
  352. quotes++
  353. }
  354. case '\\':
  355. if quotes%2 == 1 {
  356. escaping = !escaping
  357. }
  358. }
  359. n++
  360. }
  361. } else {
  362. // Simple case. Just look for the next comma.
  363. if n = strings.IndexByte(header, ','); n == -1 {
  364. n = len(header)
  365. }
  366. }
  367. functor(header[:n])
  368. if n >= len(header) {
  369. return
  370. }
  371. header = header[n+1:]
  372. }
  373. }
  374. // forEachParamter parses a given parameter list, calling functor
  375. // on each valid parameter. If functor returns false, we stop processing.
  376. // It expects a leading ';'.
  377. // See: https://www.rfc-editor.org/rfc/rfc9110#section-5.6.6
  378. // According to RFC-9110 2.4, it is up to our discretion whether
  379. // to attempt to recover from errors in HTTP semantics. Therefor,
  380. // we take the simple approach and exit early when a semantic error
  381. // is detected in the header.
  382. //
  383. // parameter = parameter-name "=" parameter-value
  384. // parameter-name = token
  385. // parameter-value = ( token / quoted-string )
  386. // parameters = *( OWS ";" OWS [ parameter ] )
  387. func forEachParameter(params string, functor func(string, string) bool) {
  388. for len(params) > 0 {
  389. // eat OWS ";" OWS
  390. params = utils.TrimLeft(params, ' ')
  391. if len(params) == 0 || params[0] != ';' {
  392. return
  393. }
  394. params = utils.TrimLeft(params[1:], ' ')
  395. n := 0
  396. // make sure the parameter is at least one character long
  397. if len(params) == 0 || !validHeaderFieldByte(params[n]) {
  398. return
  399. }
  400. n++
  401. for n < len(params) && validHeaderFieldByte(params[n]) {
  402. n++
  403. }
  404. // We should hit a '=' (that has more characters after it)
  405. // If not, the parameter is invalid.
  406. // param=foo
  407. // ~~~~~^
  408. if n >= len(params)-1 || params[n] != '=' {
  409. return
  410. }
  411. param := params[:n]
  412. n++
  413. if params[n] == '"' {
  414. // Handle quoted strings and quoted-pairs (i.e., characters escaped with \ )
  415. // See: https://www.rfc-editor.org/rfc/rfc9110#section-5.6.4
  416. foundEndQuote := false
  417. escaping := false
  418. n++
  419. m := n
  420. for ; n < len(params); n++ {
  421. if params[n] == '"' && !escaping {
  422. foundEndQuote = true
  423. break
  424. }
  425. // Recipients that process the value of a quoted-string MUST handle
  426. // a quoted-pair as if it were replaced by the octet following the backslash
  427. escaping = params[n] == '\\' && !escaping
  428. }
  429. if !foundEndQuote {
  430. // Not a valid parameter
  431. return
  432. }
  433. if !functor(param, params[m:n]) {
  434. return
  435. }
  436. n++
  437. } else if validHeaderFieldByte(params[n]) {
  438. // Parse a normal value, which should just be a token.
  439. m := n
  440. n++
  441. for n < len(params) && validHeaderFieldByte(params[n]) {
  442. n++
  443. }
  444. if !functor(param, params[m:n]) {
  445. return
  446. }
  447. } else {
  448. // Value was invalid
  449. return
  450. }
  451. params = params[n:]
  452. }
  453. }
  454. // validHeaderFieldByte returns true if a valid tchar
  455. //
  456. // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
  457. // "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
  458. //
  459. // See: https://www.rfc-editor.org/rfc/rfc9110#section-5.6.2
  460. // Function copied from net/textproto:
  461. // https://github.com/golang/go/blob/master/src/net/textproto/reader.go#L663
  462. func validHeaderFieldByte(c byte) bool {
  463. // mask is a 128-bit bitmap with 1s for allowed bytes,
  464. // so that the byte c can be tested with a shift and an and.
  465. // If c >= 128, then 1<<c and 1<<(c-64) will both be zero,
  466. // and this function will return false.
  467. const mask = 0 |
  468. (1<<(10)-1)<<'0' |
  469. (1<<(26)-1)<<'a' |
  470. (1<<(26)-1)<<'A' |
  471. 1<<'!' |
  472. 1<<'#' |
  473. 1<<'$' |
  474. 1<<'%' |
  475. 1<<'&' |
  476. 1<<'\'' |
  477. 1<<'*' |
  478. 1<<'+' |
  479. 1<<'-' |
  480. 1<<'.' |
  481. 1<<'^' |
  482. 1<<'_' |
  483. 1<<'`' |
  484. 1<<'|' |
  485. 1<<'~'
  486. return ((uint64(1)<<c)&(mask&(1<<64-1)) |
  487. (uint64(1)<<(c-64))&(mask>>64)) != 0
  488. }
  489. // getOffer return valid offer for header negotiation
  490. func getOffer(header string, isAccepted func(spec, offer, specParams string) bool, offers ...string) string {
  491. if len(offers) == 0 {
  492. return ""
  493. }
  494. if header == "" {
  495. return offers[0]
  496. }
  497. acceptedTypes := make([]acceptedType, 0, 8)
  498. order := 0
  499. // Parse header and get accepted types with their quality and specificity
  500. // See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
  501. forEachMediaRange(header, func(accept string) {
  502. order++
  503. spec, quality, params := accept, 1.0, ""
  504. if i := strings.IndexByte(accept, ';'); i != -1 {
  505. spec = accept[:i]
  506. // The vast majority of requests will have only the q parameter with
  507. // no whitespace. Check this first to see if we can skip
  508. // the more involved parsing.
  509. if strings.HasPrefix(accept[i:], ";q=") && strings.IndexByte(accept[i+3:], ';') == -1 {
  510. if q, err := fasthttp.ParseUfloat([]byte(utils.TrimRight(accept[i+3:], ' '))); err == nil {
  511. quality = q
  512. }
  513. } else {
  514. hasParams := false
  515. forEachParameter(accept[i:], func(param, val string) bool {
  516. if param == "q" || param == "Q" {
  517. if q, err := fasthttp.ParseUfloat([]byte(val)); err == nil {
  518. quality = q
  519. }
  520. return false
  521. }
  522. hasParams = true
  523. return true
  524. })
  525. if hasParams {
  526. params = accept[i:]
  527. }
  528. }
  529. // Skip this accept type if quality is 0.0
  530. // See: https://www.rfc-editor.org/rfc/rfc9110#quality.values
  531. if quality == 0.0 {
  532. return
  533. }
  534. }
  535. spec = utils.TrimRight(spec, ' ')
  536. // Get specificity
  537. var specificity int
  538. // check for wildcard this could be a mime */* or a wildcard character *
  539. if spec == "*/*" || spec == "*" {
  540. specificity = 1
  541. } else if strings.HasSuffix(spec, "/*") {
  542. specificity = 2
  543. } else if strings.IndexByte(spec, '/') != -1 {
  544. specificity = 3
  545. } else {
  546. specificity = 4
  547. }
  548. // Add to accepted types
  549. acceptedTypes = append(acceptedTypes, acceptedType{spec, quality, specificity, order, params})
  550. })
  551. if len(acceptedTypes) > 1 {
  552. // Sort accepted types by quality and specificity, preserving order of equal elements
  553. sortAcceptedTypes(&acceptedTypes)
  554. }
  555. // Find the first offer that matches the accepted types
  556. for _, acceptedType := range acceptedTypes {
  557. for _, offer := range offers {
  558. if len(offer) == 0 {
  559. continue
  560. }
  561. if isAccepted(acceptedType.spec, offer, acceptedType.params) {
  562. return offer
  563. }
  564. }
  565. }
  566. return ""
  567. }
  568. // sortAcceptedTypes sorts accepted types by quality and specificity, preserving order of equal elements
  569. // A type with parameters has higher priority than an equivalent one without parameters.
  570. // e.g., text/html;a=1;b=2 comes before text/html;a=1
  571. // See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
  572. func sortAcceptedTypes(acceptedTypes *[]acceptedType) {
  573. if acceptedTypes == nil || len(*acceptedTypes) < 2 {
  574. return
  575. }
  576. at := *acceptedTypes
  577. for i := 1; i < len(at); i++ {
  578. lo, hi := 0, i-1
  579. for lo <= hi {
  580. mid := (lo + hi) / 2
  581. if at[i].quality < at[mid].quality ||
  582. (at[i].quality == at[mid].quality && at[i].specificity < at[mid].specificity) ||
  583. (at[i].quality == at[mid].quality && at[i].specificity < at[mid].specificity && len(at[i].params) < len(at[mid].params)) ||
  584. (at[i].quality == at[mid].quality && at[i].specificity == at[mid].specificity && len(at[i].params) == len(at[mid].params) && at[i].order > at[mid].order) {
  585. lo = mid + 1
  586. } else {
  587. hi = mid - 1
  588. }
  589. }
  590. for j := i; j > lo; j-- {
  591. at[j-1], at[j] = at[j], at[j-1]
  592. }
  593. }
  594. }
  595. func matchEtag(s, etag string) bool {
  596. if s == etag || s == "W/"+etag || "W/"+s == etag {
  597. return true
  598. }
  599. return false
  600. }
  601. func (app *App) isEtagStale(etag string, noneMatchBytes []byte) bool {
  602. var start, end int
  603. // Adapted from:
  604. // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L110
  605. for i := range noneMatchBytes {
  606. switch noneMatchBytes[i] {
  607. case 0x20:
  608. if start == end {
  609. start = i + 1
  610. end = i + 1
  611. }
  612. case 0x2c:
  613. if matchEtag(app.getString(noneMatchBytes[start:end]), etag) {
  614. return false
  615. }
  616. start = i + 1
  617. end = i + 1
  618. default:
  619. end = i + 1
  620. }
  621. }
  622. return !matchEtag(app.getString(noneMatchBytes[start:end]), etag)
  623. }
  624. func parseAddr(raw string) (string, string) { //nolint:revive // Returns (host, port)
  625. if i := strings.LastIndex(raw, ":"); i != -1 {
  626. return raw[:i], raw[i+1:]
  627. }
  628. return raw, ""
  629. }
  630. const noCacheValue = "no-cache"
  631. // isNoCache checks if the cacheControl header value is a `no-cache`.
  632. func isNoCache(cacheControl string) bool {
  633. i := strings.Index(cacheControl, noCacheValue)
  634. if i == -1 {
  635. return false
  636. }
  637. // Xno-cache
  638. if i > 0 && !(cacheControl[i-1] == ' ' || cacheControl[i-1] == ',') {
  639. return false
  640. }
  641. // bla bla, no-cache
  642. if i+len(noCacheValue) == len(cacheControl) {
  643. return true
  644. }
  645. // bla bla, no-cacheX
  646. if cacheControl[i+len(noCacheValue)] != ',' {
  647. return false
  648. }
  649. // OK
  650. return true
  651. }
  652. type testConn struct {
  653. r bytes.Buffer
  654. w bytes.Buffer
  655. }
  656. func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) } //nolint:wrapcheck // This must not be wrapped
  657. func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) } //nolint:wrapcheck // This must not be wrapped
  658. func (*testConn) Close() error { return nil }
  659. func (*testConn) LocalAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} }
  660. func (*testConn) RemoteAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} }
  661. func (*testConn) SetDeadline(_ time.Time) error { return nil }
  662. func (*testConn) SetReadDeadline(_ time.Time) error { return nil }
  663. func (*testConn) SetWriteDeadline(_ time.Time) error { return nil }
  664. func getStringImmutable(b []byte) string {
  665. return string(b)
  666. }
  667. func getBytesImmutable(s string) []byte {
  668. return []byte(s)
  669. }
  670. // HTTP methods and their unique INTs
  671. func (app *App) methodInt(s string) int {
  672. // For better performance
  673. if len(app.configured.RequestMethods) == 0 {
  674. // TODO: Use iota instead
  675. switch s {
  676. case MethodGet:
  677. return 0
  678. case MethodHead:
  679. return 1
  680. case MethodPost:
  681. return 2
  682. case MethodPut:
  683. return 3
  684. case MethodDelete:
  685. return 4
  686. case MethodConnect:
  687. return 5
  688. case MethodOptions:
  689. return 6
  690. case MethodTrace:
  691. return 7
  692. case MethodPatch:
  693. return 8
  694. default:
  695. return -1
  696. }
  697. }
  698. // For method customization
  699. for i, v := range app.config.RequestMethods {
  700. if s == v {
  701. return i
  702. }
  703. }
  704. return -1
  705. }
  706. // IsMethodSafe reports whether the HTTP method is considered safe.
  707. // See https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.1
  708. func IsMethodSafe(m string) bool {
  709. switch m {
  710. case MethodGet,
  711. MethodHead,
  712. MethodOptions,
  713. MethodTrace:
  714. return true
  715. default:
  716. return false
  717. }
  718. }
  719. // IsMethodIdempotent reports whether the HTTP method is considered idempotent.
  720. // See https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2
  721. func IsMethodIdempotent(m string) bool {
  722. if IsMethodSafe(m) {
  723. return true
  724. }
  725. switch m {
  726. case MethodPut, MethodDelete:
  727. return true
  728. default:
  729. return false
  730. }
  731. }
  732. // HTTP methods were copied from net/http.
  733. const (
  734. MethodGet = "GET" // RFC 7231, 4.3.1
  735. MethodHead = "HEAD" // RFC 7231, 4.3.2
  736. MethodPost = "POST" // RFC 7231, 4.3.3
  737. MethodPut = "PUT" // RFC 7231, 4.3.4
  738. MethodPatch = "PATCH" // RFC 5789
  739. MethodDelete = "DELETE" // RFC 7231, 4.3.5
  740. MethodConnect = "CONNECT" // RFC 7231, 4.3.6
  741. MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
  742. MethodTrace = "TRACE" // RFC 7231, 4.3.8
  743. methodUse = "USE"
  744. )
  745. // MIME types that are commonly used
  746. const (
  747. MIMETextXML = "text/xml"
  748. MIMETextHTML = "text/html"
  749. MIMETextPlain = "text/plain"
  750. MIMETextJavaScript = "text/javascript"
  751. MIMEApplicationXML = "application/xml"
  752. MIMEApplicationJSON = "application/json"
  753. // Deprecated: use MIMETextJavaScript instead
  754. MIMEApplicationJavaScript = "application/javascript"
  755. MIMEApplicationForm = "application/x-www-form-urlencoded"
  756. MIMEOctetStream = "application/octet-stream"
  757. MIMEMultipartForm = "multipart/form-data"
  758. MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
  759. MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
  760. MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
  761. MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
  762. MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
  763. MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
  764. // Deprecated: use MIMETextJavaScriptCharsetUTF8 instead
  765. MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
  766. )
  767. // HTTP status codes were copied from net/http with the following updates:
  768. // - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation
  769. // - Add StatusSwitchProxy (306)
  770. // NOTE: Keep this list in sync with statusMessage
  771. const (
  772. StatusContinue = 100 // RFC 9110, 15.2.1
  773. StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2
  774. StatusProcessing = 102 // RFC 2518, 10.1
  775. StatusEarlyHints = 103 // RFC 8297
  776. StatusOK = 200 // RFC 9110, 15.3.1
  777. StatusCreated = 201 // RFC 9110, 15.3.2
  778. StatusAccepted = 202 // RFC 9110, 15.3.3
  779. StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4
  780. StatusNoContent = 204 // RFC 9110, 15.3.5
  781. StatusResetContent = 205 // RFC 9110, 15.3.6
  782. StatusPartialContent = 206 // RFC 9110, 15.3.7
  783. StatusMultiStatus = 207 // RFC 4918, 11.1
  784. StatusAlreadyReported = 208 // RFC 5842, 7.1
  785. StatusIMUsed = 226 // RFC 3229, 10.4.1
  786. StatusMultipleChoices = 300 // RFC 9110, 15.4.1
  787. StatusMovedPermanently = 301 // RFC 9110, 15.4.2
  788. StatusFound = 302 // RFC 9110, 15.4.3
  789. StatusSeeOther = 303 // RFC 9110, 15.4.4
  790. StatusNotModified = 304 // RFC 9110, 15.4.5
  791. StatusUseProxy = 305 // RFC 9110, 15.4.6
  792. StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused)
  793. StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
  794. StatusPermanentRedirect = 308 // RFC 9110, 15.4.9
  795. StatusBadRequest = 400 // RFC 9110, 15.5.1
  796. StatusUnauthorized = 401 // RFC 9110, 15.5.2
  797. StatusPaymentRequired = 402 // RFC 9110, 15.5.3
  798. StatusForbidden = 403 // RFC 9110, 15.5.4
  799. StatusNotFound = 404 // RFC 9110, 15.5.5
  800. StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6
  801. StatusNotAcceptable = 406 // RFC 9110, 15.5.7
  802. StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8
  803. StatusRequestTimeout = 408 // RFC 9110, 15.5.9
  804. StatusConflict = 409 // RFC 9110, 15.5.10
  805. StatusGone = 410 // RFC 9110, 15.5.11
  806. StatusLengthRequired = 411 // RFC 9110, 15.5.12
  807. StatusPreconditionFailed = 412 // RFC 9110, 15.5.13
  808. StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14
  809. StatusRequestURITooLong = 414 // RFC 9110, 15.5.15
  810. StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16
  811. StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
  812. StatusExpectationFailed = 417 // RFC 9110, 15.5.18
  813. StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused)
  814. StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20
  815. StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21
  816. StatusLocked = 423 // RFC 4918, 11.3
  817. StatusFailedDependency = 424 // RFC 4918, 11.4
  818. StatusTooEarly = 425 // RFC 8470, 5.2.
  819. StatusUpgradeRequired = 426 // RFC 9110, 15.5.22
  820. StatusPreconditionRequired = 428 // RFC 6585, 3
  821. StatusTooManyRequests = 429 // RFC 6585, 4
  822. StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
  823. StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
  824. StatusInternalServerError = 500 // RFC 9110, 15.6.1
  825. StatusNotImplemented = 501 // RFC 9110, 15.6.2
  826. StatusBadGateway = 502 // RFC 9110, 15.6.3
  827. StatusServiceUnavailable = 503 // RFC 9110, 15.6.4
  828. StatusGatewayTimeout = 504 // RFC 9110, 15.6.5
  829. StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6
  830. StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
  831. StatusInsufficientStorage = 507 // RFC 4918, 11.5
  832. StatusLoopDetected = 508 // RFC 5842, 7.2
  833. StatusNotExtended = 510 // RFC 2774, 7
  834. StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
  835. )
  836. // Errors
  837. var (
  838. ErrBadRequest = NewError(StatusBadRequest) // 400
  839. ErrUnauthorized = NewError(StatusUnauthorized) // 401
  840. ErrPaymentRequired = NewError(StatusPaymentRequired) // 402
  841. ErrForbidden = NewError(StatusForbidden) // 403
  842. ErrNotFound = NewError(StatusNotFound) // 404
  843. ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405
  844. ErrNotAcceptable = NewError(StatusNotAcceptable) // 406
  845. ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407
  846. ErrRequestTimeout = NewError(StatusRequestTimeout) // 408
  847. ErrConflict = NewError(StatusConflict) // 409
  848. ErrGone = NewError(StatusGone) // 410
  849. ErrLengthRequired = NewError(StatusLengthRequired) // 411
  850. ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412
  851. ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413
  852. ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414
  853. ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415
  854. ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416
  855. ErrExpectationFailed = NewError(StatusExpectationFailed) // 417
  856. ErrTeapot = NewError(StatusTeapot) // 418
  857. ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421
  858. ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422
  859. ErrLocked = NewError(StatusLocked) // 423
  860. ErrFailedDependency = NewError(StatusFailedDependency) // 424
  861. ErrTooEarly = NewError(StatusTooEarly) // 425
  862. ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426
  863. ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428
  864. ErrTooManyRequests = NewError(StatusTooManyRequests) // 429
  865. ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431
  866. ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451
  867. ErrInternalServerError = NewError(StatusInternalServerError) // 500
  868. ErrNotImplemented = NewError(StatusNotImplemented) // 501
  869. ErrBadGateway = NewError(StatusBadGateway) // 502
  870. ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503
  871. ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504
  872. ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505
  873. ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506
  874. ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507
  875. ErrLoopDetected = NewError(StatusLoopDetected) // 508
  876. ErrNotExtended = NewError(StatusNotExtended) // 510
  877. ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511
  878. )
  879. // HTTP Headers were copied from net/http.
  880. const (
  881. HeaderAuthorization = "Authorization"
  882. HeaderProxyAuthenticate = "Proxy-Authenticate"
  883. HeaderProxyAuthorization = "Proxy-Authorization"
  884. HeaderWWWAuthenticate = "WWW-Authenticate"
  885. HeaderAge = "Age"
  886. HeaderCacheControl = "Cache-Control"
  887. HeaderClearSiteData = "Clear-Site-Data"
  888. HeaderExpires = "Expires"
  889. HeaderPragma = "Pragma"
  890. HeaderWarning = "Warning"
  891. HeaderAcceptCH = "Accept-CH"
  892. HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
  893. HeaderContentDPR = "Content-DPR"
  894. HeaderDPR = "DPR"
  895. HeaderEarlyData = "Early-Data"
  896. HeaderSaveData = "Save-Data"
  897. HeaderViewportWidth = "Viewport-Width"
  898. HeaderWidth = "Width"
  899. HeaderETag = "ETag"
  900. HeaderIfMatch = "If-Match"
  901. HeaderIfModifiedSince = "If-Modified-Since"
  902. HeaderIfNoneMatch = "If-None-Match"
  903. HeaderIfUnmodifiedSince = "If-Unmodified-Since"
  904. HeaderLastModified = "Last-Modified"
  905. HeaderVary = "Vary"
  906. HeaderConnection = "Connection"
  907. HeaderKeepAlive = "Keep-Alive"
  908. HeaderAccept = "Accept"
  909. HeaderAcceptCharset = "Accept-Charset"
  910. HeaderAcceptEncoding = "Accept-Encoding"
  911. HeaderAcceptLanguage = "Accept-Language"
  912. HeaderCookie = "Cookie"
  913. HeaderExpect = "Expect"
  914. HeaderMaxForwards = "Max-Forwards"
  915. HeaderSetCookie = "Set-Cookie"
  916. HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
  917. HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
  918. HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
  919. HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
  920. HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
  921. HeaderAccessControlMaxAge = "Access-Control-Max-Age"
  922. HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
  923. HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
  924. HeaderOrigin = "Origin"
  925. HeaderTimingAllowOrigin = "Timing-Allow-Origin"
  926. HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies"
  927. HeaderDNT = "DNT"
  928. HeaderTk = "Tk"
  929. HeaderContentDisposition = "Content-Disposition"
  930. HeaderContentEncoding = "Content-Encoding"
  931. HeaderContentLanguage = "Content-Language"
  932. HeaderContentLength = "Content-Length"
  933. HeaderContentLocation = "Content-Location"
  934. HeaderContentType = "Content-Type"
  935. HeaderForwarded = "Forwarded"
  936. HeaderVia = "Via"
  937. HeaderXForwardedFor = "X-Forwarded-For"
  938. HeaderXForwardedHost = "X-Forwarded-Host"
  939. HeaderXForwardedProto = "X-Forwarded-Proto"
  940. HeaderXForwardedProtocol = "X-Forwarded-Protocol"
  941. HeaderXForwardedSsl = "X-Forwarded-Ssl"
  942. HeaderXUrlScheme = "X-Url-Scheme"
  943. HeaderLocation = "Location"
  944. HeaderFrom = "From"
  945. HeaderHost = "Host"
  946. HeaderReferer = "Referer"
  947. HeaderReferrerPolicy = "Referrer-Policy"
  948. HeaderUserAgent = "User-Agent"
  949. HeaderAllow = "Allow"
  950. HeaderServer = "Server"
  951. HeaderAcceptRanges = "Accept-Ranges"
  952. HeaderContentRange = "Content-Range"
  953. HeaderIfRange = "If-Range"
  954. HeaderRange = "Range"
  955. HeaderContentSecurityPolicy = "Content-Security-Policy"
  956. HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
  957. HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
  958. HeaderExpectCT = "Expect-CT"
  959. // Deprecated: use HeaderPermissionsPolicy instead
  960. HeaderFeaturePolicy = "Feature-Policy"
  961. HeaderPermissionsPolicy = "Permissions-Policy"
  962. HeaderPublicKeyPins = "Public-Key-Pins"
  963. HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only"
  964. HeaderStrictTransportSecurity = "Strict-Transport-Security"
  965. HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
  966. HeaderXContentTypeOptions = "X-Content-Type-Options"
  967. HeaderXDownloadOptions = "X-Download-Options"
  968. HeaderXFrameOptions = "X-Frame-Options"
  969. HeaderXPoweredBy = "X-Powered-By"
  970. HeaderXXSSProtection = "X-XSS-Protection"
  971. HeaderLastEventID = "Last-Event-ID"
  972. HeaderNEL = "NEL"
  973. HeaderPingFrom = "Ping-From"
  974. HeaderPingTo = "Ping-To"
  975. HeaderReportTo = "Report-To"
  976. HeaderTE = "TE"
  977. HeaderTrailer = "Trailer"
  978. HeaderTransferEncoding = "Transfer-Encoding"
  979. HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
  980. HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions"
  981. HeaderSecWebSocketKey = "Sec-WebSocket-Key"
  982. HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol"
  983. HeaderSecWebSocketVersion = "Sec-WebSocket-Version"
  984. HeaderAcceptPatch = "Accept-Patch"
  985. HeaderAcceptPushPolicy = "Accept-Push-Policy"
  986. HeaderAcceptSignature = "Accept-Signature"
  987. HeaderAltSvc = "Alt-Svc"
  988. HeaderDate = "Date"
  989. HeaderIndex = "Index"
  990. HeaderLargeAllocation = "Large-Allocation"
  991. HeaderLink = "Link"
  992. HeaderPushPolicy = "Push-Policy"
  993. HeaderRetryAfter = "Retry-After"
  994. HeaderServerTiming = "Server-Timing"
  995. HeaderSignature = "Signature"
  996. HeaderSignedHeaders = "Signed-Headers"
  997. HeaderSourceMap = "SourceMap"
  998. HeaderUpgrade = "Upgrade"
  999. HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control"
  1000. HeaderXPingback = "X-Pingback"
  1001. HeaderXRequestID = "X-Request-ID"
  1002. HeaderXRequestedWith = "X-Requested-With"
  1003. HeaderXRobotsTag = "X-Robots-Tag"
  1004. HeaderXUACompatible = "X-UA-Compatible"
  1005. )
  1006. // Network types that are commonly used
  1007. const (
  1008. NetworkTCP = "tcp"
  1009. NetworkTCP4 = "tcp4"
  1010. NetworkTCP6 = "tcp6"
  1011. )
  1012. // Compression types
  1013. const (
  1014. StrGzip = "gzip"
  1015. StrBr = "br"
  1016. StrDeflate = "deflate"
  1017. StrBrotli = "brotli"
  1018. )
  1019. // Cookie SameSite
  1020. // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7
  1021. const (
  1022. CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set.
  1023. CookieSameSiteLaxMode = "lax"
  1024. CookieSameSiteStrictMode = "strict"
  1025. CookieSameSiteNoneMode = "none"
  1026. )
  1027. // Route Constraints
  1028. const (
  1029. ConstraintInt = "int"
  1030. ConstraintBool = "bool"
  1031. ConstraintFloat = "float"
  1032. ConstraintAlpha = "alpha"
  1033. ConstraintGuid = "guid" //nolint:revive,stylecheck // TODO: Rename to "ConstraintGUID" in v3
  1034. ConstraintMinLen = "minLen"
  1035. ConstraintMaxLen = "maxLen"
  1036. ConstraintLen = "len"
  1037. ConstraintBetweenLen = "betweenLen"
  1038. ConstraintMinLenLower = "minlen"
  1039. ConstraintMaxLenLower = "maxlen"
  1040. ConstraintBetweenLenLower = "betweenlen"
  1041. ConstraintMin = "min"
  1042. ConstraintMax = "max"
  1043. ConstraintRange = "range"
  1044. ConstraintDatetime = "datetime"
  1045. ConstraintRegex = "regex"
  1046. )
  1047. func IndexRune(str string, needle int32) bool {
  1048. for _, b := range str {
  1049. if b == needle {
  1050. return true
  1051. }
  1052. }
  1053. return false
  1054. }