mirror of
https://github.com/golang/go
synced 2024-11-22 08:54:39 -07:00
net/http: do not send malformed cookie domain attribute
Malformed domain attributes are not sent in a Set-Cookie header. Instead the domain attribute is dropped which turns the cookie into a host-only cookie. This is much safer than dropping characters from domain attribute. Domain attributes with a leading dot '.' are still allowed, even if discouraged by RFC 6265 section 4.1.1. Fixes #6013 R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/12745043
This commit is contained in:
parent
e838334beb
commit
4f86a96ac9
@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -145,7 +146,15 @@ func (c *Cookie) String() string {
|
||||
fmt.Fprintf(&b, "; Path=%s", sanitizeCookiePath(c.Path))
|
||||
}
|
||||
if len(c.Domain) > 0 {
|
||||
fmt.Fprintf(&b, "; Domain=%s", sanitizeCookieDomain(c.Domain))
|
||||
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.
|
||||
fmt.Fprintf(&b, "; Domain=%s", c.Domain)
|
||||
} else {
|
||||
log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute",
|
||||
c.Domain)
|
||||
}
|
||||
}
|
||||
if c.Expires.Unix() > 0 {
|
||||
fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123))
|
||||
@ -208,26 +217,78 @@ func readCookies(h Header, filter string) []*Cookie {
|
||||
return cookies
|
||||
}
|
||||
|
||||
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
|
||||
|
||||
// http://tools.ietf.org/html/rfc6265#section-4.1.1
|
||||
// domain-av = "Domain=" domain-value
|
||||
// domain-value = <subdomain>
|
||||
// ; defined in [RFC1034], Section 3.5, as
|
||||
// ; enhanced by [RFC1123], Section 2.1
|
||||
func sanitizeCookieDomain(v string) string {
|
||||
// TODO: implement http://tools.ietf.org/html/rfc1034#section-3.5
|
||||
return oldCookieValueSanitizer.Replace(v)
|
||||
// validCookieDomain returns wheter v is a valid cookie domain-value.
|
||||
func validCookieDomain(v string) bool {
|
||||
if isCookieDomainName(v) {
|
||||
return true
|
||||
}
|
||||
if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCookieDomainName returns 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(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", "-")
|
||||
|
||||
func sanitizeCookieName(n string) string {
|
||||
return cookieNameSanitizer.Replace(n)
|
||||
}
|
||||
|
||||
// This is the replacer used in the original Go cookie code.
|
||||
// It's not correct, but it's here for now until it's replaced.
|
||||
var oldCookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
|
||||
|
||||
// http://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
|
||||
|
@ -32,6 +32,22 @@ var writeSetCookiesTests = []struct {
|
||||
&Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
|
||||
"cookie-4=four; Path=/restricted/",
|
||||
},
|
||||
{
|
||||
&Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
|
||||
"cookie-5=five",
|
||||
},
|
||||
{
|
||||
&Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
|
||||
"cookie-6=six",
|
||||
},
|
||||
{
|
||||
&Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
|
||||
"cookie-7=seven; Domain=127.0.0.1",
|
||||
},
|
||||
{
|
||||
&Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
|
||||
"cookie-8=eight",
|
||||
},
|
||||
}
|
||||
|
||||
func TestWriteSetCookies(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user