jar.go 13 KB

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