res.go 33 KB

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