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