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 }