397 lines
8.4 KiB
Go
397 lines
8.4 KiB
Go
package fasthttp
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var zeroTime time.Time
|
|
|
|
var (
|
|
// CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.
|
|
CookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
|
|
|
// CookieExpireUnlimited indicates that the cookie doesn't expire.
|
|
CookieExpireUnlimited = zeroTime
|
|
)
|
|
|
|
// AcquireCookie returns an empty Cookie object from the pool.
|
|
//
|
|
// The returned object may be returned back to the pool with ReleaseCookie.
|
|
// This allows reducing GC load.
|
|
func AcquireCookie() *Cookie {
|
|
return cookiePool.Get().(*Cookie)
|
|
}
|
|
|
|
// ReleaseCookie returns the Cookie object acquired with AcquireCookie back
|
|
// to the pool.
|
|
//
|
|
// Do not access released Cookie object, otherwise data races may occur.
|
|
func ReleaseCookie(c *Cookie) {
|
|
c.Reset()
|
|
cookiePool.Put(c)
|
|
}
|
|
|
|
var cookiePool = &sync.Pool{
|
|
New: func() interface{} {
|
|
return &Cookie{}
|
|
},
|
|
}
|
|
|
|
// Cookie represents HTTP response cookie.
|
|
//
|
|
// Do not copy Cookie objects. Create new object and use CopyTo instead.
|
|
//
|
|
// Cookie instance MUST NOT be used from concurrently running goroutines.
|
|
type Cookie struct {
|
|
noCopy noCopy
|
|
|
|
key []byte
|
|
value []byte
|
|
expire time.Time
|
|
domain []byte
|
|
path []byte
|
|
|
|
httpOnly bool
|
|
secure bool
|
|
|
|
bufKV argsKV
|
|
buf []byte
|
|
}
|
|
|
|
// CopyTo copies src cookie to c.
|
|
func (c *Cookie) CopyTo(src *Cookie) {
|
|
c.Reset()
|
|
c.key = append(c.key[:0], src.key...)
|
|
c.value = append(c.value[:0], src.value...)
|
|
c.expire = src.expire
|
|
c.domain = append(c.domain[:0], src.domain...)
|
|
c.path = append(c.path[:0], src.path...)
|
|
c.httpOnly = src.httpOnly
|
|
c.secure = src.secure
|
|
}
|
|
|
|
// HTTPOnly returns true if the cookie is http only.
|
|
func (c *Cookie) HTTPOnly() bool {
|
|
return c.httpOnly
|
|
}
|
|
|
|
// SetHTTPOnly sets cookie's httpOnly flag to the given value.
|
|
func (c *Cookie) SetHTTPOnly(httpOnly bool) {
|
|
c.httpOnly = httpOnly
|
|
}
|
|
|
|
// Secure returns true if the cookie is secure.
|
|
func (c *Cookie) Secure() bool {
|
|
return c.secure
|
|
}
|
|
|
|
// SetSecure sets cookie's secure flag to the given value.
|
|
func (c *Cookie) SetSecure(secure bool) {
|
|
c.secure = secure
|
|
}
|
|
|
|
// Path returns cookie path.
|
|
func (c *Cookie) Path() []byte {
|
|
return c.path
|
|
}
|
|
|
|
// SetPath sets cookie path.
|
|
func (c *Cookie) SetPath(path string) {
|
|
c.buf = append(c.buf[:0], path...)
|
|
c.path = normalizePath(c.path, c.buf)
|
|
}
|
|
|
|
// SetPathBytes sets cookie path.
|
|
func (c *Cookie) SetPathBytes(path []byte) {
|
|
c.buf = append(c.buf[:0], path...)
|
|
c.path = normalizePath(c.path, c.buf)
|
|
}
|
|
|
|
// Domain returns cookie domain.
|
|
//
|
|
// The returned domain is valid until the next Cookie modification method call.
|
|
func (c *Cookie) Domain() []byte {
|
|
return c.domain
|
|
}
|
|
|
|
// SetDomain sets cookie domain.
|
|
func (c *Cookie) SetDomain(domain string) {
|
|
c.domain = append(c.domain[:0], domain...)
|
|
}
|
|
|
|
// SetDomainBytes sets cookie domain.
|
|
func (c *Cookie) SetDomainBytes(domain []byte) {
|
|
c.domain = append(c.domain[:0], domain...)
|
|
}
|
|
|
|
// Expire returns cookie expiration time.
|
|
//
|
|
// CookieExpireUnlimited is returned if cookie doesn't expire
|
|
func (c *Cookie) Expire() time.Time {
|
|
expire := c.expire
|
|
if expire.IsZero() {
|
|
expire = CookieExpireUnlimited
|
|
}
|
|
return expire
|
|
}
|
|
|
|
// SetExpire sets cookie expiration time.
|
|
//
|
|
// Set expiration time to CookieExpireDelete for expiring (deleting)
|
|
// the cookie on the client.
|
|
//
|
|
// By default cookie lifetime is limited by browser session.
|
|
func (c *Cookie) SetExpire(expire time.Time) {
|
|
c.expire = expire
|
|
}
|
|
|
|
// Value returns cookie value.
|
|
//
|
|
// The returned value is valid until the next Cookie modification method call.
|
|
func (c *Cookie) Value() []byte {
|
|
return c.value
|
|
}
|
|
|
|
// SetValue sets cookie value.
|
|
func (c *Cookie) SetValue(value string) {
|
|
c.value = append(c.value[:0], value...)
|
|
}
|
|
|
|
// SetValueBytes sets cookie value.
|
|
func (c *Cookie) SetValueBytes(value []byte) {
|
|
c.value = append(c.value[:0], value...)
|
|
}
|
|
|
|
// Key returns cookie name.
|
|
//
|
|
// The returned value is valid until the next Cookie modification method call.
|
|
func (c *Cookie) Key() []byte {
|
|
return c.key
|
|
}
|
|
|
|
// SetKey sets cookie name.
|
|
func (c *Cookie) SetKey(key string) {
|
|
c.key = append(c.key[:0], key...)
|
|
}
|
|
|
|
// SetKeyBytes sets cookie name.
|
|
func (c *Cookie) SetKeyBytes(key []byte) {
|
|
c.key = append(c.key[:0], key...)
|
|
}
|
|
|
|
// Reset clears the cookie.
|
|
func (c *Cookie) Reset() {
|
|
c.key = c.key[:0]
|
|
c.value = c.value[:0]
|
|
c.expire = zeroTime
|
|
c.domain = c.domain[:0]
|
|
c.path = c.path[:0]
|
|
c.httpOnly = false
|
|
c.secure = false
|
|
}
|
|
|
|
// AppendBytes appends cookie representation to dst and returns
|
|
// the extended dst.
|
|
func (c *Cookie) AppendBytes(dst []byte) []byte {
|
|
if len(c.key) > 0 {
|
|
dst = append(dst, c.key...)
|
|
dst = append(dst, '=')
|
|
}
|
|
dst = append(dst, c.value...)
|
|
|
|
if !c.expire.IsZero() {
|
|
c.bufKV.value = AppendHTTPDate(c.bufKV.value[:0], c.expire)
|
|
dst = append(dst, ';', ' ')
|
|
dst = append(dst, strCookieExpires...)
|
|
dst = append(dst, '=')
|
|
dst = append(dst, c.bufKV.value...)
|
|
}
|
|
if len(c.domain) > 0 {
|
|
dst = appendCookiePart(dst, strCookieDomain, c.domain)
|
|
}
|
|
if len(c.path) > 0 {
|
|
dst = appendCookiePart(dst, strCookiePath, c.path)
|
|
}
|
|
if c.httpOnly {
|
|
dst = append(dst, ';', ' ')
|
|
dst = append(dst, strCookieHTTPOnly...)
|
|
}
|
|
if c.secure {
|
|
dst = append(dst, ';', ' ')
|
|
dst = append(dst, strCookieSecure...)
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// Cookie returns cookie representation.
|
|
//
|
|
// The returned value is valid until the next call to Cookie methods.
|
|
func (c *Cookie) Cookie() []byte {
|
|
c.buf = c.AppendBytes(c.buf[:0])
|
|
return c.buf
|
|
}
|
|
|
|
// String returns cookie representation.
|
|
func (c *Cookie) String() string {
|
|
return string(c.Cookie())
|
|
}
|
|
|
|
// WriteTo writes cookie representation to w.
|
|
//
|
|
// WriteTo implements io.WriterTo interface.
|
|
func (c *Cookie) WriteTo(w io.Writer) (int64, error) {
|
|
n, err := w.Write(c.Cookie())
|
|
return int64(n), err
|
|
}
|
|
|
|
var errNoCookies = errors.New("no cookies found")
|
|
|
|
// Parse parses Set-Cookie header.
|
|
func (c *Cookie) Parse(src string) error {
|
|
c.buf = append(c.buf[:0], src...)
|
|
return c.ParseBytes(c.buf)
|
|
}
|
|
|
|
// ParseBytes parses Set-Cookie header.
|
|
func (c *Cookie) ParseBytes(src []byte) error {
|
|
c.Reset()
|
|
|
|
var s cookieScanner
|
|
s.b = src
|
|
|
|
kv := &c.bufKV
|
|
if !s.next(kv) {
|
|
return errNoCookies
|
|
}
|
|
|
|
c.key = append(c.key[:0], kv.key...)
|
|
c.value = append(c.value[:0], kv.value...)
|
|
|
|
for s.next(kv) {
|
|
if len(kv.key) == 0 && len(kv.value) == 0 {
|
|
continue
|
|
}
|
|
switch string(kv.key) {
|
|
case "expires":
|
|
v := b2s(kv.value)
|
|
exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.expire = exptime
|
|
case "domain":
|
|
c.domain = append(c.domain[:0], kv.value...)
|
|
case "path":
|
|
c.path = append(c.path[:0], kv.value...)
|
|
case "":
|
|
switch string(kv.value) {
|
|
case "HttpOnly":
|
|
c.httpOnly = true
|
|
case "secure":
|
|
c.secure = true
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func appendCookiePart(dst, key, value []byte) []byte {
|
|
dst = append(dst, ';', ' ')
|
|
dst = append(dst, key...)
|
|
dst = append(dst, '=')
|
|
return append(dst, value...)
|
|
}
|
|
|
|
func getCookieKey(dst, src []byte) []byte {
|
|
n := bytes.IndexByte(src, '=')
|
|
if n >= 0 {
|
|
src = src[:n]
|
|
}
|
|
return decodeCookieArg(dst, src, false)
|
|
}
|
|
|
|
func appendRequestCookieBytes(dst []byte, cookies []argsKV) []byte {
|
|
for i, n := 0, len(cookies); i < n; i++ {
|
|
kv := &cookies[i]
|
|
if len(kv.key) > 0 {
|
|
dst = append(dst, kv.key...)
|
|
dst = append(dst, '=')
|
|
}
|
|
dst = append(dst, kv.value...)
|
|
if i+1 < n {
|
|
dst = append(dst, ';', ' ')
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func parseRequestCookies(cookies []argsKV, src []byte) []argsKV {
|
|
var s cookieScanner
|
|
s.b = src
|
|
var kv *argsKV
|
|
cookies, kv = allocArg(cookies)
|
|
for s.next(kv) {
|
|
if len(kv.key) > 0 || len(kv.value) > 0 {
|
|
cookies, kv = allocArg(cookies)
|
|
}
|
|
}
|
|
return releaseArg(cookies)
|
|
}
|
|
|
|
type cookieScanner struct {
|
|
b []byte
|
|
}
|
|
|
|
func (s *cookieScanner) next(kv *argsKV) bool {
|
|
b := s.b
|
|
if len(b) == 0 {
|
|
return false
|
|
}
|
|
|
|
isKey := true
|
|
k := 0
|
|
for i, c := range b {
|
|
switch c {
|
|
case '=':
|
|
if isKey {
|
|
isKey = false
|
|
kv.key = decodeCookieArg(kv.key, b[:i], false)
|
|
k = i + 1
|
|
}
|
|
case ';':
|
|
if isKey {
|
|
kv.key = kv.key[:0]
|
|
}
|
|
kv.value = decodeCookieArg(kv.value, b[k:i], true)
|
|
s.b = b[i+1:]
|
|
return true
|
|
}
|
|
}
|
|
|
|
if isKey {
|
|
kv.key = kv.key[:0]
|
|
}
|
|
kv.value = decodeCookieArg(kv.value, b[k:], true)
|
|
s.b = b[len(b):]
|
|
return true
|
|
}
|
|
|
|
func decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {
|
|
for len(src) > 0 && src[0] == ' ' {
|
|
src = src[1:]
|
|
}
|
|
for len(src) > 0 && src[len(src)-1] == ' ' {
|
|
src = src[:len(src)-1]
|
|
}
|
|
if skipQuotes {
|
|
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
|
|
src = src[1 : len(src)-1]
|
|
}
|
|
}
|
|
return append(dst[:0], src...)
|
|
}
|