// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http

import (
	
	
	
	
	
	
	
	
	
)

// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
//
// See https://tools.ietf.org/html/rfc6265 for details.
type Cookie struct {
	Name   string
	Value  string
	Quoted bool // indicates whether the Value was originally quoted

	Path       string    // optional
	Domain     string    // optional
	Expires    time.Time // optional
	RawExpires string    // for reading cookies only

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge      int
	Secure      bool
	HttpOnly    bool
	SameSite    SameSite
	Partitioned bool
	Raw         string
	Unparsed    []string // Raw text of unparsed attribute-value pairs
}

// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests. The main
// goal is to mitigate the risk of cross-origin information leakage, and provide
// some protection against cross-site request forgery attacks.
//
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type SameSite int

const (
	SameSiteDefaultMode SameSite = iota + 1
	SameSiteLaxMode
	SameSiteStrictMode
	SameSiteNoneMode
)

var (
	errBlankCookie           = errors.New("http: blank cookie")
	errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
	errInvalidCookieName     = errors.New("http: invalid cookie name")
	errInvalidCookieValue    = errors.New("http: invalid cookie value")
)

// ParseCookie parses a Cookie header value and returns all the cookies
// which were set in it. Since the same cookie name can appear multiple times
// the returned Values can contain more than one value for a given key.
func ( string) ([]*Cookie, error) {
	 := strings.Split(textproto.TrimString(), ";")
	if len() == 1 && [0] == "" {
		return nil, errBlankCookie
	}
	 := make([]*Cookie, 0, len())
	for ,  := range  {
		 = textproto.TrimString()
		, ,  := strings.Cut(, "=")
		if ! {
			return nil, errEqualNotFoundInCookie
		}
		if !isCookieNameValid() {
			return nil, errInvalidCookieName
		}
		, ,  := parseCookieValue(, true)
		if ! {
			return nil, errInvalidCookieValue
		}
		 = append(, &Cookie{Name: , Value: , Quoted: })
	}
	return , nil
}

// ParseSetCookie parses a Set-Cookie header value and returns a cookie.
// It returns an error on syntax error.
func ( string) (*Cookie, error) {
	 := strings.Split(textproto.TrimString(), ";")
	if len() == 1 && [0] == "" {
		return nil, errBlankCookie
	}
	[0] = textproto.TrimString([0])
	, ,  := strings.Cut([0], "=")
	if ! {
		return nil, errEqualNotFoundInCookie
	}
	 = textproto.TrimString()
	if !isCookieNameValid() {
		return nil, errInvalidCookieName
	}
	, ,  := parseCookieValue(, true)
	if ! {
		return nil, errInvalidCookieValue
	}
	 := &Cookie{
		Name:   ,
		Value:  ,
		Quoted: ,
		Raw:    ,
	}
	for  := 1;  < len(); ++ {
		[] = textproto.TrimString([])
		if len([]) == 0 {
			continue
		}

		, ,  := strings.Cut([], "=")
		,  := ascii.ToLower()
		if ! {
			continue
		}
		, _,  = parseCookieValue(, false)
		if ! {
			.Unparsed = append(.Unparsed, [])
			continue
		}

		switch  {
		case "samesite":
			,  := ascii.ToLower()
			if ! {
				.SameSite = SameSiteDefaultMode
				continue
			}
			switch  {
			case "lax":
				.SameSite = SameSiteLaxMode
			case "strict":
				.SameSite = SameSiteStrictMode
			case "none":
				.SameSite = SameSiteNoneMode
			default:
				.SameSite = SameSiteDefaultMode
			}
			continue
		case "secure":
			.Secure = true
			continue
		case "httponly":
			.HttpOnly = true
			continue
		case "domain":
			.Domain = 
			continue
		case "max-age":
			,  := strconv.Atoi()
			if  != nil ||  != 0 && [0] == '0' {
				break
			}
			if  <= 0 {
				 = -1
			}
			.MaxAge = 
			continue
		case "expires":
			.RawExpires = 
			,  := time.Parse(time.RFC1123, )
			if  != nil {
				,  = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", )
				if  != nil {
					.Expires = time.Time{}
					break
				}
			}
			.Expires = .UTC()
			continue
		case "path":
			.Path = 
			continue
		case "partitioned":
			.Partitioned = true
			continue
		}
		.Unparsed = append(.Unparsed, [])
	}
	return , nil
}

// readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies.
func readSetCookies( Header) []*Cookie {
	 := len(["Set-Cookie"])
	if  == 0 {
		return []*Cookie{}
	}
	 := make([]*Cookie, 0, )
	for ,  := range ["Set-Cookie"] {
		if ,  := ParseSetCookie();  == nil {
			 = append(, )
		}
	}
	return 
}

// SetCookie adds a Set-Cookie header to the provided [ResponseWriter]'s headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func ( ResponseWriter,  *Cookie) {
	if  := .String();  != "" {
		.Header().Add("Set-Cookie", )
	}
}

// String returns the serialization of the cookie for use in a [Cookie]
// header (if only Name and Value are set) or a Set-Cookie response
// header (if other fields are set).
// If c is nil or c.Name is invalid, the empty string is returned.
func ( *Cookie) () string {
	if  == nil || !isCookieNameValid(.Name) {
		return ""
	}
	// extraCookieLength derived from typical length of cookie attributes
	// see RFC 6265 Sec 4.1.
	const  = 110
	var  strings.Builder
	.Grow(len(.Name) + len(.Value) + len(.Domain) + len(.Path) + )
	.WriteString(.Name)
	.WriteRune('=')
	.WriteString(sanitizeCookieValue(.Value, .Quoted))

	if len(.Path) > 0 {
		.WriteString("; Path=")
		.WriteString(sanitizeCookiePath(.Path))
	}
	if len(.Domain) > 0 {
		if validCookieDomain(.Domain) {
			// A c.Domain containing illegal characters is not
			// sanitized but simply dropped which turns the cookie
			// into a host-only cookie. A leading dot is okay
			// but won't be sent.
			 := .Domain
			if [0] == '.' {
				 = [1:]
			}
			.WriteString("; Domain=")
			.WriteString()
		} else {
			log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", .Domain)
		}
	}
	var  [len(TimeFormat)]byte
	if validCookieExpires(.Expires) {
		.WriteString("; Expires=")
		.Write(.Expires.UTC().AppendFormat([:0], TimeFormat))
	}
	if .MaxAge > 0 {
		.WriteString("; Max-Age=")
		.Write(strconv.AppendInt([:0], int64(.MaxAge), 10))
	} else if .MaxAge < 0 {
		.WriteString("; Max-Age=0")
	}
	if .HttpOnly {
		.WriteString("; HttpOnly")
	}
	if .Secure {
		.WriteString("; Secure")
	}
	switch .SameSite {
	case SameSiteDefaultMode:
		// Skip, default mode is obtained by not emitting the attribute.
	case SameSiteNoneMode:
		.WriteString("; SameSite=None")
	case SameSiteLaxMode:
		.WriteString("; SameSite=Lax")
	case SameSiteStrictMode:
		.WriteString("; SameSite=Strict")
	}
	if .Partitioned {
		.WriteString("; Partitioned")
	}
	return .String()
}

// Valid reports whether the cookie is valid.
func ( *Cookie) () error {
	if  == nil {
		return errors.New("http: nil Cookie")
	}
	if !isCookieNameValid(.Name) {
		return errors.New("http: invalid Cookie.Name")
	}
	if !.Expires.IsZero() && !validCookieExpires(.Expires) {
		return errors.New("http: invalid Cookie.Expires")
	}
	for  := 0;  < len(.Value); ++ {
		if !validCookieValueByte(.Value[]) {
			return fmt.Errorf("http: invalid byte %q in Cookie.Value", .Value[])
		}
	}
	if len(.Path) > 0 {
		for  := 0;  < len(.Path); ++ {
			if !validCookiePathByte(.Path[]) {
				return fmt.Errorf("http: invalid byte %q in Cookie.Path", .Path[])
			}
		}
	}
	if len(.Domain) > 0 {
		if !validCookieDomain(.Domain) {
			return errors.New("http: invalid Cookie.Domain")
		}
	}
	if .Partitioned {
		if !.Secure {
			return errors.New("http: partitioned cookies must be set with Secure")
		}
	}
	return nil
}

// readCookies parses all "Cookie" values from the header h and
// returns the successfully parsed Cookies.
//
// if filter isn't empty, only cookies of that name are returned.
func readCookies( Header,  string) []*Cookie {
	 := ["Cookie"]
	if len() == 0 {
		return []*Cookie{}
	}

	 := make([]*Cookie, 0, len()+strings.Count([0], ";"))
	for ,  := range  {
		 = textproto.TrimString()

		var  string
		for len() > 0 { // continue since we have rest
			, , _ = strings.Cut(, ";")
			 = textproto.TrimString()
			if  == "" {
				continue
			}
			, ,  := strings.Cut(, "=")
			 = textproto.TrimString()
			if !isCookieNameValid() {
				continue
			}
			if  != "" &&  !=  {
				continue
			}
			, ,  := parseCookieValue(, true)
			if ! {
				continue
			}
			 = append(, &Cookie{Name: , Value: , Quoted: })
		}
	}
	return 
}

// validCookieDomain reports whether v is a valid cookie domain-value.
func validCookieDomain( string) bool {
	if isCookieDomainName() {
		return true
	}
	if net.ParseIP() != nil && !strings.Contains(, ":") {
		return true
	}
	return false
}

// validCookieExpires reports whether v is a valid cookie expires-value.
func validCookieExpires( time.Time) bool {
	// IETF RFC 6265 Section 5.1.1.5, the year must not be less than 1601
	return .Year() >= 1601
}

// isCookieDomainName reports whether s is a valid domain name or a valid
// domain name with a leading dot '.'.  It is almost a direct copy of
// package net's isDomainName.
func isCookieDomainName( string) bool {
	if len() == 0 {
		return false
	}
	if len() > 255 {
		return false
	}

	if [0] == '.' {
		// A cookie a domain attribute may start with a leading dot.
		 = [1:]
	}
	 := byte('.')
	 := false // Ok once we've seen a letter.
	 := 0
	for  := 0;  < len(); ++ {
		 := []
		switch {
		default:
			return false
		case 'a' <=  &&  <= 'z' || 'A' <=  &&  <= 'Z':
			// No '_' allowed here (in contrast to package net).
			 = true
			++
		case '0' <=  &&  <= '9':
			// fine
			++
		case  == '-':
			// Byte before dash cannot be dot.
			if  == '.' {
				return false
			}
			++
		case  == '.':
			// Byte before dot cannot be dot, dash.
			if  == '.' ||  == '-' {
				return false
			}
			if  > 63 ||  == 0 {
				return false
			}
			 = 0
		}
		 = 
	}
	if  == '-' ||  > 63 {
		return false
	}

	return 
}

var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")

func sanitizeCookieName( string) string {
	return cookieNameSanitizer.Replace()
}

// sanitizeCookieValue produces a suitable cookie-value from v.
// It receives a quoted bool indicating whether the value was originally
// quoted.
// https://tools.ietf.org/html/rfc6265#section-4.1.1
//
//	cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
//	cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
//	          ; US-ASCII characters excluding CTLs,
//	          ; whitespace DQUOTE, comma, semicolon,
//	          ; and backslash
//
// We loosen this as spaces and commas are common in cookie values
// thus we produce a quoted cookie-value if v contains commas or spaces.
// See https://golang.org/issue/7243 for the discussion.
func sanitizeCookieValue( string,  bool) string {
	 = sanitizeOrWarn("Cookie.Value", validCookieValueByte, )
	if len() == 0 {
		return 
	}
	if strings.ContainsAny(, " ,") ||  {
		return `"` +  + `"`
	}
	return 
}

func validCookieValueByte( byte) bool {
	return 0x20 <=  &&  < 0x7f &&  != '"' &&  != ';' &&  != '\\'
}

// path-av           = "Path=" path-value
// path-value        = <any CHAR except CTLs or ";">
func sanitizeCookiePath( string) string {
	return sanitizeOrWarn("Cookie.Path", validCookiePathByte, )
}

func validCookiePathByte( byte) bool {
	return 0x20 <=  &&  < 0x7f &&  != ';'
}

func sanitizeOrWarn( string,  func(byte) bool,  string) string {
	 := true
	for  := 0;  < len(); ++ {
		if ([]) {
			continue
		}
		log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", [], )
		 = false
		break
	}
	if  {
		return 
	}
	 := make([]byte, 0, len())
	for  := 0;  < len(); ++ {
		if  := []; () {
			 = append(, )
		}
	}
	return string()
}

// parseCookieValue parses a cookie value according to RFC 6265.
// If allowDoubleQuote is true, parseCookieValue will consider that it
// is parsing the cookie-value;
// otherwise, it will consider that it is parsing a cookie-av value
// (cookie attribute-value).
//
// It returns the parsed cookie value, a boolean indicating whether the
// parsing was successful, and a boolean indicating whether the parsed
// value was enclosed in double quotes.
func parseCookieValue( string,  bool) ( string, ,  bool) {
	// Strip the quotes, if present.
	if  && len() > 1 && [0] == '"' && [len()-1] == '"' {
		 = [1 : len()-1]
		 = true
	}
	for  := 0;  < len(); ++ {
		if !validCookieValueByte([]) {
			return "", , false
		}
	}
	return , , true
}

func isCookieNameValid( string) bool {
	if  == "" {
		return false
	}
	return strings.IndexFunc(, isNotToken) < 0
}