// 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 httpimport ()// 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.typeCookiestruct { 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.typeSameSiteintconst (SameSiteDefaultModeSameSite = iota + 1SameSiteLaxModeSameSiteStrictModeSameSiteNoneMode)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(), ";")iflen() == 1 && [0] == "" {returnnil, errBlankCookie } := make([]*Cookie, 0, len())for , := range { = textproto.TrimString() , , := strings.Cut(, "=")if ! {returnnil, errEqualNotFoundInCookie }if !isCookieNameValid() {returnnil, errInvalidCookieName } , , := parseCookieValue(, true)if ! {returnnil, 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(), ";")iflen() == 1 && [0] == "" {returnnil, errBlankCookie } [0] = textproto.TrimString([0]) , , := strings.Cut([0], "=")if ! {returnnil, errEqualNotFoundInCookie } = textproto.TrimString()if !isCookieNameValid() {returnnil, errInvalidCookieName } , , := parseCookieValue(, true)if ! {returnnil, errInvalidCookieValue } := &Cookie{Name: ,Value: ,Quoted: ,Raw: , }for := 1; < len(); ++ { [] = textproto.TrimString([])iflen([]) == 0 {continue } , , := strings.Cut([], "=") , := ascii.ToLower()if ! {continue } , _, = parseCookieValue(, false)if ! { .Unparsed = append(.Unparsed, [])continue }switch {case"samesite": , := ascii.ToLower()if ! { .SameSite = SameSiteDefaultModecontinue }switch {case"lax": .SameSite = SameSiteLaxModecase"strict": .SameSite = SameSiteStrictModecase"none": .SameSite = SameSiteNoneModedefault: .SameSite = SameSiteDefaultMode }continuecase"secure": .Secure = truecontinuecase"httponly": .HttpOnly = truecontinuecase"domain": .Domain = continuecase"max-age": , := strconv.Atoi()if != nil || != 0 && [0] == '0' {break }if <= 0 { = -1 } .MaxAge = continuecase"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()continuecase"path": .Path = continuecase"partitioned": .Partitioned = truecontinue } .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 = 110varstrings.Builder .Grow(len(.Name) + len(.Value) + len(.Domain) + len(.Path) + ) .WriteString(.Name) .WriteRune('=') .WriteString(sanitizeCookieValue(.Value, .Quoted))iflen(.Path) > 0 { .WriteString("; Path=") .WriteString(sanitizeCookiePath(.Path)) }iflen(.Domain) > 0 {ifvalidCookieDomain(.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. := .Domainif [0] == '.' { = [1:] } .WriteString("; Domain=") .WriteString() } else {log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", .Domain) } }var [len(TimeFormat)]byteifvalidCookieExpires(.Expires) { .WriteString("; Expires=") .Write(.Expires.UTC().AppendFormat([:0], TimeFormat)) }if .MaxAge > 0 { .WriteString("; Max-Age=") .Write(strconv.AppendInt([:0], int64(.MaxAge), 10)) } elseif .MaxAge < 0 { .WriteString("; Max-Age=0") }if .HttpOnly { .WriteString("; HttpOnly") }if .Secure { .WriteString("; Secure") }switch .SameSite {caseSameSiteDefaultMode:// Skip, default mode is obtained by not emitting the attribute.caseSameSiteNoneMode: .WriteString("; SameSite=None")caseSameSiteLaxMode: .WriteString("; SameSite=Lax")caseSameSiteStrictMode: .WriteString("; SameSite=Strict") }if .Partitioned { .WriteString("; Partitioned") }return .String()}// Valid reports whether the cookie is valid.func ( *Cookie) () error {if == nil {returnerrors.New("http: nil Cookie") }if !isCookieNameValid(.Name) {returnerrors.New("http: invalid Cookie.Name") }if !.Expires.IsZero() && !validCookieExpires(.Expires) {returnerrors.New("http: invalid Cookie.Expires") }for := 0; < len(.Value); ++ {if !validCookieValueByte(.Value[]) {returnfmt.Errorf("http: invalid byte %q in Cookie.Value", .Value[]) } }iflen(.Path) > 0 {for := 0; < len(.Path); ++ {if !validCookiePathByte(.Path[]) {returnfmt.Errorf("http: invalid byte %q in Cookie.Path", .Path[]) } } }iflen(.Domain) > 0 {if !validCookieDomain(.Domain) {returnerrors.New("http: invalid Cookie.Domain") } }if .Partitioned {if !.Secure {returnerrors.New("http: partitioned cookies must be set with Secure") } }returnnil}// 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"]iflen() == 0 {return []*Cookie{} } := make([]*Cookie, 0, len()+strings.Count([0], ";"))for , := range { = textproto.TrimString()varstringforlen() > 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 {ifisCookieDomainName() {returntrue }ifnet.ParseIP() != nil && !strings.Contains(, ":") {returntrue }returnfalse}// 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 1601return .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 {iflen() == 0 {returnfalse }iflen() > 255 {returnfalse }if [0] == '.' {// A cookie a domain attribute may start with a leading dot. = [1:] } := byte('.') := false// Ok once we've seen a letter. := 0for := 0; < len(); ++ { := []switch {default:returnfalsecase'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 == '.' {returnfalse } ++case == '.':// Byte before dot cannot be dot, dash.if == '.' || == '-' {returnfalse }if > 63 || == 0 {returnfalse } = 0 } = }if == '-' || > 63 {returnfalse }return}var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")func sanitizeCookieName( string) string {returncookieNameSanitizer.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, )iflen() == 0 {return }ifstrings.ContainsAny(, " ,") || {return`"` + + `"` }return}func validCookieValueByte( byte) bool {return0x20 <= && < 0x7f && != '"' && != ';' && != '\\'}// path-av = "Path=" path-value// path-value = <any CHAR except CTLs or ";">func sanitizeCookiePath( string) string {returnsanitizeOrWarn("Cookie.Path", validCookiePathByte, )}func validCookiePathByte( byte) bool {return0x20 <= && < 0x7f && != ';'}func sanitizeOrWarn( string, func(byte) bool, string) string { := truefor := 0; < len(); ++ {if ([]) {continue }log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", [], ) = falsebreak }if {return } := make([]byte, 0, len())for := 0; < len(); ++ {if := []; () { = append(, ) } }returnstring()}// 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 == "" {returnfalse }returnstrings.IndexFunc(, isNotToken) < 0}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.