|  | // 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 ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "strconv" | 
|  | "strings" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | // This implementation is done according to RFC 6265: | 
|  | // | 
|  | //    http://tools.ietf.org/html/rfc6265 | 
|  |  | 
|  | // 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. | 
|  | type Cookie struct { | 
|  | Name       string | 
|  | Value      string | 
|  | Path       string | 
|  | Domain     string | 
|  | Expires    time.Time | 
|  | RawExpires string | 
|  |  | 
|  | // 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 | 
|  | Raw      string | 
|  | Unparsed []string // Raw text of unparsed attribute-value pairs | 
|  | } | 
|  |  | 
|  | // readSetCookies parses all "Set-Cookie" values from | 
|  | // the header h and returns the successfully parsed Cookies. | 
|  | func readSetCookies(h Header) []*Cookie { | 
|  | cookies := []*Cookie{} | 
|  | for _, line := range h["Set-Cookie"] { | 
|  | parts := strings.Split(strings.TrimSpace(line), ";") | 
|  | if len(parts) == 1 && parts[0] == "" { | 
|  | continue | 
|  | } | 
|  | parts[0] = strings.TrimSpace(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, success := parseCookieValue(value) | 
|  | if !success { | 
|  | continue | 
|  | } | 
|  | c := &Cookie{ | 
|  | Name:  name, | 
|  | Value: value, | 
|  | Raw:   line, | 
|  | } | 
|  | for i := 1; i < len(parts); i++ { | 
|  | parts[i] = strings.TrimSpace(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) | 
|  | parseCookieValueFn := parseCookieValue | 
|  | if lowerAttr == "expires" { | 
|  | parseCookieValueFn = parseCookieExpiresValue | 
|  | } | 
|  | val, success = parseCookieValueFn(val) | 
|  | if !success { | 
|  | c.Unparsed = append(c.Unparsed, parts[i]) | 
|  | continue | 
|  | } | 
|  | switch lowerAttr { | 
|  | case "secure": | 
|  | c.Secure = true | 
|  | continue | 
|  | case "httponly": | 
|  | c.HttpOnly = true | 
|  | continue | 
|  | case "domain": | 
|  | c.Domain = val | 
|  | // TODO: Add domain parsing | 
|  | continue | 
|  | case "max-age": | 
|  | secs, err := strconv.Atoi(val) | 
|  | if err != nil || secs != 0 && val[0] == '0' { | 
|  | break | 
|  | } | 
|  | if secs <= 0 { | 
|  | c.MaxAge = -1 | 
|  | } else { | 
|  | 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 | 
|  | // TODO: Add path parsing | 
|  | continue | 
|  | } | 
|  | c.Unparsed = append(c.Unparsed, parts[i]) | 
|  | } | 
|  | cookies = append(cookies, c) | 
|  | } | 
|  | return cookies | 
|  | } | 
|  |  | 
|  | // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. | 
|  | func SetCookie(w ResponseWriter, cookie *Cookie) { | 
|  | w.Header().Add("Set-Cookie", cookie.String()) | 
|  | } | 
|  |  | 
|  | // 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). | 
|  | func (c *Cookie) String() string { | 
|  | var b bytes.Buffer | 
|  | fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) | 
|  | if len(c.Path) > 0 { | 
|  | fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path)) | 
|  | } | 
|  | if len(c.Domain) > 0 { | 
|  | fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) | 
|  | } | 
|  | if c.Expires.Unix() > 0 { | 
|  | fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) | 
|  | } | 
|  | if c.MaxAge > 0 { | 
|  | fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) | 
|  | } else if c.MaxAge < 0 { | 
|  | fmt.Fprintf(&b, "; Max-Age=0") | 
|  | } | 
|  | if c.HttpOnly { | 
|  | fmt.Fprintf(&b, "; HttpOnly") | 
|  | } | 
|  | if c.Secure { | 
|  | fmt.Fprintf(&b, "; Secure") | 
|  | } | 
|  | return b.String() | 
|  | } | 
|  |  | 
|  | // 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(h Header, filter string) []*Cookie { | 
|  | cookies := []*Cookie{} | 
|  | lines, ok := h["Cookie"] | 
|  | if !ok { | 
|  | return cookies | 
|  | } | 
|  |  | 
|  | for _, line := range lines { | 
|  | parts := strings.Split(strings.TrimSpace(line), ";") | 
|  | if len(parts) == 1 && parts[0] == "" { | 
|  | continue | 
|  | } | 
|  | // Per-line attributes | 
|  | parsedPairs := 0 | 
|  | for i := 0; i < len(parts); i++ { | 
|  | parts[i] = strings.TrimSpace(parts[i]) | 
|  | if len(parts[i]) == 0 { | 
|  | continue | 
|  | } | 
|  | name, val := parts[i], "" | 
|  | if j := strings.Index(name, "="); j >= 0 { | 
|  | name, val = name[:j], name[j+1:] | 
|  | } | 
|  | if !isCookieNameValid(name) { | 
|  | continue | 
|  | } | 
|  | if filter != "" && filter != name { | 
|  | continue | 
|  | } | 
|  | val, success := parseCookieValue(val) | 
|  | if !success { | 
|  | continue | 
|  | } | 
|  | cookies = append(cookies, &Cookie{Name: name, Value: val}) | 
|  | parsedPairs++ | 
|  | } | 
|  | } | 
|  | return cookies | 
|  | } | 
|  |  | 
|  | var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") | 
|  |  | 
|  | func sanitizeName(n string) string { | 
|  | return cookieNameSanitizer.Replace(n) | 
|  | } | 
|  |  | 
|  | var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") | 
|  |  | 
|  | func sanitizeValue(v string) string { | 
|  | return cookieValueSanitizer.Replace(v) | 
|  | } | 
|  |  | 
|  | func unquoteCookieValue(v string) string { | 
|  | if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { | 
|  | return v[1 : len(v)-1] | 
|  | } | 
|  | return v | 
|  | } | 
|  |  | 
|  | func isCookieByte(c byte) bool { | 
|  | switch { | 
|  | case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, | 
|  | 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: | 
|  | return true | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func isCookieExpiresByte(c byte) (ok bool) { | 
|  | return isCookieByte(c) || c == ',' || c == ' ' | 
|  | } | 
|  |  | 
|  | func parseCookieValue(raw string) (string, bool) { | 
|  | return parseCookieValueUsing(raw, isCookieByte) | 
|  | } | 
|  |  | 
|  | func parseCookieExpiresValue(raw string) (string, bool) { | 
|  | return parseCookieValueUsing(raw, isCookieExpiresByte) | 
|  | } | 
|  |  | 
|  | func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { | 
|  | raw = unquoteCookieValue(raw) | 
|  | for i := 0; i < len(raw); i++ { | 
|  | if !validByte(raw[i]) { | 
|  | return "", false | 
|  | } | 
|  | } | 
|  | return raw, true | 
|  | } | 
|  |  | 
|  | func isCookieNameValid(raw string) bool { | 
|  | for _, c := range raw { | 
|  | if !isToken(byte(c)) { | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | } |