github.com/gofiber/fiber/v2@v2.47.0/helpers.go (about)

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