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 }