return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
}
-// domainMatch implements "domain-match" of RFC 6265 section 5.1.3.
+// domainMatch checks whether e's Domain allows sending e back to host.
+// It differs from "domain-match" of RFC 6265 section 5.1.3 because we treat
+// a cookie with an IP address in the Domain always as a host cookie.
func (e *entry) domainMatch(host string) bool {
if e.Domain == host {
return true
}
if isIP(host) {
- // According to RFC 6265 domain-matching includes not being
- // an IP address.
- // TODO: This might be relaxed as in common browsers.
- return "", false, errNoHostname
+ // RFC 6265 is not super clear here, a sensible interpretation
+ // is that cookies with an IP address in the domain-attribute
+ // are allowed.
+
+ // RFC 6265 section 5.2.3 mandates to strip an optional leading
+ // dot in the domain-attribute before processing the cookie.
+ //
+ // Most browsers don't do that for IP addresses, only curl
+ // version 7.54) and and IE (version 11) do not reject a
+ // Set-Cookie: a=1; domain=.127.0.0.1
+ // This leading dot is optional and serves only as hint for
+ // humans to indicate that a cookie with "domain=.bbc.co.uk"
+ // would be sent to every subdomain of bbc.co.uk.
+ // It just doesn't make sense on IP addresses.
+ // The other processing and validation steps in RFC 6265 just
+ // collaps to:
+ if host != domain {
+ return "", false, errIllegalDomain
+ }
+
+ // According to RFC 6265 such cookies should be treated as
+ // domain cookies.
+ // As there are no subdomains of an IP address the treatment
+ // according to RFC 6265 would be exactly the same as that of
+ // a host-only cookie. Contemporary browsers (and curl) do
+ // allows such cookies but treat them as host-only cookies.
+ // So do we as it just doesn't make sense to label them as
+ // domain cookies when there is no domain; the whole notion of
+ // domain cookies requires a domain name to be well defined.
+ return host, true, nil
}
// From here on: If the cookie is valid, it is a domain cookie (with
{"foo.sso.example.com", "sso.example.com", "sso.example.com", false, nil},
{"bar.co.uk", "bar.co.uk", "bar.co.uk", false, nil},
{"foo.bar.co.uk", ".bar.co.uk", "bar.co.uk", false, nil},
- {"127.0.0.1", "127.0.0.1", "", false, errNoHostname},
- {"2001:4860:0:2001::68", "2001:4860:0:2001::68", "2001:4860:0:2001::68", false, errNoHostname},
+ {"127.0.0.1", "127.0.0.1", "127.0.0.1", true, nil},
+ {"2001:4860:0:2001::68", "2001:4860:0:2001::68", "2001:4860:0:2001::68", true, nil},
{"www.example.com", ".", "", false, errMalformedDomain},
{"www.example.com", "..", "", false, errMalformedDomain},
{"www.example.com", "other.com", "", false, errIllegalDomain},
for _, tc := range domainAndTypeTests {
domain, hostOnly, err := jar.domainAndType(tc.host, tc.domain)
if err != tc.wantErr {
- t.Errorf("%q/%q: got %q error, want %q",
+ t.Errorf("%q/%q: got %q error, want %v",
tc.host, tc.domain, err, tc.wantErr)
continue
}
"a=1",
[]query{{"http://192.168.0.10", "a=1"}},
},
+ {
+ "Domain cookies on IP.",
+ "http://192.168.0.10",
+ []string{
+ "a=1; domain=192.168.0.10", // allowed
+ "b=2; domain=172.31.9.9", // rejected, can't set cookie for other IP
+ "c=3; domain=.192.168.0.10", // rejected like in most browsers
+ },
+ "a=1",
+ []query{
+ {"http://192.168.0.10", "a=1"},
+ {"http://172.31.9.9", ""},
+ {"http://www.fancy.192.168.0.10", ""},
+ },
+ },
{
"Port is ignored #1.",
"http://www.host.test/",
{
"TestIpAddress #3.",
"http://1.2.3.4/foo",
- []string{"a=1; domain=1.2.3.4"},
+ []string{"a=1; domain=1.2.3.3"},
"",
[]query{{"http://1.2.3.4/foo", ""}},
},
+ {
+ "TestIpAddress #4.",
+ "http://1.2.3.4/foo",
+ []string{"a=1; domain=1.2.3.4"},
+ "a=1",
+ []query{{"http://1.2.3.4/foo", "a=1"}},
+ },
{
"TestNonDottedAndTLD #2.",
"http://com./index.html",