github.com/ader1990/go@v0.0.0-20140630135419-8c24447fa791/src/pkg/net/http/cookiejar/jar.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package cookiejar implements an in-memory RFC 6265-compliant http.CookieJar. 6 package cookiejar 7 8 import ( 9 "errors" 10 "fmt" 11 "net" 12 "net/http" 13 "net/url" 14 "sort" 15 "strings" 16 "sync" 17 "time" 18 ) 19 20 // PublicSuffixList provides the public suffix of a domain. For example: 21 // - the public suffix of "example.com" is "com", 22 // - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and 23 // - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us". 24 // 25 // Implementations of PublicSuffixList must be safe for concurrent use by 26 // multiple goroutines. 27 // 28 // An implementation that always returns "" is valid and may be useful for 29 // testing but it is not secure: it means that the HTTP server for foo.com can 30 // set a cookie for bar.com. 31 // 32 // A public suffix list implementation is in the package 33 // code.google.com/p/go.net/publicsuffix. 34 type PublicSuffixList interface { 35 // PublicSuffix returns the public suffix of domain. 36 // 37 // TODO: specify which of the caller and callee is responsible for IP 38 // addresses, for leading and trailing dots, for case sensitivity, and 39 // for IDN/Punycode. 40 PublicSuffix(domain string) string 41 42 // String returns a description of the source of this public suffix 43 // list. The description will typically contain something like a time 44 // stamp or version number. 45 String() string 46 } 47 48 // Options are the options for creating a new Jar. 49 type Options struct { 50 // PublicSuffixList is the public suffix list that determines whether 51 // an HTTP server can set a cookie for a domain. 52 // 53 // A nil value is valid and may be useful for testing but it is not 54 // secure: it means that the HTTP server for foo.co.uk can set a cookie 55 // for bar.co.uk. 56 PublicSuffixList PublicSuffixList 57 } 58 59 // Jar implements the http.CookieJar interface from the net/http package. 60 type Jar struct { 61 psList PublicSuffixList 62 63 // mu locks the remaining fields. 64 mu sync.Mutex 65 66 // entries is a set of entries, keyed by their eTLD+1 and subkeyed by 67 // their name/domain/path. 68 entries map[string]map[string]entry 69 70 // nextSeqNum is the next sequence number assigned to a new cookie 71 // created SetCookies. 72 nextSeqNum uint64 73 } 74 75 // New returns a new cookie jar. A nil *Options is equivalent to a zero 76 // Options. 77 func New(o *Options) (*Jar, error) { 78 jar := &Jar{ 79 entries: make(map[string]map[string]entry), 80 } 81 if o != nil { 82 jar.psList = o.PublicSuffixList 83 } 84 return jar, nil 85 } 86 87 // entry is the internal representation of a cookie. 88 // 89 // This struct type is not used outside of this package per se, but the exported 90 // fields are those of RFC 6265. 91 type entry struct { 92 Name string 93 Value string 94 Domain string 95 Path string 96 Secure bool 97 HttpOnly bool 98 Persistent bool 99 HostOnly bool 100 Expires time.Time 101 Creation time.Time 102 LastAccess time.Time 103 104 // seqNum is a sequence number so that Cookies returns cookies in a 105 // deterministic order, even for cookies that have equal Path length and 106 // equal Creation time. This simplifies testing. 107 seqNum uint64 108 } 109 110 // Id returns the domain;path;name triple of e as an id. 111 func (e *entry) id() string { 112 return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name) 113 } 114 115 // shouldSend determines whether e's cookie qualifies to be included in a 116 // request to host/path. It is the caller's responsibility to check if the 117 // cookie is expired. 118 func (e *entry) shouldSend(https bool, host, path string) bool { 119 return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure) 120 } 121 122 // domainMatch implements "domain-match" of RFC 6265 section 5.1.3. 123 func (e *entry) domainMatch(host string) bool { 124 if e.Domain == host { 125 return true 126 } 127 return !e.HostOnly && hasDotSuffix(host, e.Domain) 128 } 129 130 // pathMatch implements "path-match" according to RFC 6265 section 5.1.4. 131 func (e *entry) pathMatch(requestPath string) bool { 132 if requestPath == e.Path { 133 return true 134 } 135 if strings.HasPrefix(requestPath, e.Path) { 136 if e.Path[len(e.Path)-1] == '/' { 137 return true // The "/any/" matches "/any/path" case. 138 } else if requestPath[len(e.Path)] == '/' { 139 return true // The "/any" matches "/any/path" case. 140 } 141 } 142 return false 143 } 144 145 // hasDotSuffix reports whether s ends in "."+suffix. 146 func hasDotSuffix(s, suffix string) bool { 147 return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix 148 } 149 150 // byPathLength is a []entry sort.Interface that sorts according to RFC 6265 151 // section 5.4 point 2: by longest path and then by earliest creation time. 152 type byPathLength []entry 153 154 func (s byPathLength) Len() int { return len(s) } 155 156 func (s byPathLength) Less(i, j int) bool { 157 if len(s[i].Path) != len(s[j].Path) { 158 return len(s[i].Path) > len(s[j].Path) 159 } 160 if !s[i].Creation.Equal(s[j].Creation) { 161 return s[i].Creation.Before(s[j].Creation) 162 } 163 return s[i].seqNum < s[j].seqNum 164 } 165 166 func (s byPathLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 167 168 // Cookies implements the Cookies method of the http.CookieJar interface. 169 // 170 // It returns an empty slice if the URL's scheme is not HTTP or HTTPS. 171 func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) { 172 return j.cookies(u, time.Now()) 173 } 174 175 // cookies is like Cookies but takes the current time as a parameter. 176 func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) { 177 if u.Scheme != "http" && u.Scheme != "https" { 178 return cookies 179 } 180 host, err := canonicalHost(u.Host) 181 if err != nil { 182 return cookies 183 } 184 key := jarKey(host, j.psList) 185 186 j.mu.Lock() 187 defer j.mu.Unlock() 188 189 submap := j.entries[key] 190 if submap == nil { 191 return cookies 192 } 193 194 https := u.Scheme == "https" 195 path := u.Path 196 if path == "" { 197 path = "/" 198 } 199 200 modified := false 201 var selected []entry 202 for id, e := range submap { 203 if e.Persistent && !e.Expires.After(now) { 204 delete(submap, id) 205 modified = true 206 continue 207 } 208 if !e.shouldSend(https, host, path) { 209 continue 210 } 211 e.LastAccess = now 212 submap[id] = e 213 selected = append(selected, e) 214 modified = true 215 } 216 if modified { 217 if len(submap) == 0 { 218 delete(j.entries, key) 219 } else { 220 j.entries[key] = submap 221 } 222 } 223 224 sort.Sort(byPathLength(selected)) 225 for _, e := range selected { 226 cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value}) 227 } 228 229 return cookies 230 } 231 232 // SetCookies implements the SetCookies method of the http.CookieJar interface. 233 // 234 // It does nothing if the URL's scheme is not HTTP or HTTPS. 235 func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) { 236 j.setCookies(u, cookies, time.Now()) 237 } 238 239 // setCookies is like SetCookies but takes the current time as parameter. 240 func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) { 241 if len(cookies) == 0 { 242 return 243 } 244 if u.Scheme != "http" && u.Scheme != "https" { 245 return 246 } 247 host, err := canonicalHost(u.Host) 248 if err != nil { 249 return 250 } 251 key := jarKey(host, j.psList) 252 defPath := defaultPath(u.Path) 253 254 j.mu.Lock() 255 defer j.mu.Unlock() 256 257 submap := j.entries[key] 258 259 modified := false 260 for _, cookie := range cookies { 261 e, remove, err := j.newEntry(cookie, now, defPath, host) 262 if err != nil { 263 continue 264 } 265 id := e.id() 266 if remove { 267 if submap != nil { 268 if _, ok := submap[id]; ok { 269 delete(submap, id) 270 modified = true 271 } 272 } 273 continue 274 } 275 if submap == nil { 276 submap = make(map[string]entry) 277 } 278 279 if old, ok := submap[id]; ok { 280 e.Creation = old.Creation 281 e.seqNum = old.seqNum 282 } else { 283 e.Creation = now 284 e.seqNum = j.nextSeqNum 285 j.nextSeqNum++ 286 } 287 e.LastAccess = now 288 submap[id] = e 289 modified = true 290 } 291 292 if modified { 293 if len(submap) == 0 { 294 delete(j.entries, key) 295 } else { 296 j.entries[key] = submap 297 } 298 } 299 } 300 301 // canonicalHost strips port from host if present and returns the canonicalized 302 // host name. 303 func canonicalHost(host string) (string, error) { 304 var err error 305 host = strings.ToLower(host) 306 if hasPort(host) { 307 host, _, err = net.SplitHostPort(host) 308 if err != nil { 309 return "", err 310 } 311 } 312 if strings.HasSuffix(host, ".") { 313 // Strip trailing dot from fully qualified domain names. 314 host = host[:len(host)-1] 315 } 316 return toASCII(host) 317 } 318 319 // hasPort reports whether host contains a port number. host may be a host 320 // name, an IPv4 or an IPv6 address. 321 func hasPort(host string) bool { 322 colons := strings.Count(host, ":") 323 if colons == 0 { 324 return false 325 } 326 if colons == 1 { 327 return true 328 } 329 return host[0] == '[' && strings.Contains(host, "]:") 330 } 331 332 // jarKey returns the key to use for a jar. 333 func jarKey(host string, psl PublicSuffixList) string { 334 if isIP(host) { 335 return host 336 } 337 338 var i int 339 if psl == nil { 340 i = strings.LastIndex(host, ".") 341 if i == -1 { 342 return host 343 } 344 } else { 345 suffix := psl.PublicSuffix(host) 346 if suffix == host { 347 return host 348 } 349 i = len(host) - len(suffix) 350 if i <= 0 || host[i-1] != '.' { 351 // The provided public suffix list psl is broken. 352 // Storing cookies under host is a safe stopgap. 353 return host 354 } 355 } 356 prevDot := strings.LastIndex(host[:i-1], ".") 357 return host[prevDot+1:] 358 } 359 360 // isIP reports whether host is an IP address. 361 func isIP(host string) bool { 362 return net.ParseIP(host) != nil 363 } 364 365 // defaultPath returns the directory part of an URL's path according to 366 // RFC 6265 section 5.1.4. 367 func defaultPath(path string) string { 368 if len(path) == 0 || path[0] != '/' { 369 return "/" // Path is empty or malformed. 370 } 371 372 i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1. 373 if i == 0 { 374 return "/" // Path has the form "/abc". 375 } 376 return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/". 377 } 378 379 // newEntry creates an entry from a http.Cookie c. now is the current time and 380 // is compared to c.Expires to determine deletion of c. defPath and host are the 381 // default-path and the canonical host name of the URL c was received from. 382 // 383 // remove records whether the jar should delete this cookie, as it has already 384 // expired with respect to now. In this case, e may be incomplete, but it will 385 // be valid to call e.id (which depends on e's Name, Domain and Path). 386 // 387 // A malformed c.Domain will result in an error. 388 func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) { 389 e.Name = c.Name 390 391 if c.Path == "" || c.Path[0] != '/' { 392 e.Path = defPath 393 } else { 394 e.Path = c.Path 395 } 396 397 e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain) 398 if err != nil { 399 return e, false, err 400 } 401 402 // MaxAge takes precedence over Expires. 403 if c.MaxAge < 0 { 404 return e, true, nil 405 } else if c.MaxAge > 0 { 406 e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second) 407 e.Persistent = true 408 } else { 409 if c.Expires.IsZero() { 410 e.Expires = endOfTime 411 e.Persistent = false 412 } else { 413 if !c.Expires.After(now) { 414 return e, true, nil 415 } 416 e.Expires = c.Expires 417 e.Persistent = true 418 } 419 } 420 421 e.Value = c.Value 422 e.Secure = c.Secure 423 e.HttpOnly = c.HttpOnly 424 425 return e, false, nil 426 } 427 428 var ( 429 errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute") 430 errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute") 431 errNoHostname = errors.New("cookiejar: no host name available (IP only)") 432 ) 433 434 // endOfTime is the time when session (non-persistent) cookies expire. 435 // This instant is representable in most date/time formats (not just 436 // Go's time.Time) and should be far enough in the future. 437 var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC) 438 439 // domainAndType determines the cookie's domain and hostOnly attribute. 440 func (j *Jar) domainAndType(host, domain string) (string, bool, error) { 441 if domain == "" { 442 // No domain attribute in the SetCookie header indicates a 443 // host cookie. 444 return host, true, nil 445 } 446 447 if isIP(host) { 448 // According to RFC 6265 domain-matching includes not being 449 // an IP address. 450 // TODO: This might be relaxed as in common browsers. 451 return "", false, errNoHostname 452 } 453 454 // From here on: If the cookie is valid, it is a domain cookie (with 455 // the one exception of a public suffix below). 456 // See RFC 6265 section 5.2.3. 457 if domain[0] == '.' { 458 domain = domain[1:] 459 } 460 461 if len(domain) == 0 || domain[0] == '.' { 462 // Received either "Domain=." or "Domain=..some.thing", 463 // both are illegal. 464 return "", false, errMalformedDomain 465 } 466 domain = strings.ToLower(domain) 467 468 if domain[len(domain)-1] == '.' { 469 // We received stuff like "Domain=www.example.com.". 470 // Browsers do handle such stuff (actually differently) but 471 // RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in 472 // requiring a reject. 4.1.2.3 is not normative, but 473 // "Domain Matching" (5.1.3) and "Canonicalized Host Names" 474 // (5.1.2) are. 475 return "", false, errMalformedDomain 476 } 477 478 // See RFC 6265 section 5.3 #5. 479 if j.psList != nil { 480 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) { 481 if host == domain { 482 // This is the one exception in which a cookie 483 // with a domain attribute is a host cookie. 484 return host, true, nil 485 } 486 return "", false, errIllegalDomain 487 } 488 } 489 490 // The domain must domain-match host: www.mycompany.com cannot 491 // set cookies for .ourcompetitors.com. 492 if host != domain && !hasDotSuffix(host, domain) { 493 return "", false, errIllegalDomain 494 } 495 496 return domain, false, nil 497 }