github.com/gofiber/fiber/v2@v2.47.0/ctx.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  	"context"
    10  	"crypto/tls"
    11  	"encoding/json"
    12  	"encoding/xml"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"mime/multipart"
    17  	"net"
    18  	"net/http"
    19  	"path/filepath"
    20  	"reflect"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"text/template"
    25  	"time"
    26  
    27  	"github.com/gofiber/fiber/v2/internal/schema"
    28  	"github.com/gofiber/fiber/v2/utils"
    29  
    30  	"github.com/savsgio/dictpool"
    31  	"github.com/valyala/bytebufferpool"
    32  	"github.com/valyala/fasthttp"
    33  )
    34  
    35  const (
    36  	schemeHTTP  = "http"
    37  	schemeHTTPS = "https"
    38  )
    39  
    40  // maxParams defines the maximum number of parameters per route.
    41  const maxParams = 30
    42  
    43  // Some constants for BodyParser, QueryParser and ReqHeaderParser.
    44  const (
    45  	queryTag     = "query"
    46  	reqHeaderTag = "reqHeader"
    47  	bodyTag      = "form"
    48  	paramsTag    = "params"
    49  )
    50  
    51  // userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
    52  const userContextKey = "__local_user_context__"
    53  
    54  var (
    55  	// decoderPoolMap helps to improve BodyParser's, QueryParser's and ReqHeaderParser's performance
    56  	decoderPoolMap = map[string]*sync.Pool{}
    57  	// tags is used to classify parser's pool
    58  	tags = []string{queryTag, bodyTag, reqHeaderTag, paramsTag}
    59  )
    60  
    61  func init() {
    62  	for _, tag := range tags {
    63  		decoderPoolMap[tag] = &sync.Pool{New: func() interface{} {
    64  			return decoderBuilder(ParserConfig{
    65  				IgnoreUnknownKeys: true,
    66  				ZeroEmpty:         true,
    67  			})
    68  		}}
    69  	}
    70  }
    71  
    72  // SetParserDecoder allow globally change the option of form decoder, update decoderPool
    73  func SetParserDecoder(parserConfig ParserConfig) {
    74  	for _, tag := range tags {
    75  		decoderPoolMap[tag] = &sync.Pool{New: func() interface{} {
    76  			return decoderBuilder(parserConfig)
    77  		}}
    78  	}
    79  }
    80  
    81  // Ctx represents the Context which hold the HTTP request and response.
    82  // It has methods for the request query string, parameters, body, HTTP headers and so on.
    83  type Ctx struct {
    84  	app                 *App                 // Reference to *App
    85  	route               *Route               // Reference to *Route
    86  	indexRoute          int                  // Index of the current route
    87  	indexHandler        int                  // Index of the current handler
    88  	method              string               // HTTP method
    89  	methodINT           int                  // HTTP method INT equivalent
    90  	baseURI             string               // HTTP base uri
    91  	path                string               // HTTP path with the modifications by the configuration -> string copy from pathBuffer
    92  	pathBuffer          []byte               // HTTP path buffer
    93  	detectionPath       string               // Route detection path                                  -> string copy from detectionPathBuffer
    94  	detectionPathBuffer []byte               // HTTP detectionPath buffer
    95  	treePath            string               // Path for the search in the tree
    96  	pathOriginal        string               // Original HTTP path
    97  	values              [maxParams]string    // Route parameter values
    98  	fasthttp            *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
    99  	matched             bool                 // Non use route matched
   100  	viewBindMap         *dictpool.Dict       // Default view map to bind template engine
   101  }
   102  
   103  // TLSHandler object
   104  type TLSHandler struct {
   105  	clientHelloInfo *tls.ClientHelloInfo
   106  }
   107  
   108  // GetClientInfo Callback function to set CHI
   109  // TODO: Why is this a getter which sets stuff?
   110  func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
   111  	t.clientHelloInfo = info
   112  	return nil, nil //nolint:nilnil // Not returning anything useful here is probably fine
   113  }
   114  
   115  // Range data for c.Range
   116  type Range struct {
   117  	Type   string
   118  	Ranges []struct {
   119  		Start int
   120  		End   int
   121  	}
   122  }
   123  
   124  // Cookie data for c.Cookie
   125  type Cookie struct {
   126  	Name        string    `json:"name"`
   127  	Value       string    `json:"value"`
   128  	Path        string    `json:"path"`
   129  	Domain      string    `json:"domain"`
   130  	MaxAge      int       `json:"max_age"`
   131  	Expires     time.Time `json:"expires"`
   132  	Secure      bool      `json:"secure"`
   133  	HTTPOnly    bool      `json:"http_only"`
   134  	SameSite    string    `json:"same_site"`
   135  	SessionOnly bool      `json:"session_only"`
   136  }
   137  
   138  // Views is the interface that wraps the Render function.
   139  type Views interface {
   140  	Load() error
   141  	Render(io.Writer, string, interface{}, ...string) error
   142  }
   143  
   144  // ParserType require two element, type and converter for register.
   145  // Use ParserType with BodyParser for parsing custom type in form data.
   146  type ParserType struct {
   147  	Customtype interface{}
   148  	Converter  func(string) reflect.Value
   149  }
   150  
   151  // ParserConfig form decoder config for SetParserDecoder
   152  type ParserConfig struct {
   153  	IgnoreUnknownKeys bool
   154  	SetAliasTag       string
   155  	ParserType        []ParserType
   156  	ZeroEmpty         bool
   157  }
   158  
   159  // AcquireCtx retrieves a new Ctx from the pool.
   160  func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
   161  	c, ok := app.pool.Get().(*Ctx)
   162  	if !ok {
   163  		panic(fmt.Errorf("failed to type-assert to *Ctx"))
   164  	}
   165  	// Set app reference
   166  	c.app = app
   167  	// Reset route and handler index
   168  	c.indexRoute = -1
   169  	c.indexHandler = 0
   170  	// Reset matched flag
   171  	c.matched = false
   172  	// Set paths
   173  	c.pathOriginal = app.getString(fctx.URI().PathOriginal())
   174  	// Set method
   175  	c.method = app.getString(fctx.Request.Header.Method())
   176  	c.methodINT = app.methodInt(c.method)
   177  	// Attach *fasthttp.RequestCtx to ctx
   178  	c.fasthttp = fctx
   179  	// reset base uri
   180  	c.baseURI = ""
   181  	// Prettify path
   182  	c.configDependentPaths()
   183  	return c
   184  }
   185  
   186  // ReleaseCtx releases the ctx back into the pool.
   187  func (app *App) ReleaseCtx(c *Ctx) {
   188  	// Reset values
   189  	c.route = nil
   190  	c.fasthttp = nil
   191  	if c.viewBindMap != nil {
   192  		dictpool.ReleaseDict(c.viewBindMap)
   193  		c.viewBindMap = nil
   194  	}
   195  	app.pool.Put(c)
   196  }
   197  
   198  // Accepts checks if the specified extensions or content types are acceptable.
   199  func (c *Ctx) Accepts(offers ...string) string {
   200  	return getOffer(c.Get(HeaderAccept), acceptsOfferType, offers...)
   201  }
   202  
   203  // AcceptsCharsets checks if the specified charset is acceptable.
   204  func (c *Ctx) AcceptsCharsets(offers ...string) string {
   205  	return getOffer(c.Get(HeaderAcceptCharset), acceptsOffer, offers...)
   206  }
   207  
   208  // AcceptsEncodings checks if the specified encoding is acceptable.
   209  func (c *Ctx) AcceptsEncodings(offers ...string) string {
   210  	return getOffer(c.Get(HeaderAcceptEncoding), acceptsOffer, offers...)
   211  }
   212  
   213  // AcceptsLanguages checks if the specified language is acceptable.
   214  func (c *Ctx) AcceptsLanguages(offers ...string) string {
   215  	return getOffer(c.Get(HeaderAcceptLanguage), acceptsOffer, offers...)
   216  }
   217  
   218  // App returns the *App reference to the instance of the Fiber application
   219  func (c *Ctx) App() *App {
   220  	return c.app
   221  }
   222  
   223  // Append the specified value to the HTTP response header field.
   224  // If the header is not already set, it creates the header with the specified value.
   225  func (c *Ctx) Append(field string, values ...string) {
   226  	if len(values) == 0 {
   227  		return
   228  	}
   229  	h := c.app.getString(c.fasthttp.Response.Header.Peek(field))
   230  	originalH := h
   231  	for _, value := range values {
   232  		if len(h) == 0 {
   233  			h = value
   234  		} else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) &&
   235  			!strings.Contains(h, " "+value+",") {
   236  			h += ", " + value
   237  		}
   238  	}
   239  	if originalH != h {
   240  		c.Set(field, h)
   241  	}
   242  }
   243  
   244  // Attachment sets the HTTP response Content-Disposition header field to attachment.
   245  func (c *Ctx) Attachment(filename ...string) {
   246  	if len(filename) > 0 {
   247  		fname := filepath.Base(filename[0])
   248  		c.Type(filepath.Ext(fname))
   249  
   250  		c.setCanonical(HeaderContentDisposition, `attachment; filename="`+c.app.quoteString(fname)+`"`)
   251  		return
   252  	}
   253  	c.setCanonical(HeaderContentDisposition, "attachment")
   254  }
   255  
   256  // BaseURL returns (protocol + host + base path).
   257  func (c *Ctx) BaseURL() string {
   258  	// TODO: Could be improved: 53.8 ns/op  32 B/op  1 allocs/op
   259  	// Should work like https://codeigniter.com/user_guide/helpers/url_helper.html
   260  	if c.baseURI != "" {
   261  		return c.baseURI
   262  	}
   263  	c.baseURI = c.Protocol() + "://" + c.Hostname()
   264  	return c.baseURI
   265  }
   266  
   267  // Body contains the raw body submitted in a POST request.
   268  // Returned value is only valid within the handler. Do not store any references.
   269  // Make copies or use the Immutable setting instead.
   270  func (c *Ctx) Body() []byte {
   271  	var err error
   272  	var encoding string
   273  	var body []byte
   274  	// faster than peek
   275  	c.Request().Header.VisitAll(func(key, value []byte) {
   276  		if c.app.getString(key) == HeaderContentEncoding {
   277  			encoding = c.app.getString(value)
   278  		}
   279  	})
   280  
   281  	switch encoding {
   282  	case StrGzip:
   283  		body, err = c.fasthttp.Request.BodyGunzip()
   284  	case StrBr, StrBrotli:
   285  		body, err = c.fasthttp.Request.BodyUnbrotli()
   286  	case StrDeflate:
   287  		body, err = c.fasthttp.Request.BodyInflate()
   288  	default:
   289  		body = c.fasthttp.Request.Body()
   290  	}
   291  
   292  	if err != nil {
   293  		return []byte(err.Error())
   294  	}
   295  
   296  	return body
   297  }
   298  
   299  func decoderBuilder(parserConfig ParserConfig) interface{} {
   300  	decoder := schema.NewDecoder()
   301  	decoder.IgnoreUnknownKeys(parserConfig.IgnoreUnknownKeys)
   302  	if parserConfig.SetAliasTag != "" {
   303  		decoder.SetAliasTag(parserConfig.SetAliasTag)
   304  	}
   305  	for _, v := range parserConfig.ParserType {
   306  		decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter)
   307  	}
   308  	decoder.ZeroEmpty(parserConfig.ZeroEmpty)
   309  	return decoder
   310  }
   311  
   312  // BodyParser binds the request body to a struct.
   313  // It supports decoding the following content types based on the Content-Type header:
   314  // application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data
   315  // If none of the content types above are matched, it will return a ErrUnprocessableEntity error
   316  func (c *Ctx) BodyParser(out interface{}) error {
   317  	// Get content-type
   318  	ctype := utils.ToLower(c.app.getString(c.fasthttp.Request.Header.ContentType()))
   319  
   320  	ctype = utils.ParseVendorSpecificContentType(ctype)
   321  
   322  	// Parse body accordingly
   323  	if strings.HasPrefix(ctype, MIMEApplicationJSON) {
   324  		return c.app.config.JSONDecoder(c.Body(), out)
   325  	}
   326  	if strings.HasPrefix(ctype, MIMEApplicationForm) {
   327  		data := make(map[string][]string)
   328  		var err error
   329  
   330  		c.fasthttp.PostArgs().VisitAll(func(key, val []byte) {
   331  			if err != nil {
   332  				return
   333  			}
   334  
   335  			k := c.app.getString(key)
   336  			v := c.app.getString(val)
   337  
   338  			if strings.Contains(k, "[") {
   339  				k, err = parseParamSquareBrackets(k)
   340  			}
   341  
   342  			if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
   343  				values := strings.Split(v, ",")
   344  				for i := 0; i < len(values); i++ {
   345  					data[k] = append(data[k], values[i])
   346  				}
   347  			} else {
   348  				data[k] = append(data[k], v)
   349  			}
   350  		})
   351  
   352  		return c.parseToStruct(bodyTag, out, data)
   353  	}
   354  	if strings.HasPrefix(ctype, MIMEMultipartForm) {
   355  		data, err := c.fasthttp.MultipartForm()
   356  		if err != nil {
   357  			return err
   358  		}
   359  		return c.parseToStruct(bodyTag, out, data.Value)
   360  	}
   361  	if strings.HasPrefix(ctype, MIMETextXML) || strings.HasPrefix(ctype, MIMEApplicationXML) {
   362  		if err := xml.Unmarshal(c.Body(), out); err != nil {
   363  			return fmt.Errorf("failed to unmarshal: %w", err)
   364  		}
   365  		return nil
   366  	}
   367  	// No suitable content type found
   368  	return ErrUnprocessableEntity
   369  }
   370  
   371  // ClearCookie expires a specific cookie by key on the client side.
   372  // If no key is provided it expires all cookies that came with the request.
   373  func (c *Ctx) ClearCookie(key ...string) {
   374  	if len(key) > 0 {
   375  		for i := range key {
   376  			c.fasthttp.Response.Header.DelClientCookie(key[i])
   377  		}
   378  		return
   379  	}
   380  	c.fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) {
   381  		c.fasthttp.Response.Header.DelClientCookieBytes(k)
   382  	})
   383  }
   384  
   385  // Context returns *fasthttp.RequestCtx that carries a deadline
   386  // a cancellation signal, and other values across API boundaries.
   387  func (c *Ctx) Context() *fasthttp.RequestCtx {
   388  	return c.fasthttp
   389  }
   390  
   391  // UserContext returns a context implementation that was set by
   392  // user earlier or returns a non-nil, empty context,if it was not set earlier.
   393  func (c *Ctx) UserContext() context.Context {
   394  	ctx, ok := c.fasthttp.UserValue(userContextKey).(context.Context)
   395  	if !ok {
   396  		ctx = context.Background()
   397  		c.SetUserContext(ctx)
   398  	}
   399  
   400  	return ctx
   401  }
   402  
   403  // SetUserContext sets a context implementation by user.
   404  func (c *Ctx) SetUserContext(ctx context.Context) {
   405  	c.fasthttp.SetUserValue(userContextKey, ctx)
   406  }
   407  
   408  // Cookie sets a cookie by passing a cookie struct.
   409  func (c *Ctx) Cookie(cookie *Cookie) {
   410  	fcookie := fasthttp.AcquireCookie()
   411  	fcookie.SetKey(cookie.Name)
   412  	fcookie.SetValue(cookie.Value)
   413  	fcookie.SetPath(cookie.Path)
   414  	fcookie.SetDomain(cookie.Domain)
   415  	// only set max age and expiry when SessionOnly is false
   416  	// i.e. cookie supposed to last beyond browser session
   417  	// refer: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie
   418  	if !cookie.SessionOnly {
   419  		fcookie.SetMaxAge(cookie.MaxAge)
   420  		fcookie.SetExpire(cookie.Expires)
   421  	}
   422  	fcookie.SetSecure(cookie.Secure)
   423  	fcookie.SetHTTPOnly(cookie.HTTPOnly)
   424  
   425  	switch utils.ToLower(cookie.SameSite) {
   426  	case CookieSameSiteStrictMode:
   427  		fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
   428  	case CookieSameSiteNoneMode:
   429  		fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
   430  	case CookieSameSiteDisabled:
   431  		fcookie.SetSameSite(fasthttp.CookieSameSiteDisabled)
   432  	default:
   433  		fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
   434  	}
   435  
   436  	c.fasthttp.Response.Header.SetCookie(fcookie)
   437  	fasthttp.ReleaseCookie(fcookie)
   438  }
   439  
   440  // Cookies are used for getting a cookie value by key.
   441  // Defaults to the empty string "" if the cookie doesn't exist.
   442  // If a default value is given, it will return that value if the cookie doesn't exist.
   443  // The returned value is only valid within the handler. Do not store any references.
   444  // Make copies or use the Immutable setting to use the value outside the Handler.
   445  func (c *Ctx) Cookies(key string, defaultValue ...string) string {
   446  	return defaultString(c.app.getString(c.fasthttp.Request.Header.Cookie(key)), defaultValue)
   447  }
   448  
   449  // Download transfers the file from path as an attachment.
   450  // Typically, browsers will prompt the user for download.
   451  // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
   452  // Override this default with the filename parameter.
   453  func (c *Ctx) Download(file string, filename ...string) error {
   454  	var fname string
   455  	if len(filename) > 0 {
   456  		fname = filename[0]
   457  	} else {
   458  		fname = filepath.Base(file)
   459  	}
   460  	c.setCanonical(HeaderContentDisposition, `attachment; filename="`+c.app.quoteString(fname)+`"`)
   461  	return c.SendFile(file)
   462  }
   463  
   464  // Request return the *fasthttp.Request object
   465  // This allows you to use all fasthttp request methods
   466  // https://godoc.org/github.com/valyala/fasthttp#Request
   467  func (c *Ctx) Request() *fasthttp.Request {
   468  	return &c.fasthttp.Request
   469  }
   470  
   471  // Response return the *fasthttp.Response object
   472  // This allows you to use all fasthttp response methods
   473  // https://godoc.org/github.com/valyala/fasthttp#Response
   474  func (c *Ctx) Response() *fasthttp.Response {
   475  	return &c.fasthttp.Response
   476  }
   477  
   478  // Format performs content-negotiation on the Accept HTTP header.
   479  // It uses Accepts to select a proper format.
   480  // If the header is not specified or there is no proper format, text/plain is used.
   481  func (c *Ctx) Format(body interface{}) error {
   482  	// Get accepted content type
   483  	accept := c.Accepts("html", "json", "txt", "xml")
   484  	// Set accepted content type
   485  	c.Type(accept)
   486  	// Type convert provided body
   487  	var b string
   488  	switch val := body.(type) {
   489  	case string:
   490  		b = val
   491  	case []byte:
   492  		b = c.app.getString(val)
   493  	default:
   494  		b = fmt.Sprintf("%v", val)
   495  	}
   496  
   497  	// Format based on the accept content type
   498  	switch accept {
   499  	case "html":
   500  		return c.SendString("<p>" + b + "</p>")
   501  	case "json":
   502  		return c.JSON(body)
   503  	case "txt":
   504  		return c.SendString(b)
   505  	case "xml":
   506  		return c.XML(body)
   507  	}
   508  	return c.SendString(b)
   509  }
   510  
   511  // FormFile returns the first file by key from a MultipartForm.
   512  func (c *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
   513  	return c.fasthttp.FormFile(key)
   514  }
   515  
   516  // FormValue returns the first value by key from a MultipartForm.
   517  // Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order.
   518  // Defaults to the empty string "" if the form value doesn't exist.
   519  // If a default value is given, it will return that value if the form value does not exist.
   520  // Returned value is only valid within the handler. Do not store any references.
   521  // Make copies or use the Immutable setting instead.
   522  func (c *Ctx) FormValue(key string, defaultValue ...string) string {
   523  	return defaultString(c.app.getString(c.fasthttp.FormValue(key)), defaultValue)
   524  }
   525  
   526  // Fresh returns true when the response is still “fresh” in the client's cache,
   527  // otherwise false is returned to indicate that the client cache is now stale
   528  // and the full response should be sent.
   529  // When a client sends the Cache-Control: no-cache request header to indicate an end-to-end
   530  // reload request, this module will return false to make handling these requests transparent.
   531  // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L33
   532  func (c *Ctx) Fresh() bool {
   533  	// fields
   534  	modifiedSince := c.Get(HeaderIfModifiedSince)
   535  	noneMatch := c.Get(HeaderIfNoneMatch)
   536  
   537  	// unconditional request
   538  	if modifiedSince == "" && noneMatch == "" {
   539  		return false
   540  	}
   541  
   542  	// Always return stale when Cache-Control: no-cache
   543  	// to support end-to-end reload requests
   544  	// https://tools.ietf.org/html/rfc2616#section-14.9.4
   545  	cacheControl := c.Get(HeaderCacheControl)
   546  	if cacheControl != "" && isNoCache(cacheControl) {
   547  		return false
   548  	}
   549  
   550  	// if-none-match
   551  	if noneMatch != "" && noneMatch != "*" {
   552  		etag := c.app.getString(c.fasthttp.Response.Header.Peek(HeaderETag))
   553  		if etag == "" {
   554  			return false
   555  		}
   556  		if c.app.isEtagStale(etag, c.app.getBytes(noneMatch)) {
   557  			return false
   558  		}
   559  
   560  		if modifiedSince != "" {
   561  			lastModified := c.app.getString(c.fasthttp.Response.Header.Peek(HeaderLastModified))
   562  			if lastModified != "" {
   563  				lastModifiedTime, err := http.ParseTime(lastModified)
   564  				if err != nil {
   565  					return false
   566  				}
   567  				modifiedSinceTime, err := http.ParseTime(modifiedSince)
   568  				if err != nil {
   569  					return false
   570  				}
   571  				return lastModifiedTime.Before(modifiedSinceTime)
   572  			}
   573  		}
   574  	}
   575  	return true
   576  }
   577  
   578  // Get returns the HTTP request header specified by field.
   579  // Field names are case-insensitive
   580  // Returned value is only valid within the handler. Do not store any references.
   581  // Make copies or use the Immutable setting instead.
   582  func (c *Ctx) Get(key string, defaultValue ...string) string {
   583  	return defaultString(c.app.getString(c.fasthttp.Request.Header.Peek(key)), defaultValue)
   584  }
   585  
   586  // GetRespHeader returns the HTTP response header specified by field.
   587  // Field names are case-insensitive
   588  // Returned value is only valid within the handler. Do not store any references.
   589  // Make copies or use the Immutable setting instead.
   590  func (c *Ctx) GetRespHeader(key string, defaultValue ...string) string {
   591  	return defaultString(c.app.getString(c.fasthttp.Response.Header.Peek(key)), defaultValue)
   592  }
   593  
   594  // GetReqHeaders returns the HTTP request headers.
   595  // Returned value is only valid within the handler. Do not store any references.
   596  // Make copies or use the Immutable setting instead.
   597  func (c *Ctx) GetReqHeaders() map[string]string {
   598  	headers := make(map[string]string)
   599  	c.Request().Header.VisitAll(func(k, v []byte) {
   600  		headers[c.app.getString(k)] = c.app.getString(v)
   601  	})
   602  
   603  	return headers
   604  }
   605  
   606  // GetRespHeaders returns the HTTP response headers.
   607  // Returned value is only valid within the handler. Do not store any references.
   608  // Make copies or use the Immutable setting instead.
   609  func (c *Ctx) GetRespHeaders() map[string]string {
   610  	headers := make(map[string]string)
   611  	c.Response().Header.VisitAll(func(k, v []byte) {
   612  		headers[c.app.getString(k)] = c.app.getString(v)
   613  	})
   614  
   615  	return headers
   616  }
   617  
   618  // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header.
   619  // Returned value is only valid within the handler. Do not store any references.
   620  // Make copies or use the Immutable setting instead.
   621  // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
   622  func (c *Ctx) Hostname() string {
   623  	if c.IsProxyTrusted() {
   624  		if host := c.Get(HeaderXForwardedHost); len(host) > 0 {
   625  			commaPos := strings.Index(host, ",")
   626  			if commaPos != -1 {
   627  				return host[:commaPos]
   628  			}
   629  			return host
   630  		}
   631  	}
   632  	return c.app.getString(c.fasthttp.Request.URI().Host())
   633  }
   634  
   635  // Port returns the remote port of the request.
   636  func (c *Ctx) Port() string {
   637  	tcpaddr, ok := c.fasthttp.RemoteAddr().(*net.TCPAddr)
   638  	if !ok {
   639  		panic(fmt.Errorf("failed to type-assert to *net.TCPAddr"))
   640  	}
   641  	return strconv.Itoa(tcpaddr.Port)
   642  }
   643  
   644  // IP returns the remote IP address of the request.
   645  // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address.
   646  // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
   647  func (c *Ctx) IP() string {
   648  	if c.IsProxyTrusted() && len(c.app.config.ProxyHeader) > 0 {
   649  		return c.extractIPFromHeader(c.app.config.ProxyHeader)
   650  	}
   651  
   652  	return c.fasthttp.RemoteIP().String()
   653  }
   654  
   655  // extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear.
   656  // When IP validation is enabled, any invalid IPs will be omitted.
   657  func (c *Ctx) extractIPsFromHeader(header string) []string {
   658  	// TODO: Reuse the c.extractIPFromHeader func somehow in here
   659  
   660  	headerValue := c.Get(header)
   661  
   662  	// We can't know how many IPs we will return, but we will try to guess with this constant division.
   663  	// Counting ',' makes function slower for about 50ns in general case.
   664  	const maxEstimatedCount = 8
   665  	estimatedCount := len(headerValue) / maxEstimatedCount
   666  	if estimatedCount > maxEstimatedCount {
   667  		estimatedCount = maxEstimatedCount // Avoid big allocation on big header
   668  	}
   669  
   670  	ipsFound := make([]string, 0, estimatedCount)
   671  
   672  	i := 0
   673  	j := -1
   674  
   675  iploop:
   676  	for {
   677  		var v4, v6 bool
   678  
   679  		// Manually splitting string without allocating slice, working with parts directly
   680  		i, j = j+1, j+2
   681  
   682  		if j > len(headerValue) {
   683  			break
   684  		}
   685  
   686  		for j < len(headerValue) && headerValue[j] != ',' {
   687  			if headerValue[j] == ':' {
   688  				v6 = true
   689  			} else if headerValue[j] == '.' {
   690  				v4 = true
   691  			}
   692  			j++
   693  		}
   694  
   695  		for i < j && headerValue[i] == ' ' {
   696  			i++
   697  		}
   698  
   699  		s := utils.TrimRight(headerValue[i:j], ' ')
   700  
   701  		if c.app.config.EnableIPValidation {
   702  			// Skip validation if IP is clearly not IPv4/IPv6, otherwise validate without allocations
   703  			if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) {
   704  				continue iploop
   705  			}
   706  		}
   707  
   708  		ipsFound = append(ipsFound, s)
   709  	}
   710  
   711  	return ipsFound
   712  }
   713  
   714  // extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled.
   715  // currently, it will return the first valid IP address in header.
   716  // when IP validation is disabled, it will simply return the value of the header without any inspection.
   717  // Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string.
   718  func (c *Ctx) extractIPFromHeader(header string) string {
   719  	if c.app.config.EnableIPValidation {
   720  		headerValue := c.Get(header)
   721  
   722  		i := 0
   723  		j := -1
   724  
   725  	iploop:
   726  		for {
   727  			var v4, v6 bool
   728  
   729  			// Manually splitting string without allocating slice, working with parts directly
   730  			i, j = j+1, j+2
   731  
   732  			if j > len(headerValue) {
   733  				break
   734  			}
   735  
   736  			for j < len(headerValue) && headerValue[j] != ',' {
   737  				if headerValue[j] == ':' {
   738  					v6 = true
   739  				} else if headerValue[j] == '.' {
   740  					v4 = true
   741  				}
   742  				j++
   743  			}
   744  
   745  			for i < j && headerValue[i] == ' ' {
   746  				i++
   747  			}
   748  
   749  			s := utils.TrimRight(headerValue[i:j], ' ')
   750  
   751  			if c.app.config.EnableIPValidation {
   752  				if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) {
   753  					continue iploop
   754  				}
   755  			}
   756  
   757  			return s
   758  		}
   759  
   760  		return c.fasthttp.RemoteIP().String()
   761  	}
   762  
   763  	// default behavior if IP validation is not enabled is just to return whatever value is
   764  	// in the proxy header. Even if it is empty or invalid
   765  	return c.Get(c.app.config.ProxyHeader)
   766  }
   767  
   768  // IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header.
   769  // When IP validation is enabled, only valid IPs are returned.
   770  func (c *Ctx) IPs() []string {
   771  	return c.extractIPsFromHeader(HeaderXForwardedFor)
   772  }
   773  
   774  // Is returns the matching content type,
   775  // if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter
   776  func (c *Ctx) Is(extension string) bool {
   777  	extensionHeader := utils.GetMIME(extension)
   778  	if extensionHeader == "" {
   779  		return false
   780  	}
   781  
   782  	return strings.HasPrefix(
   783  		utils.TrimLeft(c.app.getString(c.fasthttp.Request.Header.ContentType()), ' '),
   784  		extensionHeader,
   785  	)
   786  }
   787  
   788  // JSON converts any interface or string to JSON.
   789  // Array and slice values encode as JSON arrays,
   790  // except that []byte encodes as a base64-encoded string,
   791  // and a nil slice encodes as the null JSON value.
   792  // This method also sets the content header to application/json.
   793  func (c *Ctx) JSON(data interface{}) error {
   794  	raw, err := c.app.config.JSONEncoder(data)
   795  	if err != nil {
   796  		return err
   797  	}
   798  	c.fasthttp.Response.SetBodyRaw(raw)
   799  	c.fasthttp.Response.Header.SetContentType(MIMEApplicationJSON)
   800  	return nil
   801  }
   802  
   803  // JSONP sends a JSON response with JSONP support.
   804  // This method is identical to JSON, except that it opts-in to JSONP callback support.
   805  // By default, the callback name is simply callback.
   806  func (c *Ctx) JSONP(data interface{}, callback ...string) error {
   807  	raw, err := json.Marshal(data)
   808  	if err != nil {
   809  		return fmt.Errorf("failed to marshal: %w", err)
   810  	}
   811  
   812  	var result, cb string
   813  
   814  	if len(callback) > 0 {
   815  		cb = callback[0]
   816  	} else {
   817  		cb = "callback"
   818  	}
   819  
   820  	result = cb + "(" + c.app.getString(raw) + ");"
   821  
   822  	c.setCanonical(HeaderXContentTypeOptions, "nosniff")
   823  	c.fasthttp.Response.Header.SetContentType(MIMETextJavaScriptCharsetUTF8)
   824  	return c.SendString(result)
   825  }
   826  
   827  // XML converts any interface or string to XML.
   828  // This method also sets the content header to application/xml.
   829  func (c *Ctx) XML(data interface{}) error {
   830  	raw, err := c.app.config.XMLEncoder(data)
   831  	if err != nil {
   832  		return err
   833  	}
   834  	c.fasthttp.Response.SetBodyRaw(raw)
   835  	c.fasthttp.Response.Header.SetContentType(MIMEApplicationXML)
   836  	return nil
   837  }
   838  
   839  // Links joins the links followed by the property to populate the response's Link HTTP header field.
   840  func (c *Ctx) Links(link ...string) {
   841  	if len(link) == 0 {
   842  		return
   843  	}
   844  	bb := bytebufferpool.Get()
   845  	for i := range link {
   846  		if i%2 == 0 {
   847  			_ = bb.WriteByte('<')          //nolint:errcheck // This will never fail
   848  			_, _ = bb.WriteString(link[i]) //nolint:errcheck // This will never fail
   849  			_ = bb.WriteByte('>')          //nolint:errcheck // This will never fail
   850  		} else {
   851  			_, _ = bb.WriteString(`; rel="` + link[i] + `",`) //nolint:errcheck // This will never fail
   852  		}
   853  	}
   854  	c.setCanonical(HeaderLink, utils.TrimRight(c.app.getString(bb.Bytes()), ','))
   855  	bytebufferpool.Put(bb)
   856  }
   857  
   858  // Locals makes it possible to pass interface{} values under keys scoped to the request
   859  // and therefore available to all following routes that match the request.
   860  func (c *Ctx) Locals(key interface{}, value ...interface{}) interface{} {
   861  	if len(value) == 0 {
   862  		return c.fasthttp.UserValue(key)
   863  	}
   864  	c.fasthttp.SetUserValue(key, value[0])
   865  	return value[0]
   866  }
   867  
   868  // Location sets the response Location HTTP header to the specified path parameter.
   869  func (c *Ctx) Location(path string) {
   870  	c.setCanonical(HeaderLocation, path)
   871  }
   872  
   873  // Method contains a string corresponding to the HTTP method of the request: GET, POST, PUT and so on.
   874  func (c *Ctx) Method(override ...string) string {
   875  	if len(override) > 0 {
   876  		method := utils.ToUpper(override[0])
   877  		mINT := c.app.methodInt(method)
   878  		if mINT == -1 {
   879  			return c.method
   880  		}
   881  		c.method = method
   882  		c.methodINT = mINT
   883  	}
   884  	return c.method
   885  }
   886  
   887  // MultipartForm parse form entries from binary.
   888  // This returns a map[string][]string, so given a key the value will be a string slice.
   889  func (c *Ctx) MultipartForm() (*multipart.Form, error) {
   890  	return c.fasthttp.MultipartForm()
   891  }
   892  
   893  // ClientHelloInfo return CHI from context
   894  func (c *Ctx) ClientHelloInfo() *tls.ClientHelloInfo {
   895  	if c.app.tlsHandler != nil {
   896  		return c.app.tlsHandler.clientHelloInfo
   897  	}
   898  
   899  	return nil
   900  }
   901  
   902  // Next executes the next method in the stack that matches the current route.
   903  func (c *Ctx) Next() error {
   904  	// Increment handler index
   905  	c.indexHandler++
   906  	var err error
   907  	// Did we executed all route handlers?
   908  	if c.indexHandler < len(c.route.Handlers) {
   909  		// Continue route stack
   910  		err = c.route.Handlers[c.indexHandler](c)
   911  	} else {
   912  		// Continue handler stack
   913  		_, err = c.app.next(c)
   914  	}
   915  	return err
   916  }
   917  
   918  // RestartRouting instead of going to the next handler. This may be useful after
   919  // changing the request path. Note that handlers might be executed again.
   920  func (c *Ctx) RestartRouting() error {
   921  	c.indexRoute = -1
   922  	_, err := c.app.next(c)
   923  	return err
   924  }
   925  
   926  // OriginalURL contains the original request URL.
   927  // Returned value is only valid within the handler. Do not store any references.
   928  // Make copies or use the Immutable setting to use the value outside the Handler.
   929  func (c *Ctx) OriginalURL() string {
   930  	return c.app.getString(c.fasthttp.Request.Header.RequestURI())
   931  }
   932  
   933  // Params is used to get the route parameters.
   934  // Defaults to empty string "" if the param doesn't exist.
   935  // If a default value is given, it will return that value if the param doesn't exist.
   936  // Returned value is only valid within the handler. Do not store any references.
   937  // Make copies or use the Immutable setting to use the value outside the Handler.
   938  func (c *Ctx) Params(key string, defaultValue ...string) string {
   939  	if key == "*" || key == "+" {
   940  		key += "1"
   941  	}
   942  	for i := range c.route.Params {
   943  		if len(key) != len(c.route.Params[i]) {
   944  			continue
   945  		}
   946  		if c.route.Params[i] == key || (!c.app.config.CaseSensitive && utils.EqualFold(c.route.Params[i], key)) {
   947  			// in case values are not here
   948  			if len(c.values) <= i || len(c.values[i]) == 0 {
   949  				break
   950  			}
   951  			return c.values[i]
   952  		}
   953  	}
   954  	return defaultString("", defaultValue)
   955  }
   956  
   957  // AllParams Params is used to get all route parameters.
   958  // Using Params method to get params.
   959  func (c *Ctx) AllParams() map[string]string {
   960  	params := make(map[string]string, len(c.route.Params))
   961  	for _, param := range c.route.Params {
   962  		params[param] = c.Params(param)
   963  	}
   964  
   965  	return params
   966  }
   967  
   968  // ParamsParser binds the param string to a struct.
   969  func (c *Ctx) ParamsParser(out interface{}) error {
   970  	params := make(map[string][]string, len(c.route.Params))
   971  	for _, param := range c.route.Params {
   972  		params[param] = append(params[param], c.Params(param))
   973  	}
   974  	return c.parseToStruct(paramsTag, out, params)
   975  }
   976  
   977  // ParamsInt is used to get an integer from the route parameters
   978  // it defaults to zero if the parameter is not found or if the
   979  // parameter cannot be converted to an integer
   980  // If a default value is given, it will return that value in case the param
   981  // doesn't exist or cannot be converted to an integer
   982  func (c *Ctx) ParamsInt(key string, defaultValue ...int) (int, error) {
   983  	// Use Atoi to convert the param to an int or return zero and an error
   984  	value, err := strconv.Atoi(c.Params(key))
   985  	if err != nil {
   986  		if len(defaultValue) > 0 {
   987  			return defaultValue[0], nil
   988  		}
   989  		return 0, fmt.Errorf("failed to convert: %w", err)
   990  	}
   991  
   992  	return value, nil
   993  }
   994  
   995  // Path returns the path part of the request URL.
   996  // Optionally, you could override the path.
   997  func (c *Ctx) Path(override ...string) string {
   998  	if len(override) != 0 && c.path != override[0] {
   999  		// Set new path to context
  1000  		c.pathOriginal = override[0]
  1001  
  1002  		// Set new path to request context
  1003  		c.fasthttp.Request.URI().SetPath(c.pathOriginal)
  1004  		// Prettify path
  1005  		c.configDependentPaths()
  1006  	}
  1007  	return c.path
  1008  }
  1009  
  1010  // Protocol contains the request protocol string: http or https for TLS requests.
  1011  // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
  1012  func (c *Ctx) Protocol() string {
  1013  	if c.fasthttp.IsTLS() {
  1014  		return schemeHTTPS
  1015  	}
  1016  	if !c.IsProxyTrusted() {
  1017  		return schemeHTTP
  1018  	}
  1019  
  1020  	scheme := schemeHTTP
  1021  	const lenXHeaderName = 12
  1022  	c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
  1023  		if len(key) < lenXHeaderName {
  1024  			return // Neither "X-Forwarded-" nor "X-Url-Scheme"
  1025  		}
  1026  		switch {
  1027  		case bytes.HasPrefix(key, []byte("X-Forwarded-")):
  1028  			if bytes.Equal(key, []byte(HeaderXForwardedProto)) ||
  1029  				bytes.Equal(key, []byte(HeaderXForwardedProtocol)) {
  1030  				v := c.app.getString(val)
  1031  				commaPos := strings.Index(v, ",")
  1032  				if commaPos != -1 {
  1033  					scheme = v[:commaPos]
  1034  				} else {
  1035  					scheme = v
  1036  				}
  1037  			} else if bytes.Equal(key, []byte(HeaderXForwardedSsl)) && bytes.Equal(val, []byte("on")) {
  1038  				scheme = schemeHTTPS
  1039  			}
  1040  
  1041  		case bytes.Equal(key, []byte(HeaderXUrlScheme)):
  1042  			scheme = c.app.getString(val)
  1043  		}
  1044  	})
  1045  	return scheme
  1046  }
  1047  
  1048  // Query returns the query string parameter in the url.
  1049  // Defaults to empty string "" if the query doesn't exist.
  1050  // If a default value is given, it will return that value if the query doesn't exist.
  1051  // Returned value is only valid within the handler. Do not store any references.
  1052  // Make copies or use the Immutable setting to use the value outside the Handler.
  1053  func (c *Ctx) Query(key string, defaultValue ...string) string {
  1054  	return defaultString(c.app.getString(c.fasthttp.QueryArgs().Peek(key)), defaultValue)
  1055  }
  1056  
  1057  // Queries returns a map of query parameters and their values.
  1058  //
  1059  // GET /?name=alex&wanna_cake=2&id=
  1060  // Queries()["name"] == "alex"
  1061  // Queries()["wanna_cake"] == "2"
  1062  // Queries()["id"] == ""
  1063  //
  1064  // GET /?field1=value1&field1=value2&field2=value3
  1065  // Queries()["field1"] == "value2"
  1066  // Queries()["field2"] == "value3"
  1067  //
  1068  // GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3
  1069  // Queries()["list_a"] == "3"
  1070  // Queries()["list_b[]"] == "3"
  1071  // Queries()["list_c"] == "1,2,3"
  1072  //
  1073  // GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending
  1074  // Queries()["filters.author.name"] == "John"
  1075  // Queries()["filters.category.name"] == "Technology"
  1076  // Queries()["filters[customer][name]"] == "Alice"
  1077  // Queries()["filters[status]"] == "pending"
  1078  func (c *Ctx) Queries() map[string]string {
  1079  	m := make(map[string]string, c.Context().QueryArgs().Len())
  1080  	c.Context().QueryArgs().VisitAll(func(key, value []byte) {
  1081  		m[c.app.getString(key)] = c.app.getString(value)
  1082  	})
  1083  	return m
  1084  }
  1085  
  1086  // QueryInt returns integer value of key string parameter in the url.
  1087  // Default to empty or invalid key is 0.
  1088  //
  1089  //	GET /?name=alex&wanna_cake=2&id=
  1090  //	QueryInt("wanna_cake", 1) == 2
  1091  //	QueryInt("name", 1) == 1
  1092  //	QueryInt("id", 1) == 1
  1093  //	QueryInt("id") == 0
  1094  func (c *Ctx) QueryInt(key string, defaultValue ...int) int {
  1095  	// Use Atoi to convert the param to an int or return zero and an error
  1096  	value, err := strconv.Atoi(c.app.getString(c.fasthttp.QueryArgs().Peek(key)))
  1097  	if err != nil {
  1098  		if len(defaultValue) > 0 {
  1099  			return defaultValue[0]
  1100  		}
  1101  		return 0
  1102  	}
  1103  
  1104  	return value
  1105  }
  1106  
  1107  // QueryBool returns bool value of key string parameter in the url.
  1108  // Default to empty or invalid key is true.
  1109  //
  1110  //	Get /?name=alex&want_pizza=false&id=
  1111  //	QueryBool("want_pizza") == false
  1112  //	QueryBool("want_pizza", true) == false
  1113  //	QueryBool("name") == false
  1114  //	QueryBool("name", true) == true
  1115  //	QueryBool("id") == false
  1116  //	QueryBool("id", true) == true
  1117  func (c *Ctx) QueryBool(key string, defaultValue ...bool) bool {
  1118  	value, err := strconv.ParseBool(c.app.getString(c.fasthttp.QueryArgs().Peek(key)))
  1119  	if err != nil {
  1120  		if len(defaultValue) > 0 {
  1121  			return defaultValue[0]
  1122  		}
  1123  		return false
  1124  	}
  1125  	return value
  1126  }
  1127  
  1128  // QueryFloat returns float64 value of key string parameter in the url.
  1129  // Default to empty or invalid key is 0.
  1130  //
  1131  //	GET /?name=alex&amount=32.23&id=
  1132  //	QueryFloat("amount") = 32.23
  1133  //	QueryFloat("amount", 3) = 32.23
  1134  //	QueryFloat("name", 1) = 1
  1135  //	QueryFloat("name") = 0
  1136  //	QueryFloat("id", 3) = 3
  1137  func (c *Ctx) QueryFloat(key string, defaultValue ...float64) float64 {
  1138  	// use strconv.ParseFloat to convert the param to a float or return zero and an error.
  1139  	value, err := strconv.ParseFloat(c.app.getString(c.fasthttp.QueryArgs().Peek(key)), 64)
  1140  	if err != nil {
  1141  		if len(defaultValue) > 0 {
  1142  			return defaultValue[0]
  1143  		}
  1144  		return 0
  1145  	}
  1146  	return value
  1147  }
  1148  
  1149  // QueryParser binds the query string to a struct.
  1150  func (c *Ctx) QueryParser(out interface{}) error {
  1151  	data := make(map[string][]string)
  1152  	var err error
  1153  
  1154  	c.fasthttp.QueryArgs().VisitAll(func(key, val []byte) {
  1155  		if err != nil {
  1156  			return
  1157  		}
  1158  
  1159  		k := c.app.getString(key)
  1160  		v := c.app.getString(val)
  1161  
  1162  		if strings.Contains(k, "[") {
  1163  			k, err = parseParamSquareBrackets(k)
  1164  		}
  1165  
  1166  		if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
  1167  			values := strings.Split(v, ",")
  1168  			for i := 0; i < len(values); i++ {
  1169  				data[k] = append(data[k], values[i])
  1170  			}
  1171  		} else {
  1172  			data[k] = append(data[k], v)
  1173  		}
  1174  	})
  1175  
  1176  	if err != nil {
  1177  		return err
  1178  	}
  1179  
  1180  	return c.parseToStruct(queryTag, out, data)
  1181  }
  1182  
  1183  func parseParamSquareBrackets(k string) (string, error) {
  1184  	bb := bytebufferpool.Get()
  1185  	defer bytebufferpool.Put(bb)
  1186  
  1187  	kbytes := []byte(k)
  1188  
  1189  	for i, b := range kbytes {
  1190  		if b == '[' && kbytes[i+1] != ']' {
  1191  			if err := bb.WriteByte('.'); err != nil {
  1192  				return "", fmt.Errorf("failed to write: %w", err)
  1193  			}
  1194  		}
  1195  
  1196  		if b == '[' || b == ']' {
  1197  			continue
  1198  		}
  1199  
  1200  		if err := bb.WriteByte(b); err != nil {
  1201  			return "", fmt.Errorf("failed to write: %w", err)
  1202  		}
  1203  	}
  1204  
  1205  	return bb.String(), nil
  1206  }
  1207  
  1208  // ReqHeaderParser binds the request header strings to a struct.
  1209  func (c *Ctx) ReqHeaderParser(out interface{}) error {
  1210  	data := make(map[string][]string)
  1211  	c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
  1212  		k := c.app.getString(key)
  1213  		v := c.app.getString(val)
  1214  
  1215  		if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
  1216  			values := strings.Split(v, ",")
  1217  			for i := 0; i < len(values); i++ {
  1218  				data[k] = append(data[k], values[i])
  1219  			}
  1220  		} else {
  1221  			data[k] = append(data[k], v)
  1222  		}
  1223  	})
  1224  
  1225  	return c.parseToStruct(reqHeaderTag, out, data)
  1226  }
  1227  
  1228  func (*Ctx) parseToStruct(aliasTag string, out interface{}, data map[string][]string) error {
  1229  	// Get decoder from pool
  1230  	schemaDecoder, ok := decoderPoolMap[aliasTag].Get().(*schema.Decoder)
  1231  	if !ok {
  1232  		panic(fmt.Errorf("failed to type-assert to *schema.Decoder"))
  1233  	}
  1234  	defer decoderPoolMap[aliasTag].Put(schemaDecoder)
  1235  
  1236  	// Set alias tag
  1237  	schemaDecoder.SetAliasTag(aliasTag)
  1238  
  1239  	if err := schemaDecoder.Decode(out, data); err != nil {
  1240  		return fmt.Errorf("failed to decode: %w", err)
  1241  	}
  1242  
  1243  	return nil
  1244  }
  1245  
  1246  func equalFieldType(out interface{}, kind reflect.Kind, key string) bool {
  1247  	// Get type of interface
  1248  	outTyp := reflect.TypeOf(out).Elem()
  1249  	key = utils.ToLower(key)
  1250  	// Must be a struct to match a field
  1251  	if outTyp.Kind() != reflect.Struct {
  1252  		return false
  1253  	}
  1254  	// Copy interface to an value to be used
  1255  	outVal := reflect.ValueOf(out).Elem()
  1256  	// Loop over each field
  1257  	for i := 0; i < outTyp.NumField(); i++ {
  1258  		// Get field value data
  1259  		structField := outVal.Field(i)
  1260  		// Can this field be changed?
  1261  		if !structField.CanSet() {
  1262  			continue
  1263  		}
  1264  		// Get field key data
  1265  		typeField := outTyp.Field(i)
  1266  		// Get type of field key
  1267  		structFieldKind := structField.Kind()
  1268  		// Does the field type equals input?
  1269  		if structFieldKind != kind {
  1270  			continue
  1271  		}
  1272  		// Get tag from field if exist
  1273  		inputFieldName := typeField.Tag.Get(queryTag)
  1274  		if inputFieldName == "" {
  1275  			inputFieldName = typeField.Name
  1276  		} else {
  1277  			inputFieldName = strings.Split(inputFieldName, ",")[0]
  1278  		}
  1279  		// Compare field/tag with provided key
  1280  		if utils.ToLower(inputFieldName) == key {
  1281  			return true
  1282  		}
  1283  	}
  1284  	return false
  1285  }
  1286  
  1287  var (
  1288  	ErrRangeMalformed     = errors.New("range: malformed range header string")
  1289  	ErrRangeUnsatisfiable = errors.New("range: unsatisfiable range")
  1290  )
  1291  
  1292  // Range returns a struct containing the type and a slice of ranges.
  1293  func (c *Ctx) Range(size int) (Range, error) {
  1294  	var rangeData Range
  1295  	rangeStr := c.Get(HeaderRange)
  1296  	if rangeStr == "" || !strings.Contains(rangeStr, "=") {
  1297  		return rangeData, ErrRangeMalformed
  1298  	}
  1299  	data := strings.Split(rangeStr, "=")
  1300  	const expectedDataParts = 2
  1301  	if len(data) != expectedDataParts {
  1302  		return rangeData, ErrRangeMalformed
  1303  	}
  1304  	rangeData.Type = data[0]
  1305  	arr := strings.Split(data[1], ",")
  1306  	for i := 0; i < len(arr); i++ {
  1307  		item := strings.Split(arr[i], "-")
  1308  		if len(item) == 1 {
  1309  			return rangeData, ErrRangeMalformed
  1310  		}
  1311  		start, startErr := strconv.Atoi(item[0])
  1312  		end, endErr := strconv.Atoi(item[1])
  1313  		if startErr != nil { // -nnn
  1314  			start = size - end
  1315  			end = size - 1
  1316  		} else if endErr != nil { // nnn-
  1317  			end = size - 1
  1318  		}
  1319  		if end > size-1 { // limit last-byte-pos to current length
  1320  			end = size - 1
  1321  		}
  1322  		if start > end || start < 0 {
  1323  			continue
  1324  		}
  1325  		rangeData.Ranges = append(rangeData.Ranges, struct {
  1326  			Start int
  1327  			End   int
  1328  		}{
  1329  			start,
  1330  			end,
  1331  		})
  1332  	}
  1333  	if len(rangeData.Ranges) < 1 {
  1334  		return rangeData, ErrRangeUnsatisfiable
  1335  	}
  1336  
  1337  	return rangeData, nil
  1338  }
  1339  
  1340  // Redirect to the URL derived from the specified path, with specified status.
  1341  // If status is not specified, status defaults to 302 Found.
  1342  func (c *Ctx) Redirect(location string, status ...int) error {
  1343  	c.setCanonical(HeaderLocation, location)
  1344  	if len(status) > 0 {
  1345  		c.Status(status[0])
  1346  	} else {
  1347  		c.Status(StatusFound)
  1348  	}
  1349  	return nil
  1350  }
  1351  
  1352  // Bind Add vars to default view var map binding to template engine.
  1353  // Variables are read by the Render method and may be overwritten.
  1354  func (c *Ctx) Bind(vars Map) error {
  1355  	// init viewBindMap - lazy map
  1356  	if c.viewBindMap == nil {
  1357  		c.viewBindMap = dictpool.AcquireDict()
  1358  	}
  1359  	for k, v := range vars {
  1360  		c.viewBindMap.Set(k, v)
  1361  	}
  1362  
  1363  	return nil
  1364  }
  1365  
  1366  // getLocationFromRoute get URL location from route using parameters
  1367  func (c *Ctx) getLocationFromRoute(route Route, params Map) (string, error) {
  1368  	buf := bytebufferpool.Get()
  1369  	for _, segment := range route.routeParser.segs {
  1370  		if !segment.IsParam {
  1371  			_, err := buf.WriteString(segment.Const)
  1372  			if err != nil {
  1373  				return "", fmt.Errorf("failed to write string: %w", err)
  1374  			}
  1375  			continue
  1376  		}
  1377  
  1378  		for key, val := range params {
  1379  			isSame := key == segment.ParamName || (!c.app.config.CaseSensitive && utils.EqualFold(key, segment.ParamName))
  1380  			isGreedy := segment.IsGreedy && len(key) == 1 && isInCharset(key[0], greedyParameters)
  1381  			if isSame || isGreedy {
  1382  				_, err := buf.WriteString(utils.ToString(val))
  1383  				if err != nil {
  1384  					return "", fmt.Errorf("failed to write string: %w", err)
  1385  				}
  1386  			}
  1387  		}
  1388  	}
  1389  	location := buf.String()
  1390  	// release buffer
  1391  	bytebufferpool.Put(buf)
  1392  	return location, nil
  1393  }
  1394  
  1395  // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831"
  1396  func (c *Ctx) GetRouteURL(routeName string, params Map) (string, error) {
  1397  	return c.getLocationFromRoute(c.App().GetRoute(routeName), params)
  1398  }
  1399  
  1400  // RedirectToRoute to the Route registered in the app with appropriate parameters
  1401  // If status is not specified, status defaults to 302 Found.
  1402  // If you want to send queries to route, you must add "queries" key typed as map[string]string to params.
  1403  func (c *Ctx) RedirectToRoute(routeName string, params Map, status ...int) error {
  1404  	location, err := c.getLocationFromRoute(c.App().GetRoute(routeName), params)
  1405  	if err != nil {
  1406  		return err
  1407  	}
  1408  
  1409  	// Check queries
  1410  	if queries, ok := params["queries"].(map[string]string); ok {
  1411  		queryText := bytebufferpool.Get()
  1412  		defer bytebufferpool.Put(queryText)
  1413  
  1414  		i := 1
  1415  		for k, v := range queries {
  1416  			_, _ = queryText.WriteString(k + "=" + v) //nolint:errcheck // This will never fail
  1417  
  1418  			if i != len(queries) {
  1419  				_, _ = queryText.WriteString("&") //nolint:errcheck // This will never fail
  1420  			}
  1421  			i++
  1422  		}
  1423  
  1424  		return c.Redirect(location+"?"+queryText.String(), status...)
  1425  	}
  1426  	return c.Redirect(location, status...)
  1427  }
  1428  
  1429  // RedirectBack to the URL to referer
  1430  // If status is not specified, status defaults to 302 Found.
  1431  func (c *Ctx) RedirectBack(fallback string, status ...int) error {
  1432  	location := c.Get(HeaderReferer)
  1433  	if location == "" {
  1434  		location = fallback
  1435  	}
  1436  	return c.Redirect(location, status...)
  1437  }
  1438  
  1439  // Render a template with data and sends a text/html response.
  1440  // We support the following engines: html, amber, handlebars, mustache, pug
  1441  func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
  1442  	// Get new buffer from pool
  1443  	buf := bytebufferpool.Get()
  1444  	defer bytebufferpool.Put(buf)
  1445  
  1446  	// Pass-locals-to-views, bind, appListKeys
  1447  	c.renderExtensions(bind)
  1448  
  1449  	var rendered bool
  1450  	for i := len(c.app.mountFields.appListKeys) - 1; i >= 0; i-- {
  1451  		prefix := c.app.mountFields.appListKeys[i]
  1452  		app := c.app.mountFields.appList[prefix]
  1453  		if prefix == "" || strings.Contains(c.OriginalURL(), prefix) {
  1454  			if len(layouts) == 0 && app.config.ViewsLayout != "" {
  1455  				layouts = []string{
  1456  					app.config.ViewsLayout,
  1457  				}
  1458  			}
  1459  
  1460  			// Render template from Views
  1461  			if app.config.Views != nil {
  1462  				if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil {
  1463  					return fmt.Errorf("failed to render: %w", err)
  1464  				}
  1465  
  1466  				rendered = true
  1467  				break
  1468  			}
  1469  		}
  1470  	}
  1471  
  1472  	if !rendered {
  1473  		// Render raw template using 'name' as filepath if no engine is set
  1474  		var tmpl *template.Template
  1475  		if _, err := readContent(buf, name); err != nil {
  1476  			return err
  1477  		}
  1478  		// Parse template
  1479  		tmpl, err := template.New("").Parse(c.app.getString(buf.Bytes()))
  1480  		if err != nil {
  1481  			return fmt.Errorf("failed to parse: %w", err)
  1482  		}
  1483  		buf.Reset()
  1484  		// Render template
  1485  		if err := tmpl.Execute(buf, bind); err != nil {
  1486  			return fmt.Errorf("failed to execute: %w", err)
  1487  		}
  1488  	}
  1489  
  1490  	// Set Content-Type to text/html
  1491  	c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8)
  1492  	// Set rendered template to body
  1493  	c.fasthttp.Response.SetBody(buf.Bytes())
  1494  
  1495  	return nil
  1496  }
  1497  
  1498  func (c *Ctx) renderExtensions(bind interface{}) {
  1499  	if bindMap, ok := bind.(Map); ok {
  1500  		// Bind view map
  1501  		if c.viewBindMap != nil {
  1502  			for _, v := range c.viewBindMap.D {
  1503  				// make sure key does not exist already
  1504  				if _, ok := bindMap[v.Key]; !ok {
  1505  					bindMap[v.Key] = v.Value
  1506  				}
  1507  			}
  1508  		}
  1509  
  1510  		// Check if the PassLocalsToViews option is enabled (by default it is disabled)
  1511  		if c.app.config.PassLocalsToViews {
  1512  			// Loop through each local and set it in the map
  1513  			c.fasthttp.VisitUserValues(func(key []byte, val interface{}) {
  1514  				// check if bindMap doesn't contain the key
  1515  				if _, ok := bindMap[c.app.getString(key)]; !ok {
  1516  					// Set the key and value in the bindMap
  1517  					bindMap[c.app.getString(key)] = val
  1518  				}
  1519  			})
  1520  		}
  1521  	}
  1522  
  1523  	if len(c.app.mountFields.appListKeys) == 0 {
  1524  		c.app.generateAppListKeys()
  1525  	}
  1526  }
  1527  
  1528  // Route returns the matched Route struct.
  1529  func (c *Ctx) Route() *Route {
  1530  	if c.route == nil {
  1531  		// Fallback for fasthttp error handler
  1532  		return &Route{
  1533  			path:     c.pathOriginal,
  1534  			Path:     c.pathOriginal,
  1535  			Method:   c.method,
  1536  			Handlers: make([]Handler, 0),
  1537  			Params:   make([]string, 0),
  1538  		}
  1539  	}
  1540  	return c.route
  1541  }
  1542  
  1543  // SaveFile saves any multipart file to disk.
  1544  func (*Ctx) SaveFile(fileheader *multipart.FileHeader, path string) error {
  1545  	return fasthttp.SaveMultipartFile(fileheader, path)
  1546  }
  1547  
  1548  // SaveFileToStorage saves any multipart file to an external storage system.
  1549  func (*Ctx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error {
  1550  	file, err := fileheader.Open()
  1551  	if err != nil {
  1552  		return fmt.Errorf("failed to open: %w", err)
  1553  	}
  1554  
  1555  	content, err := io.ReadAll(file)
  1556  	if err != nil {
  1557  		return fmt.Errorf("failed to read: %w", err)
  1558  	}
  1559  
  1560  	if err := storage.Set(path, content, 0); err != nil {
  1561  		return fmt.Errorf("failed to store: %w", err)
  1562  	}
  1563  
  1564  	return nil
  1565  }
  1566  
  1567  // Secure returns whether a secure connection was established.
  1568  func (c *Ctx) Secure() bool {
  1569  	return c.Protocol() == schemeHTTPS
  1570  }
  1571  
  1572  // Send sets the HTTP response body without copying it.
  1573  // From this point onward the body argument must not be changed.
  1574  func (c *Ctx) Send(body []byte) error {
  1575  	// Write response body
  1576  	c.fasthttp.Response.SetBodyRaw(body)
  1577  	return nil
  1578  }
  1579  
  1580  var (
  1581  	sendFileOnce    sync.Once
  1582  	sendFileFS      *fasthttp.FS
  1583  	sendFileHandler fasthttp.RequestHandler
  1584  )
  1585  
  1586  // SendFile transfers the file from the given path.
  1587  // The file is not compressed by default, enable this by passing a 'true' argument
  1588  // Sets the Content-Type response HTTP header field based on the filenames extension.
  1589  func (c *Ctx) SendFile(file string, compress ...bool) error {
  1590  	// Save the filename, we will need it in the error message if the file isn't found
  1591  	filename := file
  1592  
  1593  	// https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134
  1594  	sendFileOnce.Do(func() {
  1595  		const cacheDuration = 10 * time.Second
  1596  		sendFileFS = &fasthttp.FS{
  1597  			Root:                 "",
  1598  			AllowEmptyRoot:       true,
  1599  			GenerateIndexPages:   false,
  1600  			AcceptByteRange:      true,
  1601  			Compress:             true,
  1602  			CompressedFileSuffix: c.app.config.CompressedFileSuffix,
  1603  			CacheDuration:        cacheDuration,
  1604  			IndexNames:           []string{"index.html"},
  1605  			PathNotFound: func(ctx *fasthttp.RequestCtx) {
  1606  				ctx.Response.SetStatusCode(StatusNotFound)
  1607  			},
  1608  		}
  1609  		sendFileHandler = sendFileFS.NewRequestHandler()
  1610  	})
  1611  
  1612  	// Keep original path for mutable params
  1613  	c.pathOriginal = utils.CopyString(c.pathOriginal)
  1614  	// Disable compression
  1615  	if len(compress) == 0 || !compress[0] {
  1616  		// https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55
  1617  		c.fasthttp.Request.Header.Del(HeaderAcceptEncoding)
  1618  	}
  1619  	// copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments
  1620  	if len(file) == 0 || !filepath.IsAbs(file) {
  1621  		// extend relative path to absolute path
  1622  		hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\')
  1623  
  1624  		var err error
  1625  		file = filepath.FromSlash(file)
  1626  		if file, err = filepath.Abs(file); err != nil {
  1627  			return fmt.Errorf("failed to determine abs file path: %w", err)
  1628  		}
  1629  		if hasTrailingSlash {
  1630  			file += "/"
  1631  		}
  1632  	}
  1633  	// convert the path to forward slashes regardless the OS in order to set the URI properly
  1634  	// the handler will convert back to OS path separator before opening the file
  1635  	file = filepath.ToSlash(file)
  1636  
  1637  	// Restore the original requested URL
  1638  	originalURL := utils.CopyString(c.OriginalURL())
  1639  	defer c.fasthttp.Request.SetRequestURI(originalURL)
  1640  	// Set new URI for fileHandler
  1641  	c.fasthttp.Request.SetRequestURI(file)
  1642  	// Save status code
  1643  	status := c.fasthttp.Response.StatusCode()
  1644  	// Serve file
  1645  	sendFileHandler(c.fasthttp)
  1646  	// Get the status code which is set by fasthttp
  1647  	fsStatus := c.fasthttp.Response.StatusCode()
  1648  	// Set the status code set by the user if it is different from the fasthttp status code and 200
  1649  	if status != fsStatus && status != StatusOK {
  1650  		c.Status(status)
  1651  	}
  1652  	// Check for error
  1653  	if status != StatusNotFound && fsStatus == StatusNotFound {
  1654  		return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename))
  1655  	}
  1656  	return nil
  1657  }
  1658  
  1659  // SendStatus sets the HTTP status code and if the response body is empty,
  1660  // it sets the correct status message in the body.
  1661  func (c *Ctx) SendStatus(status int) error {
  1662  	c.Status(status)
  1663  
  1664  	// Only set status body when there is no response body
  1665  	if len(c.fasthttp.Response.Body()) == 0 {
  1666  		return c.SendString(utils.StatusMessage(status))
  1667  	}
  1668  
  1669  	return nil
  1670  }
  1671  
  1672  // SendString sets the HTTP response body for string types.
  1673  // This means no type assertion, recommended for faster performance
  1674  func (c *Ctx) SendString(body string) error {
  1675  	c.fasthttp.Response.SetBodyString(body)
  1676  
  1677  	return nil
  1678  }
  1679  
  1680  // SendStream sets response body stream and optional body size.
  1681  func (c *Ctx) SendStream(stream io.Reader, size ...int) error {
  1682  	if len(size) > 0 && size[0] >= 0 {
  1683  		c.fasthttp.Response.SetBodyStream(stream, size[0])
  1684  	} else {
  1685  		c.fasthttp.Response.SetBodyStream(stream, -1)
  1686  	}
  1687  
  1688  	return nil
  1689  }
  1690  
  1691  // Set sets the response's HTTP header field to the specified key, value.
  1692  func (c *Ctx) Set(key, val string) {
  1693  	c.fasthttp.Response.Header.Set(key, val)
  1694  }
  1695  
  1696  func (c *Ctx) setCanonical(key, val string) {
  1697  	c.fasthttp.Response.Header.SetCanonical(c.app.getBytes(key), c.app.getBytes(val))
  1698  }
  1699  
  1700  // Subdomains returns a string slice of subdomains in the domain name of the request.
  1701  // The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments.
  1702  func (c *Ctx) Subdomains(offset ...int) []string {
  1703  	o := 2
  1704  	if len(offset) > 0 {
  1705  		o = offset[0]
  1706  	}
  1707  	subdomains := strings.Split(c.Hostname(), ".")
  1708  	l := len(subdomains) - o
  1709  	// Check index to avoid slice bounds out of range panic
  1710  	if l < 0 {
  1711  		l = len(subdomains)
  1712  	}
  1713  	subdomains = subdomains[:l]
  1714  	return subdomains
  1715  }
  1716  
  1717  // Stale is not implemented yet, pull requests are welcome!
  1718  func (c *Ctx) Stale() bool {
  1719  	return !c.Fresh()
  1720  }
  1721  
  1722  // Status sets the HTTP status for the response.
  1723  // This method is chainable.
  1724  func (c *Ctx) Status(status int) *Ctx {
  1725  	c.fasthttp.Response.SetStatusCode(status)
  1726  	return c
  1727  }
  1728  
  1729  // String returns unique string representation of the ctx.
  1730  //
  1731  // The returned value may be useful for logging.
  1732  func (c *Ctx) String() string {
  1733  	return fmt.Sprintf(
  1734  		"#%016X - %s <-> %s - %s %s",
  1735  		c.fasthttp.ID(),
  1736  		c.fasthttp.LocalAddr(),
  1737  		c.fasthttp.RemoteAddr(),
  1738  		c.fasthttp.Request.Header.Method(),
  1739  		c.fasthttp.URI().FullURI(),
  1740  	)
  1741  }
  1742  
  1743  // Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
  1744  func (c *Ctx) Type(extension string, charset ...string) *Ctx {
  1745  	if len(charset) > 0 {
  1746  		c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0])
  1747  	} else {
  1748  		c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension))
  1749  	}
  1750  	return c
  1751  }
  1752  
  1753  // Vary adds the given header field to the Vary response header.
  1754  // This will append the header, if not already listed, otherwise leaves it listed in the current location.
  1755  func (c *Ctx) Vary(fields ...string) {
  1756  	c.Append(HeaderVary, fields...)
  1757  }
  1758  
  1759  // Write appends p into response body.
  1760  func (c *Ctx) Write(p []byte) (int, error) {
  1761  	c.fasthttp.Response.AppendBody(p)
  1762  	return len(p), nil
  1763  }
  1764  
  1765  // Writef appends f & a into response body writer.
  1766  func (c *Ctx) Writef(f string, a ...interface{}) (int, error) {
  1767  	//nolint:wrapcheck // This must not be wrapped
  1768  	return fmt.Fprintf(c.fasthttp.Response.BodyWriter(), f, a...)
  1769  }
  1770  
  1771  // WriteString appends s to response body.
  1772  func (c *Ctx) WriteString(s string) (int, error) {
  1773  	c.fasthttp.Response.AppendBodyString(s)
  1774  	return len(s), nil
  1775  }
  1776  
  1777  // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest,
  1778  // indicating that the request was issued by a client library (such as jQuery).
  1779  func (c *Ctx) XHR() bool {
  1780  	return utils.EqualFoldBytes(c.app.getBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest"))
  1781  }
  1782  
  1783  // configDependentPaths set paths for route recognition and prepared paths for the user,
  1784  // here the features for caseSensitive, decoded paths, strict paths are evaluated
  1785  func (c *Ctx) configDependentPaths() {
  1786  	c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...)
  1787  	// If UnescapePath enabled, we decode the path and save it for the framework user
  1788  	if c.app.config.UnescapePath {
  1789  		c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer)
  1790  	}
  1791  	c.path = c.app.getString(c.pathBuffer)
  1792  
  1793  	// another path is specified which is for routing recognition only
  1794  	// use the path that was changed by the previous configuration flags
  1795  	c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...)
  1796  	// If CaseSensitive is disabled, we lowercase the original path
  1797  	if !c.app.config.CaseSensitive {
  1798  		c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer)
  1799  	}
  1800  	// If StrictRouting is disabled, we strip all trailing slashes
  1801  	if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
  1802  		c.detectionPathBuffer = utils.TrimRightBytes(c.detectionPathBuffer, '/')
  1803  	}
  1804  	c.detectionPath = c.app.getString(c.detectionPathBuffer)
  1805  
  1806  	// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
  1807  	// since the first three characters area select a list of routes
  1808  	c.treePath = c.treePath[0:0]
  1809  	const maxDetectionPaths = 3
  1810  	if len(c.detectionPath) >= maxDetectionPaths {
  1811  		c.treePath = c.detectionPath[:maxDetectionPaths]
  1812  	}
  1813  }
  1814  
  1815  func (c *Ctx) IsProxyTrusted() bool {
  1816  	if !c.app.config.EnableTrustedProxyCheck {
  1817  		return true
  1818  	}
  1819  
  1820  	ip := c.fasthttp.RemoteIP()
  1821  
  1822  	if _, trusted := c.app.config.trustedProxiesMap[ip.String()]; trusted {
  1823  		return true
  1824  	}
  1825  
  1826  	for _, ipNet := range c.app.config.trustedProxyRanges {
  1827  		if ipNet.Contains(ip) {
  1828  			return true
  1829  		}
  1830  	}
  1831  
  1832  	return false
  1833  }
  1834  
  1835  // IsLocalHost will return true if address is a localhost address.
  1836  func (*Ctx) isLocalHost(address string) bool {
  1837  	localHosts := []string{"127.0.0.1", "0.0.0.0", "::1"}
  1838  	for _, h := range localHosts {
  1839  		if strings.Contains(address, h) {
  1840  			return true
  1841  		}
  1842  	}
  1843  	return false
  1844  }
  1845  
  1846  // IsFromLocal will return true if request came from local.
  1847  func (c *Ctx) IsFromLocal() bool {
  1848  	ips := c.IPs()
  1849  	if len(ips) == 0 {
  1850  		ips = append(ips, c.IP())
  1851  	}
  1852  	return c.isLocalHost(ips[0])
  1853  }