// 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

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

// 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"] {
		 := strings.Split(textproto.TrimString(), ";")
		if len() == 1 && [0] == "" {
			continue
		}
		[0] = textproto.TrimString([0])
		, ,  := strings.Cut([0], "=")
		if ! {
			continue
		}
		 = textproto.TrimString()
		if !isCookieNameValid() {
			continue
		}
		,  = parseCookieValue(, true)
		if ! {
			continue
		}
		 := &Cookie{
			Name:  ,
			Value: ,
			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
			}
			.Unparsed = append(.Unparsed, [])
		}
		 = 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))

	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")
	}
	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")
		}
	}
	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: })
		}
	}
	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.
// 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
// but we produce a quoted cookie-value if and only if v contains
// commas or spaces.
// See https://golang.org/issue/7243 for the discussion.
func sanitizeCookieValue( string) 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()
}

func parseCookieValue( string,  bool) (string, bool) {
	// Strip the quotes, if present.
	if  && len() > 1 && [0] == '"' && [len()-1] == '"' {
		 = [1 : len()-1]
	}
	for  := 0;  < len(); ++ {
		if !validCookieValueByte([]) {
			return "", false
		}
	}
	return , true
}

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