// 定义了cookie结构体,以及各个字段的判断合法方法,以及解析cookie和生成cookie的方法 // 一个Cookie实例 表示在HTTP响应的Set-Cookie头或HTTP请求的Cookie头中发送的HTTP Cookie type Cookie struct { Name string Value string Path string // optional Domain string // optional // 范围 Expires time.Time // optional // 过期时间 RawExpires string // for reading cookies only // 可读过期时间 MaxAge int // 0表示未指定,<0表示立即删除cookie,存在和给定的最大年龄属性(以秒为单位) Secure bool // 是否安全 HttpOnly bool // 是否仅http携带 SameSite SameSite // 相同网址,int Raw string Unparsed []string // 原始未解析文本 } // SameSite允许服务器定义cookie属性,使得浏览器无法将此cookie与跨站点请求一起发送。 其主要目标是降低跨源信息泄漏的风险,并提供一些针对跨站点请求伪造攻击的保护。 type SameSite int // 默认模式1,lax模式2,Strict模式3,None模式4 const ( SameSiteDefaultMode SameSite = iota + 1 SameSiteLaxMode SameSiteStrictMode SameSiteNoneMode ) // readSetCookies 解析Header中的所有“Set Cookie”值 // "Set Cookie" 用于把cookie 发送到客户端浏览器, 每一个写入cookie都会生成一个Set-Cookie. func readSetCookies(h Header) []*Cookie { cookieCount := len(h["Set-Cookie"]) if cookieCount == 0 { return []*Cookie{} } cookies := make([]*Cookie, 0, cookieCount) for _, line := range h["Set-Cookie"] { parts := strings.Split(textproto.TrimString(line), ";") if len(parts) == 1 && parts[0] == "" { continue } parts[0] = textproto.TrimString(parts[0]) j := strings.Index(parts[0], "=") if j < 0 { continue } name, value := parts[0][:j], parts[0][j+1:] if !isCookieNameValid(name) { continue } value, ok := parseCookieValue(value, true) if !ok { continue } c := &Cookie{ Name: name, Value: value, Raw: line, } for i := 1; i < len(parts); i++ { parts[i] = textproto.TrimString(parts[i]) if len(parts[i]) == 0 { continue } attr, val := parts[i], "" if j := strings.Index(attr, "="); j >= 0 { attr, val = attr[:j], attr[j+1:] } lowerAttr := strings.ToLower(attr) val, ok = parseCookieValue(val, false) if !ok { c.Unparsed = append(c.Unparsed, parts[i]) continue } switch lowerAttr { case "samesite": lowerVal := strings.ToLower(val) switch lowerVal { case "lax": c.SameSite = SameSiteLaxMode case "strict": c.SameSite = SameSiteStrictMode case "none": c.SameSite = SameSiteNoneMode default: c.SameSite = SameSiteDefaultMode } continue case "secure": c.Secure = true continue case "httponly": c.HttpOnly = true continue case "domain": c.Domain = val continue case "max-age": secs, err := strconv.Atoi(val) if err != nil || secs != 0 && val[0] == '0' { break } if secs <= 0 { secs = -1 } c.MaxAge = secs continue case "expires": c.RawExpires = val exptime, err := time.Parse(time.RFC1123, val) if err != nil { exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) if err != nil { c.Expires = time.Time{} break } } c.Expires = exptime.UTC() continue case "path": c.Path = val continue } c.Unparsed = append(c.Unparsed, parts[i]) } cookies = append(cookies, c) } return cookies } // SetCookie将Set-Cookie头添加到提供的ResponseWriter的头中,提供的cookie必须具有有效名称 func SetCookie(w ResponseWriter, cookie *Cookie) { // 将cookie转string,当成v添加到响应写头中 if v := cookie.String(); v != "" { w.Header().Add("Set-Cookie", v) } } // String 返回cookie的序列化,以便在Cookieheader 或者 Set-Cookie response header中使用 // 如果设置了其他字段,序列化结果只能用于HTTP回复的Set-Cookie头 func (c *Cookie) String() string { if c == nil || !isCookieNameValid(c.Name) { return "" } // extraCookieLength 派生自cookie属性的典型长度 const extraCookieLength = 110 var b strings.Builder // 定义 字符串高效编写方法 的生成器 b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength) // 如果需要的话,Grow会增加b的容量 b.WriteString(c.Name) // Name 附加到b的缓冲区 b.WriteRune('=') b.WriteString(sanitizeCookieValue(c.Value)) // 从 缓存的Value 生成一个合适的cookie值。(name=value) if len(c.Path) > 0 { b.WriteString("; Path=") b.WriteString(sanitizeCookiePath(c.Path)) // (;Path=Path) } if len(c.Domain) > 0 { if validCookieDomain(c.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. d := c.Domain if d[0] == '.' { d = d[1:] } b.WriteString("; Domain=") b.WriteString(d) } else { log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain) } } var buf [len(TimeFormat)]byte if validCookieExpires(c.Expires) { b.WriteString("; Expires=") b.Write(c.Expires.UTC().AppendFormat(buf[:0], TimeFormat)) } if c.MaxAge > 0 { b.WriteString("; Max-Age=") b.Write(strconv.AppendInt(buf[:0], int64(c.MaxAge), 10)) } else if c.MaxAge < 0 { b.WriteString("; Max-Age=0") } if c.HttpOnly { b.WriteString("; HttpOnly") } if c.Secure { b.WriteString("; Secure") } switch c.SameSite { case SameSiteDefaultMode: b.WriteString("; SameSite") case SameSiteNoneMode: b.WriteString("; SameSite=None") case SameSiteLaxMode: b.WriteString("; SameSite=Lax") case SameSiteStrictMode: b.WriteString("; SameSite=Strict") } return b.String() } // readCookies 使用过滤器 解析来自头h和h的所有“Cookie”值 func readCookies(h Header, filter string) []*Cookie { lines := h["Cookie"] if len(lines) == 0 { return []*Cookie{} } cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";")) for _, line := range lines { line = textproto.TrimString(line) var part string for len(line) > 0 { // continue since we have rest if splitIndex := strings.Index(line, ";"); splitIndex > 0 { part, line = line[:splitIndex], line[splitIndex+1:] } else { part, line = line, "" } part = textproto.TrimString(part) if len(part) == 0 { continue } name, val := part, "" if j := strings.Index(part, "="); j >= 0 { name, val = name[:j], name[j+1:] } if !isCookieNameValid(name) { continue } if filter != "" && filter != name { continue } val, ok := parseCookieValue(val, true) if !ok { continue } cookies = append(cookies, &Cookie{Name: name, Value: val}) } } return cookies } // validCookieDomain 报告v是否是有效的cookie域值 func validCookieDomain(v string) bool { if isCookieDomainName(v) { return true } if net.ParseIP(v) != nil && !strings.Contains(v, ":") { return true } return false } // validCookieExpires 报告v是否是有效的cookie expires值。 func validCookieExpires(t time.Time) bool { // 年份不得小于1601 return t.Year() >= 1601 } // isCookieDomainName 报告s是有效域名还是前导点为“.”的有效域名 func isCookieDomainName(s string) bool { if len(s) == 0 { return false } if len(s) > 255 { return false } if s[0] == '.' { // A cookie a domain attribute may start with a leading dot. s = s[1:] } last := byte('.') ok := false // Ok once we've seen a letter. partlen := 0 for i := 0; i < len(s); i++ { c := s[i] switch { default: return false case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': // No '_' allowed here (in contrast to package net). ok = true partlen++ case '0' <= c && c <= '9': // fine partlen++ case c == '-': // Byte before dash cannot be dot. if last == '.' { return false } partlen++ case c == '.': // Byte before dot cannot be dot, dash. if last == '.' || last == '-' { return false } if partlen > 63 || partlen == 0 { return false } partlen = 0 } last = c } if last == '-' || partlen > 63 { return false } return ok } var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") // 清理cookie的name(用n代替) func sanitizeCookieName(n string) string { return cookieNameSanitizer.Replace(n) } // sanitizeCookieValue 从v生成一个合适的cookie值 func sanitizeCookieValue(v string) string { v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) if len(v) == 0 { return v } if strings.IndexByte(v, ' ') >= 0 || strings.IndexByte(v, ',') >= 0 { return `"` + v + `"` } return v } func validCookieValueByte(b byte) bool { return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' } // path-av = "Path=" path-value // path-value = <any CHAR except CTLs or ";"> func sanitizeCookiePath(v string) string { return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v) } // 判断是否有效路径字节 func validCookiePathByte(b byte) bool { return 0x20 <= b && b < 0x7f && b != ';' } // 净化或警告(传文件名) func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { ok := true for i := 0; i < len(v); i++ { if valid(v[i]) { continue } log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName) ok = false break } if ok { return v } buf := make([]byte, 0, len(v)) for i := 0; i < len(v); i++ { if b := v[i]; valid(b) { buf = append(buf, b) } } return string(buf) } // 解析cookie的value成string func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { // Strip the quotes, if present. if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { raw = raw[1 : len(raw)-1] } for i := 0; i < len(raw); i++ { if !validCookieValueByte(raw[i]) { return "", false } } return raw, true } func isCookieNameValid(raw string) bool { if raw == "" { return false } return strings.IndexFunc(raw, isNotToken) < 0 }