Skip to content

Commit 4f86a96

Browse files
vdoblerbradfitz
authored andcommitted
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 golang#6013 R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/12745043
1 parent e838334 commit 4f86a96

File tree

2 files changed

+91
-14
lines changed

2 files changed

+91
-14
lines changed

src/pkg/net/http/cookie.go

+75-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"fmt"
1010
"log"
11+
"net"
1112
"strconv"
1213
"strings"
1314
"time"
@@ -145,7 +146,15 @@ func (c *Cookie) String() string {
145146
fmt.Fprintf(&b, "; Path=%s", sanitizeCookiePath(c.Path))
146147
}
147148
if len(c.Domain) > 0 {
148-
fmt.Fprintf(&b, "; Domain=%s", sanitizeCookieDomain(c.Domain))
149+
if validCookieDomain(c.Domain) {
150+
// A c.Domain containing illegal characters is not
151+
// sanitized but simply dropped which turns the cookie
152+
// into a host-only cookie.
153+
fmt.Fprintf(&b, "; Domain=%s", c.Domain)
154+
} else {
155+
log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute",
156+
c.Domain)
157+
}
149158
}
150159
if c.Expires.Unix() > 0 {
151160
fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123))
@@ -208,26 +217,78 @@ func readCookies(h Header, filter string) []*Cookie {
208217
return cookies
209218
}
210219

211-
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
220+
// validCookieDomain returns wheter v is a valid cookie domain-value.
221+
func validCookieDomain(v string) bool {
222+
if isCookieDomainName(v) {
223+
return true
224+
}
225+
if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
226+
return true
227+
}
228+
return false
229+
}
212230

213-
// http://tools.ietf.org/html/rfc6265#section-4.1.1
214-
// domain-av = "Domain=" domain-value
215-
// domain-value = <subdomain>
216-
// ; defined in [RFC1034], Section 3.5, as
217-
// ; enhanced by [RFC1123], Section 2.1
218-
func sanitizeCookieDomain(v string) string {
219-
// TODO: implement http://tools.ietf.org/html/rfc1034#section-3.5
220-
return oldCookieValueSanitizer.Replace(v)
231+
// isCookieDomainName returns whether s is a valid domain name or a valid
232+
// domain name with a leading dot '.'. It is almost a direct copy of
233+
// package net's isDomainName.
234+
func isCookieDomainName(s string) bool {
235+
if len(s) == 0 {
236+
return false
237+
}
238+
if len(s) > 255 {
239+
return false
240+
}
241+
242+
if s[0] == '.' {
243+
// A cookie a domain attribute may start with a leading dot.
244+
s = s[1:]
245+
}
246+
last := byte('.')
247+
ok := false // Ok once we've seen a letter.
248+
partlen := 0
249+
for i := 0; i < len(s); i++ {
250+
c := s[i]
251+
switch {
252+
default:
253+
return false
254+
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
255+
// No '_' allowed here (in contrast to package net).
256+
ok = true
257+
partlen++
258+
case '0' <= c && c <= '9':
259+
// fine
260+
partlen++
261+
case c == '-':
262+
// Byte before dash cannot be dot.
263+
if last == '.' {
264+
return false
265+
}
266+
partlen++
267+
case c == '.':
268+
// Byte before dot cannot be dot, dash.
269+
if last == '.' || last == '-' {
270+
return false
271+
}
272+
if partlen > 63 || partlen == 0 {
273+
return false
274+
}
275+
partlen = 0
276+
}
277+
last = c
278+
}
279+
if last == '-' || partlen > 63 {
280+
return false
281+
}
282+
283+
return ok
221284
}
222285

286+
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
287+
223288
func sanitizeCookieName(n string) string {
224289
return cookieNameSanitizer.Replace(n)
225290
}
226291

227-
// This is the replacer used in the original Go cookie code.
228-
// It's not correct, but it's here for now until it's replaced.
229-
var oldCookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
230-
231292
// http://tools.ietf.org/html/rfc6265#section-4.1.1
232293
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
233294
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E

src/pkg/net/http/cookie_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ var writeSetCookiesTests = []struct {
3232
&Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
3333
"cookie-4=four; Path=/restricted/",
3434
},
35+
{
36+
&Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
37+
"cookie-5=five",
38+
},
39+
{
40+
&Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
41+
"cookie-6=six",
42+
},
43+
{
44+
&Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
45+
"cookie-7=seven; Domain=127.0.0.1",
46+
},
47+
{
48+
&Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
49+
"cookie-8=eight",
50+
},
3551
}
3652

3753
func TestWriteSetCookies(t *testing.T) {

0 commit comments

Comments
 (0)