diff options
Diffstat (limited to 'src/pkg/http/cookie.go')
-rw-r--r-- | src/pkg/http/cookie.go | 117 |
1 files changed, 76 insertions, 41 deletions
diff --git a/src/pkg/http/cookie.go b/src/pkg/http/cookie.go index 2c01826a1..eb61a7001 100644 --- a/src/pkg/http/cookie.go +++ b/src/pkg/http/cookie.go @@ -15,9 +15,9 @@ import ( "time" ) -// This implementation is done according to IETF draft-ietf-httpstate-cookie-23, found at +// This implementation is done according to RFC 6265: // -// http://tools.ietf.org/html/draft-ietf-httpstate-cookie-23 +// 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. @@ -81,12 +81,17 @@ func readSetCookies(h Header) []*Cookie { if j := strings.Index(attr, "="); j >= 0 { attr, val = attr[:j], attr[j+1:] } - val, success = parseCookieValue(val) + 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 strings.ToLower(attr) { + switch lowerAttr { case "secure": c.Secure = true continue @@ -112,8 +117,11 @@ func readSetCookies(h Header) []*Cookie { c.RawExpires = val exptime, err := time.Parse(time.RFC1123, val) if err != nil { - c.Expires = time.Time{} - break + exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) + if err != nil { + c.Expires = time.Time{} + break + } } c.Expires = *exptime continue @@ -130,6 +138,37 @@ func readSetCookies(h Header) []*Cookie { return cookies } +// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. +func SetCookie(w ResponseWriter, cookie *Cookie) { + var b bytes.Buffer + writeSetCookieToBuffer(&b, cookie) + w.Header().Add("Set-Cookie", b.String()) +} + +func writeSetCookieToBuffer(buf *bytes.Buffer, c *Cookie) { + fmt.Fprintf(buf, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) + if len(c.Path) > 0 { + fmt.Fprintf(buf, "; Path=%s", sanitizeValue(c.Path)) + } + if len(c.Domain) > 0 { + fmt.Fprintf(buf, "; Domain=%s", sanitizeValue(c.Domain)) + } + if len(c.Expires.Zone) > 0 { + fmt.Fprintf(buf, "; Expires=%s", c.Expires.Format(time.RFC1123)) + } + if c.MaxAge > 0 { + fmt.Fprintf(buf, "; Max-Age=%d", c.MaxAge) + } else if c.MaxAge < 0 { + fmt.Fprintf(buf, "; Max-Age=0") + } + if c.HttpOnly { + fmt.Fprintf(buf, "; HttpOnly") + } + if c.Secure { + fmt.Fprintf(buf, "; Secure") + } +} + // writeSetCookies writes the wire representation of the set-cookies // to w. Each cookie is written on a separate "Set-Cookie: " line. // This choice is made because HTTP parsers tend to have a limit on @@ -142,27 +181,7 @@ func writeSetCookies(w io.Writer, kk []*Cookie) os.Error { var b bytes.Buffer for _, c := range kk { b.Reset() - 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 len(c.Expires.Zone) > 0 { - fmt.Fprintf(&b, "; Expires=%s", c.Expires.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") - } + writeSetCookieToBuffer(&b, c) lines = append(lines, "Set-Cookie: "+b.String()+"\r\n") } sort.SortStrings(lines) @@ -218,22 +237,26 @@ func readCookies(h Header) []*Cookie { return cookies } -// writeCookies writes the wire representation of the cookies -// to w. Each cookie is written on a separate "Cookie: " line. -// This choice is made because HTTP parsers tend to have a limit on -// line-length, so it seems safer to place cookies on separate lines. +// writeCookies writes the wire representation of the cookies to +// w. According to RFC 6265 section 5.4, writeCookies does not +// attach more than one Cookie header field. That means all +// cookies, if any, are written into the same line, separated by +// semicolon. func writeCookies(w io.Writer, kk []*Cookie) os.Error { - lines := make([]string, 0, len(kk)) - for _, c := range kk { - lines = append(lines, fmt.Sprintf("Cookie: %s=%s\r\n", sanitizeName(c.Name), sanitizeValue(c.Value))) + if len(kk) == 0 { + return nil } - sort.SortStrings(lines) - for _, l := range lines { - if _, err := io.WriteString(w, l); err != nil { - return err + var buf bytes.Buffer + fmt.Fprintf(&buf, "Cookie: ") + for i, c := range kk { + if i > 0 { + fmt.Fprintf(&buf, "; ") } + fmt.Fprintf(&buf, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) } - return nil + fmt.Fprintf(&buf, "\r\n") + _, err := w.Write(buf.Bytes()) + return err } func sanitizeName(n string) string { @@ -257,7 +280,7 @@ func unquoteCookieValue(v string) string { } func isCookieByte(c byte) bool { - switch true { + switch { case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: return true @@ -265,10 +288,22 @@ func isCookieByte(c byte) bool { 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 !isCookieByte(raw[i]) { + if !validByte(raw[i]) { return "", false } } |