cookie.go 15 KB

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