github.com/boomhut/fiber/v2@v2.0.0-20230603160335-b65c856e57d3/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/boomhut/fiber/v2/internal/schema"
    28  	"github.com/boomhut/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  // QueryInt returns integer value of key string parameter in the url.
  1058  // Default to empty or invalid key is 0.
  1059  //
  1060  //	GET /?name=alex&wanna_cake=2&id=
  1061  //	QueryInt("wanna_cake", 1) == 2
  1062  //	QueryInt("name", 1) == 1
  1063  //	QueryInt("id", 1) == 1
  1064  //	QueryInt("id") == 0
  1065  func (c *Ctx) QueryInt(key string, defaultValue ...int) int {
  1066  	// Use Atoi to convert the param to an int or return zero and an error
  1067  	value, err := strconv.Atoi(c.app.getString(c.fasthttp.QueryArgs().Peek(key)))
  1068  	if err != nil {
  1069  		if len(defaultValue) > 0 {
  1070  			return defaultValue[0]
  1071  		}
  1072  		return 0
  1073  	}
  1074  
  1075  	return value
  1076  }
  1077  
  1078  // QueryBool returns bool value of key string parameter in the url.
  1079  // Default to empty or invalid key is true.
  1080  //
  1081  //	Get /?name=alex&want_pizza=false&id=
  1082  //	QueryBool("want_pizza") == false
  1083  //	QueryBool("want_pizza", true) == false
  1084  //	QueryBool("name") == false
  1085  //	QueryBool("name", true) == true
  1086  //	QueryBool("id") == false
  1087  //	QueryBool("id", true) == true
  1088  func (c *Ctx) QueryBool(key string, defaultValue ...bool) bool {
  1089  	value, err := strconv.ParseBool(c.app.getString(c.fasthttp.QueryArgs().Peek(key)))
  1090  	if err != nil {
  1091  		if len(defaultValue) > 0 {
  1092  			return defaultValue[0]
  1093  		}
  1094  		return false
  1095  	}
  1096  	return value
  1097  }
  1098  
  1099  // QueryFloat returns float64 value of key string parameter in the url.
  1100  // Default to empty or invalid key is 0.
  1101  //
  1102  //	GET /?name=alex&amount=32.23&id=
  1103  //	QueryFloat("amount") = 32.23
  1104  //	QueryFloat("amount", 3) = 32.23
  1105  //	QueryFloat("name", 1) = 1
  1106  //	QueryFloat("name") = 0
  1107  //	QueryFloat("id", 3) = 3
  1108  func (c *Ctx) QueryFloat(key string, defaultValue ...float64) float64 {
  1109  	// use strconv.ParseFloat to convert the param to a float or return zero and an error.
  1110  	value, err := strconv.ParseFloat(c.app.getString(c.fasthttp.QueryArgs().Peek(key)), 64)
  1111  	if err != nil {
  1112  		if len(defaultValue) > 0 {
  1113  			return defaultValue[0]
  1114  		}
  1115  		return 0
  1116  	}
  1117  	return value
  1118  }
  1119  
  1120  // QueryParser binds the query string to a struct.
  1121  func (c *Ctx) QueryParser(out interface{}) error {
  1122  	data := make(map[string][]string)
  1123  	var err error
  1124  
  1125  	c.fasthttp.QueryArgs().VisitAll(func(key, val []byte) {
  1126  		if err != nil {
  1127  			return
  1128  		}
  1129  
  1130  		k := c.app.getString(key)
  1131  		v := c.app.getString(val)
  1132  
  1133  		if strings.Contains(k, "[") {
  1134  			k, err = parseParamSquareBrackets(k)
  1135  		}
  1136  
  1137  		if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
  1138  			values := strings.Split(v, ",")
  1139  			for i := 0; i < len(values); i++ {
  1140  				data[k] = append(data[k], values[i])
  1141  			}
  1142  		} else {
  1143  			data[k] = append(data[k], v)
  1144  		}
  1145  	})
  1146  
  1147  	if err != nil {
  1148  		return err
  1149  	}
  1150  
  1151  	return c.parseToStruct(queryTag, out, data)
  1152  }
  1153  
  1154  func parseParamSquareBrackets(k string) (string, error) {
  1155  	bb := bytebufferpool.Get()
  1156  	defer bytebufferpool.Put(bb)
  1157  
  1158  	kbytes := []byte(k)
  1159  
  1160  	for i, b := range kbytes {
  1161  		if b == '[' && kbytes[i+1] != ']' {
  1162  			if err := bb.WriteByte('.'); err != nil {
  1163  				return "", fmt.Errorf("failed to write: %w", err)
  1164  			}
  1165  		}
  1166  
  1167  		if b == '[' || b == ']' {
  1168  			continue
  1169  		}
  1170  
  1171  		if err := bb.WriteByte(b); err != nil {
  1172  			return "", fmt.Errorf("failed to write: %w", err)
  1173  		}
  1174  	}
  1175  
  1176  	return bb.String(), nil
  1177  }
  1178  
  1179  // ReqHeaderParser binds the request header strings to a struct.
  1180  func (c *Ctx) ReqHeaderParser(out interface{}) error {
  1181  	data := make(map[string][]string)
  1182  	c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
  1183  		k := c.app.getString(key)
  1184  		v := c.app.getString(val)
  1185  
  1186  		if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
  1187  			values := strings.Split(v, ",")
  1188  			for i := 0; i < len(values); i++ {
  1189  				data[k] = append(data[k], values[i])
  1190  			}
  1191  		} else {
  1192  			data[k] = append(data[k], v)
  1193  		}
  1194  	})
  1195  
  1196  	return c.parseToStruct(reqHeaderTag, out, data)
  1197  }
  1198  
  1199  func (*Ctx) parseToStruct(aliasTag string, out interface{}, data map[string][]string) error {
  1200  	// Get decoder from pool
  1201  	schemaDecoder, ok := decoderPoolMap[aliasTag].Get().(*schema.Decoder)
  1202  	if !ok {
  1203  		panic(fmt.Errorf("failed to type-assert to *schema.Decoder"))
  1204  	}
  1205  	defer decoderPoolMap[aliasTag].Put(schemaDecoder)
  1206  
  1207  	// Set alias tag
  1208  	schemaDecoder.SetAliasTag(aliasTag)
  1209  
  1210  	if err := schemaDecoder.Decode(out, data); err != nil {
  1211  		return fmt.Errorf("failed to decode: %w", err)
  1212  	}
  1213  
  1214  	return nil
  1215  }
  1216  
  1217  func equalFieldType(out interface{}, kind reflect.Kind, key string) bool {
  1218  	// Get type of interface
  1219  	outTyp := reflect.TypeOf(out).Elem()
  1220  	key = utils.ToLower(key)
  1221  	// Must be a struct to match a field
  1222  	if outTyp.Kind() != reflect.Struct {
  1223  		return false
  1224  	}
  1225  	// Copy interface to an value to be used
  1226  	outVal := reflect.ValueOf(out).Elem()
  1227  	// Loop over each field
  1228  	for i := 0; i < outTyp.NumField(); i++ {
  1229  		// Get field value data
  1230  		structField := outVal.Field(i)
  1231  		// Can this field be changed?
  1232  		if !structField.CanSet() {
  1233  			continue
  1234  		}
  1235  		// Get field key data
  1236  		typeField := outTyp.Field(i)
  1237  		// Get type of field key
  1238  		structFieldKind := structField.Kind()
  1239  		// Does the field type equals input?
  1240  		if structFieldKind != kind {
  1241  			continue
  1242  		}
  1243  		// Get tag from field if exist
  1244  		inputFieldName := typeField.Tag.Get(queryTag)
  1245  		if inputFieldName == "" {
  1246  			inputFieldName = typeField.Name
  1247  		} else {
  1248  			inputFieldName = strings.Split(inputFieldName, ",")[0]
  1249  		}
  1250  		// Compare field/tag with provided key
  1251  		if utils.ToLower(inputFieldName) == key {
  1252  			return true
  1253  		}
  1254  	}
  1255  	return false
  1256  }
  1257  
  1258  var (
  1259  	ErrRangeMalformed     = errors.New("range: malformed range header string")
  1260  	ErrRangeUnsatisfiable = errors.New("range: unsatisfiable range")
  1261  )
  1262  
  1263  // Range returns a struct containing the type and a slice of ranges.
  1264  func (c *Ctx) Range(size int) (Range, error) {
  1265  	var rangeData Range
  1266  	rangeStr := c.Get(HeaderRange)
  1267  	if rangeStr == "" || !strings.Contains(rangeStr, "=") {
  1268  		return rangeData, ErrRangeMalformed
  1269  	}
  1270  	data := strings.Split(rangeStr, "=")
  1271  	const expectedDataParts = 2
  1272  	if len(data) != expectedDataParts {
  1273  		return rangeData, ErrRangeMalformed
  1274  	}
  1275  	rangeData.Type = data[0]
  1276  	arr := strings.Split(data[1], ",")
  1277  	for i := 0; i < len(arr); i++ {
  1278  		item := strings.Split(arr[i], "-")
  1279  		if len(item) == 1 {
  1280  			return rangeData, ErrRangeMalformed
  1281  		}
  1282  		start, startErr := strconv.Atoi(item[0])
  1283  		end, endErr := strconv.Atoi(item[1])
  1284  		if startErr != nil { // -nnn
  1285  			start = size - end
  1286  			end = size - 1
  1287  		} else if endErr != nil { // nnn-
  1288  			end = size - 1
  1289  		}
  1290  		if end > size-1 { // limit last-byte-pos to current length
  1291  			end = size - 1
  1292  		}
  1293  		if start > end || start < 0 {
  1294  			continue
  1295  		}
  1296  		rangeData.Ranges = append(rangeData.Ranges, struct {
  1297  			Start int
  1298  			End   int
  1299  		}{
  1300  			start,
  1301  			end,
  1302  		})
  1303  	}
  1304  	if len(rangeData.Ranges) < 1 {
  1305  		return rangeData, ErrRangeUnsatisfiable
  1306  	}
  1307  
  1308  	return rangeData, nil
  1309  }
  1310  
  1311  // Redirect to the URL derived from the specified path, with specified status.
  1312  // If status is not specified, status defaults to 302 Found.
  1313  func (c *Ctx) Redirect(location string, status ...int) error {
  1314  	c.setCanonical(HeaderLocation, location)
  1315  	if len(status) > 0 {
  1316  		c.Status(status[0])
  1317  	} else {
  1318  		c.Status(StatusFound)
  1319  	}
  1320  	return nil
  1321  }
  1322  
  1323  // Bind Add vars to default view var map binding to template engine.
  1324  // Variables are read by the Render method and may be overwritten.
  1325  func (c *Ctx) Bind(vars Map) error {
  1326  	// init viewBindMap - lazy map
  1327  	if c.viewBindMap == nil {
  1328  		c.viewBindMap = dictpool.AcquireDict()
  1329  	}
  1330  	for k, v := range vars {
  1331  		c.viewBindMap.Set(k, v)
  1332  	}
  1333  
  1334  	return nil
  1335  }
  1336  
  1337  // getLocationFromRoute get URL location from route using parameters
  1338  func (c *Ctx) getLocationFromRoute(route Route, params Map) (string, error) {
  1339  	buf := bytebufferpool.Get()
  1340  	for _, segment := range route.routeParser.segs {
  1341  		if !segment.IsParam {
  1342  			_, err := buf.WriteString(segment.Const)
  1343  			if err != nil {
  1344  				return "", fmt.Errorf("failed to write string: %w", err)
  1345  			}
  1346  			continue
  1347  		}
  1348  
  1349  		for key, val := range params {
  1350  			isSame := key == segment.ParamName || (!c.app.config.CaseSensitive && utils.EqualFold(key, segment.ParamName))
  1351  			isGreedy := segment.IsGreedy && len(key) == 1 && isInCharset(key[0], greedyParameters)
  1352  			if isSame || isGreedy {
  1353  				_, err := buf.WriteString(utils.ToString(val))
  1354  				if err != nil {
  1355  					return "", fmt.Errorf("failed to write string: %w", err)
  1356  				}
  1357  			}
  1358  		}
  1359  	}
  1360  	location := buf.String()
  1361  	// release buffer
  1362  	bytebufferpool.Put(buf)
  1363  	return location, nil
  1364  }
  1365  
  1366  // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831"
  1367  func (c *Ctx) GetRouteURL(routeName string, params Map) (string, error) {
  1368  	return c.getLocationFromRoute(c.App().GetRoute(routeName), params)
  1369  }
  1370  
  1371  // RedirectToRoute to the Route registered in the app with appropriate parameters
  1372  // If status is not specified, status defaults to 302 Found.
  1373  // If you want to send queries to route, you must add "queries" key typed as map[string]string to params.
  1374  func (c *Ctx) RedirectToRoute(routeName string, params Map, status ...int) error {
  1375  	location, err := c.getLocationFromRoute(c.App().GetRoute(routeName), params)
  1376  	if err != nil {
  1377  		return err
  1378  	}
  1379  
  1380  	// Check queries
  1381  	if queries, ok := params["queries"].(map[string]string); ok {
  1382  		queryText := bytebufferpool.Get()
  1383  		defer bytebufferpool.Put(queryText)
  1384  
  1385  		i := 1
  1386  		for k, v := range queries {
  1387  			_, _ = queryText.WriteString(k + "=" + v) //nolint:errcheck // This will never fail
  1388  
  1389  			if i != len(queries) {
  1390  				_, _ = queryText.WriteString("&") //nolint:errcheck // This will never fail
  1391  			}
  1392  			i++
  1393  		}
  1394  
  1395  		return c.Redirect(location+"?"+queryText.String(), status...)
  1396  	}
  1397  	return c.Redirect(location, status...)
  1398  }
  1399  
  1400  // RedirectBack to the URL to referer
  1401  // If status is not specified, status defaults to 302 Found.
  1402  func (c *Ctx) RedirectBack(fallback string, status ...int) error {
  1403  	location := c.Get(HeaderReferer)
  1404  	if location == "" {
  1405  		location = fallback
  1406  	}
  1407  	return c.Redirect(location, status...)
  1408  }
  1409  
  1410  // Render a template with data and sends a text/html response.
  1411  // We support the following engines: html, amber, handlebars, mustache, pug
  1412  func (c *Ctx) Render(name string, bind interface{}, layouts ...string) error {
  1413  	// Get new buffer from pool
  1414  	buf := bytebufferpool.Get()
  1415  	defer bytebufferpool.Put(buf)
  1416  
  1417  	// Pass-locals-to-views, bind, appListKeys
  1418  	c.renderExtensions(bind)
  1419  
  1420  	var rendered bool
  1421  	for i := len(c.app.mountFields.appListKeys) - 1; i >= 0; i-- {
  1422  		prefix := c.app.mountFields.appListKeys[i]
  1423  		app := c.app.mountFields.appList[prefix]
  1424  		if prefix == "" || strings.Contains(c.OriginalURL(), prefix) {
  1425  			if len(layouts) == 0 && app.config.ViewsLayout != "" {
  1426  				layouts = []string{
  1427  					app.config.ViewsLayout,
  1428  				}
  1429  			}
  1430  
  1431  			// Render template from Views
  1432  			if app.config.Views != nil {
  1433  				if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil {
  1434  					return fmt.Errorf("failed to render: %w", err)
  1435  				}
  1436  
  1437  				rendered = true
  1438  				break
  1439  			}
  1440  		}
  1441  	}
  1442  
  1443  	if !rendered {
  1444  		// Render raw template using 'name' as filepath if no engine is set
  1445  		var tmpl *template.Template
  1446  		if _, err := readContent(buf, name); err != nil {
  1447  			return err
  1448  		}
  1449  		// Parse template
  1450  		tmpl, err := template.New("").Parse(c.app.getString(buf.Bytes()))
  1451  		if err != nil {
  1452  			return fmt.Errorf("failed to parse: %w", err)
  1453  		}
  1454  		buf.Reset()
  1455  		// Render template
  1456  		if err := tmpl.Execute(buf, bind); err != nil {
  1457  			return fmt.Errorf("failed to execute: %w", err)
  1458  		}
  1459  	}
  1460  
  1461  	// Set Content-Type to text/html
  1462  	c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8)
  1463  	// Set rendered template to body
  1464  	c.fasthttp.Response.SetBody(buf.Bytes())
  1465  
  1466  	return nil
  1467  }
  1468  
  1469  func (c *Ctx) renderExtensions(bind interface{}) {
  1470  	if bindMap, ok := bind.(Map); ok {
  1471  		// Bind view map
  1472  		if c.viewBindMap != nil {
  1473  			for _, v := range c.viewBindMap.D {
  1474  				// make sure key does not exist already
  1475  				if _, ok := bindMap[v.Key]; !ok {
  1476  					bindMap[v.Key] = v.Value
  1477  				}
  1478  			}
  1479  		}
  1480  
  1481  		// Check if the PassLocalsToViews option is enabled (by default it is disabled)
  1482  		if c.app.config.PassLocalsToViews {
  1483  			// Loop through each local and set it in the map
  1484  			c.fasthttp.VisitUserValues(func(key []byte, val interface{}) {
  1485  				// check if bindMap doesn't contain the key
  1486  				if _, ok := bindMap[c.app.getString(key)]; !ok {
  1487  					// Set the key and value in the bindMap
  1488  					bindMap[c.app.getString(key)] = val
  1489  				}
  1490  			})
  1491  		}
  1492  	}
  1493  
  1494  	if len(c.app.mountFields.appListKeys) == 0 {
  1495  		c.app.generateAppListKeys()
  1496  	}
  1497  }
  1498  
  1499  // Route returns the matched Route struct.
  1500  func (c *Ctx) Route() *Route {
  1501  	if c.route == nil {
  1502  		// Fallback for fasthttp error handler
  1503  		return &Route{
  1504  			path:     c.pathOriginal,
  1505  			Path:     c.pathOriginal,
  1506  			Method:   c.method,
  1507  			Handlers: make([]Handler, 0),
  1508  			Params:   make([]string, 0),
  1509  		}
  1510  	}
  1511  	return c.route
  1512  }
  1513  
  1514  // SaveFile saves any multipart file to disk.
  1515  func (*Ctx) SaveFile(fileheader *multipart.FileHeader, path string) error {
  1516  	return fasthttp.SaveMultipartFile(fileheader, path)
  1517  }
  1518  
  1519  // SaveFileToStorage saves any multipart file to an external storage system.
  1520  func (*Ctx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error {
  1521  	file, err := fileheader.Open()
  1522  	if err != nil {
  1523  		return fmt.Errorf("failed to open: %w", err)
  1524  	}
  1525  
  1526  	content, err := io.ReadAll(file)
  1527  	if err != nil {
  1528  		return fmt.Errorf("failed to read: %w", err)
  1529  	}
  1530  
  1531  	if err := storage.Set(path, content, 0); err != nil {
  1532  		return fmt.Errorf("failed to store: %w", err)
  1533  	}
  1534  
  1535  	return nil
  1536  }
  1537  
  1538  // Secure returns whether a secure connection was established.
  1539  func (c *Ctx) Secure() bool {
  1540  	return c.Protocol() == schemeHTTPS
  1541  }
  1542  
  1543  // Send sets the HTTP response body without copying it.
  1544  // From this point onward the body argument must not be changed.
  1545  func (c *Ctx) Send(body []byte) error {
  1546  	// Write response body
  1547  	c.fasthttp.Response.SetBodyRaw(body)
  1548  	return nil
  1549  }
  1550  
  1551  var (
  1552  	sendFileOnce    sync.Once
  1553  	sendFileFS      *fasthttp.FS
  1554  	sendFileHandler fasthttp.RequestHandler
  1555  )
  1556  
  1557  // SendFile transfers the file from the given path.
  1558  // The file is not compressed by default, enable this by passing a 'true' argument
  1559  // Sets the Content-Type response HTTP header field based on the filenames extension.
  1560  func (c *Ctx) SendFile(file string, compress ...bool) error {
  1561  	// Save the filename, we will need it in the error message if the file isn't found
  1562  	filename := file
  1563  
  1564  	// https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134
  1565  	sendFileOnce.Do(func() {
  1566  		const cacheDuration = 10 * time.Second
  1567  		sendFileFS = &fasthttp.FS{
  1568  			Root:                 "",
  1569  			AllowEmptyRoot:       true,
  1570  			GenerateIndexPages:   false,
  1571  			AcceptByteRange:      true,
  1572  			Compress:             true,
  1573  			CompressedFileSuffix: c.app.config.CompressedFileSuffix,
  1574  			CacheDuration:        cacheDuration,
  1575  			IndexNames:           []string{"index.html"},
  1576  			PathNotFound: func(ctx *fasthttp.RequestCtx) {
  1577  				ctx.Response.SetStatusCode(StatusNotFound)
  1578  			},
  1579  		}
  1580  		sendFileHandler = sendFileFS.NewRequestHandler()
  1581  	})
  1582  
  1583  	// Keep original path for mutable params
  1584  	c.pathOriginal = utils.CopyString(c.pathOriginal)
  1585  	// Disable compression
  1586  	if len(compress) == 0 || !compress[0] {
  1587  		// https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55
  1588  		c.fasthttp.Request.Header.Del(HeaderAcceptEncoding)
  1589  	}
  1590  	// copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments
  1591  	if len(file) == 0 || !filepath.IsAbs(file) {
  1592  		// extend relative path to absolute path
  1593  		hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\')
  1594  
  1595  		var err error
  1596  		file = filepath.FromSlash(file)
  1597  		if file, err = filepath.Abs(file); err != nil {
  1598  			return fmt.Errorf("failed to determine abs file path: %w", err)
  1599  		}
  1600  		if hasTrailingSlash {
  1601  			file += "/"
  1602  		}
  1603  	}
  1604  	// convert the path to forward slashes regardless the OS in order to set the URI properly
  1605  	// the handler will convert back to OS path separator before opening the file
  1606  	file = filepath.ToSlash(file)
  1607  
  1608  	// Restore the original requested URL
  1609  	originalURL := utils.CopyString(c.OriginalURL())
  1610  	defer c.fasthttp.Request.SetRequestURI(originalURL)
  1611  	// Set new URI for fileHandler
  1612  	c.fasthttp.Request.SetRequestURI(file)
  1613  	// Save status code
  1614  	status := c.fasthttp.Response.StatusCode()
  1615  	// Serve file
  1616  	sendFileHandler(c.fasthttp)
  1617  	// Get the status code which is set by fasthttp
  1618  	fsStatus := c.fasthttp.Response.StatusCode()
  1619  	// Set the status code set by the user if it is different from the fasthttp status code and 200
  1620  	if status != fsStatus && status != StatusOK {
  1621  		c.Status(status)
  1622  	}
  1623  	// Check for error
  1624  	if status != StatusNotFound && fsStatus == StatusNotFound {
  1625  		return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename))
  1626  	}
  1627  	return nil
  1628  }
  1629  
  1630  // SendStatus sets the HTTP status code and if the response body is empty,
  1631  // it sets the correct status message in the body.
  1632  func (c *Ctx) SendStatus(status int) error {
  1633  	c.Status(status)
  1634  
  1635  	// Only set status body when there is no response body
  1636  	if len(c.fasthttp.Response.Body()) == 0 {
  1637  		return c.SendString(utils.StatusMessage(status))
  1638  	}
  1639  
  1640  	return nil
  1641  }
  1642  
  1643  // SendString sets the HTTP response body for string types.
  1644  // This means no type assertion, recommended for faster performance
  1645  func (c *Ctx) SendString(body string) error {
  1646  	c.fasthttp.Response.SetBodyString(body)
  1647  
  1648  	return nil
  1649  }
  1650  
  1651  // SendStream sets response body stream and optional body size.
  1652  func (c *Ctx) SendStream(stream io.Reader, size ...int) error {
  1653  	if len(size) > 0 && size[0] >= 0 {
  1654  		c.fasthttp.Response.SetBodyStream(stream, size[0])
  1655  	} else {
  1656  		c.fasthttp.Response.SetBodyStream(stream, -1)
  1657  	}
  1658  
  1659  	return nil
  1660  }
  1661  
  1662  // Set sets the response's HTTP header field to the specified key, value.
  1663  func (c *Ctx) Set(key, val string) {
  1664  	c.fasthttp.Response.Header.Set(key, val)
  1665  }
  1666  
  1667  func (c *Ctx) setCanonical(key, val string) {
  1668  	c.fasthttp.Response.Header.SetCanonical(c.app.getBytes(key), c.app.getBytes(val))
  1669  }
  1670  
  1671  // Subdomains returns a string slice of subdomains in the domain name of the request.
  1672  // The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments.
  1673  func (c *Ctx) Subdomains(offset ...int) []string {
  1674  	o := 2
  1675  	if len(offset) > 0 {
  1676  		o = offset[0]
  1677  	}
  1678  	subdomains := strings.Split(c.Hostname(), ".")
  1679  	l := len(subdomains) - o
  1680  	// Check index to avoid slice bounds out of range panic
  1681  	if l < 0 {
  1682  		l = len(subdomains)
  1683  	}
  1684  	subdomains = subdomains[:l]
  1685  	return subdomains
  1686  }
  1687  
  1688  // Stale is not implemented yet, pull requests are welcome!
  1689  func (c *Ctx) Stale() bool {
  1690  	return !c.Fresh()
  1691  }
  1692  
  1693  // Status sets the HTTP status for the response.
  1694  // This method is chainable.
  1695  func (c *Ctx) Status(status int) *Ctx {
  1696  	c.fasthttp.Response.SetStatusCode(status)
  1697  	return c
  1698  }
  1699  
  1700  // String returns unique string representation of the ctx.
  1701  //
  1702  // The returned value may be useful for logging.
  1703  func (c *Ctx) String() string {
  1704  	return fmt.Sprintf(
  1705  		"#%016X - %s <-> %s - %s %s",
  1706  		c.fasthttp.ID(),
  1707  		c.fasthttp.LocalAddr(),
  1708  		c.fasthttp.RemoteAddr(),
  1709  		c.fasthttp.Request.Header.Method(),
  1710  		c.fasthttp.URI().FullURI(),
  1711  	)
  1712  }
  1713  
  1714  // Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
  1715  func (c *Ctx) Type(extension string, charset ...string) *Ctx {
  1716  	if len(charset) > 0 {
  1717  		c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0])
  1718  	} else {
  1719  		c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension))
  1720  	}
  1721  	return c
  1722  }
  1723  
  1724  // Vary adds the given header field to the Vary response header.
  1725  // This will append the header, if not already listed, otherwise leaves it listed in the current location.
  1726  func (c *Ctx) Vary(fields ...string) {
  1727  	c.Append(HeaderVary, fields...)
  1728  }
  1729  
  1730  // Write appends p into response body.
  1731  func (c *Ctx) Write(p []byte) (int, error) {
  1732  	c.fasthttp.Response.AppendBody(p)
  1733  	return len(p), nil
  1734  }
  1735  
  1736  // Writef appends f & a into response body writer.
  1737  func (c *Ctx) Writef(f string, a ...interface{}) (int, error) {
  1738  	//nolint:wrapcheck // This must not be wrapped
  1739  	return fmt.Fprintf(c.fasthttp.Response.BodyWriter(), f, a...)
  1740  }
  1741  
  1742  // WriteString appends s to response body.
  1743  func (c *Ctx) WriteString(s string) (int, error) {
  1744  	c.fasthttp.Response.AppendBodyString(s)
  1745  	return len(s), nil
  1746  }
  1747  
  1748  // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest,
  1749  // indicating that the request was issued by a client library (such as jQuery).
  1750  func (c *Ctx) XHR() bool {
  1751  	return utils.EqualFoldBytes(c.app.getBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest"))
  1752  }
  1753  
  1754  // configDependentPaths set paths for route recognition and prepared paths for the user,
  1755  // here the features for caseSensitive, decoded paths, strict paths are evaluated
  1756  func (c *Ctx) configDependentPaths() {
  1757  	c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...)
  1758  	// If UnescapePath enabled, we decode the path and save it for the framework user
  1759  	if c.app.config.UnescapePath {
  1760  		c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer)
  1761  	}
  1762  	c.path = c.app.getString(c.pathBuffer)
  1763  
  1764  	// another path is specified which is for routing recognition only
  1765  	// use the path that was changed by the previous configuration flags
  1766  	c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...)
  1767  	// If CaseSensitive is disabled, we lowercase the original path
  1768  	if !c.app.config.CaseSensitive {
  1769  		c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer)
  1770  	}
  1771  	// If StrictRouting is disabled, we strip all trailing slashes
  1772  	if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
  1773  		c.detectionPathBuffer = utils.TrimRightBytes(c.detectionPathBuffer, '/')
  1774  	}
  1775  	c.detectionPath = c.app.getString(c.detectionPathBuffer)
  1776  
  1777  	// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
  1778  	// since the first three characters area select a list of routes
  1779  	c.treePath = c.treePath[0:0]
  1780  	const maxDetectionPaths = 3
  1781  	if len(c.detectionPath) >= maxDetectionPaths {
  1782  		c.treePath = c.detectionPath[:maxDetectionPaths]
  1783  	}
  1784  }
  1785  
  1786  func (c *Ctx) IsProxyTrusted() bool {
  1787  	if !c.app.config.EnableTrustedProxyCheck {
  1788  		return true
  1789  	}
  1790  
  1791  	ip := c.fasthttp.RemoteIP()
  1792  
  1793  	if _, trusted := c.app.config.trustedProxiesMap[ip.String()]; trusted {
  1794  		return true
  1795  	}
  1796  
  1797  	for _, ipNet := range c.app.config.trustedProxyRanges {
  1798  		if ipNet.Contains(ip) {
  1799  			return true
  1800  		}
  1801  	}
  1802  
  1803  	return false
  1804  }
  1805  
  1806  // IsLocalHost will return true if address is a localhost address.
  1807  func (*Ctx) isLocalHost(address string) bool {
  1808  	localHosts := []string{"127.0.0.1", "0.0.0.0", "::1"}
  1809  	for _, h := range localHosts {
  1810  		if strings.Contains(address, h) {
  1811  			return true
  1812  		}
  1813  	}
  1814  	return false
  1815  }
  1816  
  1817  // IsFromLocal will return true if request came from local.
  1818  func (c *Ctx) IsFromLocal() bool {
  1819  	ips := c.IPs()
  1820  	if len(ips) == 0 {
  1821  		ips = append(ips, c.IP())
  1822  	}
  1823  	return c.isLocalHost(ips[0])
  1824  }