helpers.go 40 KB

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