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...) }