package http
import (
)
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
SameSite SameSite
Raw string
Unparsed []string
}
type SameSite int
const (
SameSiteDefaultMode SameSite = iota + 1
SameSiteLaxMode
SameSiteStrictMode
SameSiteNoneMode
)
func ( 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.Index([0], "=")
if < 0 {
continue
}
, := [0][:], [0][+1:]
if !isCookieNameValid() {
continue
}
, := parseCookieValue(, true)
if ! {
continue
}
:= &Cookie{
Name: ,
Value: ,
Raw: ,
}
for := 1; < len(); ++ {
[] = textproto.TrimString([])
if len([]) == 0 {
continue
}
, := [], ""
if := strings.Index(, "="); >= 0 {
, = [:], [+1:]
}
:= strings.ToLower()
, = parseCookieValue(, false)
if ! {
.Unparsed = append(.Unparsed, [])
continue
}
switch {
case "samesite":
:= strings.ToLower()
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
}
func ( ResponseWriter, *Cookie) {
if := .String(); != "" {
.Header().Add("Set-Cookie", )
}
}
func ( *Cookie) () string {
if == nil || !isCookieNameValid(.Name) {
return ""
}
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) {
:= .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:
case SameSiteNoneMode:
.WriteString("; SameSite=None")
case SameSiteLaxMode:
.WriteString("; SameSite=Lax")
case SameSiteStrictMode:
.WriteString("; SameSite=Strict")
}
return .String()
}
func ( 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 {
if := strings.Index(, ";"); > 0 {
, = [:], [+1:]
} else {
, = , ""
}
= textproto.TrimString()
if len() == 0 {
continue
}
, := , ""
if := strings.Index(, "="); >= 0 {
, = [:], [+1:]
}
if !isCookieNameValid() {
continue
}
if != "" && != {
continue
}
, := parseCookieValue(, true)
if ! {
continue
}
= append(, &Cookie{Name: , Value: })
}
}
return
}
func ( string) bool {
if isCookieDomainName() {
return true
}
if net.ParseIP() != nil && !strings.Contains(, ":") {
return true
}
return false
}
func ( time.Time) bool {
return .Year() >= 1601
}
func ( string) bool {
if len() == 0 {
return false
}
if len() > 255 {
return false
}
if [0] == '.' {
= [1:]
}
:= byte('.')
:= false
:= 0
for := 0; < len(); ++ {
:= []
switch {
default:
return false
case 'a' <= && <= 'z' || 'A' <= && <= 'Z':
= true
++
case '0' <= && <= '9':
++
case == '-':
if == '.' {
return false
}
++
case == '.':
if == '.' || == '-' {
return false
}
if > 63 || == 0 {
return false
}
= 0
}
=
}
if == '-' || > 63 {
return false
}
return
}
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
func ( string) string {
return cookieNameSanitizer.Replace()
}
func ( string) string {
= sanitizeOrWarn("Cookie.Value", validCookieValueByte, )
if len() == 0 {
return
}
if strings.IndexByte(, ' ') >= 0 || strings.IndexByte(, ',') >= 0 {
return `"` + + `"`
}
return
}
func ( byte) bool {
return 0x20 <= && < 0x7f && != '"' && != ';' && != '\\'
}
func ( string) string {
return sanitizeOrWarn("Cookie.Path", validCookiePathByte, )
}
func ( byte) bool {
return 0x20 <= && < 0x7f && != ';'
}
func ( 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 ( string, bool) (string, bool) {
if && len() > 1 && [0] == '"' && [len()-1] == '"' {
= [1 : len()-1]
}
for := 0; < len(); ++ {
if !validCookieValueByte([]) {
return "", false
}
}
return , true
}
func ( string) bool {
if == "" {
return false
}
return strings.IndexFunc(, isNotToken) < 0
}