make Ben's cool range select hack work with Push 2
[ardour.git] / tools / bug_tool / ClientCookie / _MSIECookieJar.py
1 """Mozilla / Netscape cookie loading / saving.
2
3 Copyright 1997-1999 Gisle Aas (libwww-perl)
4 Copyright 2002-2003 Johnny Lee <typo_pl@hotmail.com> (MSIE Perl code)
5 Copyright 2002-2003 John J Lee <jjl@pobox.com> (The Python port)
6
7 This code is free software; you can redistribute it and/or modify it under
8 the terms of the BSD License (see the file COPYING included with the
9 distribution).
10
11 """
12
13 import os, re, string, time, struct
14 if os.name == "nt":
15     import _winreg
16
17 from _ClientCookie import CookieJar, Cookie, MISSING_FILENAME_TEXT
18 from _Util import startswith
19 from _Debug import debug
20
21 try: True
22 except NameError:
23     True = 1
24     False = 0
25
26
27 def regload(path, leaf):
28     key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, _winreg.KEY_ALL_ACCESS)
29     try:
30         value = _winreg.QueryValueEx(key, leaf)[0]
31     except WindowsError:
32         value = None
33     return value
34
35 WIN32_EPOCH = 0x019db1ded53e8000L  # 1970 Jan 01 00:00:00 in Win32 FILETIME
36
37 def epoch_time_offset_from_win32_filetime(filetime):
38     """Convert from win32 filetime to seconds-since-epoch value.
39
40     MSIE stores create and expire times as Win32 FILETIME, which is 64
41     bits of 100 nanosecond intervals since Jan 01 1601.
42
43     Cookies code expects time in 32-bit value expressed in seconds since
44     the epoch (Jan 01 1970).
45
46     """
47     if filetime < WIN32_EPOCH:
48         raise ValueError("filetime (%d) is before epoch (%d)" %
49                          (filetime, WIN32_EPOCH))
50
51     return divmod((filetime - WIN32_EPOCH), 10000000L)[0]
52
53 def binary_to_char(c): return "%02X" % ord(c)
54 def binary_to_str(d): return string.join(map(binary_to_char, list(d)), "")
55
56 class MSIECookieJar(CookieJar):
57     """
58     This class differs from CookieJar only in the format it uses to load cookies
59     from a file.
60
61     MSIECookieJar can read the cookie files of Microsoft Internet Explorer
62     (MSIE) for Windows, versions 5 and 6, on Windows NT and XP respectively.
63     Other configurations may also work, but are untested.  Saving cookies in
64     MSIE format is NOT supported.  If you save cookies, they'll be in the usual
65     Set-Cookie3 format, which you can read back in using an instance of the
66     plain old CookieJar class.  Don't save using the same filename that you
67     loaded cookies from, because you may succeed in clobbering your MSIE
68     cookies index file!
69
70     You should be able to have LWP share Internet Explorer's cookies like
71     this (note you need to supply a username to load_from_registry if you're on
72     Windows 9x):
73
74     cookies = MSIECookieJar(delayload=1)
75     # find cookies index file in registry and load cookies from it
76     cookies.load_from_registry()
77     opener = ClientCookie.build_opener(ClientCookie.HTTPHandler(cookies))
78     response = opener.open("http://foo.bar.com/")
79
80     Iterating over a delayloaded MSIECookieJar instance will not cause any
81     cookies to be read from disk.  To force reading of all cookies from disk,
82     call read_all_cookies.  Note that the following methods iterate over self:
83     clear_temporary_cookies, clear_expired_cookies, __len__, __repr__, __str__
84     and as_string.
85
86     Additional methods:
87
88     load_from_registry(ignore_discard=False, ignore_expires=False,
89                        username=None)
90     load_cookie_data(filename, ignore_discard=False, ignore_expires=False)
91     read_all_cookies()
92
93     """
94     magic_re = re.compile(r"Client UrlCache MMF Ver \d\.\d.*")
95     padding = "\x0d\xf0\xad\x0b"
96
97     msie_domain_re = re.compile(r"^([^/]+)(/.*)$")
98     cookie_re = re.compile("Cookie\:.+\@([\x21-\xFF]+).*?"
99                            "(.+\@[\x21-\xFF]+\.txt)")
100
101     # path under HKEY_CURRENT_USER from which to get location of index.dat
102     reg_path = r"software\microsoft\windows" \
103                r"\currentversion\explorer\shell folders"
104     reg_key = "Cookies"
105
106     def __init__(self, *args, **kwargs):
107         apply(CookieJar.__init__, (self, args, kwargs))
108         self._delayload_domains = {}
109
110     def set_cookie(self, cookie):
111         if self.delayload:
112             self._delayload_domain(cookie.domain)
113         CookieJar.set_cookie(self, cookie)
114
115     def _cookies_for_domain(self, domain, request, unverifiable):
116         debug("Checking %s for cookies to return" % domain)
117         if not self.policy.domain_return_ok(domain, request, unverifiable):
118             return []
119
120         if self.delayload:
121             self._delayload_domain(domain)
122
123         return CookieJar._cookies_for_domain(
124             self, domain, request, unverifiable)
125
126     def read_all_cookies(self):
127         """Eagerly read in all cookies."""
128         if self.delayload:
129             for domain in self._delayload_domains.keys():
130                 self._delayload_domain(domain)
131
132     def _delayload_domain(self, domain):
133         # if necessary, lazily load cookies for this domain
134         delayload_info = self._delayload_domains.get(domain)
135         if delayload_info is not None:
136             cookie_file, ignore_discard, ignore_expires = delayload_info
137             try:
138                 self.load_cookie_data(cookie_file,
139                                       ignore_discard, ignore_expires)
140             except IOError:
141                 debug("error reading cookie file, skipping: %s" % cookie_file)
142             else:
143                 del self._delayload_domains[domain]
144
145     def _load_cookies_from_file(self, filename):
146         cookies = []
147
148         cookies_fh = open(filename)
149
150         try:
151             while 1:
152                 key = cookies_fh.readline()
153                 if key == "": break
154
155                 rl = cookies_fh.readline
156                 def getlong(rl=rl): return long(rl().rstrip())
157                 def getstr(rl=rl): return rl().rstrip()
158
159                 key = key.rstrip()
160                 value = getstr()
161                 domain_path = getstr()
162                 flags = getlong()  # 0x2000 bit is for secure I think
163                 lo_expire = getlong()
164                 hi_expire = getlong()
165                 lo_create = getlong()
166                 hi_create = getlong()
167                 sep = getstr()
168
169                 if "" in (key, value, domain_path, flags, hi_expire, lo_expire,
170                           hi_create, lo_create, sep) or (sep != "*"):
171                     break
172
173                 m = self.msie_domain_re.search(domain_path)
174                 if m:
175                     domain = m.group(1)
176                     path = m.group(2)
177
178                     cookies.append({"KEY": key, "VALUE": value, "DOMAIN": domain,
179                                     "PATH": path, "FLAGS": flags, "HIXP": hi_expire,
180                                     "LOXP": lo_expire, "HICREATE": hi_create,
181                                     "LOCREATE": lo_create})
182         finally:
183             cookies_fh.close()
184
185         return cookies
186
187     def load_cookie_data(self, filename,
188                          ignore_discard=False, ignore_expires=False):
189         """Load cookies from file containing actual cookie data.
190
191         Old cookies are kept unless overwritten by newly loaded ones.
192
193         You should not call this method if the delayload attribute is set.
194
195         I think each of these files contain all cookies for one user, domain,
196         and path.
197
198         filename: file containing cookies -- usually found in a file like
199          C:\WINNT\Profiles\joe\Cookies\joe@blah[1].txt
200
201         """
202         now = int(time.time())
203
204         cookie_data = self._load_cookies_from_file(filename)
205
206         for cookie in cookie_data:
207             flags = cookie["FLAGS"]
208             secure = ((flags & 0x2000) != 0)
209             filetime = (cookie["HIXP"] << 32) + cookie["LOXP"]
210             expires = epoch_time_offset_from_win32_filetime(filetime)
211             if expires < now:
212                 discard = True
213             else:
214                 discard = False
215             domain = cookie["DOMAIN"]
216             initial_dot = startswith(domain, ".")
217             if initial_dot:
218                 domain_specified = True
219             else:
220                 # MSIE 5 does not record whether the domain cookie-attribute
221                 # was specified.
222                 # Assuming it wasn't is conservative, because with strict
223                 # domain matching this will match less frequently; with regular
224                 # Netscape tail-matching, this will match at exactly the same
225                 # times that domain_specified = True would.  It also means we
226                 # don't have to prepend a dot to achieve consistency with our
227                 # own & Mozilla's domain-munging scheme.
228                 domain_specified = False
229
230             # assume path_specified is false
231             # XXX is there other stuff in here? -- eg. comment, commentURL?
232             c = Cookie(0,
233                        cookie["KEY"], cookie["VALUE"],
234                        None, False,
235                        domain, domain_specified, initial_dot,
236                        cookie["PATH"], False,
237                        secure,
238                        expires,
239                        discard,
240                        None,
241                        None,
242                        {"flags": flags})
243             if not ignore_discard and c.discard:
244                 continue
245             if not ignore_expires and c.is_expired(now):
246                 continue
247             self.set_cookie(c)
248
249     def load_from_registry(self, ignore_discard=False, ignore_expires=False,
250                            username=None):
251         """
252         username: only required on win9x
253
254         """
255         cookies_dir = regload(self.reg_path, self.reg_key)
256         filename = os.path.normpath(os.path.join(cookies_dir, "INDEX.DAT"))
257         self.load(filename, ignore_discard, ignore_expires, username)
258
259     def load(self, filename, ignore_discard=False, ignore_expires=False,
260              username=None):
261         """Load cookies from an MSIE 'index.dat' cookies index file.
262
263         filename: full path to cookie index file
264         username: only required on win9x
265
266         """
267         if filename is None:
268             if self.filename is not None: filename = self.filename
269             else: raise ValueError(MISSING_FILENAME_TEXT)
270
271         index = open(filename, "rb")
272
273         try:
274             self._really_load(index, filename, ignore_discard, ignore_expires,
275                               username)
276         finally:
277             index.close()
278
279     def _really_load(self, index, filename, ignore_discard, ignore_expires,
280                      username):
281         now = int(time.time())
282
283         if username is None:
284             username = string.lower(os.environ['USERNAME'])
285
286         cookie_dir = os.path.dirname(filename)
287
288         data = index.read(256)
289         if len(data) != 256:
290             raise IOError("%s file is too short" % filename)
291
292         # Cookies' index.dat file starts with 32 bytes of signature
293         # followed by an offset to the first record, stored as a little-
294         # endian DWORD.
295         sig, size, data = data[:32], data[32:36], data[36:]
296         size = struct.unpack("<L", size)[0]
297
298         # check that sig is valid
299         if not self.magic_re.match(sig) or size != 0x4000:
300             raise IOError("%s ['%s' %s] does not seem to contain cookies" %
301                           (str(filename), sig, size))
302
303         # skip to start of first record
304         index.seek(size, 0)
305
306         sector = 128  # size of sector in bytes
307
308         while 1:
309             data = ""
310
311             # Cookies are usually in two contiguous sectors, so read in two
312             # sectors and adjust if not a Cookie.
313             to_read = 2 * sector
314             d = index.read(to_read)
315             if len(d) != to_read:
316                 break
317             data = data + d
318
319             # Each record starts with a 4-byte signature and a count
320             # (little-endian DWORD) of sectors for the record.
321             sig, size, data = data[:4], data[4:8], data[8:]
322             size = struct.unpack("<L", size)[0]
323
324             to_read = (size - 2) * sector
325
326 ##             from urllib import quote
327 ##             print "data", quote(data)
328 ##             print "sig", quote(sig)
329 ##             print "size in sectors", size
330 ##             print "size in bytes", size*sector
331 ##             print "size in units of 16 bytes", (size*sector) / 16
332 ##             print "size to read in bytes", to_read
333 ##             print
334
335             if sig != "URL ":
336                 assert (sig in ("HASH", "LEAK",
337                                 self.padding, "\x00\x00\x00\x00"),
338                         "unrecognized MSIE index.dat record: %s" %
339                         binary_to_str(sig))
340                 if sig == "\x00\x00\x00\x00":
341                     # assume we've got all the cookies, and stop
342                     break
343                 if sig == self.padding:
344                     continue
345                 # skip the rest of this record
346                 assert to_read >= 0
347                 if size != 2:
348                     assert to_read != 0
349                     index.seek(to_read, 1)
350                 continue
351
352             # read in rest of record if necessary
353             if size > 2:
354                 more_data = index.read(to_read)
355                 if len(more_data) != to_read: break
356                 data = data + more_data
357
358             cookie_re = ("Cookie\:%s\@([\x21-\xFF]+).*?" % username +
359                          "(%s\@[\x21-\xFF]+\.txt)" % username)
360             m = re.search(cookie_re, data, re.I)
361             if m:
362                 cookie_file = os.path.join(cookie_dir, m.group(2))
363                 if not self.delayload:
364                     try:
365                         self.load_cookie_data(cookie_file,
366                                               ignore_discard, ignore_expires)
367                     except IOError:
368                         debug("error reading cookie file, skipping: %s" %
369                               cookie_file)
370                 else:
371                     domain = m.group(1)
372                     i = domain.find("/")
373                     if i != -1:
374                         domain = domain[:i]
375
376                     self._delayload_domains[domain] = (
377                         cookie_file, ignore_discard, ignore_expires)