res.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  1. package fiber
  2. import (
  3. "bufio"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "io/fs"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. pathpkg "path"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "unicode"
  17. "unicode/utf8"
  18. "github.com/gofiber/utils/v2"
  19. "github.com/valyala/bytebufferpool"
  20. "github.com/valyala/fasthttp"
  21. )
  22. // SendFile defines configuration options when to transfer file with SendFile.
  23. type SendFile struct {
  24. // FS is the file system to serve the static files from.
  25. // You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc.
  26. //
  27. // Optional. Default: nil
  28. FS fs.FS
  29. // When set to true, the server tries minimizing CPU usage by caching compressed files.
  30. // This works differently than the github.com/gofiber/compression middleware.
  31. // You have to set Content-Encoding header to compress the file.
  32. // Available compression methods are gzip, br, and zstd.
  33. //
  34. // Optional. Default: false
  35. Compress bool `json:"compress"`
  36. // When set to true, enables byte range requests.
  37. //
  38. // Optional. Default: false
  39. ByteRange bool `json:"byte_range"`
  40. // When set to true, enables direct download.
  41. //
  42. // Optional. Default: false
  43. Download bool `json:"download"`
  44. // Expiration duration for inactive file handlers.
  45. // Use a negative time.Duration to disable it.
  46. //
  47. // Optional. Default: 10 * time.Second
  48. CacheDuration time.Duration `json:"cache_duration"`
  49. // The value for the Cache-Control HTTP-header
  50. // that is set on the file response. MaxAge is defined in seconds.
  51. //
  52. // Optional. Default: 0
  53. MaxAge int `json:"max_age"`
  54. }
  55. // sendFileStore is used to keep the SendFile configuration and the handler.
  56. type sendFileStore struct {
  57. handler fasthttp.RequestHandler
  58. cacheControlValue string
  59. config SendFile
  60. }
  61. // configEqual compares the current SendFile config with the new one
  62. // and returns true if they are equal.
  63. //
  64. // Here we don't use reflect.DeepEqual because it is quite slow compared to manual comparison.
  65. func (sf *sendFileStore) configEqual(cfg SendFile) bool {
  66. if sf.config.FS != cfg.FS {
  67. return false
  68. }
  69. if sf.config.Compress != cfg.Compress {
  70. return false
  71. }
  72. if sf.config.ByteRange != cfg.ByteRange {
  73. return false
  74. }
  75. if sf.config.Download != cfg.Download {
  76. return false
  77. }
  78. if sf.config.CacheDuration != cfg.CacheDuration {
  79. return false
  80. }
  81. if sf.config.MaxAge != cfg.MaxAge {
  82. return false
  83. }
  84. return true
  85. }
  86. // Cookie defines the values used when configuring cookies emitted by
  87. // DefaultRes.Cookie.
  88. type Cookie struct {
  89. Expires time.Time `json:"expires"` // The expiration date of the cookie
  90. Name string `json:"name"` // The name of the cookie
  91. Value string `json:"value"` // The value of the cookie
  92. Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie
  93. Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie
  94. SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
  95. MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie
  96. Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection
  97. HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol
  98. Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar
  99. SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie
  100. }
  101. // ResFmt associates a Content Type to a fiber.Handler for c.Format
  102. type ResFmt struct {
  103. Handler func(Ctx) error
  104. MediaType string
  105. }
  106. // DefaultRes is the default implementation of Res used by DefaultCtx.
  107. //
  108. //go:generate ifacemaker --file res.go --struct DefaultRes --iface Res --pkg fiber --output res_interface_gen.go --not-exported true --iface-comment "Res is an interface for response-related Ctx methods."
  109. type DefaultRes struct {
  110. c *DefaultCtx
  111. }
  112. // App returns the *App reference to the instance of the Fiber application
  113. func (r *DefaultRes) App() *App {
  114. return r.c.app
  115. }
  116. // Append the specified value to the HTTP response header field.
  117. // If the header is not already set, it creates the header with the specified value.
  118. func (r *DefaultRes) Append(field string, values ...string) {
  119. if len(values) == 0 {
  120. return
  121. }
  122. h := r.c.app.toString(r.c.fasthttp.Response.Header.Peek(field))
  123. originalH := h
  124. for _, value := range values {
  125. if h == "" {
  126. h = value
  127. } else if !headerContainsValue(h, value) {
  128. h += ", " + value
  129. }
  130. }
  131. if originalH != h {
  132. r.Set(field, h)
  133. }
  134. }
  135. // headerContainsValue checks if a header value already contains the given value
  136. // as a comma-separated element. Per RFC 9110, list elements are separated by commas
  137. // with optional whitespace (OWS) around them.
  138. func headerContainsValue(header, value string) bool {
  139. // Empty value should never match
  140. if value == "" {
  141. return false
  142. }
  143. // Exact match (single value header)
  144. if header == value {
  145. return true
  146. }
  147. // Check each comma-separated element, handling optional whitespace (OWS)
  148. for part := range strings.SplitSeq(header, ",") {
  149. if utils.TrimSpace(part) == value {
  150. return true
  151. }
  152. }
  153. return false
  154. }
  155. func sanitizeFilename(filename string) string {
  156. for _, r := range filename {
  157. if unicode.IsControl(r) {
  158. b := make([]byte, 0, len(filename))
  159. for _, rr := range filename {
  160. if !unicode.IsControl(rr) {
  161. b = utf8.AppendRune(b, rr)
  162. }
  163. }
  164. return utils.TrimSpace(string(b))
  165. }
  166. }
  167. return utils.TrimSpace(filename)
  168. }
  169. func fallbackFilenameIfInvalid(filename string) string {
  170. if filename == "" || filename == "." {
  171. return "download"
  172. }
  173. return filename
  174. }
  175. // Attachment sets the HTTP response Content-Disposition header field to attachment.
  176. func (r *DefaultRes) Attachment(filename ...string) {
  177. if len(filename) > 0 {
  178. fname := filepath.Base(filename[0])
  179. fname = sanitizeFilename(fname)
  180. fname = fallbackFilenameIfInvalid(fname)
  181. r.Type(filepath.Ext(fname))
  182. app := r.c.app
  183. var quoted string
  184. if app.isASCII(fname) {
  185. quoted = app.quoteString(fname)
  186. } else {
  187. quoted = app.quoteRawString(fname)
  188. }
  189. disp := `attachment; filename="` + quoted + `"`
  190. if !app.isASCII(fname) {
  191. disp += `; filename*=UTF-8''` + url.PathEscape(fname)
  192. }
  193. r.setCanonical(HeaderContentDisposition, disp)
  194. return
  195. }
  196. r.setCanonical(HeaderContentDisposition, "attachment")
  197. }
  198. // ClearCookie expires a specific cookie by key on the client side.
  199. // If no key is provided it expires all cookies that came with the request.
  200. func (r *DefaultRes) ClearCookie(key ...string) {
  201. request := &r.c.fasthttp.Request
  202. response := &r.c.fasthttp.Response
  203. if len(key) > 0 {
  204. for i := range key {
  205. response.Header.DelClientCookie(key[i])
  206. }
  207. return
  208. }
  209. for k := range request.Header.Cookies() {
  210. response.Header.DelClientCookieBytes(k)
  211. }
  212. }
  213. // RequestCtx returns *fasthttp.RequestCtx that carries a deadline
  214. // a cancellation signal, and other values across API boundaries.
  215. func (r *DefaultRes) RequestCtx() *fasthttp.RequestCtx {
  216. return r.c.fasthttp
  217. }
  218. // Cookie sets a cookie by passing a cookie struct.
  219. func (r *DefaultRes) Cookie(cookie *Cookie) {
  220. if cookie.Path == "" {
  221. cookie.Path = "/"
  222. }
  223. if cookie.SessionOnly {
  224. cookie.MaxAge = 0
  225. cookie.Expires = time.Time{}
  226. }
  227. var sameSite http.SameSite
  228. switch {
  229. case utils.EqualFold(cookie.SameSite, CookieSameSiteStrictMode):
  230. sameSite = http.SameSiteStrictMode
  231. case utils.EqualFold(cookie.SameSite, CookieSameSiteNoneMode):
  232. sameSite = http.SameSiteNoneMode
  233. // SameSite=None requires Secure=true per RFC and browser requirements
  234. cookie.Secure = true
  235. case utils.EqualFold(cookie.SameSite, CookieSameSiteDisabled):
  236. sameSite = 0
  237. case utils.EqualFold(cookie.SameSite, CookieSameSiteLaxMode):
  238. sameSite = http.SameSiteLaxMode
  239. default:
  240. sameSite = http.SameSiteLaxMode
  241. }
  242. // Partitioned requires Secure=true per CHIPS spec
  243. if cookie.Partitioned {
  244. cookie.Secure = true
  245. }
  246. // create/validate cookie using net/http
  247. hc := &http.Cookie{
  248. Name: cookie.Name,
  249. Value: cookie.Value,
  250. Path: cookie.Path,
  251. Domain: cookie.Domain,
  252. Expires: cookie.Expires,
  253. MaxAge: cookie.MaxAge,
  254. Secure: cookie.Secure,
  255. HttpOnly: cookie.HTTPOnly,
  256. SameSite: sameSite,
  257. Partitioned: cookie.Partitioned,
  258. }
  259. if err := hc.Valid(); err != nil {
  260. // invalid cookies are ignored, same approach as net/http
  261. return
  262. }
  263. // create fasthttp cookie
  264. fcookie := fasthttp.AcquireCookie()
  265. fcookie.SetKey(hc.Name)
  266. fcookie.SetValue(hc.Value)
  267. fcookie.SetPath(hc.Path)
  268. fcookie.SetDomain(hc.Domain)
  269. if !cookie.SessionOnly {
  270. fcookie.SetMaxAge(hc.MaxAge)
  271. fcookie.SetExpire(hc.Expires)
  272. }
  273. fcookie.SetSecure(hc.Secure)
  274. fcookie.SetHTTPOnly(hc.HttpOnly)
  275. switch sameSite {
  276. case http.SameSiteLaxMode:
  277. fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
  278. case http.SameSiteStrictMode:
  279. fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
  280. case http.SameSiteNoneMode:
  281. fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
  282. case http.SameSiteDefaultMode:
  283. fcookie.SetSameSite(fasthttp.CookieSameSiteDefaultMode)
  284. default:
  285. fcookie.SetSameSite(fasthttp.CookieSameSiteDisabled)
  286. }
  287. fcookie.SetPartitioned(hc.Partitioned)
  288. // Set resp header
  289. r.c.fasthttp.Response.Header.SetCookie(fcookie)
  290. fasthttp.ReleaseCookie(fcookie)
  291. }
  292. // Download transfers the file from path as an attachment.
  293. // Typically, browsers will prompt the user for download.
  294. // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
  295. // Override this default with the filename parameter.
  296. func (r *DefaultRes) Download(file string, filename ...string) error {
  297. var fname string
  298. if len(filename) > 0 {
  299. fname = filepath.Base(filename[0])
  300. } else {
  301. fname = filepath.Base(file)
  302. }
  303. fname = sanitizeFilename(fname)
  304. fname = fallbackFilenameIfInvalid(fname)
  305. app := r.c.app
  306. var quoted string
  307. if app.isASCII(fname) {
  308. quoted = app.quoteString(fname)
  309. } else {
  310. quoted = app.quoteRawString(fname)
  311. }
  312. disp := `attachment; filename="` + quoted + `"`
  313. if !app.isASCII(fname) {
  314. disp += `; filename*=UTF-8''` + url.PathEscape(fname)
  315. }
  316. r.setCanonical(HeaderContentDisposition, disp)
  317. return r.SendFile(file)
  318. }
  319. // Response return the *fasthttp.Response object
  320. // This allows you to use all fasthttp response methods
  321. // https://godoc.org/github.com/valyala/fasthttp#Response
  322. func (r *DefaultRes) Response() *fasthttp.Response {
  323. return &r.c.fasthttp.Response
  324. }
  325. // Format performs content-negotiation on the Accept HTTP header.
  326. // It uses Accepts to select a proper format and calls the matching
  327. // user-provided handler function.
  328. // If no accepted format is found, and a format with MediaType "default" is given,
  329. // that default handler is called. If no format is found and no default is given,
  330. // StatusNotAcceptable is sent.
  331. func (r *DefaultRes) Format(handlers ...ResFmt) error {
  332. if len(handlers) == 0 {
  333. return ErrNoHandlers
  334. }
  335. for i, h := range handlers {
  336. if h.Handler == nil {
  337. return fmt.Errorf("format handler is nil for media type %q at index %d", h.MediaType, i)
  338. }
  339. }
  340. r.Vary(HeaderAccept)
  341. if r.c.DefaultReq.Get(HeaderAccept) == "" {
  342. r.c.fasthttp.Response.Header.SetContentType(handlers[0].MediaType)
  343. return handlers[0].Handler(r.c)
  344. }
  345. // Using an int literal as the slice capacity allows for the slice to be
  346. // allocated on the stack. The number was chosen arbitrarily as an
  347. // approximation of the maximum number of content types a user might handle.
  348. // If the user goes over, it just causes allocations, so it's not a problem.
  349. types := make([]string, 0, 8)
  350. var defaultHandler Handler
  351. for _, h := range handlers {
  352. if h.MediaType == "default" {
  353. defaultHandler = h.Handler
  354. continue
  355. }
  356. types = append(types, h.MediaType)
  357. }
  358. accept := r.c.DefaultReq.Accepts(types...) //nolint:staticcheck // It is fine to ignore the static check
  359. if accept == "" {
  360. if defaultHandler == nil {
  361. return r.SendStatus(StatusNotAcceptable)
  362. }
  363. return defaultHandler(r.c)
  364. }
  365. for _, h := range handlers {
  366. if h.MediaType == accept {
  367. r.c.fasthttp.Response.Header.SetContentType(h.MediaType)
  368. return h.Handler(r.c)
  369. }
  370. }
  371. return fmt.Errorf("%w: format: an Accept was found but no handler was called", errUnreachable)
  372. }
  373. // AutoFormat performs content-negotiation on the Accept HTTP header.
  374. // It uses Accepts to select a proper format.
  375. // The supported content types are text/html, text/plain, application/json, application/xml, application/vnd.msgpack, and application/cbor.
  376. // When text/html is selected, the body is treated as plain text and HTML-escaped before being wrapped in a `<p>` element.
  377. // For more flexible content negotiation, use Format.
  378. // If the header is not specified or there is no proper format, text/plain is used.
  379. func (r *DefaultRes) AutoFormat(body any) error {
  380. // Get accepted content type
  381. accept := r.c.DefaultReq.Accepts("html", "json", "txt", "xml", "msgpack", "cbor") //nolint:staticcheck // It is fine to ignore the static check
  382. // Set accepted content type
  383. r.Type(accept)
  384. // Type convert provided body
  385. var b string
  386. switch val := body.(type) {
  387. case string:
  388. b = val
  389. case []byte:
  390. b = r.c.app.toString(val)
  391. default:
  392. b = fmt.Sprintf("%v", val)
  393. }
  394. // Format based on the accept content type
  395. switch accept {
  396. case "txt":
  397. return r.SendString(b)
  398. case "json":
  399. return r.JSON(body)
  400. case "xml":
  401. return r.XML(body)
  402. case "html":
  403. return r.SendString("<p>" + template.HTMLEscapeString(b) + "</p>")
  404. case "msgpack":
  405. return r.MsgPack(body)
  406. case "cbor":
  407. return r.CBOR(body)
  408. }
  409. // Default case
  410. return r.SendString(b)
  411. }
  412. // Get (a.k.a. GetRespHeader) returns the HTTP response header specified by field.
  413. // Field names are case-insensitive
  414. // Returned value is only valid within the handler. Do not store any references.
  415. // Make copies or use the Immutable setting instead.
  416. func (r *DefaultRes) Get(key string, defaultValue ...string) string {
  417. return defaultString(r.c.app.toString(r.c.fasthttp.Response.Header.Peek(key)), defaultValue)
  418. }
  419. // GetHeaders (a.k.a GetRespHeaders) returns the HTTP response headers.
  420. // Returned value is only valid within the handler. Do not store any references.
  421. // Make copies or use the Immutable setting instead.
  422. func (r *DefaultRes) GetHeaders() map[string][]string {
  423. app := r.c.app
  424. respHeader := &r.c.fasthttp.Response.Header
  425. // Pre-allocate map with known header count to avoid reallocations
  426. headers := make(map[string][]string, respHeader.Len())
  427. for k, v := range respHeader.All() {
  428. key := app.toString(k)
  429. headers[key] = append(headers[key], app.toString(v))
  430. }
  431. return headers
  432. }
  433. // JSON converts any interface or string to JSON.
  434. // Array and slice values encode as JSON arrays,
  435. // except that []byte encodes as a base64-encoded string,
  436. // and a nil slice encodes as the null JSON value.
  437. // If the ctype parameter is given, this method will set the
  438. // Content-Type header equal to ctype. If ctype is not given,
  439. // The Content-Type header will be set to application/json; charset=utf-8.
  440. func (r *DefaultRes) JSON(data any, ctype ...string) error {
  441. raw, err := r.c.app.config.JSONEncoder(data)
  442. if err != nil {
  443. return err
  444. }
  445. response := &r.c.fasthttp.Response
  446. response.SetBodyRaw(raw)
  447. if len(ctype) > 0 {
  448. response.Header.SetContentType(ctype[0])
  449. } else {
  450. response.Header.SetContentType(MIMEApplicationJSONCharsetUTF8)
  451. }
  452. return nil
  453. }
  454. // MsgPack converts any interface or string to MessagePack encoded bytes.
  455. // If the ctype parameter is given, this method will set the
  456. // Content-Type header equal to ctype. If ctype is not given,
  457. // The Content-Type header will be set to application/vnd.msgpack.
  458. func (r *DefaultRes) MsgPack(data any, ctype ...string) error {
  459. raw, err := r.c.app.config.MsgPackEncoder(data)
  460. if err != nil {
  461. return err
  462. }
  463. response := &r.c.fasthttp.Response
  464. response.SetBodyRaw(raw)
  465. if len(ctype) > 0 {
  466. response.Header.SetContentType(ctype[0])
  467. } else {
  468. response.Header.SetContentType(MIMEApplicationMsgPack)
  469. }
  470. return nil
  471. }
  472. // CBOR converts any interface or string to CBOR encoded bytes.
  473. // If the ctype parameter is given, this method will set the
  474. // Content-Type header equal to ctype. If ctype is not given,
  475. // The Content-Type header will be set to application/cbor.
  476. func (r *DefaultRes) CBOR(data any, ctype ...string) error {
  477. raw, err := r.c.app.config.CBOREncoder(data)
  478. if err != nil {
  479. return err
  480. }
  481. response := &r.c.fasthttp.Response
  482. response.SetBodyRaw(raw)
  483. if len(ctype) > 0 {
  484. response.Header.SetContentType(ctype[0])
  485. } else {
  486. response.Header.SetContentType(MIMEApplicationCBOR)
  487. }
  488. return nil
  489. }
  490. // JSONP sends a JSON response with JSONP support.
  491. // This method is identical to JSON, except that it opts-in to JSONP callback support.
  492. // By default, the callback name is simply callback.
  493. func (r *DefaultRes) JSONP(data any, callback ...string) error {
  494. raw, err := r.c.app.config.JSONEncoder(data)
  495. if err != nil {
  496. return err
  497. }
  498. cb := "callback"
  499. if len(callback) > 0 {
  500. cb = callback[0]
  501. }
  502. // Build JSONP response: callback(data);
  503. // Use bytebufferpool to avoid string concatenation allocations
  504. buf := bytebufferpool.Get()
  505. buf.WriteString(cb)
  506. buf.WriteByte('(')
  507. buf.Write(raw)
  508. buf.WriteString(");")
  509. r.setCanonical(HeaderXContentTypeOptions, "nosniff")
  510. r.c.fasthttp.Response.Header.SetContentType(MIMETextJavaScriptCharsetUTF8)
  511. // Use SetBody (not SetBodyRaw) to copy the bytes before returning buffer to pool
  512. r.c.fasthttp.Response.SetBody(buf.Bytes())
  513. bytebufferpool.Put(buf)
  514. return nil
  515. }
  516. // XML converts any interface or string to XML.
  517. // This method also sets the content header to application/xml; charset=utf-8.
  518. func (r *DefaultRes) XML(data any) error {
  519. raw, err := r.c.app.config.XMLEncoder(data)
  520. if err != nil {
  521. return err
  522. }
  523. response := &r.c.fasthttp.Response
  524. response.SetBodyRaw(raw)
  525. response.Header.SetContentType(MIMEApplicationXMLCharsetUTF8)
  526. return nil
  527. }
  528. // Links joins the links followed by the property to populate the response's Link HTTP header field.
  529. func (r *DefaultRes) Links(link ...string) {
  530. if len(link) == 0 {
  531. return
  532. }
  533. bb := bytebufferpool.Get()
  534. for i := range link {
  535. if i%2 == 0 {
  536. bb.WriteByte('<')
  537. bb.WriteString(link[i])
  538. bb.WriteByte('>')
  539. } else {
  540. bb.WriteString(`; rel="`)
  541. bb.WriteString(link[i])
  542. bb.WriteString(`",`)
  543. }
  544. }
  545. r.setCanonical(HeaderLink, utils.TrimRight(r.c.app.toString(bb.Bytes()), ','))
  546. bytebufferpool.Put(bb)
  547. }
  548. // Location sets the response Location HTTP header to the specified path parameter.
  549. func (r *DefaultRes) Location(path string) {
  550. r.setCanonical(HeaderLocation, path)
  551. }
  552. // OriginalURL contains the original request URL.
  553. // Returned value is only valid within the handler. Do not store any references.
  554. // Make copies or use the Immutable setting to use the value outside the Handler.
  555. func (r *DefaultRes) OriginalURL() string {
  556. return r.c.OriginalURL()
  557. }
  558. // Redirect returns the Redirect reference.
  559. // Use Redirect().Status() to set custom redirection status code.
  560. // If status is not specified, status defaults to 303 See Other.
  561. // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection.
  562. func (r *DefaultRes) Redirect() *Redirect {
  563. return r.c.Redirect()
  564. }
  565. // ViewBind Add vars to default view var map binding to template engine.
  566. // Variables are read by the Render method and may be overwritten.
  567. func (r *DefaultRes) ViewBind(vars Map) error {
  568. return r.c.ViewBind(vars)
  569. }
  570. // getLocationFromRoute gets the URL location from a route using parameters.
  571. // Nil receivers and missing routes return ErrNotFound to match Route.URL semantics.
  572. func (r *DefaultRes) getLocationFromRoute(route *Route, params Map) (string, error) {
  573. if r == nil || route == nil || route.Path == "" {
  574. return "", ErrNotFound
  575. }
  576. return buildRouteURL(route, params)
  577. }
  578. // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831"
  579. func (r *DefaultRes) GetRouteURL(routeName string, params Map) (string, error) {
  580. route := r.c.app.GetRoute(routeName)
  581. return r.getLocationFromRoute(&route, params)
  582. }
  583. // Render a template with data and sends a text/html response.
  584. // We support the following engines: https://github.com/gofiber/template
  585. func (r *DefaultRes) Render(name string, bind any, layouts ...string) error {
  586. // Get new buffer from pool
  587. buf := bytebufferpool.Get()
  588. defer bytebufferpool.Put(buf)
  589. // Initialize empty bind map if bind is nil
  590. if bind == nil {
  591. bind = make(Map)
  592. }
  593. // Pass-locals-to-views, bind, appListKeys
  594. r.c.renderExtensions(bind)
  595. rootApp := r.c.app
  596. var rendered bool
  597. for i := len(rootApp.mountFields.appListKeys) - 1; i >= 0; i-- {
  598. prefix := rootApp.mountFields.appListKeys[i]
  599. app := rootApp.mountFields.appList[prefix]
  600. if prefix == "" || strings.Contains(r.c.OriginalURL(), prefix) {
  601. if len(layouts) == 0 && app.config.ViewsLayout != "" {
  602. layouts = []string{
  603. app.config.ViewsLayout,
  604. }
  605. }
  606. // Render template from Views
  607. if app.config.Views != nil {
  608. if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil {
  609. return fmt.Errorf("failed to render: %w", err)
  610. }
  611. rendered = true
  612. break
  613. }
  614. }
  615. }
  616. if !rendered {
  617. // Render raw template using 'name' as filepath if no engine is set
  618. var tmpl *template.Template
  619. if _, err := readContent(buf, name); err != nil {
  620. return err
  621. }
  622. // Parse template
  623. tmpl, err := template.New("").Parse(rootApp.toString(buf.Bytes()))
  624. if err != nil {
  625. return fmt.Errorf("failed to parse: %w", err)
  626. }
  627. buf.Reset()
  628. // Render template
  629. if err := tmpl.Execute(buf, bind); err != nil {
  630. return fmt.Errorf("failed to execute: %w", err)
  631. }
  632. }
  633. response := &r.c.fasthttp.Response
  634. // Set Content-Type to text/html
  635. response.Header.SetContentType(MIMETextHTMLCharsetUTF8)
  636. // Set rendered template to body
  637. response.SetBody(buf.Bytes())
  638. return nil
  639. }
  640. func (r *DefaultRes) renderExtensions(bind any) {
  641. r.c.renderExtensions(bind)
  642. }
  643. // Send sets the HTTP response body without copying it.
  644. // From this point onward the body argument must not be changed.
  645. func (r *DefaultRes) Send(body []byte) error {
  646. // Write response body
  647. r.c.fasthttp.Response.SetBodyRaw(body)
  648. return nil
  649. }
  650. // SendEarlyHints allows the server to hint to the browser what resources a page would need
  651. // so the browser can preload them while waiting for the server's full response. Only Link
  652. // headers already written to the response will be transmitted as Early Hints.
  653. //
  654. // This is a HTTP/2+ feature but all browsers will either understand it or safely ignore it.
  655. //
  656. // NOTE: Older HTTP/1.1 non-browser clients may face compatibility issues.
  657. //
  658. // See: https://developer.chrome.com/docs/web-platform/early-hints and
  659. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link#syntax
  660. func (r *DefaultRes) SendEarlyHints(hints []string) error {
  661. if len(hints) == 0 {
  662. return nil
  663. }
  664. for _, h := range hints {
  665. r.c.fasthttp.Response.Header.Add("Link", h)
  666. }
  667. return r.c.fasthttp.EarlyHints()
  668. }
  669. // SendFile transfers the file from the specified path.
  670. // By default, the file is not compressed. To enable compression, set SendFile.Compress to true.
  671. // The Content-Type response HTTP header field is set based on the file's extension.
  672. // If the file extension is missing or invalid, the Content-Type is detected from the file's format.
  673. func (r *DefaultRes) SendFile(file string, config ...SendFile) error {
  674. // Save the filename, we will need it in the error message if the file isn't found
  675. filename := file
  676. var cfg SendFile
  677. if len(config) > 0 {
  678. cfg = config[0]
  679. }
  680. if cfg.CacheDuration == 0 {
  681. cfg.CacheDuration = 10 * time.Second
  682. }
  683. var fsHandler fasthttp.RequestHandler
  684. var cacheControlValue string
  685. app := r.c.app
  686. app.sendfilesMutex.RLock()
  687. for _, sf := range app.sendfiles {
  688. if sf.configEqual(cfg) {
  689. fsHandler = sf.handler
  690. cacheControlValue = sf.cacheControlValue
  691. break
  692. }
  693. }
  694. app.sendfilesMutex.RUnlock()
  695. if fsHandler == nil {
  696. fasthttpFS := &fasthttp.FS{
  697. Root: "",
  698. FS: cfg.FS,
  699. AllowEmptyRoot: true,
  700. GenerateIndexPages: false,
  701. AcceptByteRange: cfg.ByteRange,
  702. Compress: cfg.Compress,
  703. CompressBrotli: cfg.Compress,
  704. CompressZstd: cfg.Compress,
  705. CompressedFileSuffixes: app.config.CompressedFileSuffixes,
  706. CacheDuration: cfg.CacheDuration,
  707. SkipCache: cfg.CacheDuration < 0,
  708. IndexNames: []string{"index.html"},
  709. PathNotFound: func(ctx *fasthttp.RequestCtx) {
  710. ctx.Response.SetStatusCode(StatusNotFound)
  711. },
  712. }
  713. sf := &sendFileStore{
  714. config: cfg,
  715. handler: fasthttpFS.NewRequestHandler(),
  716. }
  717. maxAge := cfg.MaxAge
  718. if maxAge > 0 {
  719. sf.cacheControlValue = "public, max-age=" + strconv.Itoa(maxAge)
  720. }
  721. // set vars
  722. fsHandler = sf.handler
  723. cacheControlValue = sf.cacheControlValue
  724. app.sendfilesMutex.Lock()
  725. app.sendfiles = append(app.sendfiles, sf)
  726. app.sendfilesMutex.Unlock()
  727. }
  728. // Keep original path for mutable params
  729. r.c.pathOriginal = utils.CopyString(r.c.pathOriginal)
  730. request := &r.c.fasthttp.Request
  731. // Delete the Accept-Encoding header if compression is disabled
  732. if !cfg.Compress {
  733. // https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55
  734. request.Header.Del(HeaderAcceptEncoding)
  735. }
  736. // copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments
  737. if file == "" || (!filepath.IsAbs(file) && cfg.FS == nil) {
  738. // extend relative path to absolute path
  739. hasTrailingSlash := file != "" && (file[len(file)-1] == '/' || file[len(file)-1] == '\\')
  740. var err error
  741. file = filepath.FromSlash(file)
  742. if file, err = filepath.Abs(file); err != nil {
  743. return fmt.Errorf("failed to determine abs file path: %w", err)
  744. }
  745. if hasTrailingSlash {
  746. file += "/"
  747. }
  748. }
  749. // convert the path to forward slashes regardless the OS in order to set the URI properly
  750. // the handler will convert back to OS path separator before opening the file
  751. file = filepath.ToSlash(file)
  752. // Restore the original requested URL
  753. originalURL := utils.CopyString(r.c.OriginalURL())
  754. defer request.SetRequestURI(originalURL)
  755. // Set new URI for fileHandler
  756. request.SetRequestURI(file)
  757. var (
  758. sendFileSize int64
  759. hasSendFileSize bool
  760. )
  761. if cfg.ByteRange && len(request.Header.Peek(HeaderRange)) > 0 {
  762. sizePath := file
  763. if cfg.FS != nil {
  764. sizePath = filepath.ToSlash(filename)
  765. }
  766. if size, err := sendFileContentLength(sizePath, cfg); err == nil {
  767. sendFileSize = size
  768. hasSendFileSize = true
  769. }
  770. }
  771. // Save status code
  772. response := &r.c.fasthttp.Response
  773. status := response.StatusCode()
  774. // Serve file
  775. fsHandler(r.c.fasthttp)
  776. // Sets the response Content-Disposition header to attachment if the Download option is true
  777. if cfg.Download {
  778. r.Attachment()
  779. }
  780. // Get the status code which is set by fasthttp
  781. fsStatus := response.StatusCode()
  782. // Check for error
  783. if status != StatusNotFound && fsStatus == StatusNotFound {
  784. return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename))
  785. }
  786. // Set the status code set by the user if it is different from the fasthttp status code and 200
  787. if status != fsStatus && status != StatusOK {
  788. r.Status(status)
  789. }
  790. // Apply cache control header
  791. if status != StatusNotFound && status != StatusForbidden {
  792. if cfg.ByteRange && hasSendFileSize && response.StatusCode() == StatusRequestedRangeNotSatisfiable && len(response.Header.Peek(HeaderContentRange)) == 0 {
  793. response.Header.Set(HeaderContentRange, "bytes */"+strconv.FormatInt(sendFileSize, 10))
  794. }
  795. if cacheControlValue != "" {
  796. response.Header.Set(HeaderCacheControl, cacheControlValue)
  797. }
  798. return nil
  799. }
  800. return nil
  801. }
  802. func sendFileContentLength(path string, cfg SendFile) (int64, error) {
  803. if cfg.FS != nil {
  804. cleanPath := pathpkg.Clean(utils.TrimLeft(path, '/'))
  805. if cleanPath == "." {
  806. cleanPath = ""
  807. }
  808. info, err := fs.Stat(cfg.FS, cleanPath)
  809. if err != nil {
  810. return 0, fmt.Errorf("stat %q: %w", cleanPath, err)
  811. }
  812. return info.Size(), nil
  813. }
  814. info, err := os.Stat(filepath.FromSlash(path))
  815. if err != nil {
  816. return 0, fmt.Errorf("stat %q: %w", path, err)
  817. }
  818. return info.Size(), nil
  819. }
  820. // SendStatus sets the HTTP status code and if the response body is empty,
  821. // it sets the correct status message in the body.
  822. func (r *DefaultRes) SendStatus(status int) error {
  823. r.Status(status)
  824. if statusDisallowsBody(status) {
  825. r.c.fasthttp.Response.ResetBody()
  826. return nil
  827. }
  828. // Only set status body when there is no response body
  829. if len(r.c.fasthttp.Response.Body()) == 0 {
  830. return r.SendString(utils.StatusMessage(status))
  831. }
  832. return nil
  833. }
  834. // SendString sets the HTTP response body for string types.
  835. // This means no type assertion, recommended for faster performance
  836. func (r *DefaultRes) SendString(body string) error {
  837. r.c.fasthttp.Response.SetBodyString(body)
  838. return nil
  839. }
  840. // SendStream sets response body stream and optional body size.
  841. func (r *DefaultRes) SendStream(stream io.Reader, size ...int) error {
  842. if len(size) > 0 && size[0] >= 0 {
  843. r.c.fasthttp.Response.SetBodyStream(stream, size[0])
  844. } else {
  845. r.c.fasthttp.Response.SetBodyStream(stream, -1)
  846. }
  847. return nil
  848. }
  849. // SendStreamWriter sets response body stream writer
  850. func (r *DefaultRes) SendStreamWriter(streamWriter func(*bufio.Writer)) error {
  851. r.c.fasthttp.Response.SetBodyStreamWriter(fasthttp.StreamWriter(streamWriter))
  852. return nil
  853. }
  854. // Set sets the response's HTTP header field to the specified key, value.
  855. func (r *DefaultRes) Set(key, val string) {
  856. r.c.fasthttp.Response.Header.Set(key, val)
  857. }
  858. func (r *DefaultRes) setCanonical(key, val string) {
  859. r.c.fasthttp.Response.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val))
  860. }
  861. // Status sets the HTTP status for the response.
  862. // This method is chainable.
  863. func (r *DefaultRes) Status(status int) Ctx {
  864. r.c.fasthttp.Response.SetStatusCode(status)
  865. return r.c
  866. }
  867. func statusDisallowsBody(status int) bool {
  868. // As per RFC 9110, 1xx (Informational) responses cannot have a body.
  869. if status >= 100 && status < 200 {
  870. return true
  871. }
  872. switch status {
  873. case StatusNoContent, StatusResetContent, StatusNotModified:
  874. return true
  875. default:
  876. return false
  877. }
  878. }
  879. // Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
  880. func (r *DefaultRes) Type(extension string, charset ...string) Ctx {
  881. mimeType := utils.GetMIME(extension)
  882. if len(charset) > 0 {
  883. r.c.fasthttp.Response.Header.SetContentType(mimeType + "; charset=" + charset[0])
  884. } else {
  885. // Automatically add UTF-8 charset for text-based MIME types
  886. if shouldIncludeCharset(mimeType) {
  887. r.c.fasthttp.Response.Header.SetContentType(mimeType + "; charset=utf-8")
  888. } else {
  889. r.c.fasthttp.Response.Header.SetContentType(mimeType)
  890. }
  891. }
  892. return r.c
  893. }
  894. // shouldIncludeCharset determines if a MIME type should include UTF-8 charset by default
  895. func shouldIncludeCharset(mimeType string) bool {
  896. // Everything under text/ gets UTF-8 by default.
  897. if strings.HasPrefix(mimeType, "text/") {
  898. return true
  899. }
  900. // Explicit application types that should default to UTF-8.
  901. switch mimeType {
  902. case MIMEApplicationJSON,
  903. MIMEApplicationJavaScript,
  904. MIMEApplicationXML:
  905. return true
  906. }
  907. // Any application/*+json or application/*+xml.
  908. if strings.HasSuffix(mimeType, "+json") || strings.HasSuffix(mimeType, "+xml") {
  909. return true
  910. }
  911. return false
  912. }
  913. // Vary adds the given header field to the Vary response header.
  914. // This will append the header, if not already listed; otherwise, leaves it listed in the current location.
  915. func (r *DefaultRes) Vary(fields ...string) {
  916. r.Append(HeaderVary, fields...)
  917. }
  918. // Write appends p into response body.
  919. func (r *DefaultRes) Write(p []byte) (int, error) {
  920. r.c.fasthttp.Response.AppendBody(p)
  921. return len(p), nil
  922. }
  923. // Writef appends f & a into response body writer.
  924. func (r *DefaultRes) Writef(f string, a ...any) (int, error) {
  925. //nolint:wrapcheck // This must not be wrapped
  926. return fmt.Fprintf(r.c.fasthttp.Response.BodyWriter(), f, a...)
  927. }
  928. // WriteString appends s to response body.
  929. func (r *DefaultRes) WriteString(s string) (int, error) {
  930. r.c.fasthttp.Response.AppendBodyString(s)
  931. return len(s), nil
  932. }
  933. // Release is a method to reset Res fields when to use ReleaseCtx()
  934. func (r *DefaultRes) release() {
  935. r.c = nil
  936. }
  937. // Drop closes the underlying connection without sending any response headers or body.
  938. // This can be useful for silently terminating client connections, such as in DDoS mitigation
  939. // or when blocking access to sensitive endpoints.
  940. func (r *DefaultRes) Drop() error {
  941. //nolint:wrapcheck // error wrapping is avoided to keep the operation lightweight and focused on connection closure.
  942. return r.c.fasthttp.Conn().Close()
  943. }
  944. // End immediately flushes the current response and closes the underlying connection.
  945. //
  946. // Note: End does not work when using streaming (e.g. fasthttp's HijackConn or SendStream),
  947. // because in streaming mode the connection is managed asynchronously and ctx.Conn() may return nil.
  948. func (r *DefaultRes) End() error {
  949. ctx := r.c.fasthttp
  950. if ctx == nil {
  951. return nil
  952. }
  953. conn := ctx.Conn()
  954. if conn == nil {
  955. return nil
  956. }
  957. bw := bufio.NewWriter(conn)
  958. if err := ctx.Response.Write(bw); err != nil {
  959. return err
  960. }
  961. if err := bw.Flush(); err != nil {
  962. return err //nolint:wrapcheck // unnecessary to wrap it
  963. }
  964. return conn.Close() //nolint:wrapcheck // unnecessary to wrap it
  965. }