cookie.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. package fasthttp
  2. import (
  3. "bytes"
  4. "errors"
  5. "io"
  6. "sync"
  7. "time"
  8. )
  9. var zeroTime time.Time
  10. var (
  11. // CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.
  12. CookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
  13. // CookieExpireUnlimited indicates that the cookie doesn't expire.
  14. CookieExpireUnlimited = zeroTime
  15. )
  16. // CookieSameSite is an enum for the mode in which the SameSite flag should be set for the given cookie.
  17. // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
  18. type CookieSameSite int
  19. const (
  20. // CookieSameSiteDisabled removes the SameSite flag.
  21. CookieSameSiteDisabled CookieSameSite = iota
  22. // CookieSameSiteDefaultMode sets the SameSite flag.
  23. CookieSameSiteDefaultMode
  24. // CookieSameSiteLaxMode sets the SameSite flag with the "Lax" parameter.
  25. CookieSameSiteLaxMode
  26. // CookieSameSiteStrictMode sets the SameSite flag with the "Strict" parameter.
  27. CookieSameSiteStrictMode
  28. // CookieSameSiteNoneMode sets the SameSite flag with the "None" parameter.
  29. // See https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
  30. CookieSameSiteNoneMode // third-party cookies are phasing out, use Partitioned cookies instead
  31. )
  32. // AcquireCookie returns an empty Cookie object from the pool.
  33. //
  34. // The returned object may be returned back to the pool with ReleaseCookie.
  35. // This allows reducing GC load.
  36. func AcquireCookie() *Cookie {
  37. return cookiePool.Get().(*Cookie)
  38. }
  39. // ReleaseCookie returns the Cookie object acquired with AcquireCookie back
  40. // to the pool.
  41. //
  42. // Do not access released Cookie object, otherwise data races may occur.
  43. func ReleaseCookie(c *Cookie) {
  44. c.Reset()
  45. cookiePool.Put(c)
  46. }
  47. var cookiePool = &sync.Pool{
  48. New: func() any {
  49. return &Cookie{}
  50. },
  51. }
  52. // Cookie represents HTTP response cookie.
  53. //
  54. // Do not copy Cookie objects. Create new object and use CopyTo instead.
  55. //
  56. // Cookie instance MUST NOT be used from concurrently running goroutines.
  57. type Cookie struct {
  58. noCopy noCopy
  59. key []byte
  60. value []byte
  61. expire time.Time
  62. maxAge int
  63. domain []byte
  64. path []byte
  65. httpOnly bool
  66. secure bool
  67. sameSite CookieSameSite
  68. partitioned bool
  69. bufKV argsKV
  70. buf []byte
  71. }
  72. // CopyTo copies src cookie to c.
  73. func (c *Cookie) CopyTo(src *Cookie) {
  74. c.Reset()
  75. c.key = append(c.key, src.key...)
  76. c.value = append(c.value, src.value...)
  77. c.expire = src.expire
  78. c.maxAge = src.maxAge
  79. c.domain = append(c.domain, src.domain...)
  80. c.path = append(c.path, src.path...)
  81. c.httpOnly = src.httpOnly
  82. c.secure = src.secure
  83. c.sameSite = src.sameSite
  84. c.partitioned = src.partitioned
  85. }
  86. // HTTPOnly returns true if the cookie is http only.
  87. func (c *Cookie) HTTPOnly() bool {
  88. return c.httpOnly
  89. }
  90. // SetHTTPOnly sets cookie's httpOnly flag to the given value.
  91. func (c *Cookie) SetHTTPOnly(httpOnly bool) {
  92. c.httpOnly = httpOnly
  93. }
  94. // Secure returns true if the cookie is secure.
  95. func (c *Cookie) Secure() bool {
  96. return c.secure
  97. }
  98. // SetSecure sets cookie's secure flag to the given value.
  99. func (c *Cookie) SetSecure(secure bool) {
  100. c.secure = secure
  101. }
  102. // SameSite returns the SameSite mode.
  103. func (c *Cookie) SameSite() CookieSameSite {
  104. return c.sameSite
  105. }
  106. // SetSameSite sets the cookie's SameSite flag to the given value.
  107. // Set value CookieSameSiteNoneMode will set Secure to true also to avoid browser rejection.
  108. func (c *Cookie) SetSameSite(mode CookieSameSite) {
  109. c.sameSite = mode
  110. if mode == CookieSameSiteNoneMode {
  111. c.SetSecure(true)
  112. }
  113. }
  114. // Partitioned returns true if the cookie is partitioned.
  115. func (c *Cookie) Partitioned() bool {
  116. return c.partitioned
  117. }
  118. // SetPartitioned sets the cookie's Partitioned flag to the given value.
  119. // Set value Partitioned to true will set Secure to true and Path to / also to avoid browser rejection.
  120. func (c *Cookie) SetPartitioned(partitioned bool) {
  121. c.partitioned = partitioned
  122. if partitioned {
  123. c.SetSecure(true)
  124. c.SetPath("/")
  125. }
  126. }
  127. // Path returns cookie path.
  128. func (c *Cookie) Path() []byte {
  129. return c.path
  130. }
  131. // SetPath sets cookie path.
  132. func (c *Cookie) SetPath(path string) {
  133. c.buf = append(c.buf[:0], path...)
  134. c.path = normalizePath(c.path, c.buf)
  135. }
  136. // SetPathBytes sets cookie path.
  137. func (c *Cookie) SetPathBytes(path []byte) {
  138. c.buf = append(c.buf[:0], path...)
  139. c.path = normalizePath(c.path, c.buf)
  140. }
  141. // Domain returns cookie domain.
  142. //
  143. // The returned value is valid until the Cookie reused or released (ReleaseCookie).
  144. // Do not store references to the returned value. Make copies instead.
  145. func (c *Cookie) Domain() []byte {
  146. return c.domain
  147. }
  148. // SetDomain sets cookie domain.
  149. func (c *Cookie) SetDomain(domain string) {
  150. c.domain = append(c.domain[:0], domain...)
  151. }
  152. // SetDomainBytes sets cookie domain.
  153. func (c *Cookie) SetDomainBytes(domain []byte) {
  154. c.domain = append(c.domain[:0], domain...)
  155. }
  156. // MaxAge returns the seconds until the cookie is meant to expire or 0
  157. // if no max age.
  158. func (c *Cookie) MaxAge() int {
  159. return c.maxAge
  160. }
  161. // SetMaxAge sets cookie expiration time based on seconds. This takes precedence
  162. // over any absolute expiry set on the cookie.
  163. //
  164. // Set max age to 0 to unset.
  165. func (c *Cookie) SetMaxAge(seconds int) {
  166. c.maxAge = seconds
  167. }
  168. // Expire returns cookie expiration time.
  169. //
  170. // CookieExpireUnlimited is returned if cookie doesn't expire.
  171. func (c *Cookie) Expire() time.Time {
  172. expire := c.expire
  173. if expire.IsZero() {
  174. expire = CookieExpireUnlimited
  175. }
  176. return expire
  177. }
  178. // SetExpire sets cookie expiration time.
  179. //
  180. // Set expiration time to CookieExpireDelete for expiring (deleting)
  181. // the cookie on the client.
  182. //
  183. // By default cookie lifetime is limited by browser session.
  184. func (c *Cookie) SetExpire(expire time.Time) {
  185. c.expire = expire
  186. }
  187. // Value returns cookie value.
  188. //
  189. // The returned value is valid until the Cookie reused or released (ReleaseCookie).
  190. // Do not store references to the returned value. Make copies instead.
  191. func (c *Cookie) Value() []byte {
  192. return c.value
  193. }
  194. // SetValue sets cookie value.
  195. func (c *Cookie) SetValue(value string) {
  196. c.value = append(c.value[:0], value...)
  197. }
  198. // SetValueBytes sets cookie value.
  199. func (c *Cookie) SetValueBytes(value []byte) {
  200. c.value = append(c.value[:0], value...)
  201. }
  202. // Key returns cookie name.
  203. //
  204. // The returned value is valid until the Cookie reused or released (ReleaseCookie).
  205. // Do not store references to the returned value. Make copies instead.
  206. func (c *Cookie) Key() []byte {
  207. return c.key
  208. }
  209. // SetKey sets cookie name.
  210. func (c *Cookie) SetKey(key string) {
  211. c.key = append(c.key[:0], key...)
  212. }
  213. // SetKeyBytes sets cookie name.
  214. func (c *Cookie) SetKeyBytes(key []byte) {
  215. c.key = append(c.key[:0], key...)
  216. }
  217. // Reset clears the cookie.
  218. func (c *Cookie) Reset() {
  219. c.key = c.key[:0]
  220. c.value = c.value[:0]
  221. c.expire = zeroTime
  222. c.maxAge = 0
  223. c.domain = c.domain[:0]
  224. c.path = c.path[:0]
  225. c.httpOnly = false
  226. c.secure = false
  227. c.sameSite = CookieSameSiteDisabled
  228. c.partitioned = false
  229. }
  230. // AppendBytes appends cookie representation to dst and returns
  231. // the extended dst.
  232. func (c *Cookie) AppendBytes(dst []byte) []byte {
  233. if len(c.key) > 0 {
  234. dst = append(dst, c.key...)
  235. dst = append(dst, '=')
  236. }
  237. dst = append(dst, c.value...)
  238. if c.maxAge > 0 {
  239. dst = append(dst, ';', ' ')
  240. dst = append(dst, strCookieMaxAge...)
  241. dst = append(dst, '=')
  242. dst = AppendUint(dst, c.maxAge)
  243. } else if !c.expire.IsZero() {
  244. c.bufKV.value = AppendHTTPDate(c.bufKV.value[:0], c.expire)
  245. dst = append(dst, ';', ' ')
  246. dst = append(dst, strCookieExpires...)
  247. dst = append(dst, '=')
  248. dst = append(dst, c.bufKV.value...)
  249. }
  250. if len(c.domain) > 0 {
  251. dst = appendCookiePart(dst, strCookieDomain, c.domain)
  252. }
  253. if len(c.path) > 0 {
  254. dst = appendCookiePart(dst, strCookiePath, c.path)
  255. }
  256. if c.httpOnly {
  257. dst = append(dst, ';', ' ')
  258. dst = append(dst, strCookieHTTPOnly...)
  259. }
  260. if c.secure {
  261. dst = append(dst, ';', ' ')
  262. dst = append(dst, strCookieSecure...)
  263. }
  264. switch c.sameSite {
  265. case CookieSameSiteDefaultMode:
  266. dst = append(dst, ';', ' ')
  267. dst = append(dst, strCookieSameSite...)
  268. case CookieSameSiteLaxMode:
  269. dst = append(dst, ';', ' ')
  270. dst = append(dst, strCookieSameSite...)
  271. dst = append(dst, '=')
  272. dst = append(dst, strCookieSameSiteLax...)
  273. case CookieSameSiteStrictMode:
  274. dst = append(dst, ';', ' ')
  275. dst = append(dst, strCookieSameSite...)
  276. dst = append(dst, '=')
  277. dst = append(dst, strCookieSameSiteStrict...)
  278. case CookieSameSiteNoneMode:
  279. dst = append(dst, ';', ' ')
  280. dst = append(dst, strCookieSameSite...)
  281. dst = append(dst, '=')
  282. dst = append(dst, strCookieSameSiteNone...)
  283. }
  284. if c.partitioned {
  285. dst = append(dst, ';', ' ')
  286. dst = append(dst, strCookiePartitioned...)
  287. }
  288. return dst
  289. }
  290. // Cookie returns cookie representation.
  291. //
  292. // The returned value is valid until the Cookie reused or released (ReleaseCookie).
  293. // Do not store references to the returned value. Make copies instead.
  294. func (c *Cookie) Cookie() []byte {
  295. c.buf = c.AppendBytes(c.buf[:0])
  296. return c.buf
  297. }
  298. // String returns cookie representation.
  299. func (c *Cookie) String() string {
  300. return string(c.Cookie())
  301. }
  302. // WriteTo writes cookie representation to w.
  303. //
  304. // WriteTo implements io.WriterTo interface.
  305. func (c *Cookie) WriteTo(w io.Writer) (int64, error) {
  306. n, err := w.Write(c.Cookie())
  307. return int64(n), err
  308. }
  309. var errNoCookies = errors.New("no cookies found")
  310. // Parse parses Set-Cookie header.
  311. func (c *Cookie) Parse(src string) error {
  312. c.buf = append(c.buf[:0], src...)
  313. return c.ParseBytes(c.buf)
  314. }
  315. // ParseBytes parses Set-Cookie header.
  316. func (c *Cookie) ParseBytes(src []byte) error {
  317. c.Reset()
  318. var s cookieScanner
  319. s.b = src
  320. kv := &c.bufKV
  321. if !s.next(kv) {
  322. return errNoCookies
  323. }
  324. c.key = append(c.key, kv.key...)
  325. c.value = append(c.value, kv.value...)
  326. for s.next(kv) {
  327. if len(kv.key) != 0 {
  328. // Case insensitive switch on first char
  329. switch kv.key[0] | 0x20 {
  330. case 'm':
  331. if caseInsensitiveCompare(strCookieMaxAge, kv.key) {
  332. maxAge, err := ParseUint(kv.value)
  333. if err != nil {
  334. return err
  335. }
  336. c.maxAge = maxAge
  337. }
  338. case 'e': // "expires"
  339. if caseInsensitiveCompare(strCookieExpires, kv.key) {
  340. v := b2s(kv.value)
  341. // Try the same two formats as net/http
  342. // See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135
  343. exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
  344. if err != nil {
  345. exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", v)
  346. if err != nil {
  347. return err
  348. }
  349. }
  350. c.expire = exptime
  351. }
  352. case 'd': // "domain"
  353. if caseInsensitiveCompare(strCookieDomain, kv.key) {
  354. c.domain = append(c.domain, kv.value...)
  355. }
  356. case 'p': // "path"
  357. if caseInsensitiveCompare(strCookiePath, kv.key) {
  358. c.path = append(c.path, kv.value...)
  359. }
  360. case 's': // "samesite"
  361. if caseInsensitiveCompare(strCookieSameSite, kv.key) {
  362. if len(kv.value) > 0 {
  363. // Case insensitive switch on first char
  364. switch kv.value[0] | 0x20 {
  365. case 'l': // "lax"
  366. if caseInsensitiveCompare(strCookieSameSiteLax, kv.value) {
  367. c.sameSite = CookieSameSiteLaxMode
  368. }
  369. case 's': // "strict"
  370. if caseInsensitiveCompare(strCookieSameSiteStrict, kv.value) {
  371. c.sameSite = CookieSameSiteStrictMode
  372. }
  373. case 'n': // "none"
  374. if caseInsensitiveCompare(strCookieSameSiteNone, kv.value) {
  375. c.sameSite = CookieSameSiteNoneMode
  376. }
  377. }
  378. }
  379. }
  380. }
  381. } else if len(kv.value) != 0 {
  382. // Case insensitive switch on first char
  383. switch kv.value[0] | 0x20 {
  384. case 'h': // "httponly"
  385. if caseInsensitiveCompare(strCookieHTTPOnly, kv.value) {
  386. c.httpOnly = true
  387. }
  388. case 's': // "secure"
  389. if caseInsensitiveCompare(strCookieSecure, kv.value) {
  390. c.secure = true
  391. } else if caseInsensitiveCompare(strCookieSameSite, kv.value) {
  392. c.sameSite = CookieSameSiteDefaultMode
  393. }
  394. case 'p': // "partitioned"
  395. if caseInsensitiveCompare(strCookiePartitioned, kv.value) {
  396. c.partitioned = true
  397. }
  398. }
  399. } // else empty or no match
  400. }
  401. return nil
  402. }
  403. func appendCookiePart(dst, key, value []byte) []byte {
  404. dst = append(dst, ';', ' ')
  405. dst = append(dst, key...)
  406. dst = append(dst, '=')
  407. return append(dst, value...)
  408. }
  409. func getCookieKey(dst, src []byte) []byte {
  410. n := bytes.IndexByte(src, '=')
  411. if n >= 0 {
  412. src = src[:n]
  413. }
  414. return decodeCookieArg(dst, src, false)
  415. }
  416. func appendRequestCookieBytes(dst []byte, cookies []argsKV) []byte {
  417. for i, n := 0, len(cookies); i < n; i++ {
  418. kv := &cookies[i]
  419. if len(kv.key) > 0 {
  420. dst = append(dst, kv.key...)
  421. dst = append(dst, '=')
  422. }
  423. dst = append(dst, kv.value...)
  424. if i+1 < n {
  425. dst = append(dst, ';', ' ')
  426. }
  427. }
  428. return dst
  429. }
  430. // For Response we can not use the above function as response cookies
  431. // already contain the key= in the value.
  432. func appendResponseCookieBytes(dst []byte, cookies []argsKV) []byte {
  433. for i, n := 0, len(cookies); i < n; i++ {
  434. kv := &cookies[i]
  435. dst = append(dst, kv.value...)
  436. if i+1 < n {
  437. dst = append(dst, ';', ' ')
  438. }
  439. }
  440. return dst
  441. }
  442. func parseRequestCookies(cookies []argsKV, src []byte) []argsKV {
  443. var s cookieScanner
  444. s.b = src
  445. var kv *argsKV
  446. cookies, kv = allocArg(cookies)
  447. for s.next(kv) {
  448. if len(kv.key) > 0 || len(kv.value) > 0 {
  449. cookies, kv = allocArg(cookies)
  450. }
  451. }
  452. return releaseArg(cookies)
  453. }
  454. type cookieScanner struct {
  455. b []byte
  456. }
  457. func (s *cookieScanner) next(kv *argsKV) bool {
  458. b := s.b
  459. if len(b) == 0 {
  460. return false
  461. }
  462. isKey := true
  463. k := 0
  464. for i, c := range b {
  465. switch c {
  466. case '=':
  467. if isKey {
  468. isKey = false
  469. kv.key = decodeCookieArg(kv.key, b[:i], false)
  470. k = i + 1
  471. }
  472. case ';':
  473. if isKey {
  474. kv.key = kv.key[:0]
  475. }
  476. kv.value = decodeCookieArg(kv.value, b[k:i], true)
  477. s.b = b[i+1:]
  478. return true
  479. }
  480. }
  481. if isKey {
  482. kv.key = kv.key[:0]
  483. }
  484. kv.value = decodeCookieArg(kv.value, b[k:], true)
  485. s.b = b[len(b):]
  486. return true
  487. }
  488. func decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {
  489. for len(src) > 0 && src[0] == ' ' {
  490. src = src[1:]
  491. }
  492. for len(src) > 0 && src[len(src)-1] == ' ' {
  493. src = src[:len(src)-1]
  494. }
  495. if skipQuotes {
  496. if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
  497. src = src[1 : len(src)-1]
  498. }
  499. }
  500. return append(dst[:0], src...)
  501. }
  502. // caseInsensitiveCompare does a case insensitive equality comparison of
  503. // two []byte. Assumes only letters need to be matched.
  504. func caseInsensitiveCompare(a, b []byte) bool {
  505. if len(a) != len(b) {
  506. return false
  507. }
  508. for i := 0; i < len(a); i++ {
  509. if a[i]|0x20 != b[i]|0x20 {
  510. return false
  511. }
  512. }
  513. return true
  514. }