blob: 4552412f67a7a7348c9b56f13ed0477d92c98b36 [file] [log] [blame]
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001"""HTTP cookie handling for web clients.
2
3This module has (now fairly distant) origins in Gisle Aas' Perl module
4HTTP::Cookies, from the libwww-perl library.
5
6Docstrings, comments and debug strings in this code refer to the
7attributes of the HTTP cookie system as cookie-attributes, to distinguish
8them clearly from Python attributes.
9
Thomas Wouters477c8d52006-05-27 19:21:47 +000010Class diagram (note that BSDDBCookieJar and the MSIE* classes are not
11distributed with the Python standard library, but are available from
12http://wwwsearch.sf.net/):
Martin v. Löwis2a6ba902004-05-31 18:22:40 +000013
14 CookieJar____
15 / \ \
16 FileCookieJar \ \
17 / | \ \ \
18 MozillaCookieJar | LWPCookieJar \ \
19 | | \
20 | ---MSIEBase | \
21 | / | | \
22 | / MSIEDBCookieJar BSDDBCookieJar
23 |/
24 MSIECookieJar
25
26"""
27
Thomas Wouters477c8d52006-05-27 19:21:47 +000028__all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy',
29 'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar']
30
31import re, urlparse, copy, time, urllib
Martin v. Löwis2a6ba902004-05-31 18:22:40 +000032try:
33 import threading as _threading
34except ImportError:
35 import dummy_threading as _threading
36import httplib # only for the default HTTP port
37from calendar import timegm
38
Thomas Wouters477c8d52006-05-27 19:21:47 +000039debug = False # set to True to enable debugging via the logging module
40logger = None
41
42def _debug(*args):
43 if not debug:
44 return
45 global logger
46 if not logger:
47 import logging
48 logger = logging.getLogger("cookielib")
49 return logger.debug(*args)
50
Martin v. Löwis2a6ba902004-05-31 18:22:40 +000051
52DEFAULT_HTTP_PORT = str(httplib.HTTP_PORT)
53MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar "
54 "instance initialised with one)")
55
Thomas Wouters477c8d52006-05-27 19:21:47 +000056def _warn_unhandled_exception():
Martin v. Löwis2a6ba902004-05-31 18:22:40 +000057 # There are a few catch-all except: statements in this module, for
Thomas Wouters477c8d52006-05-27 19:21:47 +000058 # catching input that's bad in unexpected ways. Warn if any
59 # exceptions are caught there.
Andrew M. Kuchlingae40c2f2004-07-10 18:32:12 +000060 import warnings, traceback, StringIO
Guido van Rossum34d19282007-08-09 01:03:29 +000061 f = io.StringIO()
Andrew M. Kuchlingae40c2f2004-07-10 18:32:12 +000062 traceback.print_exc(None, f)
63 msg = f.getvalue()
64 warnings.warn("cookielib bug!\n%s" % msg, stacklevel=2)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +000065
66
67# Date/time conversion
68# -----------------------------------------------------------------------------
69
70EPOCH_YEAR = 1970
71def _timegm(tt):
72 year, month, mday, hour, min, sec = tt[:6]
73 if ((year >= EPOCH_YEAR) and (1 <= month <= 12) and (1 <= mday <= 31) and
74 (0 <= hour <= 24) and (0 <= min <= 59) and (0 <= sec <= 61)):
75 return timegm(tt)
76 else:
77 return None
78
79DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
80MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
81 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
82MONTHS_LOWER = []
83for month in MONTHS: MONTHS_LOWER.append(month.lower())
84
85def time2isoz(t=None):
86 """Return a string representing time in seconds since epoch, t.
87
88 If the function is called without an argument, it will use the current
89 time.
90
91 The format of the returned string is like "YYYY-MM-DD hh:mm:ssZ",
92 representing Universal Time (UTC, aka GMT). An example of this format is:
93
94 1994-11-24 08:49:37Z
95
96 """
97 if t is None: t = time.time()
98 year, mon, mday, hour, min, sec = time.gmtime(t)[:6]
99 return "%04d-%02d-%02d %02d:%02d:%02dZ" % (
100 year, mon, mday, hour, min, sec)
101
102def time2netscape(t=None):
103 """Return a string representing time in seconds since epoch, t.
104
105 If the function is called without an argument, it will use the current
106 time.
107
108 The format of the returned string is like this:
109
110 Wed, DD-Mon-YYYY HH:MM:SS GMT
111
112 """
113 if t is None: t = time.time()
114 year, mon, mday, hour, min, sec, wday = time.gmtime(t)[:7]
115 return "%s %02d-%s-%04d %02d:%02d:%02d GMT" % (
116 DAYS[wday], mday, MONTHS[mon-1], year, hour, min, sec)
117
118
119UTC_ZONES = {"GMT": None, "UTC": None, "UT": None, "Z": None}
120
121TIMEZONE_RE = re.compile(r"^([-+])?(\d\d?):?(\d\d)?$")
122def offset_from_tz_string(tz):
123 offset = None
124 if tz in UTC_ZONES:
125 offset = 0
126 else:
127 m = TIMEZONE_RE.search(tz)
128 if m:
129 offset = 3600 * int(m.group(2))
130 if m.group(3):
131 offset = offset + 60 * int(m.group(3))
132 if m.group(1) == '-':
133 offset = -offset
134 return offset
135
136def _str2time(day, mon, yr, hr, min, sec, tz):
137 # translate month name to number
138 # month numbers start with 1 (January)
139 try:
140 mon = MONTHS_LOWER.index(mon.lower())+1
141 except ValueError:
142 # maybe it's already a number
143 try:
144 imon = int(mon)
145 except ValueError:
146 return None
147 if 1 <= imon <= 12:
148 mon = imon
149 else:
150 return None
151
152 # make sure clock elements are defined
153 if hr is None: hr = 0
154 if min is None: min = 0
155 if sec is None: sec = 0
156
157 yr = int(yr)
158 day = int(day)
159 hr = int(hr)
160 min = int(min)
161 sec = int(sec)
162
163 if yr < 1000:
164 # find "obvious" year
165 cur_yr = time.localtime(time.time())[0]
166 m = cur_yr % 100
167 tmp = yr
168 yr = yr + cur_yr - m
169 m = m - tmp
170 if abs(m) > 50:
171 if m > 0: yr = yr + 100
172 else: yr = yr - 100
173
174 # convert UTC time tuple to seconds since epoch (not timezone-adjusted)
175 t = _timegm((yr, mon, day, hr, min, sec, tz))
176
177 if t is not None:
178 # adjust time using timezone string, to get absolute time since epoch
179 if tz is None:
180 tz = "UTC"
181 tz = tz.upper()
182 offset = offset_from_tz_string(tz)
183 if offset is None:
184 return None
185 t = t - offset
186
187 return t
188
189STRICT_DATE_RE = re.compile(
190 r"^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) "
191 "(\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$")
192WEEKDAY_RE = re.compile(
193 r"^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*", re.I)
194LOOSE_HTTP_DATE_RE = re.compile(
195 r"""^
196 (\d\d?) # day
197 (?:\s+|[-\/])
198 (\w+) # month
199 (?:\s+|[-\/])
200 (\d+) # year
201 (?:
202 (?:\s+|:) # separator before clock
203 (\d\d?):(\d\d) # hour:min
204 (?::(\d\d))? # optional seconds
205 )? # optional clock
206 \s*
207 ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone
208 \s*
209 (?:\(\w+\))? # ASCII representation of timezone in parens.
210 \s*$""", re.X)
211def http2time(text):
212 """Returns time in seconds since epoch of time represented by a string.
213
214 Return value is an integer.
215
216 None is returned if the format of str is unrecognized, the time is outside
217 the representable range, or the timezone string is not recognized. If the
218 string contains no timezone, UTC is assumed.
219
220 The timezone in the string may be numerical (like "-0800" or "+0100") or a
221 string timezone (like "UTC", "GMT", "BST" or "EST"). Currently, only the
222 timezone strings equivalent to UTC (zero offset) are known to the function.
223
224 The function loosely parses the following formats:
225
226 Wed, 09 Feb 1994 22:23:32 GMT -- HTTP format
227 Tuesday, 08-Feb-94 14:15:29 GMT -- old rfc850 HTTP format
228 Tuesday, 08-Feb-1994 14:15:29 GMT -- broken rfc850 HTTP format
229 09 Feb 1994 22:23:32 GMT -- HTTP format (no weekday)
230 08-Feb-94 14:15:29 GMT -- rfc850 format (no weekday)
231 08-Feb-1994 14:15:29 GMT -- broken rfc850 format (no weekday)
232
233 The parser ignores leading and trailing whitespace. The time may be
234 absent.
235
236 If the year is given with only 2 digits, the function will select the
237 century that makes the year closest to the current date.
238
239 """
240 # fast exit for strictly conforming string
241 m = STRICT_DATE_RE.search(text)
242 if m:
243 g = m.groups()
244 mon = MONTHS_LOWER.index(g[1].lower()) + 1
245 tt = (int(g[2]), mon, int(g[0]),
246 int(g[3]), int(g[4]), float(g[5]))
247 return _timegm(tt)
248
249 # No, we need some messy parsing...
250
251 # clean up
252 text = text.lstrip()
253 text = WEEKDAY_RE.sub("", text, 1) # Useless weekday
254
255 # tz is time zone specifier string
256 day, mon, yr, hr, min, sec, tz = [None]*7
257
258 # loose regexp parse
259 m = LOOSE_HTTP_DATE_RE.search(text)
260 if m is not None:
261 day, mon, yr, hr, min, sec, tz = m.groups()
262 else:
263 return None # bad format
264
265 return _str2time(day, mon, yr, hr, min, sec, tz)
266
267ISO_DATE_RE = re.compile(
268 """^
269 (\d{4}) # year
270 [-\/]?
271 (\d\d?) # numerical month
272 [-\/]?
273 (\d\d?) # day
274 (?:
275 (?:\s+|[-:Tt]) # separator before clock
276 (\d\d?):?(\d\d) # hour:min
277 (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional)
278 )? # optional clock
279 \s*
280 ([-+]?\d\d?:?(:?\d\d)?
281 |Z|z)? # timezone (Z is "zero meridian", i.e. GMT)
282 \s*$""", re.X)
283def iso2time(text):
284 """
285 As for http2time, but parses the ISO 8601 formats:
286
287 1994-02-03 14:15:29 -0100 -- ISO 8601 format
288 1994-02-03 14:15:29 -- zone is optional
289 1994-02-03 -- only date
290 1994-02-03T14:15:29 -- Use T as separator
291 19940203T141529Z -- ISO 8601 compact format
292 19940203 -- only date
293
294 """
295 # clean up
296 text = text.lstrip()
297
298 # tz is time zone specifier string
299 day, mon, yr, hr, min, sec, tz = [None]*7
300
301 # loose regexp parse
302 m = ISO_DATE_RE.search(text)
303 if m is not None:
304 # XXX there's an extra bit of the timezone I'm ignoring here: is
305 # this the right thing to do?
306 yr, mon, day, hr, min, sec, tz, _ = m.groups()
307 else:
308 return None # bad format
309
310 return _str2time(day, mon, yr, hr, min, sec, tz)
311
312
313# Header parsing
314# -----------------------------------------------------------------------------
315
316def unmatched(match):
317 """Return unmatched part of re.Match object."""
318 start, end = match.span(0)
319 return match.string[:start]+match.string[end:]
320
321HEADER_TOKEN_RE = re.compile(r"^\s*([^=\s;,]+)")
322HEADER_QUOTED_VALUE_RE = re.compile(r"^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"")
323HEADER_VALUE_RE = re.compile(r"^\s*=\s*([^\s;,]*)")
324HEADER_ESCAPE_RE = re.compile(r"\\(.)")
325def split_header_words(header_values):
326 r"""Parse header values into a list of lists containing key,value pairs.
327
328 The function knows how to deal with ",", ";" and "=" as well as quoted
329 values after "=". A list of space separated tokens are parsed as if they
330 were separated by ";".
331
332 If the header_values passed as argument contains multiple values, then they
333 are treated as if they were a single value separated by comma ",".
334
335 This means that this function is useful for parsing header fields that
336 follow this syntax (BNF as from the HTTP/1.1 specification, but we relax
337 the requirement for tokens).
338
339 headers = #header
340 header = (token | parameter) *( [";"] (token | parameter))
341
342 token = 1*<any CHAR except CTLs or separators>
343 separators = "(" | ")" | "<" | ">" | "@"
344 | "," | ";" | ":" | "\" | <">
345 | "/" | "[" | "]" | "?" | "="
346 | "{" | "}" | SP | HT
347
348 quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
349 qdtext = <any TEXT except <">>
350 quoted-pair = "\" CHAR
351
352 parameter = attribute "=" value
353 attribute = token
354 value = token | quoted-string
355
356 Each header is represented by a list of key/value pairs. The value for a
357 simple token (not part of a parameter) is None. Syntactically incorrect
358 headers will not necessarily be parsed as you would want.
359
360 This is easier to describe with some examples:
361
362 >>> split_header_words(['foo="bar"; port="80,81"; discard, bar=baz'])
363 [[('foo', 'bar'), ('port', '80,81'), ('discard', None)], [('bar', 'baz')]]
364 >>> split_header_words(['text/html; charset="iso-8859-1"'])
365 [[('text/html', None), ('charset', 'iso-8859-1')]]
366 >>> split_header_words([r'Basic realm="\"foo\bar\""'])
367 [[('Basic', None), ('realm', '"foobar"')]]
368
369 """
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000370 assert not isinstance(header_values, str)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000371 result = []
372 for text in header_values:
373 orig_text = text
374 pairs = []
375 while text:
376 m = HEADER_TOKEN_RE.search(text)
377 if m:
378 text = unmatched(m)
379 name = m.group(1)
380 m = HEADER_QUOTED_VALUE_RE.search(text)
381 if m: # quoted value
382 text = unmatched(m)
383 value = m.group(1)
384 value = HEADER_ESCAPE_RE.sub(r"\1", value)
385 else:
386 m = HEADER_VALUE_RE.search(text)
387 if m: # unquoted value
388 text = unmatched(m)
389 value = m.group(1)
390 value = value.rstrip()
391 else:
392 # no value, a lone token
393 value = None
394 pairs.append((name, value))
395 elif text.lstrip().startswith(","):
396 # concatenated headers, as per RFC 2616 section 4.2
397 text = text.lstrip()[1:]
398 if pairs: result.append(pairs)
399 pairs = []
400 else:
401 # skip junk
402 non_junk, nr_junk_chars = re.subn("^[=\s;]*", "", text)
403 assert nr_junk_chars > 0, (
404 "split_header_words bug: '%s', '%s', %s" %
405 (orig_text, text, pairs))
406 text = non_junk
407 if pairs: result.append(pairs)
408 return result
409
410HEADER_JOIN_ESCAPE_RE = re.compile(r"([\"\\])")
411def join_header_words(lists):
412 """Do the inverse (almost) of the conversion done by split_header_words.
413
414 Takes a list of lists of (key, value) pairs and produces a single header
415 value. Attribute values are quoted if needed.
416
417 >>> join_header_words([[("text/plain", None), ("charset", "iso-8859/1")]])
418 'text/plain; charset="iso-8859/1"'
419 >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859/1")]])
420 'text/plain, charset="iso-8859/1"'
421
422 """
423 headers = []
424 for pairs in lists:
425 attr = []
426 for k, v in pairs:
427 if v is not None:
428 if not re.search(r"^\w+$", v):
429 v = HEADER_JOIN_ESCAPE_RE.sub(r"\\\1", v) # escape " and \
430 v = '"%s"' % v
431 k = "%s=%s" % (k, v)
432 attr.append(k)
433 if attr: headers.append("; ".join(attr))
434 return ", ".join(headers)
435
436def parse_ns_headers(ns_headers):
437 """Ad-hoc parser for Netscape protocol cookie-attributes.
438
439 The old Netscape cookie format for Set-Cookie can for instance contain
440 an unquoted "," in the expires field, so we have to use this ad-hoc
441 parser instead of split_header_words.
442
443 XXX This may not make the best possible effort to parse all the crap
444 that Netscape Cookie headers contain. Ronald Tschalar's HTTPClient
445 parser is probably better, so could do worse than following that if
446 this ever gives any trouble.
447
448 Currently, this is also used for parsing RFC 2109 cookies.
449
450 """
451 known_attrs = ("expires", "domain", "path", "secure",
452 # RFC 2109 attrs (may turn up in Netscape cookies, too)
453 "port", "max-age")
454
455 result = []
456 for ns_header in ns_headers:
457 pairs = []
458 version_set = False
Martin v. Löwis4ea3ead2005-03-03 10:48:12 +0000459 for ii, param in enumerate(re.split(r";\s*", ns_header)):
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000460 param = param.rstrip()
461 if param == "": continue
462 if "=" not in param:
Martin v. Löwisc5574e82005-03-03 10:57:37 +0000463 k, v = param, None
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000464 else:
465 k, v = re.split(r"\s*=\s*", param, 1)
466 k = k.lstrip()
Martin v. Löwis4ea3ead2005-03-03 10:48:12 +0000467 if ii != 0:
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000468 lc = k.lower()
469 if lc in known_attrs:
470 k = lc
471 if k == "version":
Neal Norwitz71dad722005-12-23 21:43:48 +0000472 # This is an RFC 2109 cookie.
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000473 version_set = True
474 if k == "expires":
475 # convert expires date to seconds since epoch
476 if v.startswith('"'): v = v[1:]
477 if v.endswith('"'): v = v[:-1]
478 v = http2time(v) # None if invalid
479 pairs.append((k, v))
480
481 if pairs:
482 if not version_set:
483 pairs.append(("version", "0"))
484 result.append(pairs)
485
486 return result
487
488
489IPV4_RE = re.compile(r"\.\d+$")
490def is_HDN(text):
491 """Return True if text is a host domain name."""
492 # XXX
493 # This may well be wrong. Which RFC is HDN defined in, if any (for
494 # the purposes of RFC 2965)?
495 # For the current implementation, what about IPv6? Remember to look
496 # at other uses of IPV4_RE also, if change this.
497 if IPV4_RE.search(text):
498 return False
499 if text == "":
500 return False
501 if text[0] == "." or text[-1] == ".":
502 return False
503 return True
504
505def domain_match(A, B):
506 """Return True if domain A domain-matches domain B, according to RFC 2965.
507
508 A and B may be host domain names or IP addresses.
509
510 RFC 2965, section 1:
511
512 Host names can be specified either as an IP address or a HDN string.
513 Sometimes we compare one host name with another. (Such comparisons SHALL
514 be case-insensitive.) Host A's name domain-matches host B's if
515
516 * their host name strings string-compare equal; or
517
518 * A is a HDN string and has the form NB, where N is a non-empty
519 name string, B has the form .B', and B' is a HDN string. (So,
520 x.y.com domain-matches .Y.com but not Y.com.)
521
522 Note that domain-match is not a commutative operation: a.b.c.com
523 domain-matches .c.com, but not the reverse.
524
525 """
526 # Note that, if A or B are IP addresses, the only relevant part of the
527 # definition of the domain-match algorithm is the direct string-compare.
528 A = A.lower()
529 B = B.lower()
530 if A == B:
531 return True
532 if not is_HDN(A):
533 return False
534 i = A.rfind(B)
535 if i == -1 or i == 0:
536 # A does not have form NB, or N is the empty string
537 return False
538 if not B.startswith("."):
539 return False
540 if not is_HDN(B[1:]):
541 return False
542 return True
543
544def liberal_is_HDN(text):
545 """Return True if text is a sort-of-like a host domain name.
546
547 For accepting/blocking domains.
548
549 """
550 if IPV4_RE.search(text):
551 return False
552 return True
553
554def user_domain_match(A, B):
555 """For blocking/accepting domains.
556
557 A and B may be host domain names or IP addresses.
558
559 """
560 A = A.lower()
561 B = B.lower()
562 if not (liberal_is_HDN(A) and liberal_is_HDN(B)):
563 if A == B:
564 # equal IP addresses
565 return True
566 return False
567 initial_dot = B.startswith(".")
568 if initial_dot and A.endswith(B):
569 return True
570 if not initial_dot and A == B:
571 return True
572 return False
573
574cut_port_re = re.compile(r":\d+$")
575def request_host(request):
576 """Return request-host, as defined by RFC 2965.
577
578 Variation from RFC: returned value is lowercased, for convenient
579 comparison.
580
581 """
582 url = request.get_full_url()
583 host = urlparse.urlparse(url)[1]
584 if host == "":
585 host = request.get_header("Host", "")
586
587 # remove port, if present
588 host = cut_port_re.sub("", host, 1)
589 return host.lower()
590
591def eff_request_host(request):
592 """Return a tuple (request-host, effective request-host name).
593
594 As defined by RFC 2965, except both are lowercased.
595
596 """
597 erhn = req_host = request_host(request)
598 if req_host.find(".") == -1 and not IPV4_RE.search(req_host):
599 erhn = req_host + ".local"
600 return req_host, erhn
601
602def request_path(request):
603 """request-URI, as defined by RFC 2965."""
604 url = request.get_full_url()
605 #scheme, netloc, path, parameters, query, frag = urlparse.urlparse(url)
606 #req_path = escape_path("".join(urlparse.urlparse(url)[2:]))
607 path, parameters, query, frag = urlparse.urlparse(url)[2:]
608 if parameters:
609 path = "%s;%s" % (path, parameters)
610 path = escape_path(path)
611 req_path = urlparse.urlunparse(("", "", path, "", query, frag))
612 if not req_path.startswith("/"):
613 # fix bad RFC 2396 absoluteURI
614 req_path = "/"+req_path
615 return req_path
616
617def request_port(request):
618 host = request.get_host()
619 i = host.find(':')
620 if i >= 0:
621 port = host[i+1:]
622 try:
623 int(port)
624 except ValueError:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000625 _debug("nonnumeric port: '%s'", port)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000626 return None
627 else:
628 port = DEFAULT_HTTP_PORT
629 return port
630
631# Characters in addition to A-Z, a-z, 0-9, '_', '.', and '-' that don't
632# need to be escaped to form a valid HTTP URL (RFCs 2396 and 1738).
633HTTP_PATH_SAFE = "%/;:@&=+$,!~*'()"
634ESCAPED_CHAR_RE = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])")
635def uppercase_escaped_char(match):
636 return "%%%s" % match.group(1).upper()
637def escape_path(path):
638 """Escape any invalid characters in HTTP URL, and uppercase all escapes."""
639 # There's no knowing what character encoding was used to create URLs
640 # containing %-escapes, but since we have to pick one to escape invalid
641 # path characters, we pick UTF-8, as recommended in the HTML 4.0
642 # specification:
643 # http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
644 # And here, kind of: draft-fielding-uri-rfc2396bis-03
645 # (And in draft IRI specification: draft-duerst-iri-05)
646 # (And here, for new URI schemes: RFC 2718)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000647 path = urllib.quote(path, HTTP_PATH_SAFE)
648 path = ESCAPED_CHAR_RE.sub(uppercase_escaped_char, path)
649 return path
650
651def reach(h):
652 """Return reach of host h, as defined by RFC 2965, section 1.
653
654 The reach R of a host name H is defined as follows:
655
656 * If
657
658 - H is the host domain name of a host; and,
659
660 - H has the form A.B; and
661
662 - A has no embedded (that is, interior) dots; and
663
664 - B has at least one embedded dot, or B is the string "local".
665 then the reach of H is .B.
666
667 * Otherwise, the reach of H is H.
668
669 >>> reach("www.acme.com")
670 '.acme.com'
671 >>> reach("acme.com")
672 'acme.com'
673 >>> reach("acme.local")
674 '.local'
675
676 """
677 i = h.find(".")
678 if i >= 0:
679 #a = h[:i] # this line is only here to show what a is
680 b = h[i+1:]
681 i = b.find(".")
682 if is_HDN(h) and (i >= 0 or b == "local"):
683 return "."+b
684 return h
685
686def is_third_party(request):
687 """
688
689 RFC 2965, section 3.3.6:
690
691 An unverifiable transaction is to a third-party host if its request-
692 host U does not domain-match the reach R of the request-host O in the
693 origin transaction.
694
695 """
696 req_host = request_host(request)
697 if not domain_match(req_host, reach(request.get_origin_req_host())):
698 return True
699 else:
700 return False
701
702
703class Cookie:
704 """HTTP Cookie.
705
706 This class represents both Netscape and RFC 2965 cookies.
707
708 This is deliberately a very simple class. It just holds attributes. It's
709 possible to construct Cookie instances that don't comply with the cookie
710 standards. CookieJar.make_cookies is the factory function for Cookie
711 objects -- it deals with cookie parsing, supplying defaults, and
712 normalising to the representation used in this class. CookiePolicy is
713 responsible for checking them to see whether they should be accepted from
714 and returned to the server.
715
716 Note that the port may be present in the headers, but unspecified ("Port"
717 rather than"Port=80", for example); if this is the case, port is None.
718
719 """
720
721 def __init__(self, version, name, value,
722 port, port_specified,
723 domain, domain_specified, domain_initial_dot,
724 path, path_specified,
725 secure,
726 expires,
727 discard,
728 comment,
729 comment_url,
Neal Norwitz71dad722005-12-23 21:43:48 +0000730 rest,
731 rfc2109=False,
732 ):
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000733
734 if version is not None: version = int(version)
735 if expires is not None: expires = int(expires)
736 if port is None and port_specified is True:
737 raise ValueError("if port is None, port_specified must be false")
738
739 self.version = version
740 self.name = name
741 self.value = value
742 self.port = port
743 self.port_specified = port_specified
744 # normalise case, as per RFC 2965 section 3.3.3
745 self.domain = domain.lower()
746 self.domain_specified = domain_specified
747 # Sigh. We need to know whether the domain given in the
748 # cookie-attribute had an initial dot, in order to follow RFC 2965
749 # (as clarified in draft errata). Needed for the returned $Domain
750 # value.
751 self.domain_initial_dot = domain_initial_dot
752 self.path = path
753 self.path_specified = path_specified
754 self.secure = secure
755 self.expires = expires
756 self.discard = discard
757 self.comment = comment
758 self.comment_url = comment_url
Neal Norwitz71dad722005-12-23 21:43:48 +0000759 self.rfc2109 = rfc2109
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000760
761 self._rest = copy.copy(rest)
762
763 def has_nonstandard_attr(self, name):
764 return name in self._rest
765 def get_nonstandard_attr(self, name, default=None):
766 return self._rest.get(name, default)
767 def set_nonstandard_attr(self, name, value):
768 self._rest[name] = value
769
770 def is_expired(self, now=None):
771 if now is None: now = time.time()
772 if (self.expires is not None) and (self.expires <= now):
773 return True
774 return False
775
776 def __str__(self):
777 if self.port is None: p = ""
778 else: p = ":"+self.port
779 limit = self.domain + p + self.path
780 if self.value is not None:
781 namevalue = "%s=%s" % (self.name, self.value)
782 else:
783 namevalue = self.name
784 return "<Cookie %s for %s>" % (namevalue, limit)
785
786 def __repr__(self):
787 args = []
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000788 for name in ("version", "name", "value",
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000789 "port", "port_specified",
790 "domain", "domain_specified", "domain_initial_dot",
791 "path", "path_specified",
792 "secure", "expires", "discard", "comment", "comment_url",
Raymond Hettingerdbecd932005-02-06 06:57:08 +0000793 ):
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000794 attr = getattr(self, name)
795 args.append("%s=%s" % (name, repr(attr)))
796 args.append("rest=%s" % repr(self._rest))
Neal Norwitz71dad722005-12-23 21:43:48 +0000797 args.append("rfc2109=%s" % repr(self.rfc2109))
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000798 return "Cookie(%s)" % ", ".join(args)
799
800
801class CookiePolicy:
802 """Defines which cookies get accepted from and returned to server.
803
804 May also modify cookies, though this is probably a bad idea.
805
806 The subclass DefaultCookiePolicy defines the standard rules for Netscape
807 and RFC 2965 cookies -- override that if you want a customised policy.
808
809 """
810 def set_ok(self, cookie, request):
811 """Return true if (and only if) cookie should be accepted from server.
812
813 Currently, pre-expired cookies never get this far -- the CookieJar
814 class deletes such cookies itself.
815
816 """
817 raise NotImplementedError()
818
819 def return_ok(self, cookie, request):
820 """Return true if (and only if) cookie should be returned to server."""
821 raise NotImplementedError()
822
823 def domain_return_ok(self, domain, request):
824 """Return false if cookies should not be returned, given cookie domain.
825 """
826 return True
827
828 def path_return_ok(self, path, request):
829 """Return false if cookies should not be returned, given cookie path.
830 """
831 return True
832
833
834class DefaultCookiePolicy(CookiePolicy):
835 """Implements the standard rules for accepting and returning cookies."""
836
837 DomainStrictNoDots = 1
838 DomainStrictNonDomain = 2
839 DomainRFC2965Match = 4
840
841 DomainLiberal = 0
842 DomainStrict = DomainStrictNoDots|DomainStrictNonDomain
843
844 def __init__(self,
845 blocked_domains=None, allowed_domains=None,
846 netscape=True, rfc2965=False,
Neal Norwitz71dad722005-12-23 21:43:48 +0000847 rfc2109_as_netscape=None,
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000848 hide_cookie2=False,
849 strict_domain=False,
850 strict_rfc2965_unverifiable=True,
851 strict_ns_unverifiable=False,
852 strict_ns_domain=DomainLiberal,
853 strict_ns_set_initial_dollar=False,
854 strict_ns_set_path=False,
855 ):
856 """Constructor arguments should be passed as keyword arguments only."""
857 self.netscape = netscape
858 self.rfc2965 = rfc2965
Neal Norwitz71dad722005-12-23 21:43:48 +0000859 self.rfc2109_as_netscape = rfc2109_as_netscape
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000860 self.hide_cookie2 = hide_cookie2
861 self.strict_domain = strict_domain
862 self.strict_rfc2965_unverifiable = strict_rfc2965_unverifiable
863 self.strict_ns_unverifiable = strict_ns_unverifiable
864 self.strict_ns_domain = strict_ns_domain
865 self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar
866 self.strict_ns_set_path = strict_ns_set_path
867
868 if blocked_domains is not None:
869 self._blocked_domains = tuple(blocked_domains)
870 else:
871 self._blocked_domains = ()
872
873 if allowed_domains is not None:
874 allowed_domains = tuple(allowed_domains)
875 self._allowed_domains = allowed_domains
876
877 def blocked_domains(self):
878 """Return the sequence of blocked domains (as a tuple)."""
879 return self._blocked_domains
880 def set_blocked_domains(self, blocked_domains):
881 """Set the sequence of blocked domains."""
882 self._blocked_domains = tuple(blocked_domains)
883
884 def is_blocked(self, domain):
885 for blocked_domain in self._blocked_domains:
886 if user_domain_match(domain, blocked_domain):
887 return True
888 return False
889
890 def allowed_domains(self):
891 """Return None, or the sequence of allowed domains (as a tuple)."""
892 return self._allowed_domains
893 def set_allowed_domains(self, allowed_domains):
894 """Set the sequence of allowed domains, or None."""
895 if allowed_domains is not None:
896 allowed_domains = tuple(allowed_domains)
897 self._allowed_domains = allowed_domains
898
899 def is_not_allowed(self, domain):
900 if self._allowed_domains is None:
901 return False
902 for allowed_domain in self._allowed_domains:
903 if user_domain_match(domain, allowed_domain):
904 return False
905 return True
906
907 def set_ok(self, cookie, request):
908 """
909 If you override .set_ok(), be sure to call this method. If it returns
910 false, so should your subclass (assuming your subclass wants to be more
911 strict about which cookies to accept).
912
913 """
Thomas Wouters477c8d52006-05-27 19:21:47 +0000914 _debug(" - checking cookie %s=%s", cookie.name, cookie.value)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000915
916 assert cookie.name is not None
917
918 for n in "version", "verifiability", "name", "path", "domain", "port":
919 fn_name = "set_ok_"+n
920 fn = getattr(self, fn_name)
921 if not fn(cookie, request):
922 return False
923
924 return True
925
926 def set_ok_version(self, cookie, request):
927 if cookie.version is None:
928 # Version is always set to 0 by parse_ns_headers if it's a Netscape
929 # cookie, so this must be an invalid RFC 2965 cookie.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000930 _debug(" Set-Cookie2 without version attribute (%s=%s)",
931 cookie.name, cookie.value)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000932 return False
933 if cookie.version > 0 and not self.rfc2965:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000934 _debug(" RFC 2965 cookies are switched off")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000935 return False
936 elif cookie.version == 0 and not self.netscape:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000937 _debug(" Netscape cookies are switched off")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000938 return False
939 return True
940
941 def set_ok_verifiability(self, cookie, request):
942 if request.is_unverifiable() and is_third_party(request):
943 if cookie.version > 0 and self.strict_rfc2965_unverifiable:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000944 _debug(" third-party RFC 2965 cookie during "
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000945 "unverifiable transaction")
946 return False
947 elif cookie.version == 0 and self.strict_ns_unverifiable:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000948 _debug(" third-party Netscape cookie during "
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000949 "unverifiable transaction")
950 return False
951 return True
952
953 def set_ok_name(self, cookie, request):
954 # Try and stop servers setting V0 cookies designed to hack other
955 # servers that know both V0 and V1 protocols.
956 if (cookie.version == 0 and self.strict_ns_set_initial_dollar and
957 cookie.name.startswith("$")):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000958 _debug(" illegal name (starts with '$'): '%s'", cookie.name)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000959 return False
960 return True
961
962 def set_ok_path(self, cookie, request):
963 if cookie.path_specified:
964 req_path = request_path(request)
965 if ((cookie.version > 0 or
966 (cookie.version == 0 and self.strict_ns_set_path)) and
967 not req_path.startswith(cookie.path)):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000968 _debug(" path attribute %s is not a prefix of request "
969 "path %s", cookie.path, req_path)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000970 return False
971 return True
972
973 def set_ok_domain(self, cookie, request):
974 if self.is_blocked(cookie.domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000975 _debug(" domain %s is in user block-list", cookie.domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000976 return False
977 if self.is_not_allowed(cookie.domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000978 _debug(" domain %s is not in user allow-list", cookie.domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000979 return False
980 if cookie.domain_specified:
981 req_host, erhn = eff_request_host(request)
982 domain = cookie.domain
983 if self.strict_domain and (domain.count(".") >= 2):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000984 # XXX This should probably be compared with the Konqueror
985 # (kcookiejar.cpp) and Mozilla implementations, but it's a
986 # losing battle.
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000987 i = domain.rfind(".")
988 j = domain.rfind(".", 0, i)
989 if j == 0: # domain like .foo.bar
990 tld = domain[i+1:]
991 sld = domain[j+1:i]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000992 if sld.lower() in ("co", "ac", "com", "edu", "org", "net",
993 "gov", "mil", "int", "aero", "biz", "cat", "coop",
994 "info", "jobs", "mobi", "museum", "name", "pro",
995 "travel", "eu") and len(tld) == 2:
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000996 # domain like .co.uk
Thomas Wouters477c8d52006-05-27 19:21:47 +0000997 _debug(" country-code second level domain %s", domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000998 return False
999 if domain.startswith("."):
1000 undotted_domain = domain[1:]
1001 else:
1002 undotted_domain = domain
1003 embedded_dots = (undotted_domain.find(".") >= 0)
1004 if not embedded_dots and domain != ".local":
Thomas Wouters477c8d52006-05-27 19:21:47 +00001005 _debug(" non-local domain %s contains no embedded dot",
1006 domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001007 return False
1008 if cookie.version == 0:
1009 if (not erhn.endswith(domain) and
1010 (not erhn.startswith(".") and
1011 not ("."+erhn).endswith(domain))):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001012 _debug(" effective request-host %s (even with added "
1013 "initial dot) does not end end with %s",
1014 erhn, domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001015 return False
1016 if (cookie.version > 0 or
1017 (self.strict_ns_domain & self.DomainRFC2965Match)):
1018 if not domain_match(erhn, domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001019 _debug(" effective request-host %s does not domain-match "
1020 "%s", erhn, domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001021 return False
1022 if (cookie.version > 0 or
1023 (self.strict_ns_domain & self.DomainStrictNoDots)):
1024 host_prefix = req_host[:-len(domain)]
1025 if (host_prefix.find(".") >= 0 and
1026 not IPV4_RE.search(req_host)):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001027 _debug(" host prefix %s for domain %s contains a dot",
1028 host_prefix, domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001029 return False
1030 return True
1031
1032 def set_ok_port(self, cookie, request):
1033 if cookie.port_specified:
1034 req_port = request_port(request)
1035 if req_port is None:
1036 req_port = "80"
1037 else:
1038 req_port = str(req_port)
1039 for p in cookie.port.split(","):
1040 try:
1041 int(p)
1042 except ValueError:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001043 _debug(" bad port %s (not numeric)", p)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001044 return False
1045 if p == req_port:
1046 break
1047 else:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001048 _debug(" request port (%s) not found in %s",
1049 req_port, cookie.port)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001050 return False
1051 return True
1052
1053 def return_ok(self, cookie, request):
1054 """
1055 If you override .return_ok(), be sure to call this method. If it
1056 returns false, so should your subclass (assuming your subclass wants to
1057 be more strict about which cookies to return).
1058
1059 """
1060 # Path has already been checked by .path_return_ok(), and domain
1061 # blocking done by .domain_return_ok().
Thomas Wouters477c8d52006-05-27 19:21:47 +00001062 _debug(" - checking cookie %s=%s", cookie.name, cookie.value)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001063
1064 for n in "version", "verifiability", "secure", "expires", "port", "domain":
1065 fn_name = "return_ok_"+n
1066 fn = getattr(self, fn_name)
1067 if not fn(cookie, request):
1068 return False
1069 return True
1070
1071 def return_ok_version(self, cookie, request):
1072 if cookie.version > 0 and not self.rfc2965:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073 _debug(" RFC 2965 cookies are switched off")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001074 return False
1075 elif cookie.version == 0 and not self.netscape:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001076 _debug(" Netscape cookies are switched off")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001077 return False
1078 return True
1079
1080 def return_ok_verifiability(self, cookie, request):
1081 if request.is_unverifiable() and is_third_party(request):
1082 if cookie.version > 0 and self.strict_rfc2965_unverifiable:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001083 _debug(" third-party RFC 2965 cookie during unverifiable "
1084 "transaction")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001085 return False
1086 elif cookie.version == 0 and self.strict_ns_unverifiable:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001087 _debug(" third-party Netscape cookie during unverifiable "
1088 "transaction")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001089 return False
1090 return True
1091
1092 def return_ok_secure(self, cookie, request):
1093 if cookie.secure and request.get_type() != "https":
Thomas Wouters477c8d52006-05-27 19:21:47 +00001094 _debug(" secure cookie with non-secure request")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001095 return False
1096 return True
1097
1098 def return_ok_expires(self, cookie, request):
1099 if cookie.is_expired(self._now):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001100 _debug(" cookie expired")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001101 return False
1102 return True
1103
1104 def return_ok_port(self, cookie, request):
1105 if cookie.port:
1106 req_port = request_port(request)
1107 if req_port is None:
1108 req_port = "80"
1109 for p in cookie.port.split(","):
1110 if p == req_port:
1111 break
1112 else:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001113 _debug(" request port %s does not match cookie port %s",
1114 req_port, cookie.port)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001115 return False
1116 return True
1117
1118 def return_ok_domain(self, cookie, request):
1119 req_host, erhn = eff_request_host(request)
1120 domain = cookie.domain
1121
1122 # strict check of non-domain cookies: Mozilla does this, MSIE5 doesn't
1123 if (cookie.version == 0 and
1124 (self.strict_ns_domain & self.DomainStrictNonDomain) and
1125 not cookie.domain_specified and domain != erhn):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001126 _debug(" cookie with unspecified domain does not string-compare "
1127 "equal to request domain")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001128 return False
1129
1130 if cookie.version > 0 and not domain_match(erhn, domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001131 _debug(" effective request-host name %s does not domain-match "
1132 "RFC 2965 cookie domain %s", erhn, domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001133 return False
1134 if cookie.version == 0 and not ("."+erhn).endswith(domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001135 _debug(" request-host %s does not match Netscape cookie domain "
1136 "%s", req_host, domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001137 return False
1138 return True
1139
1140 def domain_return_ok(self, domain, request):
1141 # Liberal check of. This is here as an optimization to avoid
1142 # having to load lots of MSIE cookie files unless necessary.
1143 req_host, erhn = eff_request_host(request)
1144 if not req_host.startswith("."):
Raymond Hettingerbab41432005-02-05 01:31:19 +00001145 req_host = "."+req_host
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001146 if not erhn.startswith("."):
Raymond Hettingerbab41432005-02-05 01:31:19 +00001147 erhn = "."+erhn
1148 if not (req_host.endswith(domain) or erhn.endswith(domain)):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001149 #_debug(" request domain %s does not match cookie domain %s",
1150 # req_host, domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001151 return False
1152
1153 if self.is_blocked(domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154 _debug(" domain %s is in user block-list", domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001155 return False
1156 if self.is_not_allowed(domain):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157 _debug(" domain %s is not in user allow-list", domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001158 return False
1159
1160 return True
1161
1162 def path_return_ok(self, path, request):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001163 _debug("- checking cookie path=%s", path)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001164 req_path = request_path(request)
1165 if not req_path.startswith(path):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001166 _debug(" %s does not path-match %s", req_path, path)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001167 return False
1168 return True
1169
1170
1171def vals_sorted_by_key(adict):
Guido van Rossumcc2b0162007-02-11 06:12:03 +00001172 keys = sorted(adict.keys())
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001173 return map(adict.get, keys)
1174
1175def deepvalues(mapping):
1176 """Iterates over nested mapping, depth-first, in sorted order by key."""
1177 values = vals_sorted_by_key(mapping)
1178 for obj in values:
1179 mapping = False
1180 try:
1181 obj.items
1182 except AttributeError:
1183 pass
1184 else:
1185 mapping = True
1186 for subobj in deepvalues(obj):
1187 yield subobj
1188 if not mapping:
1189 yield obj
1190
1191
1192# Used as second parameter to dict.get() method, to distinguish absent
1193# dict key from one with a None value.
1194class Absent: pass
1195
1196class CookieJar:
1197 """Collection of HTTP cookies.
1198
1199 You may not need to know about this class: try
1200 urllib2.build_opener(HTTPCookieProcessor).open(url).
1201
1202 """
1203
1204 non_word_re = re.compile(r"\W")
1205 quote_re = re.compile(r"([\"\\])")
1206 strict_domain_re = re.compile(r"\.?[^.]*")
1207 domain_re = re.compile(r"[^.]*")
1208 dots_re = re.compile(r"^\.+")
1209
1210 magic_re = r"^\#LWP-Cookies-(\d+\.\d+)"
1211
1212 def __init__(self, policy=None):
1213 if policy is None:
1214 policy = DefaultCookiePolicy()
1215 self._policy = policy
1216
1217 self._cookies_lock = _threading.RLock()
1218 self._cookies = {}
1219
1220 def set_policy(self, policy):
1221 self._policy = policy
1222
1223 def _cookies_for_domain(self, domain, request):
1224 cookies = []
1225 if not self._policy.domain_return_ok(domain, request):
1226 return []
Thomas Wouters477c8d52006-05-27 19:21:47 +00001227 _debug("Checking %s for cookies to return", domain)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001228 cookies_by_path = self._cookies[domain]
1229 for path in cookies_by_path.keys():
1230 if not self._policy.path_return_ok(path, request):
1231 continue
1232 cookies_by_name = cookies_by_path[path]
1233 for cookie in cookies_by_name.values():
1234 if not self._policy.return_ok(cookie, request):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001235 _debug(" not returning cookie")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001236 continue
Thomas Wouters477c8d52006-05-27 19:21:47 +00001237 _debug(" it's a match")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001238 cookies.append(cookie)
1239 return cookies
1240
1241 def _cookies_for_request(self, request):
1242 """Return a list of cookies to be returned to server."""
1243 cookies = []
1244 for domain in self._cookies.keys():
1245 cookies.extend(self._cookies_for_domain(domain, request))
1246 return cookies
1247
1248 def _cookie_attrs(self, cookies):
1249 """Return a list of cookie-attributes to be returned to server.
1250
1251 like ['foo="bar"; $Path="/"', ...]
1252
1253 The $Version attribute is also added when appropriate (currently only
1254 once per request).
1255
1256 """
1257 # add cookies in order of most specific (ie. longest) path first
1258 def decreasing_size(a, b): return cmp(len(b.path), len(a.path))
1259 cookies.sort(decreasing_size)
1260
1261 version_set = False
1262
1263 attrs = []
1264 for cookie in cookies:
1265 # set version of Cookie header
1266 # XXX
1267 # What should it be if multiple matching Set-Cookie headers have
1268 # different versions themselves?
1269 # Answer: there is no answer; was supposed to be settled by
1270 # RFC 2965 errata, but that may never appear...
1271 version = cookie.version
1272 if not version_set:
1273 version_set = True
1274 if version > 0:
1275 attrs.append("$Version=%s" % version)
1276
1277 # quote cookie value if necessary
1278 # (not for Netscape protocol, which already has any quotes
1279 # intact, due to the poorly-specified Netscape Cookie: syntax)
1280 if ((cookie.value is not None) and
1281 self.non_word_re.search(cookie.value) and version > 0):
1282 value = self.quote_re.sub(r"\\\1", cookie.value)
1283 else:
1284 value = cookie.value
1285
1286 # add cookie-attributes to be returned in Cookie header
1287 if cookie.value is None:
1288 attrs.append(cookie.name)
1289 else:
1290 attrs.append("%s=%s" % (cookie.name, value))
1291 if version > 0:
1292 if cookie.path_specified:
1293 attrs.append('$Path="%s"' % cookie.path)
1294 if cookie.domain.startswith("."):
1295 domain = cookie.domain
1296 if (not cookie.domain_initial_dot and
1297 domain.startswith(".")):
1298 domain = domain[1:]
1299 attrs.append('$Domain="%s"' % domain)
1300 if cookie.port is not None:
1301 p = "$Port"
1302 if cookie.port_specified:
1303 p = p + ('="%s"' % cookie.port)
1304 attrs.append(p)
1305
1306 return attrs
1307
1308 def add_cookie_header(self, request):
1309 """Add correct Cookie: header to request (urllib2.Request object).
1310
1311 The Cookie2 header is also added unless policy.hide_cookie2 is true.
1312
1313 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001314 _debug("add_cookie_header")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001315 self._cookies_lock.acquire()
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001316 try:
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001317
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001318 self._policy._now = self._now = int(time.time())
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001319
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001320 cookies = self._cookies_for_request(request)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001321
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001322 attrs = self._cookie_attrs(cookies)
1323 if attrs:
1324 if not request.has_header("Cookie"):
1325 request.add_unredirected_header(
1326 "Cookie", "; ".join(attrs))
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001327
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001328 # if necessary, advertise that we know RFC 2965
1329 if (self._policy.rfc2965 and not self._policy.hide_cookie2 and
1330 not request.has_header("Cookie2")):
1331 for cookie in cookies:
1332 if cookie.version != 1:
1333 request.add_unredirected_header("Cookie2", '$Version="1"')
1334 break
1335
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001336 finally:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001337 self._cookies_lock.release()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001338
1339 self.clear_expired_cookies()
1340
1341 def _normalized_cookie_tuples(self, attrs_set):
1342 """Return list of tuples containing normalised cookie information.
1343
1344 attrs_set is the list of lists of key,value pairs extracted from
1345 the Set-Cookie or Set-Cookie2 headers.
1346
1347 Tuples are name, value, standard, rest, where name and value are the
1348 cookie name and value, standard is a dictionary containing the standard
1349 cookie-attributes (discard, secure, version, expires or max-age,
1350 domain, path and port) and rest is a dictionary containing the rest of
1351 the cookie-attributes.
1352
1353 """
1354 cookie_tuples = []
1355
1356 boolean_attrs = "discard", "secure"
1357 value_attrs = ("version",
1358 "expires", "max-age",
1359 "domain", "path", "port",
1360 "comment", "commenturl")
1361
1362 for cookie_attrs in attrs_set:
1363 name, value = cookie_attrs[0]
1364
1365 # Build dictionary of standard cookie-attributes (standard) and
1366 # dictionary of other cookie-attributes (rest).
1367
1368 # Note: expiry time is normalised to seconds since epoch. V0
1369 # cookies should have the Expires cookie-attribute, and V1 cookies
1370 # should have Max-Age, but since V1 includes RFC 2109 cookies (and
1371 # since V0 cookies may be a mish-mash of Netscape and RFC 2109), we
1372 # accept either (but prefer Max-Age).
1373 max_age_set = False
1374
1375 bad_cookie = False
1376
1377 standard = {}
1378 rest = {}
1379 for k, v in cookie_attrs[1:]:
1380 lc = k.lower()
1381 # don't lose case distinction for unknown fields
1382 if lc in value_attrs or lc in boolean_attrs:
1383 k = lc
1384 if k in boolean_attrs and v is None:
1385 # boolean cookie-attribute is present, but has no value
1386 # (like "discard", rather than "port=80")
1387 v = True
1388 if k in standard:
1389 # only first value is significant
1390 continue
1391 if k == "domain":
1392 if v is None:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001393 _debug(" missing value for domain attribute")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001394 bad_cookie = True
1395 break
1396 # RFC 2965 section 3.3.3
1397 v = v.lower()
1398 if k == "expires":
1399 if max_age_set:
1400 # Prefer max-age to expires (like Mozilla)
1401 continue
1402 if v is None:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001403 _debug(" missing or invalid value for expires "
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001404 "attribute: treating as session cookie")
1405 continue
1406 if k == "max-age":
1407 max_age_set = True
1408 try:
1409 v = int(v)
1410 except ValueError:
Thomas Wouters477c8d52006-05-27 19:21:47 +00001411 _debug(" missing or invalid (non-numeric) value for "
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001412 "max-age attribute")
1413 bad_cookie = True
1414 break
1415 # convert RFC 2965 Max-Age to seconds since epoch
1416 # XXX Strictly you're supposed to follow RFC 2616
1417 # age-calculation rules. Remember that zero Max-Age is a
1418 # is a request to discard (old and new) cookie, though.
1419 k = "expires"
1420 v = self._now + v
1421 if (k in value_attrs) or (k in boolean_attrs):
1422 if (v is None and
Raymond Hettingerdbecd932005-02-06 06:57:08 +00001423 k not in ("port", "comment", "commenturl")):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001424 _debug(" missing value for %s attribute" % k)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001425 bad_cookie = True
1426 break
1427 standard[k] = v
1428 else:
1429 rest[k] = v
1430
1431 if bad_cookie:
1432 continue
1433
1434 cookie_tuples.append((name, value, standard, rest))
1435
1436 return cookie_tuples
1437
1438 def _cookie_from_cookie_tuple(self, tup, request):
1439 # standard is dict of standard cookie-attributes, rest is dict of the
1440 # rest of them
1441 name, value, standard, rest = tup
1442
1443 domain = standard.get("domain", Absent)
1444 path = standard.get("path", Absent)
1445 port = standard.get("port", Absent)
1446 expires = standard.get("expires", Absent)
1447
1448 # set the easy defaults
1449 version = standard.get("version", None)
1450 if version is not None: version = int(version)
1451 secure = standard.get("secure", False)
1452 # (discard is also set if expires is Absent)
1453 discard = standard.get("discard", False)
1454 comment = standard.get("comment", None)
1455 comment_url = standard.get("commenturl", None)
1456
1457 # set default path
1458 if path is not Absent and path != "":
1459 path_specified = True
1460 path = escape_path(path)
1461 else:
1462 path_specified = False
1463 path = request_path(request)
1464 i = path.rfind("/")
1465 if i != -1:
1466 if version == 0:
1467 # Netscape spec parts company from reality here
1468 path = path[:i]
1469 else:
1470 path = path[:i+1]
1471 if len(path) == 0: path = "/"
1472
1473 # set default domain
1474 domain_specified = domain is not Absent
1475 # but first we have to remember whether it starts with a dot
1476 domain_initial_dot = False
1477 if domain_specified:
1478 domain_initial_dot = bool(domain.startswith("."))
1479 if domain is Absent:
1480 req_host, erhn = eff_request_host(request)
1481 domain = erhn
1482 elif not domain.startswith("."):
1483 domain = "."+domain
1484
1485 # set default port
1486 port_specified = False
1487 if port is not Absent:
1488 if port is None:
1489 # Port attr present, but has no value: default to request port.
1490 # Cookie should then only be sent back on that port.
1491 port = request_port(request)
1492 else:
1493 port_specified = True
1494 port = re.sub(r"\s+", "", port)
1495 else:
1496 # No port attr present. Cookie can be sent back on any port.
1497 port = None
1498
1499 # set default expires and discard
1500 if expires is Absent:
1501 expires = None
1502 discard = True
1503 elif expires <= self._now:
1504 # Expiry date in past is request to delete cookie. This can't be
1505 # in DefaultCookiePolicy, because can't delete cookies there.
1506 try:
1507 self.clear(domain, path, name)
1508 except KeyError:
1509 pass
Thomas Wouters477c8d52006-05-27 19:21:47 +00001510 _debug("Expiring cookie, domain='%s', path='%s', name='%s'",
1511 domain, path, name)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001512 return None
1513
1514 return Cookie(version,
1515 name, value,
1516 port, port_specified,
1517 domain, domain_specified, domain_initial_dot,
1518 path, path_specified,
1519 secure,
1520 expires,
1521 discard,
1522 comment,
1523 comment_url,
1524 rest)
1525
1526 def _cookies_from_attrs_set(self, attrs_set, request):
1527 cookie_tuples = self._normalized_cookie_tuples(attrs_set)
1528
1529 cookies = []
1530 for tup in cookie_tuples:
1531 cookie = self._cookie_from_cookie_tuple(tup, request)
1532 if cookie: cookies.append(cookie)
1533 return cookies
1534
Neal Norwitz71dad722005-12-23 21:43:48 +00001535 def _process_rfc2109_cookies(self, cookies):
1536 rfc2109_as_ns = getattr(self._policy, 'rfc2109_as_netscape', None)
1537 if rfc2109_as_ns is None:
1538 rfc2109_as_ns = not self._policy.rfc2965
1539 for cookie in cookies:
1540 if cookie.version == 1:
1541 cookie.rfc2109 = True
Tim Peters536cf992005-12-25 23:18:31 +00001542 if rfc2109_as_ns:
Neal Norwitz71dad722005-12-23 21:43:48 +00001543 # treat 2109 cookies as Netscape cookies rather than
1544 # as RFC2965 cookies
1545 cookie.version = 0
1546
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001547 def make_cookies(self, response, request):
1548 """Return sequence of Cookie objects extracted from response object."""
1549 # get cookie-attributes for RFC 2965 and Netscape protocols
1550 headers = response.info()
1551 rfc2965_hdrs = headers.getheaders("Set-Cookie2")
1552 ns_hdrs = headers.getheaders("Set-Cookie")
1553
1554 rfc2965 = self._policy.rfc2965
1555 netscape = self._policy.netscape
1556
1557 if ((not rfc2965_hdrs and not ns_hdrs) or
1558 (not ns_hdrs and not rfc2965) or
1559 (not rfc2965_hdrs and not netscape) or
1560 (not netscape and not rfc2965)):
1561 return [] # no relevant cookie headers: quick exit
1562
1563 try:
1564 cookies = self._cookies_from_attrs_set(
1565 split_header_words(rfc2965_hdrs), request)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001566 except Exception:
1567 _warn_unhandled_exception()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001568 cookies = []
1569
1570 if ns_hdrs and netscape:
1571 try:
Neal Norwitz71dad722005-12-23 21:43:48 +00001572 # RFC 2109 and Netscape cookies
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001573 ns_cookies = self._cookies_from_attrs_set(
1574 parse_ns_headers(ns_hdrs), request)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001575 except Exception:
1576 _warn_unhandled_exception()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001577 ns_cookies = []
Neal Norwitz71dad722005-12-23 21:43:48 +00001578 self._process_rfc2109_cookies(ns_cookies)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001579
1580 # Look for Netscape cookies (from Set-Cookie headers) that match
1581 # corresponding RFC 2965 cookies (from Set-Cookie2 headers).
1582 # For each match, keep the RFC 2965 cookie and ignore the Netscape
1583 # cookie (RFC 2965 section 9.1). Actually, RFC 2109 cookies are
1584 # bundled in with the Netscape cookies for this purpose, which is
1585 # reasonable behaviour.
1586 if rfc2965:
1587 lookup = {}
1588 for cookie in cookies:
1589 lookup[(cookie.domain, cookie.path, cookie.name)] = None
1590
1591 def no_matching_rfc2965(ns_cookie, lookup=lookup):
1592 key = ns_cookie.domain, ns_cookie.path, ns_cookie.name
1593 return key not in lookup
1594 ns_cookies = filter(no_matching_rfc2965, ns_cookies)
1595
1596 if ns_cookies:
1597 cookies.extend(ns_cookies)
1598
1599 return cookies
1600
1601 def set_cookie_if_ok(self, cookie, request):
1602 """Set a cookie if policy says it's OK to do so."""
1603 self._cookies_lock.acquire()
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001604 try:
1605 self._policy._now = self._now = int(time.time())
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001606
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001607 if self._policy.set_ok(cookie, request):
1608 self.set_cookie(cookie)
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001609
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001610
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001611 finally:
1612 self._cookies_lock.release()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001613
1614 def set_cookie(self, cookie):
1615 """Set a cookie, without checking whether or not it should be set."""
1616 c = self._cookies
1617 self._cookies_lock.acquire()
1618 try:
1619 if cookie.domain not in c: c[cookie.domain] = {}
1620 c2 = c[cookie.domain]
1621 if cookie.path not in c2: c2[cookie.path] = {}
1622 c3 = c2[cookie.path]
1623 c3[cookie.name] = cookie
1624 finally:
1625 self._cookies_lock.release()
1626
1627 def extract_cookies(self, response, request):
1628 """Extract cookies from response, where allowable given the request."""
Thomas Wouters477c8d52006-05-27 19:21:47 +00001629 _debug("extract_cookies: %s", response.info())
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001630 self._cookies_lock.acquire()
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001631 try:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001632 self._policy._now = self._now = int(time.time())
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001633
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001634 for cookie in self.make_cookies(response, request):
1635 if self._policy.set_ok(cookie, request):
1636 _debug(" setting cookie: %s", cookie)
1637 self.set_cookie(cookie)
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001638 finally:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001639 self._cookies_lock.release()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001640
1641 def clear(self, domain=None, path=None, name=None):
1642 """Clear some cookies.
1643
1644 Invoking this method without arguments will clear all cookies. If
1645 given a single argument, only cookies belonging to that domain will be
1646 removed. If given two arguments, cookies belonging to the specified
1647 path within that domain are removed. If given three arguments, then
1648 the cookie with the specified name, path and domain is removed.
1649
1650 Raises KeyError if no matching cookie exists.
1651
1652 """
1653 if name is not None:
1654 if (domain is None) or (path is None):
1655 raise ValueError(
1656 "domain and path must be given to remove a cookie by name")
1657 del self._cookies[domain][path][name]
1658 elif path is not None:
1659 if domain is None:
1660 raise ValueError(
1661 "domain must be given to remove cookies by path")
1662 del self._cookies[domain][path]
1663 elif domain is not None:
1664 del self._cookies[domain]
1665 else:
1666 self._cookies = {}
1667
1668 def clear_session_cookies(self):
1669 """Discard all session cookies.
1670
1671 Note that the .save() method won't save session cookies anyway, unless
1672 you ask otherwise by passing a true ignore_discard argument.
1673
1674 """
1675 self._cookies_lock.acquire()
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001676 try:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001677 for cookie in self:
1678 if cookie.discard:
1679 self.clear(cookie.domain, cookie.path, cookie.name)
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001680 finally:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001681 self._cookies_lock.release()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001682
1683 def clear_expired_cookies(self):
1684 """Discard all expired cookies.
1685
1686 You probably don't need to call this method: expired cookies are never
1687 sent back to the server (provided you're using DefaultCookiePolicy),
1688 this method is called by CookieJar itself every so often, and the
1689 .save() method won't save expired cookies anyway (unless you ask
1690 otherwise by passing a true ignore_expires argument).
1691
1692 """
1693 self._cookies_lock.acquire()
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001694 try:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001695 now = time.time()
1696 for cookie in self:
1697 if cookie.is_expired(now):
1698 self.clear(cookie.domain, cookie.path, cookie.name)
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001699 finally:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001700 self._cookies_lock.release()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001701
1702 def __iter__(self):
1703 return deepvalues(self._cookies)
1704
1705 def __len__(self):
1706 """Return number of contained cookies."""
1707 i = 0
1708 for cookie in self: i = i + 1
1709 return i
1710
1711 def __repr__(self):
1712 r = []
1713 for cookie in self: r.append(repr(cookie))
1714 return "<%s[%s]>" % (self.__class__, ", ".join(r))
1715
1716 def __str__(self):
1717 r = []
1718 for cookie in self: r.append(str(cookie))
1719 return "<%s[%s]>" % (self.__class__, ", ".join(r))
1720
1721
Neal Norwitz3e7de592005-12-23 21:24:35 +00001722# derives from IOError for backwards-compatibility with Python 2.4.0
1723class LoadError(IOError): pass
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001724
1725class FileCookieJar(CookieJar):
1726 """CookieJar that can be loaded from and saved to a file."""
1727
1728 def __init__(self, filename=None, delayload=False, policy=None):
1729 """
1730 Cookies are NOT loaded from the named file until either the .load() or
1731 .revert() method is called.
1732
1733 """
1734 CookieJar.__init__(self, policy)
1735 if filename is not None:
1736 try:
1737 filename+""
1738 except:
1739 raise ValueError("filename must be string-like")
1740 self.filename = filename
1741 self.delayload = bool(delayload)
1742
1743 def save(self, filename=None, ignore_discard=False, ignore_expires=False):
1744 """Save cookies to a file."""
1745 raise NotImplementedError()
1746
1747 def load(self, filename=None, ignore_discard=False, ignore_expires=False):
1748 """Load cookies from a file."""
1749 if filename is None:
1750 if self.filename is not None: filename = self.filename
1751 else: raise ValueError(MISSING_FILENAME_TEXT)
1752
1753 f = open(filename)
1754 try:
1755 self._really_load(f, filename, ignore_discard, ignore_expires)
1756 finally:
1757 f.close()
1758
1759 def revert(self, filename=None,
1760 ignore_discard=False, ignore_expires=False):
1761 """Clear all cookies and reload cookies from a saved file.
1762
1763 Raises LoadError (or IOError) if reversion is not successful; the
1764 object's state will not be altered if this happens.
1765
1766 """
1767 if filename is None:
1768 if self.filename is not None: filename = self.filename
1769 else: raise ValueError(MISSING_FILENAME_TEXT)
1770
1771 self._cookies_lock.acquire()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001772 try:
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001773
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001774 old_state = copy.deepcopy(self._cookies)
1775 self._cookies = {}
1776 try:
1777 self.load(filename, ignore_discard, ignore_expires)
1778 except (LoadError, IOError):
1779 self._cookies = old_state
1780 raise
Thomas Wouters902d6eb2007-01-09 23:18:33 +00001781
1782 finally:
Thomas Wouters9fe394c2007-02-05 01:24:16 +00001783 self._cookies_lock.release()
Martin v. Löwis2a6ba902004-05-31 18:22:40 +00001784
1785from _LWPCookieJar import LWPCookieJar, lwp_cookie_str
1786from _MozillaCookieJar import MozillaCookieJar