package fasthttp

import (
	
	
	
	
	
)

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
)

// CookieSameSite is an enum for the mode in which the SameSite flag should be set for the given cookie.
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type CookieSameSite int

const (
	// CookieSameSiteDisabled removes the SameSite flag
	CookieSameSiteDisabled CookieSameSite = iota
	// CookieSameSiteDefaultMode sets the SameSite flag
	CookieSameSiteDefaultMode
	// CookieSameSiteLaxMode sets the SameSite flag with the "Lax" parameter
	CookieSameSiteLaxMode
	// CookieSameSiteStrictMode sets the SameSite flag with the "Strict" parameter
	CookieSameSiteStrictMode
	// CookieSameSiteNoneMode sets the SameSite flag with the "None" parameter
	// see https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
	CookieSameSiteNoneMode
)

// 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 () *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 ( *Cookie) {
	.Reset()
	cookiePool.Put()
}

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
	maxAge int
	domain []byte
	path   []byte

	httpOnly bool
	secure   bool
	sameSite CookieSameSite

	bufKV argsKV
	buf   []byte
}

// CopyTo copies src cookie to c.
func ( *Cookie) ( *Cookie) {
	.Reset()
	.key = append(.key, .key...)
	.value = append(.value, .value...)
	.expire = .expire
	.maxAge = .maxAge
	.domain = append(.domain, .domain...)
	.path = append(.path, .path...)
	.httpOnly = .httpOnly
	.secure = .secure
	.sameSite = .sameSite
}

// HTTPOnly returns true if the cookie is http only.
func ( *Cookie) () bool {
	return .httpOnly
}

// SetHTTPOnly sets cookie's httpOnly flag to the given value.
func ( *Cookie) ( bool) {
	.httpOnly = 
}

// Secure returns true if the cookie is secure.
func ( *Cookie) () bool {
	return .secure
}

// SetSecure sets cookie's secure flag to the given value.
func ( *Cookie) ( bool) {
	.secure = 
}

// SameSite returns the SameSite mode.
func ( *Cookie) () CookieSameSite {
	return .sameSite
}

// SetSameSite sets the cookie's SameSite flag to the given value.
// set value CookieSameSiteNoneMode will set Secure to true also to avoid browser rejection
func ( *Cookie) ( CookieSameSite) {
	.sameSite = 
	if  == CookieSameSiteNoneMode {
		.SetSecure(true)
	}
}

// Path returns cookie path.
func ( *Cookie) () []byte {
	return .path
}

// SetPath sets cookie path.
func ( *Cookie) ( string) {
	.buf = append(.buf[:0], ...)
	.path = normalizePath(.path, .buf)
}

// SetPathBytes sets cookie path.
func ( *Cookie) ( []byte) {
	.buf = append(.buf[:0], ...)
	.path = normalizePath(.path, .buf)
}

// Domain returns cookie domain.
//
// The returned value is valid until the Cookie reused or released (ReleaseCookie).
// Do not store references to the returned value. Make copies instead.
func ( *Cookie) () []byte {
	return .domain
}

// SetDomain sets cookie domain.
func ( *Cookie) ( string) {
	.domain = append(.domain[:0], ...)
}

// SetDomainBytes sets cookie domain.
func ( *Cookie) ( []byte) {
	.domain = append(.domain[:0], ...)
}

// MaxAge returns the seconds until the cookie is meant to expire or 0
// if no max age.
func ( *Cookie) () int {
	return .maxAge
}

// SetMaxAge sets cookie expiration time based on seconds. This takes precedence
// over any absolute expiry set on the cookie
//
// Set max age to 0 to unset
func ( *Cookie) ( int) {
	.maxAge = 
}

// Expire returns cookie expiration time.
//
// CookieExpireUnlimited is returned if cookie doesn't expire
func ( *Cookie) () time.Time {
	 := .expire
	if .IsZero() {
		 = CookieExpireUnlimited
	}
	return 
}

// 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 ( *Cookie) ( time.Time) {
	.expire = 
}

// Value returns cookie value.
//
// The returned value is valid until the Cookie reused or released (ReleaseCookie).
// Do not store references to the returned value. Make copies instead.
func ( *Cookie) () []byte {
	return .value
}

// SetValue sets cookie value.
func ( *Cookie) ( string) {
	.value = append(.value[:0], ...)
}

// SetValueBytes sets cookie value.
func ( *Cookie) ( []byte) {
	.value = append(.value[:0], ...)
}

// Key returns cookie name.
//
// The returned value is valid until the Cookie reused or released (ReleaseCookie).
// Do not store references to the returned value. Make copies instead.
func ( *Cookie) () []byte {
	return .key
}

// SetKey sets cookie name.
func ( *Cookie) ( string) {
	.key = append(.key[:0], ...)
}

// SetKeyBytes sets cookie name.
func ( *Cookie) ( []byte) {
	.key = append(.key[:0], ...)
}

// Reset clears the cookie.
func ( *Cookie) () {
	.key = .key[:0]
	.value = .value[:0]
	.expire = zeroTime
	.maxAge = 0
	.domain = .domain[:0]
	.path = .path[:0]
	.httpOnly = false
	.secure = false
	.sameSite = CookieSameSiteDisabled
}

// AppendBytes appends cookie representation to dst and returns
// the extended dst.
func ( *Cookie) ( []byte) []byte {
	if len(.key) > 0 {
		 = append(, .key...)
		 = append(, '=')
	}
	 = append(, .value...)

	if .maxAge > 0 {
		 = append(, ';', ' ')
		 = append(, strCookieMaxAge...)
		 = append(, '=')
		 = AppendUint(, .maxAge)
	} else if !.expire.IsZero() {
		.bufKV.value = AppendHTTPDate(.bufKV.value[:0], .expire)
		 = append(, ';', ' ')
		 = append(, strCookieExpires...)
		 = append(, '=')
		 = append(, .bufKV.value...)
	}
	if len(.domain) > 0 {
		 = appendCookiePart(, strCookieDomain, .domain)
	}
	if len(.path) > 0 {
		 = appendCookiePart(, strCookiePath, .path)
	}
	if .httpOnly {
		 = append(, ';', ' ')
		 = append(, strCookieHTTPOnly...)
	}
	if .secure {
		 = append(, ';', ' ')
		 = append(, strCookieSecure...)
	}
	switch .sameSite {
	case CookieSameSiteDefaultMode:
		 = append(, ';', ' ')
		 = append(, strCookieSameSite...)
	case CookieSameSiteLaxMode:
		 = append(, ';', ' ')
		 = append(, strCookieSameSite...)
		 = append(, '=')
		 = append(, strCookieSameSiteLax...)
	case CookieSameSiteStrictMode:
		 = append(, ';', ' ')
		 = append(, strCookieSameSite...)
		 = append(, '=')
		 = append(, strCookieSameSiteStrict...)
	case CookieSameSiteNoneMode:
		 = append(, ';', ' ')
		 = append(, strCookieSameSite...)
		 = append(, '=')
		 = append(, strCookieSameSiteNone...)
	}
	return 
}

// Cookie returns cookie representation.
//
// The returned value is valid until the Cookie reused or released (ReleaseCookie).
// Do not store references to the returned value. Make copies instead.
func ( *Cookie) () []byte {
	.buf = .AppendBytes(.buf[:0])
	return .buf
}

// String returns cookie representation.
func ( *Cookie) () string {
	return string(.Cookie())
}

// WriteTo writes cookie representation to w.
//
// WriteTo implements io.WriterTo interface.
func ( *Cookie) ( io.Writer) (int64, error) {
	,  := .Write(.Cookie())
	return int64(), 
}

var errNoCookies = errors.New("no cookies found")

// Parse parses Set-Cookie header.
func ( *Cookie) ( string) error {
	.buf = append(.buf[:0], ...)
	return .ParseBytes(.buf)
}

// ParseBytes parses Set-Cookie header.
func ( *Cookie) ( []byte) error {
	.Reset()

	var  cookieScanner
	.b = 

	 := &.bufKV
	if !.next() {
		return errNoCookies
	}

	.key = append(.key, .key...)
	.value = append(.value, .value...)

	for .next() {
		if len(.key) != 0 {
			// Case insensitive switch on first char
			switch .key[0] | 0x20 {
			case 'm':
				if caseInsensitiveCompare(strCookieMaxAge, .key) {
					,  := ParseUint(.value)
					if  != nil {
						return 
					}
					.maxAge = 
				}

			case 'e': // "expires"
				if caseInsensitiveCompare(strCookieExpires, .key) {
					 := b2s(.value)
					// Try the same two formats as net/http
					// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135
					,  := time.ParseInLocation(time.RFC1123, , time.UTC)
					if  != nil {
						,  = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", )
						if  != nil {
							return 
						}
					}
					.expire = 
				}

			case 'd': // "domain"
				if caseInsensitiveCompare(strCookieDomain, .key) {
					.domain = append(.domain, .value...)
				}

			case 'p': // "path"
				if caseInsensitiveCompare(strCookiePath, .key) {
					.path = append(.path, .value...)
				}

			case 's': // "samesite"
				if caseInsensitiveCompare(strCookieSameSite, .key) {
					if len(.value) > 0 {
						// Case insensitive switch on first char
						switch .value[0] | 0x20 {
						case 'l': // "lax"
							if caseInsensitiveCompare(strCookieSameSiteLax, .value) {
								.sameSite = CookieSameSiteLaxMode
							}
						case 's': // "strict"
							if caseInsensitiveCompare(strCookieSameSiteStrict, .value) {
								.sameSite = CookieSameSiteStrictMode
							}
						case 'n': // "none"
							if caseInsensitiveCompare(strCookieSameSiteNone, .value) {
								.sameSite = CookieSameSiteNoneMode
							}
						}
					}
				}
			}
		} else if len(.value) != 0 {
			// Case insensitive switch on first char
			switch .value[0] | 0x20 {
			case 'h': // "httponly"
				if caseInsensitiveCompare(strCookieHTTPOnly, .value) {
					.httpOnly = true
				}

			case 's': // "secure"
				if caseInsensitiveCompare(strCookieSecure, .value) {
					.secure = true
				} else if caseInsensitiveCompare(strCookieSameSite, .value) {
					.sameSite = CookieSameSiteDefaultMode
				}
			}
		} // else empty or no match
	}
	return nil
}

func appendCookiePart(, ,  []byte) []byte {
	 = append(, ';', ' ')
	 = append(, ...)
	 = append(, '=')
	return append(, ...)
}

func getCookieKey(,  []byte) []byte {
	 := bytes.IndexByte(, '=')
	if  >= 0 {
		 = [:]
	}
	return decodeCookieArg(, , false)
}

func appendRequestCookieBytes( []byte,  []argsKV) []byte {
	for ,  := 0, len();  < ; ++ {
		 := &[]
		if len(.key) > 0 {
			 = append(, .key...)
			 = append(, '=')
		}
		 = append(, .value...)
		if +1 <  {
			 = append(, ';', ' ')
		}
	}
	return 
}

// For Response we can not use the above function as response cookies
// already contain the key= in the value.
func appendResponseCookieBytes( []byte,  []argsKV) []byte {
	for ,  := 0, len();  < ; ++ {
		 := &[]
		 = append(, .value...)
		if +1 <  {
			 = append(, ';', ' ')
		}
	}
	return 
}

func parseRequestCookies( []argsKV,  []byte) []argsKV {
	var  cookieScanner
	.b = 
	var  *argsKV
	,  = allocArg()
	for .next() {
		if len(.key) > 0 || len(.value) > 0 {
			,  = allocArg()
		}
	}
	return releaseArg()
}

type cookieScanner struct {
	b []byte
}

func ( *cookieScanner) ( *argsKV) bool {
	 := .b
	if len() == 0 {
		return false
	}

	 := true
	 := 0
	for ,  := range  {
		switch  {
		case '=':
			if  {
				 = false
				.key = decodeCookieArg(.key, [:], false)
				 =  + 1
			}
		case ';':
			if  {
				.key = .key[:0]
			}
			.value = decodeCookieArg(.value, [:], true)
			.b = [+1:]
			return true
		}
	}

	if  {
		.key = .key[:0]
	}
	.value = decodeCookieArg(.value, [:], true)
	.b = [len():]
	return true
}

func decodeCookieArg(,  []byte,  bool) []byte {
	for len() > 0 && [0] == ' ' {
		 = [1:]
	}
	for len() > 0 && [len()-1] == ' ' {
		 = [:len()-1]
	}
	if  {
		if len() > 1 && [0] == '"' && [len()-1] == '"' {
			 = [1 : len()-1]
		}
	}
	return append([:0], ...)
}

// caseInsensitiveCompare does a case insensitive equality comparison of
// two []byte. Assumes only letters need to be matched.
func caseInsensitiveCompare(,  []byte) bool {
	if len() != len() {
		return false
	}
	for  := 0;  < len(); ++ {
		if []|0x20 != []|0x20 {
			return false
		}
	}
	return true
}