home *** CD-ROM | disk | FTP | other *** search
/ Enter 2004 April / enter-2004-04.iso / files / EVE_1424_100181.exe / _ClientCookie.py < prev    next >
Encoding:
Python Source  |  2004-04-20  |  92.5 KB  |  2,335 lines

  1. """HTTP cookie handling for web clients.
  2.  
  3. ClientCookie is a Python module for handling HTTP cookies on the client
  4. side, useful for accessing web sites that require cookies to be set and
  5. then returned later.  It also provides some other (optional) useful stuff:
  6. HTTP-EQUIV handling, zero-time Refresh handling, and lazily-seekable
  7. responses.  It has developed from a port of Gisle Aas' Perl module
  8. HTTP::Cookies, from the libwww-perl library.
  9.  
  10. Cookies are a general mechanism which server side connections can use to
  11. both store and retrieve information on the client side of the connection.
  12. For more information about cookies, refer to the following:
  13.  
  14. http://www.netscape.com/newsref/std/cookie_spec.html
  15. http://www.cookiecentral.com/
  16.  
  17. This module also implements the new style cookies described in RFC 2965.
  18. The two variants of cookies are supposed to be able to coexist happily.
  19. RFC 2965 handling can be switched off completely if required.
  20.  
  21. http://www.ietf.org/rfc/rfc2965.txt
  22.  
  23.  
  24. Examples
  25. --------------------------------------------------------------------------
  26.  
  27.  import ClientCookie
  28.  response = ClientCookie.urlopen("http://foo.bar.com/")
  29.  
  30. This function behaves identically to urllib2.urlopen, except that it deals
  31. with cookies automatically.  That's probably all you need to know.
  32.  
  33. Here is a more complicated example, involving Request objects (useful if
  34. you want to pass Requests around, add headers to them, etc.):
  35.  
  36.  import ClientCookie
  37.  import urllib2
  38.  request = urllib2.Request("http://www.acme.com/")
  39.  # note we're using the urlopen from ClientCookie, not urllib2
  40.  response = ClientCookie.urlopen(request)
  41.  # let's say this next request requires a cookie that was set in response
  42.  request2 = urllib2.Request("http://www.acme.com/flying_machines.html")
  43.  response2 = ClientCookie.urlopen(request2)
  44.  
  45. In these examples, the workings are hidden inside the ClientCookie.urlopen
  46. method, which is an extension of urllib2.urlopen.  Redirects, proxies and
  47. cookies are handled automatically by this function.  Other, lower-level,
  48. cookie-aware extensions of urllib2 callables provided are: build_opener,
  49. install_opener, HTTPHandler and HTTPSHandler (if your Python installation
  50. has HTTPS support).  A bugfixed HTTPRedirectHandler is also included (the
  51. bug, related to redirection, should be fixed in 2.3, but hasn't been yet).
  52. Note that extraction and setting of RFC 2965 cookies (but not Netscape
  53. cookies) is currently turned off during automatic urllib2 redirects (until
  54. I figure out exactly when they're allowed).
  55.  
  56. An example at a slightly lower level shows what the module is doing more
  57. clearly:
  58.  
  59.  import ClientCookie
  60.  import urllib2
  61.  request = urllib2.Request("http://www.acme.com/")
  62.  response = urllib2.urlopen(request)
  63.  c = ClientCookie.Cookies()
  64.  c.extract_cookies(response, request)
  65.  # let's say this next request requires a cookie that was set in response
  66.  request2 = urllib2.Request("http://www.acme.com/flying_machines.html")
  67.  c.add_cookie_header(request2)
  68.  response2 = urllib2.urlopen(request2)
  69.  
  70.  print response2.geturl()
  71.  print response2.info()  # headers
  72.  for line in response2.readlines():  # body
  73.      print line
  74.  
  75. The Cookie class does all the work.  There are essentially two operations:
  76. extract_cookies extracts HTTP cookies from Set-Cookie (the original
  77. Netscape cookie standard) and Set-Cookie2 (RFC 2965) headers from a
  78. response if and only if they should be set given the request, and
  79. add_cookie_header adds Cookie headers if and only if they are appropriate
  80. for a particular HTTP request.  Incoming cookies are checked for
  81. acceptability based on the host name, etc.  Cookies are only set on
  82. outgoing requests if they match the request's host name, path, etc.
  83. Cookies may be also be saved to and loaded from a file.
  84.  
  85. Note that if you're using ClientCookie.urlopen (or
  86. ClientCookie.HTTPHandler or ClientCookie.HTTPSHandler), you don't need to
  87. call extract_cookies or add_cookie header yourself.  If, on the other
  88. hand, you don't want to use urllib2, you will need to use this pair of
  89. methods.  You can make your own request and response objects, which must
  90. support the interfaces described in the docstrings of extract_cookies and
  91. add_cookie_header.
  92.  
  93.  
  94. Important note
  95. --------------------------------------------------------------------------
  96.  
  97. The distribution includes some associated modules (_HTTPDate and
  98. _HeadersUtil) upon which ClientCookie depends, also ported from
  99. libwww-perl.  These associated modules may change or disappear with time,
  100. so don't rely on them staying put.  Anything you can import directly from
  101. the ClientCookie package, and that doesn't start with a single underscore,
  102. will not go away.
  103.  
  104.  
  105. Cooperating with Netscape and Internet Explorer
  106. --------------------------------------------------------------------------
  107.  
  108. The subclass NetscapeCookies differs from Cookies only in storing cookies
  109. using a different, Netscape-compatible, file format.  This Netscape-
  110. compatible format loses some information when you save cookies to a file.
  111. Cookies itself uses a libwww-perl specific format (`Set-Cookie3').  Python
  112. and Netscape should be able to share a cookies file (note that the file
  113. location here will differ on non-unix OSes):
  114.  
  115. import os
  116. home = os.environ["HOME"]
  117. cookies = NetscapeCookies(
  118.     file=os.path.join(home, "/.netscape/cookies.txt"),
  119.     autosave=1)
  120.  
  121. XXX Does this work when Netscape is running?  Probably not.
  122.  
  123. MSIECookies (UNTESTED!) does the same for Microsoft Internet Explorer
  124. (MSIE) 5.x and 6.x on Windows, but does not allow saving cookies (because
  125. nobody has fully decoded the file format).
  126.  
  127.  
  128. Using your own Cookies instance
  129. --------------------------------------------------------------------------
  130.  
  131. If you want to use the higher-level urllib2-like interface, but need to
  132. get at the cookies (usually only needed for debugging, or saving cookies
  133. between sessions) and/or pass arguments to the Cookies constructor,
  134. HTTPHandler and HTTPSHandler accept a Cookies instance in their cookies
  135. keyword argument.
  136.  
  137. The urlopen function uses an OpenerDirector instance to do its work, so if
  138. you want to use urlopen, install your own OpenerDirector using the
  139. ClientCookie.install_opener function, then proceed as usual:
  140.  
  141. import ClientCookie
  142. from ClientCookie import Cookies
  143. cookies = Cookies(netscape_only=1, blocked_domains=["doubleclick.net"])
  144. # Build an OpenerDirector that uses an HTTPHandler that uses the cookies
  145. # instance we've just made.  build_opener will add other handlers (such
  146. # as FTPHandler) automatically, so we only have to pass an HTTPHandler.
  147. opener = ClientCookie.build_opener(ClientCookie.HTTPHandler(cookies))
  148. ClientCookie.install_opener(opener)
  149. r = urlopen("http://www.adverts-r-us.co.uk/")
  150.  
  151. Note that the OpenerDirector instance used by urlopen is global, and
  152. shares the Cookies instance you pass in: all code that uses
  153. ClientCookie.urlopen will therefore be sharing the same set of cookies.
  154. If you don't want global cookies, build your own OpenerDirector object
  155. using ClientCookie.build_opener as shown above, then use it directly
  156. instead of calling urlopen:
  157.  
  158. r = opener.open("http://acme.com/")  # GET
  159. r = opener.open("http://acme.com/", data)  # POST
  160.  
  161.  
  162. Optional goodies: HTTP-EQUIV, Refresh and seekable responses
  163. --------------------------------------------------------------------------
  164.  
  165. These are implemented as three arguments to the HTTPHandler and
  166. HTTPSHandler constructors.  Example code is below.
  167.  
  168. seekable_responses:
  169.  
  170. By default, ClientCookie's response objects are seekable.  Seeking is done
  171. lazily (ie. the response object only reads from the socket as necessary,
  172. rather than slurping in all the data before the response is returned to
  173. you), but if you don't want it, you can turn it off.
  174.  
  175. handle_http_equiv:
  176.  
  177. The <META HTTP-EQUIV> tag is a way of including data in HTML to be treated
  178. as if it were part of the HTTP headers.  ClientCookie can automatically
  179. read these tags and add the HTTP-EQUIV headers to the response object's
  180. real HTTP headers.  The HTML is left unchanged.
  181.  
  182. handle_refresh:
  183.  
  184. The Refresh HTTP header is a non-standard header which is widely used.  It
  185. requests that the user-agent follow a URL after a specified time delay.
  186. ClientCookie can treat these headers (which may have been set in <META
  187. HTTP-EQUIV> tags) as if they were 302 redirections.
  188.  
  189. import ClientCookie
  190. from ClientCookie import Cookies
  191. cookies = Cookies()
  192. hh = ClientCookie.HTTPHandler(cookies,
  193.     seekable_responses=0, handle_refresh=1)
  194. opener = ClientCookie.build_opener(hh)
  195. opener.open("http://www.rhubarb.com/")
  196.  
  197.  
  198. Adding headers
  199. --------------------------------------------------------------------------
  200.  
  201. Adding headers is done like so:
  202.  
  203. import ClientCookie, urllib2
  204. req = urllib2.Request("http://foobar.com/")
  205. req.add_header("Referer", "http://wwwsearch.sourceforge.net/ClientCookie/")
  206. r = ClientCookie.urlopen(req)
  207.  
  208. You can also use the headers argument to the urllib2.Request constructor.
  209. Removing headers should not be necessary, because urllib2.Request objects
  210. start out with no headers.
  211.  
  212.  
  213. Changing the automatically-added headers (User-Agent)
  214. --------------------------------------------------------------------------
  215.  
  216. urllib2.OpenerDirector automatically adds a User-Agent header to every
  217. request.
  218.  
  219. Again, since ClientCookie.urlopen uses an OpenerDirector instance, you
  220. need to install your own OpenerDirector using the
  221. ClientCookie.install_opener function to change this behaviour.
  222.  
  223. import ClientCookie
  224. cookies = ClientCookie.Cookies()
  225. opener = ClientCookie.build_opener(ClientCookie.HTTPHandler(cookies))
  226. opener.add_headers = [("User-agent", "Mozilla/4.76")]
  227. ClientCookie.install_opener(opener)
  228. r = urlopen("http://acme.com/")
  229.  
  230. Again, you can always call opener.open directly (instead of urlopen) if
  231. you don't want global cookies.
  232.  
  233.  
  234. Debugging
  235. --------------------------------------------------------------------------
  236.  
  237. First, a few common problems.  The most frequent mistake people seem to
  238. make is to use ClientCookie.urlopen, *and* the extract_cookies and
  239. add_cookie_header methods on a cookie object themselves.  If you use
  240. ClientCookie.urlopen, the module handles extraction and adding of cookies
  241. by itself, so you should not call extract_cookies or add_cookie_header.
  242.  
  243. If things don't seem to be working as expected, the first thing to try is
  244. to switch off RFC 2965 handling, using the netscape_only argument to the
  245. Cookies constructor.  This is because few browsers implement it, so it is
  246. likely that some servers incorrectly implement it.  This switch is also
  247. useful because ClientCookie does not yet fully implement redirects with
  248. RFC 2965 cookies.  2965 cookies are always switched off during redirects,
  249. while the standard allows setting and returning cookies under some
  250. circumstances, which will probably cause some servers to refuse to provide
  251. content.  XXX actually, there are probably almost no RFC 2965 servers out
  252. there, at least ATM...
  253.  
  254. Are you sure the server is sending you any cookies in the first place?
  255. Maybe the server is keeping track of state in some other way (HIDDEN HTML
  256. form entries (possibly in a separate page referenced by a frame),
  257. URL-encoded session keys, IP address)?  Perhaps some embedded script in
  258. the HTML is setting cookies (see below)?  Maybe you messed up your
  259. request, and the server is sending you some standard failure page (even if
  260. the page doesn't appear to indicate any failure).  Sometimes, a server
  261. wants particular headers set to the values it expects, or it won't play
  262. nicely.  The most frequent offenders here are the Referer [sic] and / or
  263. User-Agent HTTP headers.  See above for how to change the value of the
  264. User-Agent header; otherwise, use Request.add_header.  The User-Agent
  265. header may need to be set to a value like that above.  The Referer header
  266. may need to be set to the URL that the server expects you to have followed
  267. a link from.  Occasionally, it may even be that operators deliberately
  268. configure a server to insist on precisely the headers that the big two
  269. browsers (MS Internet Explorer and Netscape) generate, but remember that
  270. incompetence (possibly on your part) is more probable than deliberate
  271. sabotage.
  272.  
  273. When you save to a file, single-session cookies will expire unless you
  274. explicitly request otherwise by setting ignore_discard to true in the
  275. Cookies constructor.  This may be your problem if you find cookies are
  276. going away after saving and loading.
  277.  
  278. If none of the advice above seems to solve your problem, the last resort
  279. is to compare the headers and data that you are sending out with those
  280. that a browser emits.  Of course, you'll want to check that the browser is
  281. able to do manually what you're trying to achieve programatically before
  282. minutely examining the headers.  Make sure that what you do manually is
  283. *exactly* the same as what you're trying to do from Python -- you may
  284. simply be hitting a server bug that only gets revealed if you view pages
  285. in a particular order, for example.  In order to see what your browser is
  286. sending to the server, you can use a TCP network sniffer (netcat --
  287. usually installed as nc, or ethereal, for example), or use a feature like
  288. lynx's -trace switch.  If nothing is obviously wrong with the requests
  289. your program is sending, you may have to temporarily switch to sending
  290. HTTP headers (with httplib).  Start by copying Netscape or IE slavishly
  291. (apart from session IDs, etc., of course), then begin the tedious process
  292. of mutating your headers and data until they match what your higher-level
  293. code was sending.  This will reliably find your problem.
  294.  
  295. You can globally turn on display of HTTP headers:
  296.  
  297. import ClientCookie
  298. ClientCookie.HTTP_DEBUG = 1
  299.  
  300. (Note that doing this won't work:
  301.  
  302. from ClientCookie import HTTP_DEBUG
  303. HTTP_DEBUG = 1
  304.  
  305. If you don't understand that, you've misunderstood what the = operator
  306. does.)
  307.  
  308. Alternatively, you can examine your individual request and response
  309. objects to see what's going on.  ClientCookie's responses are seek()able
  310. unless you request otherwise.
  311.  
  312. If you would like to see what is going on in ClientCookie's tiny mind, do
  313. this:
  314.  
  315. ClientCookie.CLIENTCOOKIE_DEBUG = 1
  316.  
  317.  
  318. Embedded script that sets cookies
  319. --------------------------------------------------------------------------
  320.  
  321. It is possible to embed script in HTML pages (within <SCRIPT>here</SCRIPT>
  322. tags) -- Javascript / ECMAScript, VBScript, or even Python -- that causes
  323. cookies to be set in a browser.  If you come across this in a page you
  324. want to automate, you have three options.  Here they are, roughly in order
  325. of simplicity.  First, you can simply figure out what the embedded script
  326. is doing and imitate it by manually adding cookies to your Cookies
  327. instance.  Second, if you're working on a Windows machine (or another
  328. platform where the MSHTML COM library is available) you could give up the
  329. fight and automate Microsoft Internet Explorer (MSIE) with COM.  Third,
  330. you could get ambitious and delegate the work to an appropriate
  331. interpreter (Netscape's Javascript interpreter, for instance).
  332.  
  333.  
  334. Parsing HTTP date strings
  335. --------------------------------------------------------------------------
  336.  
  337. A function named str2time is provided by the package, which may be useful
  338. for parsing dates in HTTP headers.  str2time is intended to be liberal,
  339. since HTTP date/time formats are poorly standardised in practice.  There
  340. is no need to use this function in normal operations: Cookies instances
  341. keep track of cookie lifetimes automatically.  This function will stay
  342. around in some form, though the supported date/time formats may change.
  343.  
  344.  
  345. A final note: docstrings, comments and debug strings in this code refer to
  346. the attributes of the HTTP cookie system as cookie-attributes, to
  347. distinguish them clearly from Python attributes.
  348.  
  349.  
  350. Copyright 1997-1999 Gisle Aas
  351. Copyright 2002-2003 Johnny Lee <typo_pl@hotmail.com> (MSIE Perl code)
  352. Copyright 2002-2003 John J Lee <jjl@pobox.com> (The Python port)
  353.  
  354. This code is free software; you can redistribute it and/or modify it under
  355. the terms of the MIT License (see the file COPYING included with the
  356. distribution).
  357.  
  358. """
  359.  
  360. # XXXX
  361. # Check two-component urls work.
  362. # Write new 1.5.2 code to test ClientCookie on new site (Yahoo mail?).
  363. # Test urllib / urllib2 and ClientCookie with 1.5.2.
  364. # Fix XXXXs and test MSIECookies
  365.  
  366. VERSION = "0.3.1b"  # based on Gisle Aas's CVS revision 1.24, libwww-perl 5.64
  367.  
  368.  
  369. # These quotes from the RFC are sitting here for when I fix the redirect
  370. # behaviour (see TODO file).
  371.  
  372. # Redirects: RFC 2965, section 3.3.6:
  373. #------------------------------------
  374. #   An unverifiable transaction is to a third-party host if its request-
  375. #   host U does not domain-match the reach R of the request-host O in the
  376. #   origin transaction.
  377.  
  378. #   When it makes an unverifiable transaction, a user agent MUST disable
  379. #   all cookie processing (i.e., MUST NOT send cookies, and MUST NOT
  380. #   accept any received cookies) if the transaction is to a third-party
  381. #   host.
  382.  
  383. # request-host: RFC 2965, section 1:
  384. #   Host name (HN) means either the host domain name (HDN) or the numeric
  385. #   Internet Protocol (IP) address of a host.  The fully qualified domain
  386. #   name is preferred; use of numeric IP addresses is strongly
  387. #   discouraged.
  388.  
  389. #   The terms request-host and request-URI refer to the values the client
  390. #   would send to the server as, respectively, the host (but not port)
  391. #   and abs_path portions of the absoluteURI (http_URL) of the HTTP
  392. #   request line.  Note that request-host is a HN.
  393.  
  394. # Reach: RFC 2965, section 1:
  395.  
  396. #   The reach R of a host name H is defined as follows:
  397. #      *  If
  398. #         -  H is the host domain name of a host; and,
  399. #         -  H has the form A.B; and
  400. #         -  A has no embedded (that is, interior) dots; and
  401. #         -  B has at least one embedded dot, or B is the string "local".
  402. #            then the reach of H is .B.
  403.  
  404. #      *  Otherwise, the reach of H is H.
  405.  
  406. import sys, os, re, urlparse, string, socket, copy, struct, htmllib, formatter
  407. from urllib2 import URLError
  408. from time import time
  409.  
  410. import ClientCookie
  411. from ClientCookie._HTTPDate import str2time, time2isoz
  412. from ClientCookie._HeadersUtil import split_header_words, join_header_words
  413. from ClientCookie._Util import startswith, endswith
  414. from ClientCookie._Debug import debug
  415.  
  416. try: True
  417. except NameError:
  418.     True = 1
  419.     False = 0
  420.  
  421. CHUNK = 1024  # size of chunks fed to HTML HEAD parser, in bytes
  422. MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the Cookies "
  423.                          "instance initialised with one)")
  424.  
  425.  
  426. SPACE_DICT = {}
  427. for c in string.whitespace:
  428.     SPACE_DICT[c] = None
  429. del c
  430. def isspace(string):
  431.     for c in string:
  432.         if not SPACE_DICT.has_key(c): return False
  433.     return True
  434.  
  435. def getheaders(msg, name):
  436.     """Get all values for a header.
  437.  
  438.     This returns a list of values for headers given more than once; each
  439.     value in the result list is stripped in the same way as the result of
  440.     getheader().  If the header is not given, return an empty list.
  441.     """
  442.     result = []
  443.     current = ''
  444.     have_header = 0
  445.     for s in msg.getallmatchingheaders(name):
  446.         if isspace(s[0]):
  447.             if current:
  448.                 current = "%s\n %s" % (current, string.strip(s))
  449.             else:
  450.                 current = string.strip(s)
  451.         else:
  452.             if have_header:
  453.                 result.append(current)
  454.             current = string.strip(s[string.find(s, ":") + 1:])
  455.             have_header = 1
  456.     if have_header:
  457.         result.append(current)
  458.     return result
  459.  
  460.  
  461. IPV4_RE = re.compile(r"\.\d+$")
  462. def is_HDN(text):
  463.     """Return True if text is a host domain name."""
  464.     # XXX
  465.     # This may well be wrong.  Which RFC is HDN defined in, if any?
  466.     # For the current implementation, what about IPv6?  Remember to look
  467.     # at other uses of IPV4_RE also, if change this.
  468.     if IPV4_RE.search(text):
  469.         return False
  470.     if text == "":
  471.         return False
  472.     if text[0] == "." or text[-1] == ".":
  473.         return False
  474.     return True
  475.  
  476. def domain_match(A, B):
  477.     """Return True if domain A domain-matches domainB, according to RFC 2965.
  478.  
  479.     A and B may be host domain names or IP addresses.
  480.  
  481.     RFC 2965, section 1:
  482.  
  483.     Host names can be specified either as an IP address or a HDN string.
  484.     Sometimes we compare one host name with another.  (Such comparisons SHALL
  485.     be case-insensitive.)  Host A's name domain-matches host B's if
  486.  
  487.          *  their host name strings string-compare equal; or
  488.  
  489.          * A is a HDN string and has the form NB, where N is a non-empty
  490.             name string, B has the form .B', and B' is a HDN string.  (So,
  491.             x.y.com domain-matches .Y.com but not Y.com.)
  492.  
  493.     Note that domain-match is not a commutative operation: a.b.c.com
  494.     domain-matches .c.com, but not the reverse.
  495.  
  496.     """
  497.     # Note that, if A or B are IP addresses, the only relevant part of the
  498.     # definition of the domain-match algorithm is the direct string-compare.
  499.     A = string.lower(A)
  500.     B = string.lower(B)
  501.     if A == B:
  502.         return True
  503.     if not is_HDN(A):
  504.         return False
  505.     i = string.rfind(A, B)
  506.     if i == -1 or i == 0:
  507.         # A does not have form NB, or N is the empty string
  508.         return False
  509.     if not startswith(B, "."):
  510.         return False
  511.     if not is_HDN(B[1:]):
  512.         return False
  513.     return True
  514.  
  515. def liberal_is_HDN(text):
  516.     """Return True if text is a host domain name; for blocking domains."""
  517.     if IPV4_RE.search(text):
  518.         return False
  519.     return True
  520.  
  521. def liberal_domain_match(A, B):
  522.     """For blocking domains.
  523.  
  524.     A and B may be host domain names or IP addresses.
  525.  
  526.     "" is matched by everything, including all IP addresses:
  527.  
  528.     assert liberal_domain_match(whatever, "")
  529.  
  530.     """
  531.     A = string.lower(A)
  532.     B = string.lower(B)
  533.     if B == "":
  534.         return True
  535.     if A == B:
  536.         return True
  537.     if not (liberal_is_HDN(A) and liberal_is_HDN(B)):
  538.         return False
  539.     if endswith(A, B):
  540.         return True
  541.     return False
  542.  
  543. ## # XXXX I'm pretty sure this is incorrect -- we only need to check the
  544. ## # original request's absoluteURL.
  545. ## cut_port_re = re.compile(r":\d+$")
  546. ## def request_host(request):
  547. ##     header = request.headers.get("Host")
  548. ##     if header is not None:
  549. ##         host = header
  550. ##     else:
  551. ##         # XXX I think these two actually do the same thing, essentially, but
  552. ##         #  I'm not sure of the precise semantics of request.get_host.
  553. ##         #  Actually, I think urllib2's behaviour here is wrong (SF Python bug
  554. ##         #  413135).
  555. ##         url = request.get_full_url()
  556. ##         host = urlparse.urlparse(url)[1]
  557. ##         #host = request.get_host()
  558.  
  559. ##     return cut_port_re.sub("", host, 1)  # remove port, if present
  560.  
  561. cut_port_re = re.compile(r":\d+$")
  562. def request_host(request):
  563.     url = request.get_full_url()
  564.     host = urlparse.urlparse(url)[1]
  565.     if host == "":
  566.         host = request.headers.get("Host", "")
  567.  
  568.     return cut_port_re.sub("", host, 1)  # remove port, if present
  569.  
  570. def request_path(request):
  571.     url = request.get_full_url()
  572.     #scheme, netloc, path, parameters, query, frag = urlparse.urlparse(url)
  573.     req_path = normalize_path(string.join(urlparse.urlparse(url)[2:], ""))
  574.     if not startswith(req_path, "/"):
  575.         # fix bad RFC 2396 absoluteURI
  576.         req_path = "/"+req_path
  577.     return req_path
  578.  
  579. unescape_re = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])")
  580. normalize_re = re.compile(r"([\0-\x20\x7f-\xff])")
  581. def normalize_path(path):
  582.     """Normalise URI path so that plain string compare can be used.
  583.  
  584.     >>> normalize_path("%19\xd3%Fb%2F%25%26")
  585.     '%19%D3%FB%2F%25&'
  586.     >>> 
  587.  
  588.     In normalised form, all non-printable characters are %-escaped, and all
  589.     printable characters are given literally (not escaped).  All remaining
  590.     %-escaped characters are capitalised.  %25 and %2F are special-cased,
  591.     because they represent the printable characters "%" and "/", which are used
  592.     as escape and URI path separator characters respectively.
  593.  
  594.     """
  595.     def unescape_fn(match):
  596.         x = string.upper(match.group(1))
  597.         if x == "2F" or x == "25":
  598.             return "%%%s" % (x,)
  599.         else:
  600.             # string.atoi deprecated in 2.0, but 1.5.2 int function won't do
  601.             # radix conversion
  602.             return struct.pack("B", string.atoi(x, 16))
  603.     def normalize_fn(match):
  604.         return "%%%02X" % ord(match.group(1))
  605.     path = unescape_re.sub(unescape_fn, path)
  606.     path = normalize_re.sub(normalize_fn, path)
  607.     return path
  608.  
  609.  
  610. class Cookies:
  611.     """Collection of HTTP cookies.
  612.  
  613.     The major methods are extract_cookies and add_cookie_header; these are all
  614.     you are likely to need.  In fact, you probably don't even need to know
  615.     about this class: use the cookie-aware extensions to the urllib2 callables
  616.     provided by this module: urlopen in particular (and perhaps also
  617.     build_opener, HTTPHandler, HTTPSHandler (only if your Python has https
  618.     support compiled in), and HTTPRedirectHandler).
  619.  
  620.     You can give a sequence of domain names from which we never accept cookies,
  621.     nor return cookies to.  Use the blocked_domains argument to the
  622.     constructor, or use the blocked_domains and set_blocked_domains methods.
  623.     Note that all domains which end with elements of blocked_domains are
  624.     blocked.  IP addresses are an exception, and must match exactly.  For
  625.     example, if blocked_domains == ["acme.com", "roadrunner.org",
  626.     "192.168.1.2", ".168.1.2"], then "www.acme.com", "acme.com",
  627.     "roadrunner.org" and 192.168.1.2 are all blocked, but 193.168.1.2 is not
  628.     blocked.
  629.  
  630.     Methods:
  631.  
  632.     Cookies(filename=None,
  633.             autosave=False, ignore_discard=False,
  634.             hide_cookie2=False, netscape_only=False,
  635.             blocked_domains=None)
  636.     add_cookie_header(request)
  637.     extract_cookies(response, request)
  638.     set_cookie(version, key, val, path, domain, port, path_spec,
  639.                secure, maxage, discard, rest=None)
  640.     blocked_domains()
  641.     set_blocked_domains(blocked_domains)
  642.     save(filename=None)
  643.     load(filename=None)
  644.     revert(filename=None)
  645.     clear(domain=None, path=None, key=None)
  646.     clear_temporary_cookies()
  647.     scan(callback)
  648.     as_string(skip_discard=False)  (str(cookie) also works)
  649.  
  650.  
  651.     Public attributes
  652.  
  653.     cookies: a three-level dictionary [domain][path][key]; you probably don't
  654.      need to use this
  655.     filename: default filename for saving cookies
  656.     autosave: save cookies on instance destruction
  657.     ignore_discard: save even cookies that are requested to be discarded
  658.  
  659.     """
  660.     non_word_re = re.compile(r"\W")
  661.     quote_re = re.compile(r"([\"\\])")
  662.     port_re = re.compile(r"^_?\d+(?:,\d+)*$")
  663.     domain_re = re.compile(r"[^.]*")
  664.     dots_re = re.compile(r"^\.+")
  665.  
  666. ##     # for Netscape protocol
  667. ##     # XXXX complete?
  668. ##     special_toplevel_domains = ("com", "edu", "gov", "int", "mil", "net", "org")
  669.  
  670.     def __init__(self, filename=None,
  671.                  autosave=False, ignore_discard=False,
  672.                  hide_cookie2=False, netscape_only=False,
  673.                  blocked_domains=None, delayload=False):
  674.         """
  675.         filename: name of file in which to save and restore cookies
  676.         autosave: save to file during destruction
  677.         ignore_discard: save even cookies that the server indicates should be
  678.          discarded
  679.         hide_cookie2: don't add Cookie2 header to requests (the presence of
  680.          this header indicates to the server that we understand RFC 2965
  681.          cookies)
  682.         netscape_only: switch off RFC 2965 cookie handling altogether (implies
  683.          hide_cookie2 also)
  684.         blocked_domains: sequence of domain names that we never accept cookies
  685.          from, nor return cookies to
  686.         delayload: request that cookies are lazily loaded per-domain from disk;
  687.          this is only a hint since this is only affects performance, not
  688.          behaviour (unless the cookies on disk are changing); a Cookies object
  689.          may ignore it (in fact, only MSIECookies lazily loads cookies)
  690.  
  691.         If a filename is given and refers to a valid cookies file (as defined
  692.         by the class documentation), all cookies are loaded from it.  If
  693.         delayload is not true, this will happen immediately.
  694.  
  695.         Future keyword arguments might include (not yet implemented):
  696.  
  697.         max_cookies=None
  698.         max_cookies_per_domain=None
  699.         max_cookie_size=None
  700.  
  701.         """
  702.         self.filename = filename
  703.         self.autosave = autosave
  704.         self.ignore_discard = ignore_discard
  705.         self._hide_cookie2 = hide_cookie2
  706.         self._disallow_2965 = netscape_only
  707.         if self._disallow_2965:
  708.             self._hide_cookie2 = True
  709.         self._delayload = delayload
  710.  
  711.         if blocked_domains is not None:
  712.             self._blocked_domains = tuple(blocked_domains)
  713.         else:
  714.             self._blocked_domains = ()
  715.         self.cookies = {}
  716.  
  717.         if filename is None:
  718.             if autosave is None:
  719.                 raise ValueError, \
  720.                       "a filename must be given if autosave is requested"
  721.         else:
  722.             try: self.load(filename)
  723.             except IOError: pass
  724.  
  725.     def blocked_domains(self):
  726.         """Return the sequence of blocked domains (as a tuple)."""
  727.         return self._blocked_domains
  728.     def set_blocked_domains(self, blocked_domains):
  729.         """Set the sequence of blocked domains."""
  730.         self._blocked_domains = tuple(blocked_domains)
  731.  
  732.     def _is_blocked(self, domain):
  733.         for blocked_domain in self._blocked_domains:
  734.             if liberal_domain_match(domain, blocked_domain):
  735.                 return True
  736.         return False
  737.  
  738.     def __len__(self):
  739.         """Return number of contained cookies."""
  740.         count = [0]
  741.         def callback(args, c=count): c[0] = c[0] + 1
  742.         self.scan(callback)
  743.         return count[0]
  744.  
  745.     def _return_cookie_path_ok(self, path, req_path):
  746.         """Decide whether cookie should be returned to server, given only path.
  747.  
  748.         If cookie should be returned to server, return True.  Otherwise, return
  749.         False.
  750.  
  751.         path: path set in cookie
  752.         req_path
  753.  
  754.         """
  755.         # this is identical for Netscape and RFC 2965
  756.         debug("- checking cookie path=%s" % path)
  757.         if not startswith(req_path, path):
  758.             debug("  %s does not path-match %s" % (req_path, path))
  759.             return False
  760.         return True
  761.  
  762.     def _return_cookie_ok(self, domain, path, key, value,
  763.                           request, redirect, now):
  764.         """Decide whether cookie should be returned to server, given all info.
  765.  
  766.         If cookie should be returned to server, return true.  Otherwise, return
  767.         false.
  768.  
  769.         """
  770.         # path has already been checked by _return_cookie_path_ok
  771.         # domain should be OK thanks to the algorithm in add_cookie_header
  772.         #  that found this cookie in the first place
  773.         (version, val, port, path_specified,
  774.          secure, expires, discard, rest) = value
  775.         debug(" - checking cookie %s=%s" % (key, val))
  776.         secure_request = (request.get_type() == "https")
  777.         req_port = request.port
  778.         if req_port is None:
  779.             req_port = "80"
  780.  
  781.         if self._disallow_2965 and int(version) > 0:
  782.             debug("   RFC 2965 cookie disallowed by user")
  783.             return False
  784.         if redirect and int(version) > 0:
  785.             debug("   RFC 2965 cookie disallowed during redirect")
  786.             return False
  787.         if secure and not secure_request:
  788.             debug("   not a secure request")
  789.             return False
  790.         if expires and expires < now:
  791.             debug("   expired")
  792.             return False
  793.         if port:
  794.             for p in string.split(port, ","):
  795.                 if p == req_port:
  796.                     break
  797.             else:
  798.                 debug("   request port %s does not match cookie port %s" % (
  799.                     req_port, port))
  800.                 return False
  801.         if int(version) > 0 and self._is_netscape_domain:
  802.             debug("   domain %s applies to Netscape-style cookies only" %
  803.                   domain)
  804.             return False
  805.         if self._is_blocked(domain):
  806.             debug("   domain %s is in user block-list")
  807.             return False
  808.  
  809.         ehn = request_host(request)
  810.         if string.find(ehn, ".") == -1:
  811.             ehn = ehn + ".local"
  812.         if int(version) > 0:
  813.             # origin server effective host name should domain-match
  814.             # domain attribute of cookie
  815.             assert domain_match(ehn, domain)
  816.         else:
  817.             assert endswith(ehn, domain)
  818.  
  819.         debug("   it's a match")
  820.         return True
  821.  
  822.     def _get_cookie_attributes(self, cookies, domain, request,
  823.                                req_path, redirect, now):
  824.         """Return a list of cookie-attributes to be returned to server.
  825.  
  826.         like ['$Path="/"', ...]
  827.  
  828.         The $Version attribute is also added when appropriate (currently only
  829.         once per request).
  830.  
  831.         Also adds Cookie2 header to request, unless hide_cookie2 argument
  832.         to Cookies constructor was true.
  833.  
  834.         """
  835.         # Add cookies in order of most specific path first (i.e. longest
  836.         # path first).
  837.         paths = cookies.keys()
  838.         def decreasing_size(a, b): return cmp(len(b), len(a))
  839.         paths.sort(decreasing_size)
  840.  
  841.         cattrs = []
  842.         for path in paths:
  843.  
  844.             if not self._return_cookie_path_ok(path, req_path):
  845.                 continue
  846.  
  847.             for key, value in cookies[path].items():
  848.                 if not self._return_cookie_ok(domain, path, key, value,
  849.                                               request, redirect, now):
  850.                     continue
  851.  
  852.                 (version, val, port, path_specified,
  853.                  secure, expires, discard, rest) = value
  854.  
  855.                 # set version of Cookie header, and add Cookie2 header
  856.                 # XXX
  857.                 # What should it be if multiple matching Set-Cookie headers
  858.                 #  have different versions themselves?
  859.                 # Answer: this is undecided as of 2003-01-11 -- will be
  860.                 #  settled when RFC 2965 errata appears.
  861.                 if not self._version_has_been_set:
  862.                     self._version_has_been_set = True
  863.                     if (int(version) > 0):
  864.                         cattrs.append("$Version=%s" % version)
  865.                     elif not self._hide_cookie2:
  866.                         # advertise that we know RFC 2965
  867.                         request.add_header("Cookie2", '$Version="1"')
  868.  
  869.                 # quote cookie value if necessary
  870.                 # (not for Netscape protocol, which already has any quotes
  871.                 #  intact, due to the poorly-specified Netscape Cookie: syntax)
  872.                 if self.non_word_re.search(val) and int(version):
  873.                     val = self.quote_re.sub(r"\\\1", val)
  874.  
  875.                 # add cookie-attributes to be returned in Cookie header
  876.                 cattrs.append("%s=%s" % (key, val))
  877.                 if int(version) > 0:
  878.                     if path_specified:
  879.                         cattrs.append('$Path="%s"' % path)
  880.                     if startswith(domain, "."):
  881.                         cattrs.append('$Domain="%s"' % domain)
  882.                     if port is not None:
  883.                         p = "$Port"
  884.                         if port != "":
  885.                             p = p + ('="%s"' % port)
  886.                         cattrs.append(p)
  887.  
  888.         return cattrs
  889.  
  890.     def add_cookie_header(self, request, redirect=False):
  891.         """Add correct Cookie: header to request (urllib2.Request object).
  892.  
  893.         The Cookie2 header is also added unless the hide_cookie2 argument
  894.         to the Cookies constructor was false.
  895.  
  896.         The request object (usually a urllib2.Request instance) must support
  897.         the methods get_full_url, get_host, get_type and add_header, as
  898.         documented by urllib2, and the attributes headers (a mapping containing
  899.         the request's HTTP headers) and port (the port number).
  900.  
  901.         If redirect is true, it will be assumed that the request is to a
  902.         redirect URL, and appropriate action will be taken.  This has no effect
  903.         for Netscape cookies.  At the moment, adding of RFC 2965 cookies is
  904.         switched off entirely if the redirect argument is true: this will
  905.         change in future, to follow the RFC, which allows some cookie use
  906.         during redirections.
  907.  
  908.         """
  909.         now = time()
  910.  
  911.         # origin server effective host name
  912.         erhn = string.lower(request_host(request))
  913.         if string.find(erhn, ".") == -1:
  914.             erhn = erhn + ".local"
  915.  
  916.         req_path = request_path(request)
  917.  
  918.         cattrs = []  # cookie-attributes to be put in the "Cookie" header
  919.  
  920.         self._version_has_been_set = False
  921.         self._is_netscape_domain = False
  922.  
  923.         # Start with origin server effective host name (erhn -- say
  924.         # foo.bar.baz.com), and check all possible domains (foo.bar.baz.com,
  925.         # .bar.baz.com, bar.baz.com) for cookies.  For resulting domains that
  926.         # begin with a dot, this should ensure we have an RFC 2965
  927.         # domain-match.  For domains that don't start with a dot, we still
  928.         # have a match for Netscape protocol, but not for RFC 2965; in this
  929.         # case, self._is_netscape_domain is true.
  930.         domain = erhn
  931.         while string.find(domain, ".") != -1:
  932.             # Do we have any cookies to send back to the server for this
  933.             # domain?
  934.             debug("Checking %s for cookies" % domain)
  935.             cookies = self.cookies.get(domain)
  936.             if cookies is None:
  937.                 # XXX I *think* the only reason why we'd get a domain back
  938.                 # from _next_domain that doesn't domain-match the erhn is that
  939.                 # domain is in fact an IP address, so check for that.
  940.                 if IPV4_RE.search(domain):
  941.                     # no point in continuing, since IP addresses must string-
  942.                     # compare equal in order to domain-match
  943.                     break
  944.                 domain = self._next_domain(domain)
  945.                 continue
  946.  
  947.             # What cookie-attributes do we need to send back?
  948.             # (get_cookie_attributes also, as necessary, adds the $Version
  949.             # attribute to the returned list, and the Cookie2 header to
  950.             # request)
  951.             attrs = self._get_cookie_attributes(
  952.                 cookies, domain, request, req_path, redirect, now)
  953.             cattrs.extend(attrs)
  954.  
  955.             domain = self._next_domain(domain)
  956.  
  957.         if cattrs:
  958.             request.add_header("Cookie", string.join(cattrs, "; "))
  959.  
  960.     def _next_domain(self, domain):
  961.         """Return next domain string in which to look for stored cookies.
  962.  
  963.         Domain string must contain at least one dot.
  964.  
  965.         I say 'domain string' rather than 'domain name' because many of these
  966.         domain strings start with a dot, unlike real DNS domain names.
  967.  
  968.         """
  969.         # Try with a more general domain, alternately stripping leading
  970.         # name components and leading dots.  When this results in a domain
  971.         # with no leading dot, it is for Netscape cookie compatibility
  972.         # only:
  973.         #
  974.         # a.b.c.net     Any cookie
  975.         # .b.c.net      Any cookie
  976.         # b.c.net       Netscape cookie only
  977.         # .c.net        Any cookie
  978.         # (further stripping shouldn't match any cookies that we stored)
  979.  
  980.         if startswith(domain, "."):
  981.             domain = domain[1:]
  982.             self._is_netscape_domain = True
  983.         else:
  984.             domain = self.domain_re.sub("", domain, 1)
  985.             self._is_netscape_domain = False
  986.         return domain
  987.  
  988.     def _set_cookie_if_ok(self, key, val, hash, rest, request,
  989.                           have_ns_cookies, redirect):
  990.         """Decide whether cookie should be set, and if it should, set it."""
  991.         # find request host, path and port
  992.         # in fact we need the effective request-host name here:
  993.         erhn = string.lower(request_host(request))
  994.         if string.find(erhn, ".") == -1:
  995.             erhn = erhn + ".local"
  996.         req_path = request_path(request)
  997.         req_port = request.port
  998.         if req_port is None:
  999.             req_port = "80"
  1000.         else:
  1001.             req_port = str(req_port)
  1002.  
  1003.         # Now get the cookie info from hash, checking whether cookie is ok and
  1004.         # setting defaults.
  1005.  
  1006.         max_age = hash.get("max-age")
  1007.         version = hash.get("version")
  1008.         domain = hash.get("domain")
  1009.         path = hash.get("path")
  1010.  
  1011.         if max_age is not None:
  1012.             max_age = float(max_age)
  1013.  
  1014.         # check version
  1015.         if version is None:
  1016.             # Version is always set to 0 by _parse_ns_attrs if it's a Netscape
  1017.             # cookie, so this must be an invalid RFC 2965 cookie.
  1018.             debug("Set-Cookie2 without version attribute disallowed (%s=%s)" % (key, val))
  1019.             return
  1020.         if int(version) > 0:
  1021.             if redirect:
  1022.                 debug("Setting RFC 2965 cookie during redirect disallowed")
  1023.                 return
  1024.             if self._disallow_2965:
  1025.                 debug("Setting RFC 2965 cookie disallowed by user")
  1026.                 return
  1027.  
  1028.         # check path
  1029.         path_specified = False
  1030.         if path is not None and path != "":
  1031.             path_specified = True
  1032.             path = normalize_path(path)
  1033.             if not have_ns_cookies and not startswith(req_path, path):
  1034.                 debug("Path attribute %s is not a prefix of request path %s" %
  1035.                       (path, req_path))
  1036.                 return
  1037.         else:
  1038.             path = req_path
  1039.             i = path.rfind("/")
  1040.             if i != -1:
  1041.                 if int(version) == 0:
  1042.                     # Netscape spec parts company from reality here
  1043.                     path = path[:i]
  1044.                     #path = re.sub(r"/[^/]*$", "", path, 1)
  1045.                 else:
  1046.                     path = path[:i+1]
  1047.             if len(path) == 0: path = "/"
  1048.  
  1049.         # check domain
  1050.         if (domain is None or
  1051.             # XXX is this the best hack for Netscape protocol?  We need
  1052.             # *something*, because explicitly-set cookie domain like acme.com
  1053.             # must match erhn acme.com, whereas RFC 2965 logic below would
  1054.             # rewrite domain attribute to .acme.com, erroneously resulting in
  1055.             # no match.
  1056.             (int(version) == 0 and (domain == erhn or domain == "."+erhn))):
  1057.             domain = erhn
  1058.         else:
  1059.             if not startswith(domain, "."):
  1060.                 # Netscape protocol doesn't ask for this, but doesn't make
  1061.                 # sense otherwise (two-component domain names, like acme.com,
  1062.                 # could never set cookies if we didn't do this).
  1063.                 domain = ".%s" % domain
  1064.             if domain.startswith("."):
  1065.                 undotted_domain = domain[1:]
  1066.             else:
  1067.                 undotted_domain = domain
  1068.             nr_embedded_dots = string.count(undotted_domain, ".")
  1069.             if nr_embedded_dots == 0 and domain != ".local":
  1070.                 debug("Non-local domain %s contains no embedded dot" % domain)
  1071.                 return
  1072.             # not actually implemented by Netscape Navigator, apparently
  1073. ##             if int(version) == 0:  # Netscape cookie
  1074. ##                 tld = domain[domain.rfind(".")+1:]
  1075. ##                 if (nr_embedded_dots < 2 and
  1076. ##                     tld not in self.special_toplevel_domains):
  1077. ##                     # For example, ".acme.tx.us" is ok, but ".tx.us" is not.
  1078. ##                     debug("Domain %s contains too few dots" % domain)
  1079. ##                     # Note that the other case, where toplevel domain is
  1080. ##                     # special (.com etc. disallowed), is already taken care of
  1081. ##                     # by always requiring at least one embedded dot.
  1082. ##                     return
  1083.             if int(version) == 0:
  1084.                 # XXX maybe should just do RFC 2965 domain-match here?
  1085.                 if IPV4_RE.search(domain):
  1086.                     debug("IP-address %s illegal as domain" % domain)
  1087.                     return
  1088.                 if not endswith(erhn, domain):
  1089.                     debug("Effective request-host %s does not end with %s" % (
  1090.                           erhn, domain))
  1091.                     return
  1092.             else:
  1093.                 if not domain_match(erhn, domain):
  1094.                     debug("Effective request-host %s does not domain-match "
  1095.                           "%s" % (erhn, domain))
  1096.                     return
  1097.                 host_prefix = erhn[:-len(domain)]
  1098.                 if string.find(host_prefix, ".") != -1 and int(version) > 0:
  1099.                     debug("Host prefix %s for domain %s contains a dot" % (
  1100.                         host_prefix, domain))
  1101.                     return
  1102.         if self._is_blocked(domain):
  1103.             debug("Domain %s is in user block-list")
  1104.             return
  1105.  
  1106.         # check port
  1107.         if hash.has_key("port"):
  1108.             port = hash["port"]
  1109.             if port is None:
  1110.                 # Port attr is present, but has no value: need to remember
  1111.                 # request port so we can ensure that cookie is only sent
  1112.                 # back on that port.
  1113.                 port = req_port
  1114.             else:
  1115.                 port = re.sub(r"\s+", "", port)
  1116.                 for p in string.split(port, ","):
  1117.                     try:
  1118.                         int(p)
  1119.                     except ValueError:
  1120.                         debug("Bad port %s (not numeric)" % port)
  1121.                         return
  1122.                     if p == req_port:
  1123.                         break
  1124.                 else:
  1125.                     debug("Request port (%s) not found in %s" % (
  1126.                         req_port, port))
  1127.                     return
  1128.         else:
  1129.             # No port attr present, so will be able to send back this
  1130.             # cookie on any port.
  1131.             port = None
  1132.  
  1133.         all_attrs = hash.copy()
  1134.         all_attrs.update(rest)
  1135.  
  1136.         if self._set_cookie_ok(all_attrs):
  1137.             h = hash.get
  1138.             self.set_cookie(h("version"), key, val, path, domain, port,
  1139.                             path_specified, h("secure"), max_age,
  1140.                             h("discard"), rest)
  1141.  
  1142.     def _parse_ns_attrs(self, ns_set_strings):
  1143.         """Ad-hoc parser for Netscape protocol cookie-attributes.
  1144.  
  1145.         The old Netscape cookie format for Set-Cookie
  1146.  
  1147.         http://www.netscape.com/newsref/std/cookie_spec.html
  1148.  
  1149.         can for instance contain an unquoted "," in the expires field, so we
  1150.         have to use this ad-hoc parser instead of split_header_words.
  1151.  
  1152.         """
  1153.         now = time()
  1154.         ns_set = []
  1155.         for attrs_string in ns_set_strings:
  1156.             ns_attrs = []
  1157.             expires = False
  1158.             for param in re.split(r";\s*", attrs_string):
  1159.                 if string.rstrip(param) == "": continue
  1160.                 if "=" not in param:
  1161.                     k, v = string.rstrip(param), None
  1162.                     if k != "secure":
  1163.                         debug("unrecognised Netscape protocol boolean "
  1164.                               "cookie-attribute '%s'" % k)
  1165.                 else:
  1166.                     k, v = re.split(r"\s*=\s*", param, 1)
  1167.                     v = string.rstrip(v)
  1168.                 lc = string.lower(k)
  1169.                 if lc == "expires":
  1170.                     # convert expires date to max-age delta
  1171.                     etime = str2time(v)
  1172.                     if etime is not None:
  1173.                         ns_attrs.append(("max-age", etime - now))
  1174.                         expires = True
  1175.                 else:
  1176.                     ns_attrs.append((k, v))
  1177.  
  1178.             # XXX commented out in original Perl -- should it be here,
  1179.             #   or not?
  1180.             #ns_attrs.append(("port", req_port))
  1181.             #  anyway, it should really be this, if anything at all:
  1182.             #ns_attrs.append(("port", None))
  1183.  
  1184.             # XXX surely this is wrong: RFC 2965 *also* states that a
  1185.             # missing expiry date means should be set to expire -- so
  1186.             # why are we only doing this for Netscape cookies??
  1187.             if not expires: ns_attrs.append(("discard", None))
  1188.             ns_attrs.append(("version", "0"))
  1189.             ns_set.append(ns_attrs)
  1190.  
  1191.         return ns_set
  1192.  
  1193.     def _normalized_cookie_info(self, set):
  1194.         """Return list of tuples containing normalised cookie information.
  1195.  
  1196.         Tuples are name, value, hash, rest, where name and value are the cookie
  1197.         name and value, hash is a dictionary containing the most important
  1198.         cookie-attributes (discard, secure, version, max-age, domain, path and
  1199.         port) and rest is a dictionary containing the rest of the cookie-
  1200.         attributes.
  1201.  
  1202.         """
  1203.         cookie_tuples = []
  1204.  
  1205.         boolean_attrs = "discard", "secure"
  1206.         value_attrs = "version", "max-age", "domain", "path", "port"
  1207.         #print "set", set
  1208.  
  1209.         for cookie_attrs in set:
  1210.             name, value = cookie_attrs[0]
  1211.             debug("Attempt to set cookie %s=%s" % (name, value))
  1212.  
  1213.             # Build dictionary of common cookie-attributes (hash) and
  1214.             # dictionary of other cookie-attributes (rest).
  1215.             hash = {}
  1216.             rest = {}
  1217.             for k, v in cookie_attrs[1:]:
  1218.                 lc = string.lower(k)
  1219.                 # don't lose case distinction for unknown fields
  1220.                 if (lc in value_attrs) or (lc in boolean_attrs):
  1221.                     k = lc
  1222.                 if k in boolean_attrs:
  1223.                     if v is None:
  1224.                         # set boolean default
  1225.                         # Note that this is a default for the case where the
  1226.                         # cookie-attribute *is* present, but has no value
  1227.                         # (like "discard", as contrasted with "path=/").
  1228.                         # If the cookie-attribute *isn't* present (if "path"
  1229.                         # is missing, for example), the value stored for it
  1230.                         # will always be false.
  1231.                         v = True
  1232.                 if hash.has_key(k):
  1233.                     # only first value is significant
  1234.                     continue
  1235.                 if k == "domain":
  1236.                     # RFC 2965 section 3.3.3
  1237.                     v == string.lower(v)
  1238.                 if (k in value_attrs) or (k in boolean_attrs):
  1239.                     hash[k] = v
  1240.                 else:
  1241.                     rest[k] = v
  1242.  
  1243.             cookie_tuples.append((name, value, hash, rest))
  1244.  
  1245.         return cookie_tuples
  1246.  
  1247.     def extract_cookies(self, response, request, redirect=0):
  1248.         """Extract cookies from response, where allowable given the request.
  1249.  
  1250.         Look for allowable Set-Cookie: and Set-Cookie2: headers in the response
  1251.         object passed as argument.  Any of these headers that are found are
  1252.         used to update the state of the object (subject to the _set_cookie_ok
  1253.         method's approval).
  1254.  
  1255.         The response object (which will usually be the result of a call to
  1256.         ClientCookie.urlopen, or similar) must support the methods read,
  1257.         readline, readlines, fileno, close and info, as described in the
  1258.         documentation for the standard urllib and urllib2 modules.  In
  1259.         particular, these methods work like those on standard file objects,
  1260.         with the exception of info, which returns a mimetools.Message object.
  1261.  
  1262.         The request object (usually a urllib2.Request instance) must support
  1263.         the methods get_full_url and get_host, as documented by urllib2, and
  1264.         the attributes headers (a mapping containing the request's HTTP
  1265.         headers) and port (the port number).
  1266.  
  1267.         If redirect is true, it will be assumed that the request was to a
  1268.         redirect URL, and appropriate action will be taken.  This has no effect
  1269.         for Netscape cookies.  At the moment, extraction of RFC 2965 cookies is
  1270.         switched off entirely if the redirect argument is true: this will
  1271.         change in future, to follow the RFC, which allows some cookie use
  1272.         during redirections.
  1273.  
  1274.         """
  1275.         # get cookie-attributes for RFC 2965 and Netscape protocols
  1276.         headers = response.info()
  1277.         rfc2965_strings = getheaders(headers, "Set-Cookie2")
  1278.         ns_strings = getheaders(headers, "Set-Cookie")
  1279.  
  1280.         if ((not rfc2965_strings and not ns_strings) or
  1281.             (not ns_strings and self._disallow_2965)):
  1282.             return  # no cookie headers: quick exit
  1283.  
  1284.         # Parse out cookie-attributes from RFC 2965 Set-Cookie2 headers.
  1285.         set = split_header_words(rfc2965_strings)
  1286.         cookie_tuples = self._normalized_cookie_info(set)
  1287.  
  1288.         have_ns_cookies = False
  1289.         if ns_strings:
  1290.             # Parse out cookie-attributes from Netscape Set-Cookie headers.
  1291.             ns_set = self._parse_ns_attrs(ns_strings)
  1292.             ns_cookie_tuples = self._normalized_cookie_info(ns_set)
  1293.  
  1294.             # Look for Netscape cookies (from a Set-Cookie headers) that match
  1295.             # corresponding RFC 2965 cookies (from Set-Cookie2 headers).
  1296.             # For each match, keep the RFC 2965 cookie and ignore the Netscape
  1297.             # cookie (RFC 2965 section 9.1).
  1298.             if not self._disallow_2965:
  1299.                 # Build a dictionary of cookies that are present in Set-Cookie2
  1300.                 # headers.
  1301.                 rfc2965_cookies = {}
  1302.                 for name, value, hash, rest in cookie_tuples:
  1303.                     key = hash.get("domain", ""), hash.get("path", ""), name
  1304.                     rfc2965_cookies[key] = None
  1305.  
  1306.                 def no_matching_rfc2965(ns_cookie_tuple,
  1307.                                         rfc2965_cookies=rfc2965_cookies):
  1308.                     name, value, hash, rest = ns_cookie_tuple
  1309.                     key = hash.get("domain", ""), hash.get("path", ""), name
  1310.                     if not rfc2965_cookies.has_key(key):
  1311.                         return True
  1312.                 ns_cookie_tuples = filter(no_matching_rfc2965, ns_cookie_tuples)
  1313.  
  1314.             if ns_cookie_tuples:
  1315.                 have_ns_cookies = True
  1316.                 cookie_tuples.extend(ns_cookie_tuples)
  1317.  
  1318.         for name, value, hash, rest in cookie_tuples:
  1319.             self._set_cookie_if_ok(name, value, hash, rest,
  1320.                                    request, have_ns_cookies, redirect)
  1321.  
  1322.     def _set_cookie_ok(self, headers):
  1323.         """Return False if the cookie should not be set.
  1324.  
  1325.         This is intended for overloading by subclasses.  Do not call this
  1326.         method.
  1327.  
  1328.         The cookie has already been approved by the extract_cookies method by
  1329.         the time it gets here, so there's no need to reimplement the standard
  1330.         acceptance rules.
  1331.  
  1332.         headers: dictionary containing HTTP "Cookie" and "Cookie2" headers.
  1333.  
  1334.         """
  1335.         return True
  1336.  
  1337.     def set_cookie(self, version, key, val, path, domain, port, path_spec,
  1338.                    secure, max_age, discard, rest=None):
  1339.         """Add a cookie.
  1340.  
  1341.         The version, key, val, path, domain and port arguments are strings.
  1342.         The path_spec, secure, discard arguments are boolean values.  The
  1343.         max_age argument is a number indicating number of seconds that this
  1344.         cookie will live.  A value <= 0 will delete this cookie.  The
  1345.         dictionary rest defines various other cookie-attributes like "Comment"
  1346.         and "CommentURL".
  1347.  
  1348.         """
  1349. ##         print "set_cookie:"
  1350. ##         print " version", version
  1351. ##         print " key", key
  1352. ##         print " val", val
  1353. ##         print " path", path
  1354. ##         print " domain", domain
  1355. ##         print " port", port
  1356. ##         print " path_spec", path_spec
  1357. ##         print " secure", secure
  1358. ##         print " max_age", max_age
  1359. ##         print " discard", discard
  1360. ##         print " rest", rest
  1361. ##         if rest: print " rest is not empty"
  1362. ##         else: print " rest is empty"
  1363.  
  1364.         if path is None or not startswith(path, "/"):
  1365.             raise ValueError, "Illegal path: '%s'" % path
  1366.         if key is None or key == "" or startswith(key, "$"):
  1367.             raise ValueError, "Illegal key: '%s'" % key
  1368.  
  1369.         if port is not None:
  1370.             if not self.port_re.search(port):
  1371.                 msg = "Illegal port: '%s'" % port
  1372.                 debug(msg)
  1373.                 raise ValueError, msg
  1374.  
  1375.         # normalise case, as per RFC 2965 section 3.3.3
  1376.         # XXX RFC 1034 says should preserve case, but use case-insensitive
  1377.         # string compare.  This would complicate things here, because we're
  1378.         # using a dictionary to store cookies by domain.  I don't think this
  1379.         # really matters here.
  1380.         domain = string.lower(domain)
  1381.  
  1382.         # If no Max-Age cookie-attribute, cookie will be set to discarded. This
  1383.         # will happen on next call to clear_temporary_cookies, or on next save.
  1384.         expires = 0
  1385.         if max_age is not None:
  1386.             if max_age <= 0:
  1387.                 try:
  1388.                     del self.cookies[domain][path][key]
  1389.                 except KeyError:
  1390.                     pass
  1391.                 else:
  1392.                     debug("Expiring cookie, "
  1393.                           "domain='%s', path='%s', key='%s'" % (
  1394.                         domain, path, key))
  1395.                 return
  1396.             expires = str(time() + float(max_age))
  1397.  
  1398.         if version is None:
  1399.             version = "0"
  1400.  
  1401.         debug("Set cookie %s=%s" % (key, val))
  1402.         self._set_cookie(
  1403.             domain, path, key,
  1404.             [version, val, port, path_spec, secure, expires, discard,
  1405.              rest])
  1406.  
  1407.     def _set_cookie(self, domain, path, key, cookie_info):
  1408.         c = self.cookies
  1409.         if not c.has_key(domain): c[domain] = {}
  1410.         c2 = c[domain]
  1411.         if not c2.has_key(path): c2[path] = {}
  1412.         c3 = c2[path]
  1413.         c3[key] = cookie_info
  1414.  
  1415.     def save(self, filename=None):
  1416.         """Save cookies to a file.
  1417.  
  1418.         The cookies can be restored later using the load method.  If filename
  1419.         is not specified, the name specified during construction (if any) is
  1420.         used.  If the attribute ignore_discard is set, then even cookies marked
  1421.         to be discarded are saved.
  1422.  
  1423.         The Cookies base class saves a sequence of "Set-Cookie3" lines.
  1424.         "Set-Cookie3" is the format used by the libwww-perl libary, not known
  1425.         to be compatible with any browser.  The NetscapeCookies subclass can be
  1426.         used to save in a format compatible with Netscape.
  1427.  
  1428.         Cookies set to be discarded are only saved if the ignore_discard
  1429.         attribute is set.
  1430.  
  1431.         The implementation of this method in the Cookies base class always
  1432.         saves cookies which have expired by outliving their Max-Age
  1433.         cookie-attribute (unlike the NetscapeCookies implementation).  This may
  1434.         change in future.
  1435.  
  1436.         """
  1437.         if filename is None:
  1438.             if self.filename is not None: filename = self.filename
  1439.             else: raise ValueError, MISSING_FILENAME_TEXT
  1440.         f = open(filename, "w")
  1441.         f.write("#LWP-Cookies-1.0\n")
  1442.         f.write(self.as_string(not self.ignore_discard))
  1443.         f.close()
  1444.         self.filename = filename
  1445.  
  1446.     def load(self, filename=None):
  1447.         """Append cookies from a file.
  1448.  
  1449.         The named file must be in the format written by the save method, or
  1450.         IOError will be raised.
  1451.  
  1452.         Note for subclassers: overridden versions of this method should not
  1453.         alter the object's state other than by setting self.filename (if and
  1454.         only if the load was successful) and calling self.set_cookie.
  1455.  
  1456.         """
  1457.         if filename is None:
  1458.             if self.filename is not None: filename = self.filename
  1459.             else: raise ValueError, MISSING_FILENAME_TEXT
  1460.         f = open(filename)
  1461.         magic = f.readline()
  1462.         if not re.search(r"^\#LWP-Cookies-(\d+\.\d+)", magic):
  1463.             msg = "%s does not seem to contain cookies" % (file,)
  1464.             raise IOError, msg
  1465.  
  1466.         boolean_attrs = "path_spec", "secure", "discard"
  1467.         value_attrs = "version", "port", "path", "domain", "expires"
  1468.  
  1469.         try:
  1470.             while 1:
  1471.                 line = f.readline()
  1472.                 if line == "": break
  1473.                 header = "Set-Cookie3:"
  1474.                 if not startswith(line, header):
  1475.                     continue
  1476.                 line = string.strip(line[len(header):])
  1477.                 for cookie in split_header_words([line]):
  1478.                     key, val = cookie[0]
  1479.                     hash = {}
  1480.                     rest = {}
  1481.                     for name in boolean_attrs:
  1482.                         hash[name] = False
  1483.                     for k, v in cookie[1:]:
  1484.                         if k in boolean_attrs:
  1485.                             if v is None: v = True
  1486.                             hash[k] = v
  1487.                         elif k in value_attrs:
  1488.                             hash[k] = v
  1489.                         else:
  1490.                             rest[k] = v
  1491.                     h = hash.get
  1492.                     expires = h("expires")
  1493.                     if expires is not None:
  1494.                         expires = str2time(expires)
  1495.                     value = [h("version"), val, h("port"), h("path_spec"),
  1496.                              h("secure"), expires, h("discard"), rest]
  1497.                     self._set_cookie(h("domain"), h("path"), key, value)
  1498.         except:
  1499.             type = sys.exc_info()[0]
  1500.             if issubclass(type, IOError):
  1501.                 raise
  1502.             else:
  1503.                 raise IOError, "invalid Set-Cookie3 format file %s" % filename
  1504.         self.filename = filename
  1505.  
  1506.     def revert(self, filename=None):
  1507.         """Clear all cookies and reload cookies from a saved file.
  1508.  
  1509.         Raises IOError if reversion is not successful; the object's state will
  1510.         not be altered if this happens.
  1511.  
  1512.         """
  1513.         if filename is None:
  1514.             if self.filename is not None: filename = self.filename
  1515.             else: raise ValueError, MISSING_FILENAME_TEXT
  1516.         old_state = copy.deepcopy(self.cookies)
  1517.         self.clear()
  1518.         try:
  1519.             self.load()
  1520.         except IOError:
  1521.             self.cookies = old_state
  1522.             raise
  1523.  
  1524.     def clear(self, domain=None, path=None, key=None):
  1525.         """Clear some cookies.
  1526.  
  1527.         Invoking this method without arguments will clear all cookies.  If
  1528.         given a single argument, only cookies belonging to that domain will be
  1529.         removed.  If given two arguments, cookies belonging to the specified
  1530.         path within that domain are removed.  If given three arguments, then
  1531.         the cookie with the specified key, path and domain is removed.
  1532.  
  1533.         Raises KeyError if no matching cookie exists.
  1534.  
  1535.         """
  1536.         if key is not None:
  1537.             if (domain is None) or (path is None):
  1538.                 raise ValueError, \
  1539.                       "domain and path must be given to remove a cookie by key"
  1540.             del self.cookies[domain][path][key]
  1541.         elif path is not None:
  1542.             if domain is None:
  1543.                 raise ValueError, \
  1544.                       "domain must be given to remove cookies by path"
  1545.             del self.cookies[domain][path]
  1546.         elif domain is not None:
  1547.             del self.cookies[domain]
  1548.         else:
  1549.             self.cookies = {}
  1550.  
  1551.     def clear_temporary_cookies(self):
  1552.         """Discard all temporary cookies.
  1553.  
  1554.         Scans for all cookies held by object having either no Max-Age
  1555.         cookie-attribute or a true discard flag.  RFC 2965 says you should call
  1556.         this when the user agent shuts down.
  1557.  
  1558.         """
  1559.         def callback(args, self=self):
  1560.             if (args[9] or not args[8]):
  1561.                 # "Discard" flag set or there was no Max-Age cookie-attribute.
  1562.                 # clear the cookie, by setting negative Max-Age
  1563.                 args[8] = -1
  1564.                 apply(self.set_cookie, args)
  1565.         self.scan(callback)
  1566.  
  1567.     def __del__(self):
  1568.         if self.autosave: self.save()
  1569.  
  1570.     def scan(self, callback):
  1571.         """Apply supplied function to each stored cookie.
  1572.  
  1573.         The callback function will be invoked with a sequence argument:
  1574.  
  1575.           index  content
  1576.           --------------
  1577.           0      version
  1578.           1      key
  1579.           2      value
  1580.           3      path
  1581.           4      domain
  1582.           5      port
  1583.           6      path_specified
  1584.           7      secure
  1585.           8      expires
  1586.           9      discard
  1587.          10      dictionary containing other cookie-attributes, eg. "Comment"
  1588.  
  1589.         """
  1590.         domains = self.cookies.keys()
  1591.         domains.sort()
  1592.         for domain in domains:
  1593.             paths = self.cookies[domain].keys()
  1594.             paths.sort()
  1595.             for path in paths:
  1596.                 for key, value in self.cookies[domain][path].items():
  1597.                     (version, val, port, path_specified,
  1598.                      secure, expires, discard, rest) = value
  1599.                     if rest is None:
  1600.                         rest = {}
  1601.                     callback([version, key, val, path, domain, port,
  1602.                         path_specified, secure, expires, discard, rest])
  1603.  
  1604.     def __str__(self): return self.as_string()
  1605.  
  1606.     def as_string(self, skip_discard=False):
  1607.         """Return cookies as a string of "\n"-separated "Set-Cookie3" headers.
  1608.  
  1609.         If skip_discard is true, it will not return lines for cookies with the
  1610.         Discard cookie-attribute.
  1611.  
  1612.         str(cookies) also works.
  1613.  
  1614.         """
  1615.         result = []
  1616.         def callback(args, result=result, skip_discard=skip_discard):
  1617.             (version, key, val, path, domain, port,
  1618.              path_specified, secure, expires, discard, rest) = args
  1619.             if discard and skip_discard: return
  1620.             h = [(key, val),
  1621.                  ("path", path),
  1622.                  ("domain", domain)]
  1623.             if port is not None: h.append(("port", port))
  1624.             if path_specified: h.append(("path_spec", None))
  1625.             if secure: h.append(("secure", None))
  1626.             if expires: h.append(("expires", time2isoz(float(expires))))
  1627.             if discard: h.append(("discard", None))
  1628.             keys = rest.keys()
  1629.             keys.sort()
  1630.             for k in keys:
  1631.                 h.append((k, str(rest[k])))
  1632.             h.append(("version", version))
  1633.             result.append(("Set-Cookie3: %s" % (join_header_words([h]),)))
  1634.         self.scan(callback)
  1635.         return string.join(result+[""], "\n")
  1636.  
  1637.  
  1638. class NetscapeCookies(Cookies):
  1639.     """
  1640.     This class differs from Cookies only in the format it uses to save and load
  1641.     cookies to and from a file.  This class uses the Netscape `cookies.txt'
  1642.     format.
  1643.  
  1644.     Note that the Netscape format will lose information on saving and
  1645.     restoring.  In particular, the port number and cookie protocol version
  1646.     information is lost.  XXX path_specified, discard??
  1647.  
  1648.     Unlike the Cookies base class, this class currently checks cookie expiry
  1649.     times on saving, and expires cookies appropriately.  Cookies instead waits
  1650.     until you call clear_temporary_cookies.  This may change in future.
  1651.  
  1652.     """
  1653.     def load(self, filename=None):
  1654.         if filename is None:
  1655.             if self.filename is not None: filename = self.filename
  1656.             else: raise ValueError, MISSING_FILENAME_TEXT
  1657.         cookies = []
  1658.         f = open(filename)
  1659.         magic = f.readline()
  1660.         if not startswith(string.lstrip(magic), "# Netscape HTTP Cookie File"):
  1661.             f.close()
  1662.             raise IOError, (
  1663.                 "%s does not look like a Netscape format cookies file" % (
  1664.                 filename,))
  1665.         now = time()
  1666.  
  1667.         try:
  1668.             while 1:
  1669.                 line = f.readline()
  1670.                 if line == "": break
  1671.                 line = string.strip(line)
  1672.                 if (startswith(line, "#") or startswith(line, "$") or
  1673.                     line == ""):
  1674.                     continue
  1675.                 domain, bool1, path, secure, expires, key, val = \
  1676.                         string.split(line, "\t")
  1677.                 secure = (secure == "TRUE")
  1678.                 self.set_cookie(None, key, val, path, domain, None,
  1679.                                 0, secure, float(expires)-now, 0)
  1680.         except:
  1681.             type = sys.exc_info()[0]
  1682.             if issubclass(type, IOError):
  1683.                 raise
  1684.             else:
  1685.                 raise IOError, "invalid Netscape format file %s" % filename
  1686.         self.filename = filename
  1687.  
  1688.     def save(self, filename=None):
  1689.         if filename is None:
  1690.             if self.filename is not None: filename = self.filename
  1691.             else: raise ValueError, MISSING_FILENAME_TEXT
  1692.         f = open(filename, "w")
  1693.  
  1694.         f.write("""\
  1695.     # Netscape HTTP Cookie File
  1696.     # http://www.netscape.com/newsref/std/cookie_spec.html
  1697.     # This is a generated file!  Do not edit.
  1698.  
  1699. """)
  1700.         now = time()
  1701.         debug("Saving Netscape cookies.txt file")
  1702.         def callback(args, f=f, now=now, self=self):
  1703.             (version, key, val, path, domain, port,
  1704.              path_specified, secure, expires, discard, rest) = args
  1705.             expires = float(expires)
  1706.             if discard and not self.ignore_discard:
  1707.                 debug("   Not saving %s: marked for discard" % key)
  1708.                 return
  1709.             if not expires: expires = 0
  1710.             if now > expires:
  1711.                 debug("   Not saving %s: expired" % key)
  1712.                 return
  1713.             if secure: secure = "TRUE"
  1714.             else: secure = "FALSE"
  1715.             if startswith(domain, "."): bool = "TRUE"
  1716.             else: bool = "FALSE"
  1717.             f.write(
  1718.                 string.join([domain, bool, path, secure,
  1719.                              str(expires), key, val], "\t")+"\n")
  1720.  
  1721.         self.scan(callback)
  1722.         f.close()
  1723.         self.filename = filename
  1724.  
  1725.  
  1726. class MSIECookies(Cookies):
  1727.     """This class differs from Cookies only in the format it uses to save and
  1728.     load cookies to and from a file.
  1729.  
  1730.     WARNING: This class is UNTESTED (ie. it does not work)!
  1731.  
  1732.     This class can read Microsoft Internet Explorer 5.x and 6.x for
  1733.     Windows (MSIE) cookie files.  Does NOT support saving cookies in MSIE
  1734.     format.  If you save cookies, they'll be in the usual Set-Cookie3
  1735.     format, which you can read back in using an instance of the plain old
  1736.     Cookies class.
  1737.  
  1738.     You should be able to have LWP share Internet Explorer's cookies like
  1739.     this:
  1740.  
  1741.     XXXX how to do this with winreg, or win32all?
  1742.     cookies_dir = Registry(
  1743.         "CUser/Software/Microsoft/Windows/CurrentVersion/Explorer/"
  1744.         "Shell Folders/Cookies")
  1745.     file = os.path.join(cookies_dir, "index.dat")
  1746.     cookies = MSIECookies(file=file, delayload=1)
  1747.  
  1748.     Additional methods:
  1749.  
  1750.     load_cookies(file)
  1751.  
  1752.     """
  1753.     domain_re_2 = re.compile(r"^([^/]+)(/.*)$")
  1754.     cookie_re = re.compile("Cookie\:.+\@([\x21-\xFF]+).*?"
  1755.                            "(.+\@[\x21-\xFF]+\.txt)")
  1756.  
  1757.     win32_epoch = 0x019db1ded53e8000L  # 1970 Jan 01 00:00:00 in Win32 FILETIME
  1758.  
  1759.     def _epoch_time_offset_from_win32_filetime(self, filetime):
  1760.         """Convert from win32 filetime to seconds-since-epoch value.
  1761.  
  1762.         MSIE stores create and expire times as Win32 FILETIME, which is 64
  1763.         bits of 100 nanosecond intervals since Jan 01 1601.
  1764.  
  1765.         Cookies code expects time in 32-bit value expressed in seconds since
  1766.         the epoch (Jan 01 1970).
  1767.  
  1768.         """
  1769.         if filetime < self.win32_epoch:
  1770.             raise ValueError, "filetime (%d) is before epoch (%d)" % (
  1771.                 filetime, self.win32_epoch)
  1772.  
  1773.         return (filetime - self.win32_epoch) / 10000000L
  1774.  
  1775.     def _get_cookie_attributes(self, cookies, domain,
  1776.                                request, req_path, now, redirect):
  1777.         # lazily load cookies for this domain
  1778.         if self._delayload and cookies["//+delayload"] is not None:
  1779.             # Extract cookie filename from the cookie value, into which it
  1780.             # was stuffed by the load method.
  1781.             cookie_file = cookies["//+delayload"]["cookie"][1]
  1782.             if self.cookies.has_key(domain):
  1783.                 del self.cookies[domain]
  1784.             self.load_cookies(cookie_file)
  1785.             cookies = self.cookies[domain]
  1786.         Cookies._get_cookie_attributes(self, cookies, domain,
  1787.                                        request, req_path, now, redirect)
  1788.  
  1789.     def _load_cookies_from_file(self, filename):
  1790.         cookies = []
  1791.  
  1792.         cookies_fh = open(filename)
  1793.  
  1794.         while 1:
  1795.             key = cookies_fh.readline()
  1796.             if key == "": break
  1797.  
  1798.             key = chomp(key)
  1799.             value = cookies_fh.readline()
  1800.             value = chomp(value)
  1801.             domain_path = cookies_fh.readline()
  1802.             domain_path = chomp(domain_path)
  1803.             flags = cookies_fh.readline()
  1804.             flags = chomp(flags) # 0x2000 bit is for secure I think
  1805.             lo_expire = cookies_fh.readline()
  1806.             lo_expire = chomp(lo_expire)
  1807.             hi_expire = cookies_fh.readline()
  1808.             hi_expire = chomp(hi_expire)
  1809.             lo_create = cookies_fh.readline()
  1810.             lo_create = chomp(lo_create)
  1811.             hi_create = cookies_fh.readline()
  1812.             hi_create = chomp(hi_create)
  1813.             sep = cookies_fh.readline()
  1814.             sep = chomp(sep)
  1815.  
  1816.             if "" in (key, value, domain_path, flags, hi_expire, lo_expire,
  1817.                       hi_create, lo_create, sep) or (sep != "*"):
  1818.                 break
  1819.  
  1820.             m = self.domain_re_2.search(domain_path)
  1821.             if m:
  1822.                 domain = m.group(1)
  1823.                 path = m.group(2)
  1824.  
  1825.                 cookies.append({"KEY": key, "VALUE": value, "DOMAIN": domain,
  1826.                                 "PATH": path, "FLAGS": flags, "HIXP": hi_expire,
  1827.                                 "LOXP": lo_expire, "HICREATE": hi_create,
  1828.                                 "LOCREATE": lo_create})
  1829.  
  1830.         return cookies
  1831.  
  1832.     def load_cookies(self, filename):
  1833.         """Load cookies from file containing all cookies for one domain/user.
  1834.         """
  1835.         now = time()
  1836.  
  1837.         cookie_data = self._load_cookies_from_file(filename)
  1838.  
  1839.         for cookie in cookie_data:
  1840.             secure = ((cookie["FLAGS"] & 0x2000) != 0)
  1841.             filetime = (cookie["HIXP"] << 32) + cookie["LOXP"]
  1842.             expires = self._epoch_time_offset_from_win32_filetime(filetime)
  1843.  
  1844.             self.set_cookie(None, cookie["KEY"], cookie["VALUE"],
  1845.                             cookie["PATH"], cookie["DOMAIN"], None,
  1846.                             0, secure, expires - now, 0)
  1847.  
  1848.     def load(self, filename=None):
  1849.         """Load cookies from an MSIE 'index.dat' cookies index file.
  1850.  
  1851.         filename: full path to cookie index file
  1852.  
  1853.         """
  1854.         if filename is None:
  1855.             if self.filename is not None: filename = self.filename
  1856.             else: raise ValueError, MISSING_FILENAME_TEXT
  1857.  
  1858.         now = time()
  1859.         #user_name = string.lower(Win32::LoginName())
  1860.         # XXXX win32all, calldll, ctypes, or whatever
  1861.         # Actually, is this really needed?  Surely there's only one user per
  1862.         # index file anyway??  Maybe not, on win9x.  :(
  1863.  
  1864.         cookie_dir = os.path.dirname(filename)
  1865.  
  1866.         index = open(filename, "rb")
  1867.         data = index.read(256)
  1868.         if len(data) != 256:
  1869.             raise IOError, "%s file is too short" % filename
  1870.  
  1871.         # Cookies' index.dat file starts with 32 bytes of signature
  1872.         # followed by an offset to the first record, stored as a little-
  1873.         # endian DWORD.
  1874.         sig, size, data = data[:32], data[32:36], data[36:]
  1875.         size = struct.unpack("<L")[0]
  1876.         #sig, size = struct.unpack("a32 V", data)
  1877.  
  1878.         # check that sig is valid (only tested in IE6.0)
  1879.         if not sig.startswith("Client UrlCache MMF Ver 5.2") or size != 0x4000:
  1880.             raise IOError, ("%s ['%s' %s] does not seem to contain cookies" % (
  1881.                 filename, sig, size))
  1882.  
  1883.         index.seek(size, 0)  # skip to start of first record
  1884.  
  1885.         # Cookies are usually in two contiguous 128 byte sectors, so read
  1886.         # in two 128 byte sectors and adjust if not a Cookie.
  1887.         while 1:
  1888.             d = index.read(256)
  1889.             if len(d) != 256: break
  1890.             data = data + d
  1891.  
  1892.             # Each record starts with a 4-byte signature and a count
  1893.             # (little-endian DWORD) of 128 byte sectors for the record.
  1894.             sig, size, data = data[:4], data[4:8], data[8:]
  1895.             size = struct.unpack("<L", size)
  1896.             #sig, size = struct.unpack("a4 V", data)
  1897.  
  1898.             # '-2' takes into account the two 128 byte sectors we've just
  1899.             # read in
  1900.             size_to_read = (size-2)*128
  1901.  
  1902.             # ignore all but "URL" records
  1903.             if sig != "URL ":
  1904.                 # I've seen "HASH" and "LEAK" records
  1905.                 assert sig in "HASH", "LEAK"
  1906.                 if size > 0 and size != 2:
  1907.                     index.seek(size_to_read, 1)
  1908.                 continue
  1909.  
  1910.             # read in rest of record if necessary
  1911.             if size > 2:
  1912.                 more_data = index.read(size_to_read)
  1913.                 if len(more_data) != size_to_read:
  1914.                     break
  1915.                 data = data + more_data
  1916.  
  1917.             #cookie_re = ("Cookie\:%s\@([\x21-\xFF]+).*?"
  1918.             #             "(%s\@[\x21-\xFF]+\.txt)" % (user_name,)*2)
  1919.             #m = re.search(cookie_re, data)
  1920.             m = self.cookie_re.search(data)
  1921.             if m:
  1922.                 cookie_file = os.path.join(cookie_dir, m.group(2))
  1923.  
  1924.                 if not self._delayload:
  1925.                     self.load_cookies(cookie_file)
  1926.                 else:
  1927.                     domain = m.group(1)
  1928.                     i = domain.find("/")
  1929.                     if i != -1:
  1930.                         domain = domain[:i]
  1931.  
  1932.                     # Set a fake cookie for this domain, whose cookie value is
  1933.                     # in fact the cookie file for this domain / user.  This
  1934.                     # is used in the _get_cookie_attributes method to lazily
  1935.                     # load cookies.
  1936.                     self.set_cookie(
  1937.                         version=None,
  1938.                         key="cookie", val=cookie_file, path="//+delayload",
  1939.                         domain=domain, port=None, path_spec=False,
  1940.                         secure=False, maxage=now + 86400, discard=False)
  1941.  
  1942.  
  1943. # urllib2 support
  1944.  
  1945. try:
  1946.     from urllib2 import AbstractHTTPHandler
  1947. except ImportError:
  1948.     pass
  1949. else:
  1950.     import urllib2, urllib, httplib, urlparse, types
  1951.     from cStringIO import StringIO
  1952.     from _Util import seek_wrapper
  1953.  
  1954.     def request_method(req):
  1955.         try:
  1956.             return req.method()
  1957.         except AttributeError:
  1958.             if req.has_data():
  1959.                 return "POST"
  1960.             else:
  1961.                 return "GET"
  1962.  
  1963.     # This fixes a bug in urllib2 as of Python 2.1.3 and 2.2.1
  1964.     #  (sourceforge bug #549151 -- see file 'patch v2')
  1965.     class HTTPRedirectHandler(urllib2.BaseHandler):
  1966.         # maximum number of redirections before assuming we're in a loop
  1967.         max_redirections = 10
  1968.  
  1969.         # Implementation notes:
  1970.  
  1971.         # To avoid the server sending us into an infinite loop, the request
  1972.         # object needs to track what URLs we have already seen.  Do this by
  1973.         # adding a handler-specific attribute to the Request object.
  1974.  
  1975.         # Another handler-specific Request attribute, original_url, is used to
  1976.         # remember the URL of the original request so that it is possible to
  1977.         # decide whether or not RFC 2965 cookies should be turned on during
  1978.         # redirect.
  1979.  
  1980.         # Always unhandled redirection codes:
  1981.         # 300 Multiple Choices: should not handle this here.
  1982.         # 304 Not Modified: no need to handle here: only of interest to caches
  1983.         #     that do conditional GETs
  1984.         # 305 Use Proxy: probably not worth dealing with here
  1985.         # 306 Unused: what was this for in the previous versions of protocol??
  1986.  
  1987.         def redirect_request(self, newurl, req, fp, code, msg, headers):
  1988.             """Return a Request or None in response to a redirect.
  1989.  
  1990.             This is called by the http_error_30x methods when a redirection
  1991.             response is received.  If a redirection should take place, return a
  1992.             new Request to allow http_error_30x to perform the redirect;
  1993.             otherwise, return None to indicate that an HTTPError should be
  1994.             raised.
  1995.  
  1996.             """
  1997.             method = request_method(req)
  1998.             if (code in (301, 302, 303, 307) and method in ("GET", "HEAD") or
  1999.                 code in (302, 303) and method == "POST"):
  2000.                 return urllib2.Request(newurl, headers=req.headers)
  2001.             else:
  2002.                 return None
  2003.  
  2004.         def http_error_302(self, req, fp, code, msg, headers):
  2005.             if headers.has_key('location'):
  2006.                 newurl = headers['location']
  2007.             elif headers.has_key('uri'):
  2008.                 newurl = headers['uri']
  2009.             else:
  2010.                 return
  2011.             newurl = urlparse.urljoin(req.get_full_url(), newurl)
  2012.  
  2013.             # XXX Probably want to forget about the state of the current
  2014.             # request, although that might interact poorly with other
  2015.             # handlers that also use handler-specific request attributes
  2016.             new = self.redirect_request(newurl, req, fp, code, msg, headers)
  2017.             if new is None:
  2018.                 return
  2019.             new.original_url = req.get_full_url()
  2020.  
  2021.             # loop detection
  2022.             new.error_302_dict = {}
  2023.             if hasattr(req, 'error_302_dict'):
  2024.                 if len(req.error_302_dict)>=self.max_redirections or \
  2025.                        req.error_302_dict.has_key(newurl):
  2026.                     raise HTTPError(req.get_full_url(), code,
  2027.                                     self.inf_msg + msg, headers, fp)
  2028.                 new.error_302_dict.update(req.error_302_dict)
  2029.             new.error_302_dict[newurl] = newurl
  2030.  
  2031.             # Don't close the fp until we are sure that we won't use it
  2032.             # with HTTPError.  
  2033.             fp.read()
  2034.             fp.close()
  2035.  
  2036.             return self.parent.open(new)
  2037.  
  2038.         http_error_301 = http_error_303 = http_error_307 = http_error_302
  2039.  
  2040.         inf_msg = "The HTTP server returned a redirect error that would" \
  2041.                   "lead to an infinite loop.\n" \
  2042.                   "The last 302 error message was:\n"
  2043.  
  2044.     class addinfourlseek(seek_wrapper):
  2045.         def __init__(self, fp, hdrs, url):
  2046.             seek_wrapper.__init__(self, fp)
  2047.             self.fp = fp
  2048.             self.headers = hdrs
  2049.             self.url = url
  2050.             self.seek(0)
  2051.  
  2052.         def info(self):
  2053.             return self.headers
  2054.  
  2055.         def geturl(self):
  2056.             return self.url
  2057.  
  2058.     class AbstractHTTPHandler(urllib2.BaseHandler):
  2059.         def __init__(self, cookies=None,
  2060.                      handle_http_equiv=False, handle_refresh=False,
  2061.                      seekable_responses=True):
  2062.             if cookies is None:
  2063.                 cookies = Cookies()
  2064.             self.c = cookies
  2065.  
  2066.             if handle_http_equiv and not seekable_responses:
  2067.                 raise ValueError, ("seekable responses are required if "
  2068.                                    "handling HTTP-EQUIV headers")
  2069.  
  2070.             self._http_equiv = handle_http_equiv
  2071.             self._refresh = handle_refresh
  2072.             self._seekable_responses = seekable_responses
  2073.  
  2074.         def do_open(self, http_class, req):
  2075.             if hasattr(req, "error_302_dict") and req.error_302_dict:
  2076.                 redirect = 1
  2077.             else:
  2078.                 redirect = 0
  2079.             self.c.add_cookie_header(req, redirect=redirect)
  2080.             host = req.get_host()
  2081.             if not host:
  2082.                 raise URLError('no host given')
  2083.  
  2084.             try:
  2085.                 h = http_class(host) # will parse host:port
  2086.                 if ClientCookie.HTTP_DEBUG:
  2087.                     h.set_debuglevel(1)
  2088.                 if req.has_data():
  2089.                     data = req.get_data()
  2090.                     h.putrequest('POST', req.get_selector())
  2091.                     if not req.headers.has_key('Content-type'):
  2092.                         h.putheader('Content-type',
  2093.                                     'application/x-www-form-urlencoded')
  2094.                     if not req.headers.has_key('Content-length'):
  2095.                         h.putheader('Content-length', '%d' % len(data))
  2096.                 else:
  2097.                     h.putrequest('GET', req.get_selector())
  2098.             except socket.error, err:
  2099.                 raise URLError(err)
  2100.  
  2101.             h.putheader('Host', host)
  2102.             for args in self.parent.addheaders:
  2103.                 apply(h.putheader, args)
  2104.             for k, v in req.headers.items():
  2105.                 h.putheader(k, v)
  2106.             h.endheaders()
  2107.             if req.has_data():
  2108.                 h.send(data)
  2109.  
  2110.             code, msg, hdrs = h.getreply()
  2111.             fp = h.getfile()
  2112.  
  2113.             if self._seekable_responses:
  2114.                 response = addinfourlseek(fp, hdrs, req.get_full_url())
  2115.             else:
  2116.                 response = urllib.addinfourl(fp, hdrs, req.get_full_url())
  2117.             self.c.extract_cookies(response, req, redirect=redirect)
  2118.  
  2119.             if self._refresh and hdrs.has_key("refresh"):
  2120.                 refresh = hdrs["refresh"]
  2121.                 i = string.find(refresh, ";")
  2122.                 if i != -1:
  2123.                     time, newurl_spec = refresh[:i], refresh[i+1:]
  2124.                     i = string.find(newurl_spec, "=")
  2125.                     if i != -1:
  2126.                         if int(time) == 0:
  2127.                             newurl = newurl_spec[i+1:]
  2128.                             # fake a 302 response
  2129.                             hdrs["location"] = newurl
  2130.                             return self.parent.error(
  2131.                                 'http', req, fp, 302, msg, hdrs)
  2132.  
  2133.             if code == 200:
  2134.                 return response
  2135.             else:
  2136.                 return self.parent.error('http', req, fp, code, msg, hdrs)
  2137.  
  2138.  
  2139.     class EndOfHeadError(Exception): pass
  2140.     class HeadParser(htmllib.HTMLParser):
  2141.         # only these elements are allowed in or before HEAD of document
  2142.         head_elems = ("html", "head",
  2143.                       "title", "base",
  2144.                       "script", "style", "meta", "link", "object")
  2145.         def __init__(self):
  2146.             htmllib.HTMLParser.__init__(self, formatter.NullFormatter())
  2147.             self.http_equiv = []
  2148.  
  2149.         def start_meta(self, attrs):
  2150.             http_equiv = content = None
  2151.             for key, value in attrs:
  2152.                 if key == "http-equiv":
  2153.                     http_equiv = value
  2154.                 elif key == "content":
  2155.                     content = value
  2156.             if http_equiv is not None:
  2157.                 self.http_equiv.append((http_equiv, content))
  2158.  
  2159.         def handle_starttag(self, tag, method, attrs):
  2160.             if tag in self.head_elems:
  2161.                 method(attrs)
  2162.             else:
  2163.                 raise EndOfHeadError
  2164.  
  2165.         def handle_endtag(self, tag, method):
  2166.             if tag in self.head_elems:
  2167.                 method()
  2168.             else:
  2169.                 raise EndOfHeadError
  2170.  
  2171.         def end_head(self):
  2172.             raise EndOfHeadError
  2173.  
  2174.     def parse_head(file):
  2175.         """Return a list of key, value pairs"""
  2176.         hp = HeadParser()
  2177.         while 1:
  2178.             data = file.read(CHUNK)
  2179.             try:
  2180.                 hp.feed(data)
  2181.             except EndOfHeadError:
  2182.                 break
  2183.             if len(data) != CHUNK:
  2184.                 # this should only happen if there is no HTML body, or if
  2185.                 # CHUNK is big
  2186.                 break
  2187.         return hp.http_equiv
  2188.  
  2189.     class EQUIVMixin:
  2190.         def getreply(self):
  2191.             """Returns information about response from the server.
  2192.  
  2193.             Return value is a tuple consisting of:
  2194.             - server status code (e.g. '200' if all goes well)
  2195.             - server "reason" corresponding to status code
  2196.             - any RFC822 headers in the response from the server
  2197.  
  2198.             """
  2199.             try:
  2200.                 # response supports httplib.HTTPResponse interface
  2201.                 response = self._conn.getresponse()
  2202.             except httplib.BadStatusLine, e:
  2203.                 ### hmm. if getresponse() ever closes the socket on a bad request,
  2204.                 ### then we are going to have problems with self.sock
  2205.  
  2206.                 ### should we keep this behavior? do people use it?
  2207.                 # keep the socket open (as a file), and return it
  2208.                 self.file = self._conn.sock.makefile('rb', 0)
  2209.  
  2210.                 # close our socket -- we want to restart after any protocol error
  2211.                 self.close()
  2212.  
  2213.                 self.headers = None
  2214.                 return -1, e.line, None
  2215.  
  2216.             # response supports mimetools.Message interface
  2217.             self.headers = response.msg
  2218.             # grab HTTP-EQUIV headers and add them to the true HTTP headers
  2219.             self.file = seek_wrapper(response.fp)
  2220.             equiv_hdrs = parse_head(self.file)
  2221.             self.file.seek(0)
  2222.             for hdr, val in equiv_hdrs:
  2223.                 self.headers[hdr] = val
  2224.  
  2225.             return response.status, response.reason, response.msg
  2226.  
  2227.  
  2228.     class HTTP(EQUIVMixin, httplib.HTTP):
  2229.         """Extends httplib.HTTP to deal with HTTP-EQUIV headers.
  2230.  
  2231.         HTTP-EQUIV headers (HTTP headers in the HEAD section of the HTML
  2232.         document) are treated by this class as if they're normal HTTP
  2233.         headers.
  2234.  
  2235.         """
  2236.         pass
  2237.  
  2238.     class HTTPHandler(AbstractHTTPHandler):
  2239.         """Extends urllib2.HTTPHandler with automatic cookie handling.
  2240.  
  2241.         This class also honours zero-time Refresh headers, if the
  2242.         handle_refresh argument to the constructor is true.
  2243.  
  2244.         """
  2245.         def http_open(self, req):
  2246.             if self._http_equiv:
  2247.                 klass = HTTP
  2248.             else:
  2249.                 klass = httplib.HTTP
  2250.             return self.do_open(klass, req)
  2251.  
  2252.     if hasattr(httplib, 'HTTPS'):
  2253.         class HTTPS(EQUIVMixin, httplib.HTTPS):
  2254.             """Extends httplib.HTTPS to deal with HTTP-EQUIV headers.
  2255.  
  2256.             HTTP-EQUIV headers (HTTP headers in the HEAD section of the HTML
  2257.             document) are treated by this class as if they're normal HTTP
  2258.             headers.
  2259.  
  2260.             """
  2261.             pass
  2262.  
  2263.         class HTTPSHandler(AbstractHTTPHandler):
  2264.             """Extends urllib2.HTTPHandler with automatic cookie handling.
  2265.  
  2266.             This class also honours zero-time Refresh headers, if the
  2267.             handle_refresh argument to the constructor is true.
  2268.  
  2269.             """
  2270.             def https_open(self, req):
  2271.                 if self._http_equiv:
  2272.                     klass = HTTPS
  2273.                 else:
  2274.                     klass = httplib.HTTPS
  2275.                 return self.do_open(klass, req)
  2276.  
  2277.     def build_opener(*handlers):
  2278.         """Create an opener object from a list of handlers.
  2279.  
  2280.         The opener will use several default handlers, including support
  2281.         for HTTP and FTP.  If there is a ProxyHandler, it must be at the
  2282.         front of the list of handlers.  (Yuck.)
  2283.  
  2284.         If any of the handlers passed as arguments are subclasses of the
  2285.         default handlers, the default handlers will not be used.
  2286.         """
  2287.  
  2288.         opener = urllib2.OpenerDirector()
  2289.         default_classes = [urllib2.ProxyHandler, urllib2.UnknownHandler,
  2290.                            HTTPHandler,  # from this module (extended)
  2291.                            urllib2.HTTPDefaultErrorHandler,
  2292.                            HTTPRedirectHandler,  # from this module (bugfixed)
  2293.                            urllib2.FTPHandler, urllib2.FileHandler]
  2294.         if hasattr(httplib, 'HTTPS'):
  2295.             default_classes.append(HTTPSHandler)
  2296.         skip = []
  2297.         for klass in default_classes:
  2298.             for check in handlers:
  2299.                 if type(check) == types.ClassType:
  2300.                     if issubclass(check, klass):
  2301.                         skip.append(klass)
  2302.                 elif type(check) == types.InstanceType:
  2303.                     if isinstance(check, klass):
  2304.                         skip.append(klass)
  2305.         for klass in skip:
  2306.             default_classes.remove(klass)
  2307.  
  2308.         for klass in default_classes:
  2309.             opener.add_handler(klass())
  2310.  
  2311.         for h in handlers:
  2312.             if type(h) == types.ClassType:
  2313.                 h = h()
  2314.             opener.add_handler(h)
  2315.         return opener
  2316.  
  2317.     _opener = None
  2318.     def urlopen(url, data=None):
  2319.         global _opener
  2320.         if _opener is None:
  2321.             cookies = Cookies()
  2322.             _opener = build_opener(
  2323.                 HTTPHandler(cookies),  # from this module (extended)
  2324.                 )
  2325.         return _opener.open(url, data)
  2326.  
  2327.     def install_opener(opener):
  2328.         global _opener
  2329.         _opener = opener
  2330.