Merge lp:~hernejj/ubuntu-qa-tools/better_hugday into lp:ubuntu-qa-tools

Proposed by Jason J. Herne
Status: Needs review
Proposed branch: lp:~hernejj/ubuntu-qa-tools/better_hugday
Merge into: lp:ubuntu-qa-tools
Diff against target: 1040 lines (+834/-56)
11 files modified
README (+19/-6)
debian/changelog (+11/-0)
hugday-tools/hugday (+113/-49)
hugday_lib/cookie_lib/ChromeCookieLoader.py (+63/-0)
hugday_lib/cookie_lib/CookieFinder.py (+149/-0)
hugday_lib/cookie_lib/CookieLoader.py (+209/-0)
hugday_lib/cookie_lib/EpiphanyCookieLoader.py (+53/-0)
hugday_lib/cookie_lib/FirefoxCookieLoader.py (+60/-0)
hugday_lib/cookie_lib/KonquerorCookieLoader.py (+111/-0)
hugday_lib/cookie_lib/SeamonkeyCookieLoader.py (+43/-0)
setup.py (+3/-1)
To merge this branch: bzr merge lp:~hernejj/ubuntu-qa-tools/better_hugday
Reviewer Review Type Date Requested Status
Markus Korn Pending
Ubuntu Bug Control Pending
Review via email: mp+23257@code.launchpad.net

Commit message

Adds code to do the following:
- Supported browsers: Firefox, Epiphany, Seamonkey, Google Chrome, Konqueror
- Automatic cookie detection for all supported browsers.
- User no longer has to close the browser to find cookies.
- Cookie file format automatically detected when --cookie is given
- User can specify a browser (--browser) and cookie is automatically found within that browser's cookie files.
- Some simple code cleanup and extra commenting in hugday tool.

Description of the change

I've been working on some improvements to the hugday tool (part of the ubuntu-qa-tools) that should make hugday easier to use. Making hugday easier to use will lower the "barrier of entry" for new comers who are interested in participating in the Ubuntu Bug Days. And anything we can do to help people help Ubuntu is a good idea in my book ;).

I've added code to allow for more robust cookie handling by the "hugday init" sub-command. Here is a list of features currently implemented in my branch:
- Supported browsers: Firefox, Epiphany, Seamonkey, Google Chrome, Konqueror
- Automatic cookie detection for all supported browsers.
- User no longer has to close the browser to find cookies.
- Cookie file format automatically detected when --cookie is given
- User can specify a browser (--browser) and cookie is automatically found within that browser's cookie files.
- Some simple code cleanup and extra commenting in hugday tool.

If this is something that bug-control is interested in I'd be happy to have these features merged back into the official ubuntu-qa-tools-branch. Although I must admit I'm not sure what the process is for officially requesting such a merge.

To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

This sounds really exciting! You have followed the proper process for requesting such a merge but unfortunately I don't think we can get this into Lucid since we are past feature freeze. I'm requesting that the original author look at your changes too.

Revision history for this message
Jason J. Herne (hernejj) wrote :

> This sounds really exciting! You have followed the proper process for
> requesting such a merge but unfortunately I don't think we can get this into
> Lucid since we are past feature freeze. I'm requesting that the original
> author look at your changes too.

Hi Brian. Thanks for your interest. It's not problem for me if this has to wait for Maverick Meerkat.. I understand how the schedule works :). Once code review is complete I can always put an updated package in my PPA. Then others can test the changes if they are interested.

339. By Jason J. Herne

- Added Google Chromium support

Revision history for this message
Jason J. Herne (hernejj) wrote :

One note I'd like to make for the reviewers.

The hugday code that I replaced was searching for two cookies: MOIN_SESSION and MOIN_ID. My new code only looks for MOIN_SESSION. I'm not sure what MOIN_ID is or where it comes from but I could not find any instances of it in my testing.

If there is a valid reason to continue to search for MOIN_ID as well as MOIN_SESSION then please let me know and I'll modify this branch to look for MOIN_ID if MOIN_SESSION is not found.

Revision history for this message
Jason J. Herne (hernejj) wrote :

Just a friendly poke to find out what the status is on this review. :)

Revision history for this message
Markus Korn (thekorn) wrote :

Hey Jason,
I'm sorry, I totally forgot about this merge proposal. Reviewing this huge diff will take some time, but I hope to get it done until next weekend.

Just a side note, have you ever thought about moving your cookielib into a separate project/package? Because it seems to be useful for other python scripts too, and as a side effect it would reduce the size of this diff.

Sorry again,
and thanks for the reminder,
have a nice day,
Markus

Revision history for this message
Jason J. Herne (hernejj) wrote :

On Mon, Aug 2, 2010 at 5:14 PM, Markus Korn <email address hidden> wrote:

> Hey Jason,
> I'm sorry, I totally forgot about this merge proposal. Reviewing this huge
> diff will take some time, but I hope to get it done until next weekend.
>
> Just a side note, have you ever thought about moving your cookielib into a
> separate project/package? Because it seems to be useful for other python
> scripts too, and as a side effect it would reduce the size of this diff.
>
> Sorry again,
> and thanks for the reminder,
> have a nice day,
> Markus
> --
>
> https://code.launchpad.net/~hernejj/ubuntu-qa-tools/better_hugday/+merge/23257<https://code.launchpad.net/%7Ehernejj/ubuntu-qa-tools/better_hugday/+merge/23257>
> You are the owner of lp:~hernejj/ubuntu-qa-tools/better_hugday.
>

I had considered separating out the cookielib code but I'm not sure what
other use it has at the moment. Seems preemptive to do it without another
project that can use it.
It can always be done as follow on work when another need arises. It would
not be that hard to do.

--
- Jason J. Herne (<email address hidden>)

Unmerged revisions

339. By Jason J. Herne

- Added Google Chromium support

338. By Jason J. Herne

- Fixed errors in usage information for hugday
- Added previously forgotten cookie loader file
- Updated readme and changelog

337. By Jason J. Herne

- ChromeCookieLoader.py: Fixed bug, need to check that cookies were loaded BEFORE we iterate through them ;)
- CookieFinder.py: Added missing import of os module
- CookieFinder.py: Added Konqueror support
- CookieFinder.py: Change browser string comparison to always use lower case
- CookieFinder.py: Fixed bug, In get_cookeies(), don't return if a cookie loader has no cookies, need to check ALL cookie loaders.
- KonquerorCookieLoader.py: Initial import.

336. By Jason J. Herne

- User can now specify exactly which browser to pull cookie file from: --browser=<firefox|seamonkey|chrome|epiphany>

335. By Jason J. Herne

- bug fix: Expand ~ in path names given by user
- Fixed error mesasge text
- Added print stmt to output the CookieLoader we're using

334. By jason <jason@foobarbaz>

- Fixed timestamp handling in Chrome

333. By jason <jason@foobarbaz>

- Removed unneeded comment from SeamonkeyLoader

332. By Jason J. Herne

- Properly close connection to sqlite database files

331. By Jason J. Herne

- Rewrote existing get_credentials code to use CookieFinder when the user specifies a cookie file on the command line.

330. By Jason J. Herne

- Bug fix: Check that path exists in cookier_scanner routine before scanning it.
- Bug fix: Uncomment exception handling code (originally commented for testing purposes.)
- Cookie Finder/Loader now provides a method to detect if cookie files were loaded.
- All cookie loaders have been updated to support the above method.
- hugday now uses the above method to give a proper error message if no cookie files were found.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2009-02-10 09:50:50 +0000
3+++ README 2010-04-13 16:46:31 +0000
4@@ -93,13 +93,26 @@
5 $ hugday list --date 20090101 (for hugday at 2009-01-01)
6
7 Before actually changing content of a wikipage the hugday tools needs
8-to know the useres name and his wiki-id, 'hugday init' provides some
9-options to get this information
10- $ hugday init --user <USER> --cookie <PATH> (try to get the MOIN_ID
11- out of the mozilla-like cookie file and use USER for entries in
12- the 'Triager' column)
13+to know the user's Launchpad ID and wiki-id, 'hugday init' provides some
14+options to get this information:
15+
16+1) Let hugday automatically detect the cookie containing the wiki-id.
17+
18+ $ hugday init --user <USER>
19+
20+2) Tell hugday which web brower you most recently used to log on to
21+ wiki.ubuntu.com
22+
23+ $ hugday init --user <USER> --browser <firefox|seamonkey|chrome|epiphany|konqueror>
24+
25+3) Provide the path to a cookie file that contains a cookie for ".wiki.ubuntu.com"
26+ that has the name "MOIN_SESSION".
27+
28+ $ hugday init --user <USER> --cookie <PATH>
29+
30+4) Provide the wiki-id directly.
31+
32 $ hugday init --user <USER> --wiki-id <MOINID>
33- $ hugday init (interactive mode, not implemented yet)
34
35 To mark a bug as DONE run:
36 $ hugday close 123456
37
38=== modified file 'debian/changelog'
39--- debian/changelog 2010-03-31 19:17:37 +0000
40+++ debian/changelog 2010-04-13 16:46:31 +0000
41@@ -1,3 +1,14 @@
42+ubuntu-qa-tools (0.1.4.4) lucid; urgency=low
43+
44+ * Improvements to cookie processing in hugday tool.
45+ - Supported browsers: Firefox, Epiphany, Seamonkey, Google Chrome, Konqueror
46+ - Automatic cookie detection for all supported browsers.
47+ - user no longer has to close the browser to find cookies.
48+ - Cookie file format automatically detected when --cookie is given
49+ - User can specify a browser (--browser) and cookie is automatically found within that browser's cookie files.
50+
51+ -- Jason J. Herne <hernejj@gmail.com> Thu, 8 Apr 2010 11:54:00 -0400
52+
53 ubuntu-qa-tools (0.1.4.3) lucid; urgency=low
54
55 * Fix to work with launchpad's multi-version code.
56
57=== modified file 'hugday-tools/hugday'
58--- hugday-tools/hugday 2009-03-18 20:25:21 +0000
59+++ hugday-tools/hugday 2010-04-13 16:46:31 +0000
60@@ -19,13 +19,13 @@
61 #
62 # ##################################################################
63
64-import sys
65-import re
66-import os
67-import urllib
68-import urllib2
69-import libxml2
70+import sys, os, shutil, time, re
71+import urllib, urllib2, libxml2
72 import cookielib
73+import webbrowser
74+
75+from hugday_lib.cookie_lib.CookieFinder import CookieFinder
76+
77 try:
78 import sqlite3 as sqlite
79 except ImportError:
80@@ -55,11 +55,13 @@
81
82 RE_BUGS = "%s(%%s)%s" %(re.escape("https://bugs.launchpad.net/bugs/"), re.escape("|"))
83
84-
85+######################################################
86+# Parses Command Line using Python's optparse module #
87+######################################################
88 class CmdOptions(OptionParser):
89
90 USAGE = (
91- "\t%prog init --user <USERNAME> [--wiki-id <MOINID>] [--cookie <PATH>]\n"
92+ "\t%prog init --user <USERNAME> [--wiki-id <MOINID>] [--cookie <PATH>] [--browser <firefox|seamonkey|chrome|epiphany|konqueror>]\n"
93 "\t%prog current [--remember]\n"
94 "\t%prog close <Bugs> [--day <ID>] [--kde] [--user <TRIAGER>]\n"
95 "\t%prog list [--day <ID>] [--kde] [--filter <open|done>]\n"
96@@ -80,13 +82,15 @@
97 make_option("--wiki-id", action="store", type="string", dest="wiki_id",
98 help="the users MOIN-ID"),
99 make_option("--cookie", action="store", type="string", dest="cookie",
100- help="path to users mozilla-like cookie file")
101+ help="path to users cookie file"),
102+ make_option("--browser", action="store", type="string", dest="browser",
103+ help="Web browser that will be searched for the wiki-id cookie")
104 )
105
106 TOOLS = {
107 "init": (
108 ("user", ),
109- ("wiki_id", "cookie"),
110+ ("wiki_id", "cookie", "browser"),
111 ),
112 "current": (
113 tuple(),
114@@ -121,7 +125,7 @@
115 else:
116 self.error("Only one sub-tool allowed")
117 if tool == "close" and not options.bugs:
118- self.error("at elast one bug needed")
119+ self.error("at least one bug needed")
120 given_options = set(i for i, k in self.defaults.iteritems() if not getattr(options, i) == k)
121 needed_options = set(CmdOptions.TOOLS[tool][0]) - given_options
122 if needed_options:
123@@ -250,44 +254,93 @@
124 title = b.xpathEval("td[2]")[0].content.strip()
125 triager = b.xpathEval("td[3]")[0].content.strip() or None
126 yield (nr, title, done, triager, section)
127-
128-def get_credentials(user, wiki_id=None, cookie=None):
129+
130+################################################################################
131+# Obtain the user's moin_id. This will allow us to authenticate to the wiki in #
132+# order to make updates. The user may either provide the moin_id directly or #
133+# the user provides their browser cookie file from which we can read the ID. #
134+################################################################################
135+def get_credentials(user, wiki_id=None, cookie_file_path=None, browser_str=None):
136+
137+ # User provided their moin_id string. Just save it in the config file.
138 if wiki_id:
139 config = update_config(user=user, moin_id=wiki_id)
140- elif cookie:
141- # parse cookie
142- # this includes a workaround for new cookie format
143- result = None
144- try:
145- cj = cookielib.MozillaCookieJar()
146- cj.load(os.path.expanduser(cookie))
147- result = [i.value for i in cj if i.domain == "wiki.ubuntu.com" \
148- and i.name in ("MOIN_ID", "MOIN_SESSION")]
149- except cookielib.LoadError:
150- if not sqlite:
151- raise TypeError(("Try to read cookiefile, cannot handle "
152- "format of '%s'" %cookie))
153- try:
154- con = sqlite.connect(cookie)
155- con.row_factory = sqlite.Row
156-
157- cur = con.cursor()
158- cur.execute(("select value from moz_cookies where "
159- "(name=? or name=?) and host=?"),
160- ("MOIN_SESSION", "MOIN_ID", "wiki.ubuntu.com",))
161- #TODO: add check for expired cookie
162- result = [i["value"] for i in cur]
163- except sqlite.Error, e:
164- raise TypeError(("Error while trying to read cookie in "
165- "sql format, cannot handle "
166- "format of '%s'" %cookie))
167- if not result:
168- raise ValueError("No cookie with name 'MOIN_ID' found in '%s'" %cookie)
169- config = update_config(user=user, moin_id=result.pop())
170+
171+ # User provided their cookie file path. Attempt to read the MOIN_SESSION
172+ # cookie from the cookie file.
173+ elif cookie_file_path:
174+
175+ cookie_file_path = os.path.expanduser(cookie_file_path)
176+
177+ # Ensure cookie file exists
178+ if (not os.path.exists(cookie_file_path)):
179+ raise RuntimeError("The specified cookie file does not exist: '%s'" %cookie_file_path)
180+
181+ # Load cookie file
182+ cookie_finder = CookieFinder()
183+ cookie_file_valid = cookie_finder.set_cookie_file(cookie_file_path)
184+
185+ # Was cookie file valid?
186+ if (not cookie_file_valid):
187+ raise RuntimeError("The specified file is not a valid cookie file from a supported browser: '%s'. " %cookie_file_path)
188+
189+ # Get desired cookie
190+ cookie = cookie_finder.get_newest_cookie(domain=".wiki.ubuntu.com", name="MOIN_SESSION", ignore_expired=True)
191+
192+ # Did we find a matching cookie?
193+ if (not cookie):
194+ raise RuntimeError("Unable to find the MOIN_SESSION cookie in the given cookie file: '%s'. Please ensure you've logged in to wiki.ubuntu.com with this browser." %cookie_file_path )
195+
196+ # Found cookie! Update config file.
197+ config = update_config(user=user, moin_id=cookie['value'])
198+ print "Cookie detection succeeded."
199+
200+ # User provided a string telling us which browser they wish to
201+ # search to find the wiki-id cookie.
202+ elif browser_str:
203+
204+ # See if CookieFinder supports the user's browser
205+ cookie_finder = CookieFinder()
206+ browser_supported = cookie_finder.set_browser(browser_str)
207+
208+ if (not browser_supported):
209+ raise RuntimeError("unsupported browser '%s'" % browser_str)
210+
211+ # Get desired cookie.
212+ cookie = cookie_finder.get_newest_cookie(domain=".wiki.ubuntu.com", name="MOIN_SESSION", ignore_expired=True)
213+
214+ # Did the cookie loader find valid files to read cookies from?
215+ if (not cookie_finder.has_cookie_files()):
216+ raise RuntimeError("No valid cookie files were found for the given browser.")
217+
218+ # Did we find a matching cookie?
219+ if (not cookie):
220+ raise RuntimeError("Unable to find the MOIN_SESSION cookie in the given browser's cookie files: '%s'. Please ensure you've logged in to wiki.ubuntu.com with this browser." %browser_str)
221+
222+ # Found cookie! Update config file.
223+ config = update_config(user=user, moin_id=cookie['value'])
224+ print "Cookie detection succeeded."
225+
226+ # Only --user option was specified (--cookie & --wiki-id were omitted)
227+ # Do auto detection of cookie file.
228 else:
229- # interactive mode or openid, TBD
230- raise NotImplementedError
231-
232+
233+ # Try and automatically find the cookie we need
234+ cookie_finder = CookieFinder()
235+ cookie = cookie_finder.get_newest_cookie(domain=".wiki.ubuntu.com", name="MOIN_SESSION", ignore_expired=True)
236+
237+ # Did the cookie loader find valid files to read cookies from?
238+ if (not cookie_finder.has_cookie_files()):
239+ raise RuntimeError("No valid cookie files were found. You do not seem to be using any supported browsers.")
240+
241+ # Did we find a matching cookie?
242+ if (not cookie):
243+ raise RuntimeError("Unable to detect the MOIN_SESSION cookie. Please ensure you've logged in to wiki.ubuntu.com with a supported browser.")
244+
245+ # Write the obtained moin_id to the user's configuration file
246+ print "Cookie detection succeeded."
247+ config = update_config(user=user, moin_id=cookie['value'])
248+
249 def build_request(url, moin_id, proxy=None, data=None):
250 request = urllib2.Request(url, data)
251 #~ # moinmoin < 1.6
252@@ -379,18 +432,27 @@
253 url = "%s/KDE" %url
254 return url
255
256+
257+##########################
258+# Main Program #
259+# Execution starts here #
260+##########################
261 def main():
262+
263 cmdoptions = CmdOptions()
264 tool, options = cmdoptions.parse_args()
265+
266 if tool == "list-days":
267 print "list of all hugdays, latest first:"
268 for day in parse_all_hugdays():
269 print "\t* https://wiki.ubuntu.com/UbuntuBugDay/%s" %day
270+
271 elif tool == "current":
272 current = get_current()
273 print current
274 if options.remember:
275 update_config(current=current)
276+
277 elif tool == "list":
278 url = get_url(options.day, options.kde)
279 i = None
280@@ -399,15 +461,17 @@
281 if i is not None:
282 print "="*50
283 print "Total:", i + 1
284+
285 elif tool == "init":
286- get_credentials(options.user, options.wiki_id, options.cookie)
287+ get_credentials(options.user, options.wiki_id, options.cookie, options.browser)
288+
289 elif tool == "close":
290 url = get_url(options.day, options.kde)
291 close_bugs(options.bugs, url, options.user)
292+
293+ # FIXME: Shouldn't this be impossible since we detect this in cmdoptions.parse_args()?
294 else:
295 raise RuntimeError("unknown tool '%s'" %tool)
296-
297-
298
299 if __name__ == "__main__":
300 try:
301
302=== added directory 'hugday_lib'
303=== added file 'hugday_lib/__init__.py'
304=== added directory 'hugday_lib/cookie_lib'
305=== added file 'hugday_lib/cookie_lib/ChromeCookieLoader.py'
306--- hugday_lib/cookie_lib/ChromeCookieLoader.py 1970-01-01 00:00:00 +0000
307+++ hugday_lib/cookie_lib/ChromeCookieLoader.py 2010-04-13 16:46:31 +0000
308@@ -0,0 +1,63 @@
309+#!/usr/bin/python
310+
311+import os, shutil
312+
313+from CookieLoader import CookieLoader
314+
315+# This is a subclass of CookieLoader. Please see CookieLoader for more details.
316+class ChromeCookieLoader(CookieLoader):
317+
318+ # browser name
319+ browser_str = "chrome"
320+
321+ # List of directories to search for cookie files.
322+ cookie_file_search_path = [ os.path.join( "~" , ".config" , "google-chrome" ) ,
323+ os.path.join( "~" , ".config" , "chromium" )
324+ ]
325+
326+ # List of valid cookie file names.
327+ cookie_file_names = [ "Cookies" ]
328+
329+ # attempts to load cookies from the given file.
330+ # If cookie_file_path points to a valid readable cookie file that can be
331+ # parsed then this function will return True. If it returns false then
332+ # the specified cookie file is not readable by this cookie finder.
333+ def load_cookies_from_file(self, cookie_file_path):
334+
335+ # If the given cookie file does not exist, give up.
336+ if (not os.path.exists(cookie_file_path)):
337+ return False
338+
339+ # If we attempt to use the cookie file while the browser is running
340+ # we will get errors. Only one process is allowed to use it at a time.
341+ # To get around this we can simply copy it to /tmp and then use it.
342+ tmp_cookie_file_path = os.path.join( "/" , "tmp" , os.path.basename(cookie_file_path) )
343+ shutil.copyfile(cookie_file_path, tmp_cookie_file_path)
344+
345+ # Chrome cookie files are sqlite databases
346+ cookies = self.load_sqlite_cookie_file(tmp_cookie_file_path, table_name="cookies", domain_key="host_key", name_key="name", value_key="value", expire_time_key="expires_utc")
347+
348+ # If the load failed, then we've been given an invalid cookie file.
349+ if (not cookies):
350+ os.remove(tmp_cookie_file_path)
351+ return False
352+
353+ # Fix timestamps in Google Chrome cookies!!
354+ # Chrome stores cookies with a 64-bit integer that represents the number of Microseconds that have passed since 1/1/1601. So it's not quite a
355+ # Unix timestamp. See top comments in this file for more info: http://src.chromium.org/svn/trunk/src/base/time_posix.cc
356+ #
357+ # The basic proceedure for converting a Chrome timestamp into a Unix timestap is this: unix_time = (chrome_time / 1000000) - 11644473600
358+ for cookie in cookies:
359+ cookie['expire_time'] = (cookie['expire_time'] / 1000000) - 11644473600
360+
361+ # Loop over all cookies and add them to our cookies list
362+ for cookie in cookies:
363+ self.cookies.append(cookie)
364+
365+ # Erase temporary cookie file.
366+ os.remove(tmp_cookie_file_path)
367+
368+ # We were able to read cookies from this file.
369+ self.found_valid_cookie_file = True
370+ return True
371+
372
373=== added file 'hugday_lib/cookie_lib/CookieFinder.py'
374--- hugday_lib/cookie_lib/CookieFinder.py 1970-01-01 00:00:00 +0000
375+++ hugday_lib/cookie_lib/CookieFinder.py 2010-04-13 16:46:31 +0000
376@@ -0,0 +1,149 @@
377+from FirefoxCookieLoader import FirefoxCookieLoader
378+from EpiphanyCookieLoader import EpiphanyCookieLoader
379+from SeamonkeyCookieLoader import SeamonkeyCookieLoader
380+from ChromeCookieLoader import ChromeCookieLoader
381+from KonquerorCookieLoader import KonquerorCookieLoader
382+
383+import time, os
384+from datetime import datetime
385+
386+# Used to find/access cookies for many different web browsers. You can point
387+# CookieFinder to a specific cookie file (set_cookie_file) or you can simply
388+# perform a search (get_cookies/get_newest_cookie) and CookieFinder will
389+# automatically scan every cookie file it can find for every browser it supports.
390+class CookieFinder:
391+
392+ # List of cookie loaders. Each one scans a different browser's cookie file(s).
393+ cookie_loaders = []
394+
395+ # Creates a new CookieFinder.
396+ def __init__(self):
397+
398+ # Populate cookie loaders, one loader for each browser we support
399+ self.cookie_loaders.append( FirefoxCookieLoader() )
400+ self.cookie_loaders.append( EpiphanyCookieLoader() )
401+ self.cookie_loaders.append( SeamonkeyCookieLoader() )
402+ self.cookie_loaders.append( ChromeCookieLoader() )
403+ self.cookie_loaders.append( KonquerorCookieLoader() )
404+
405+ # Tells CookieFinder that you only wish to search the given browser for
406+ # cookies. All CookieLoaders will be search until one is found that matches
407+ # the given browser string. Once that loader is found all others will be
408+ # removed from our loaders list. All subsequent operations using this CookieFiner
409+ # will only invoke the one loader.
410+ # Returns True if a suitable CookieLoader was found. Returns False otherwise.
411+ def set_browser(self, browser_str):
412+
413+ # Avoid case comparison problems
414+ browser_str = browser_str.lower()
415+
416+ # Locate a cookie loader for the browser the user specified
417+ for cookie_loader in self.cookie_loaders:
418+
419+ if ( cookie_loader.browser_str == browser_str):
420+
421+ # We found a cookie loader that supports the user's browser.
422+ # Throw away the rest of the loaders.
423+ self.cookie_loaders = [cookie_loader]
424+
425+ # Return true and the caller will be most pleased :)
426+ return True
427+
428+ # We got to the end of the list and none of our cookie loaders match.
429+ return False
430+
431+ # Tells CookieFinder that you only want to search the given file for cookies.
432+ # If cookie_file_path points to a valid readable cookie file that can be
433+ # parsed by CookieFinder then this function will return True. If it returns
434+ # false then the specified cookie file is not readable by CookieFinder.
435+ def set_cookie_file(self, cookie_file_path):
436+
437+ # Ensure ~ is properly handled in the path
438+ cookie_file_path = os.path.expanduser(cookie_file_path)
439+
440+ # Locate a cookie loader that can read this cookie file.
441+ for cookie_loader in self.cookie_loaders:
442+
443+ if ( cookie_loader.load_cookies_from_file(cookie_file_path) ):
444+
445+ # Since we found a cookie loader that can read this file there is
446+ # no need to keep the rest around.
447+ print "Using Cookie Loader " + str(cookie_loader.__module__)
448+ self.cookie_loaders = [cookie_loader]
449+
450+ # We found a cookie loader that will read the caller's cookie
451+ # file. Return true and the caller will be most pleased :)
452+ return True
453+
454+ # We got to the end of the list and none of our cookie loaders read the file.
455+ return False
456+
457+ # Returns a list of cookies found by this CookieFinder that match the given criteria.
458+ # If caller does not specify any of the search criteria then all cookies will be
459+ # returned. An empty list is returned if no matching cookies were found.
460+ def get_cookies(self, domain=None, name=None, ignore_expired=False):
461+
462+ cookies = []
463+
464+ for cookie_loader in self.cookie_loaders:
465+
466+ # Ensure this cookie loader has its cookies loaded.
467+ found_cookies = cookie_loader.load_cookies()
468+
469+ # If no cookies were found go try next loader
470+ if(not found_cookies): continue
471+
472+ # Look for cookies matching the caller's search criteria.
473+ for cookie in cookie_loader.cookies:
474+ # If user specified a domain, skip this cookie if its domain does not match
475+ # The domain name is converted to lowercase to avoid comparison failue.
476+ # Any leading '.' character is also removed from domain name to prevent comparison failure.
477+ if domain and (not cookie['domain'].lower().lstrip(".") == domain.lower().lstrip(".")):
478+ continue
479+
480+ # If user specified a name, skip this cookie if its name does not match
481+ if name and (not cookie['name'].lower() == name.lower()):
482+ continue
483+
484+ # If user wants: skip this cookie if it has expired.
485+ if ignore_expired and (time.time() > cookie['expire_time']):
486+ continue
487+
488+ # This cookie matches, add it to the list.
489+ #print ( " Cookie: name=%s value=%s expires=%s" % (cookie['name'], cookie['value'], datetime.fromtimestamp(cookie['expire_time'])) )
490+ cookies.append(cookie)
491+
492+ # Return cookie list
493+ return cookies
494+
495+ # Returns the newest cookie found by this CookieFinder that match the given criteria.
496+ # The "newest" cookie is the cookie with the latest expiration date. This is really
497+ # only meaningful when comparing the same cookie but from different browser files.
498+ # It is useful when you just want ONE copy (the latest and greatest) of a specific
499+ # cookie but you're not sure which browser or cookie file has it.
500+ # None is returned if no matching cookie is found.
501+ def get_newest_cookie(self, domain=None, name=None, ignore_expired=False):
502+
503+ # Get list of all matching cookies
504+ cookies = self.get_cookies(domain, name, ignore_expired)
505+ if (len(cookies) == 0): return None
506+
507+ newest_cookie = cookies[0]
508+
509+ # Look for the one with the largest expire time.
510+ for cookie in cookies:
511+ if ( cookie['expire_time'] > newest_cookie['expire_time'] ):
512+ newest_cookie = cookie
513+
514+ return newest_cookie
515+
516+ # Returns True if this CookieFinder was able to find/load at least one cookie file.
517+ # This is useful to distinguish between thw following cases: 1) No cookie files are
518+ # found to load cookies from. 2) No matching cookies were found inside the cookie file(s).
519+ def has_cookie_files(self):
520+
521+ for cookie_loader in self.cookie_loaders:
522+ if (cookie_loader.found_valid_cookie_file): return True
523+
524+ return False
525+
526
527=== added file 'hugday_lib/cookie_lib/CookieLoader.py'
528--- hugday_lib/cookie_lib/CookieLoader.py 1970-01-01 00:00:00 +0000
529+++ hugday_lib/cookie_lib/CookieLoader.py 2010-04-13 16:46:31 +0000
530@@ -0,0 +1,209 @@
531+#!/usr/bin/python
532+
533+import os, cookielib
534+
535+try: import sqlite3 as sqlite
536+except ImportError:
537+ try: from pysqlite2 import dbapi2 as sqlite
538+ except ImportError: sqlite = None
539+
540+# base CookieLoader class. This class is not mean to be used directly. Instead
541+# child classes should be created to implement cookie loading for a specific
542+# browser.
543+#
544+# Used to find/access cookies for some web browser. You can specify
545+# a specific cookie file (load_cookies_from_file) or you can let this class
546+# automatically detect cookie files and load cookies from them (load_cookies).
547+class CookieLoader:
548+
549+ # Child classes should provide this string.
550+ # all lower case String (no spaces) that represents the name of the webbrowser that
551+ # this CookieLoader loads cookies for.
552+ browser_str = None
553+
554+ # List of cookies found. This is not valid until either load_cookies_from_file
555+ # has been called and has returned True or load_cookies has been called and
556+ # has returned true.
557+ #
558+ # cookie objects on this list are dictionaries and have the following properties:
559+ # 'domain' : string - Host setting the cookie
560+ # 'name' : string - Cookie name
561+ # 'value' : string - Cookie value
562+ # 'expire_time' : integer - Expire date/time (Unix Time)
563+ cookies = []
564+
565+ # Indicates that at least one valid cookie file was found during loading.
566+ found_valid_cookie_file = False
567+
568+ # Child classes should provide this list.
569+ # List of directories to search for cookie files (searched recursively).
570+ cookie_file_search_path = []
571+
572+ # Child classes should provide this list.
573+ # List of valid cookie file names. Files with these names will be search for cookies
574+ # if they are found in the paths listed in the cookie_file_search_path.
575+ cookie_file_names = []
576+
577+ # Child classes should provide this method.
578+ # attempts to load cookies from the given file.
579+ # If cookie_file_path points to a valid readable cookie file that can be
580+ # parsed then this function will return True. If it returns false then
581+ # the specified cookie file is not readable by this cookie finder.
582+ def load_cookies_from_file(self, cookie_file_path):
583+ raise NotImplementedError("This method cannot be used directly. Subclass CookieFinder and override this method.")
584+
585+ # Loads all cookies that are found in all cookie files found.
586+ # Cookie files are searched for in the paths found in
587+ # the cookie_file_search_paths list.
588+ # Returns True if at least one cookie file can be loaded, False otherwise.
589+ def load_cookies(self):
590+
591+ # If there are already cookies loaded, do not reload them, just return.
592+ # This prevents reloading the cookie list on every search since load_cookies
593+ # is called before every search.
594+ if (self.cookies):
595+ return ( len(self.cookies) > 0 )
596+
597+ # Initialize cookies to an empty list
598+ self.cookies = []
599+
600+ found_cookies = False
601+
602+ # Find all cookie files
603+ cookie_file_paths = self.find_cookie_files()
604+
605+ # Try to load each cookie file individually. If we can load any one of them then
606+ # we can declare succcess.
607+ for cookie_file_path in cookie_file_paths:
608+
609+ # If cookies are found, they are added to this objects cookies list.
610+ found_cookies = self.load_cookies_from_file(cookie_file_path)
611+
612+ return found_cookies
613+
614+ ######################################################
615+ ################# Internal Functions #################
616+ ######################################################
617+
618+ # Searches all predefined paths for cookie files. Returns a list of fully
619+ # qualified paths to cookie files found.
620+ def find_cookie_files(self):
621+
622+ cookie_file_paths = []
623+
624+ # Search each directory for cookies
625+ for cur_dir in self.cookie_file_search_path:
626+
627+ # Expand '~' in paths
628+ cur_dir = os.path.expanduser(cur_dir)
629+
630+ cur_paths = self.cookie_file_scan(cur_dir, cookie_file_paths)
631+
632+ return cookie_file_paths
633+
634+ # Recursively scan for cookie files in the target directory and return a
635+ # list of the cookie files found.
636+ def cookie_file_scan(self, target_dir, cookie_file_list):
637+
638+ # Ensure current directory exists
639+ if (not os.path.exists(target_dir)): return
640+
641+ # Loop over all files/dirs in the current_dir
642+ for cur_file in os.listdir(target_dir):
643+
644+ # Full path to current file
645+ cur_file_path = os.path.join(target_dir, cur_file)
646+
647+ # If the current file is a folder, recursively process it
648+ if( os.path.isdir(cur_file_path)):
649+
650+ # Scan it for cookie files
651+ self.cookie_file_scan(cur_file_path, cookie_file_list)
652+
653+ # The current file is a file (not a directory), see if it is a cookie file.
654+ else:
655+
656+ # If the current file matches any of the names in cookie_file_names, it is a cookie file.
657+ for name in self.cookie_file_names:
658+ if (cur_file.lower() == name.lower()):
659+ cookie_file_list.append(cur_file_path)
660+
661+ ####################################################################
662+ ################# Generic Cookie Parsing Functions #################
663+ ####################################################################
664+ # These parsing functions can be used by many different browsers. #
665+ # They are provided here for child classes to use. This avoid #
666+ # duplicating the code in each child class that can use them. #
667+ ####################################################################
668+
669+ # Loads cookies from a sqlite cookie file and returns the list of cookies.
670+ # Returns list of cookies on success and None otherwise.
671+ # The table and key names default to the ones used by Mozilla based browsers as they are the most common.
672+ def load_sqlite_cookie_file(self, cookie_file_path, table_name="moz_cookies", domain_key="host", name_key="name", value_key="value", expire_time_key="expiry"):
673+
674+ cookies = []
675+
676+ # "Connect" to cookie file :)
677+ try:
678+ con = sqlite.connect(cookie_file_path)
679+ con.row_factory = sqlite.Row
680+
681+ # Perform SQL query to obtain all cookies
682+ cur = con.cursor().execute("select * from " + table_name)
683+
684+ # If query fails, this is NOT a valid sqlite cookie file.
685+ except sqlite.Error, e:
686+ return None
687+
688+ # cookie objects returned are dictionaries and have the following properties
689+ # 'id' - ???
690+ # 'name' - Cookie name
691+ #'value' - Cookie value
692+ #'host' - Host setting the cookie
693+ #'path' - ???
694+ #'expiry' - Expire date/time (Unix Time)
695+ #'lastAccessed' - last time cookie was used.
696+ #'isSecure' - ???
697+ #'isHttpOnly' - ???
698+
699+
700+ # Build and return list of cookies.
701+ for cookie in cur:
702+ new_cookie = {}
703+ new_cookie['domain'] = cookie[domain_key]
704+ new_cookie['name'] = cookie[name_key]
705+ new_cookie['value'] = cookie[value_key]
706+ new_cookie['expire_time'] = cookie[expire_time_key]
707+ cookies.append(new_cookie)
708+
709+ # Close connection to database file
710+ con.close()
711+
712+ return cookies
713+
714+ # Loads cookies from a plain text cookie file and returns the list of cookies.
715+ # Returns list of cookies on success and None otherwise.
716+ def load_txt_cookie_file(self, cookie_file_path):
717+
718+ cookies = []
719+
720+ # Load cookie file
721+ try:
722+ cookie_jar = cookielib.MozillaCookieJar()
723+ cookie_jar.load(cookie_file_path)
724+
725+ # If load fails, this is NOT a valid plain text cookie file.
726+ except cookielib.LoadError:
727+ return None
728+
729+ # Loop over all cookies and all them to our cookies list
730+ for cookie in cookie_jar:
731+ new_cookie = {}
732+ new_cookie['domain'] = cookie.domain
733+ new_cookie['name'] = cookie.name
734+ new_cookie['value'] = cookie.value
735+ new_cookie['expire_time'] = cookie.expires # int
736+ cookies.append(new_cookie)
737+
738+ return cookies
739+
740
741=== added file 'hugday_lib/cookie_lib/EpiphanyCookieLoader.py'
742--- hugday_lib/cookie_lib/EpiphanyCookieLoader.py 1970-01-01 00:00:00 +0000
743+++ hugday_lib/cookie_lib/EpiphanyCookieLoader.py 2010-04-13 16:46:31 +0000
744@@ -0,0 +1,53 @@
745+#!/usr/bin/python
746+
747+import os, shutil
748+
749+from CookieLoader import CookieLoader
750+
751+# This is a subclass of CookieLoader. Please see CookieLoader for more details.
752+class EpiphanyCookieLoader(CookieLoader):
753+
754+ # browser name
755+ browser_str = "epiphany"
756+
757+ # List of directories to search for cookie files.
758+ cookie_file_search_path = [ os.path.join( "~" , ".gnome2" , "epiphany" ) ]
759+
760+ # List of valid cookie file names.
761+ cookie_file_names = [ "cookies.sqlite" ]
762+
763+ # attempts to load cookies from the given file.
764+ # If cookie_file_path points to a valid readable cookie file that can be
765+ # parsed then this function will return True. If it returns false then
766+ # the specified cookie file is not readable by this cookie finder.
767+ def load_cookies_from_file(self, cookie_file_path):
768+
769+ # If the given cookie file does not exist, give up.
770+ if (not os.path.exists(cookie_file_path)):
771+ return False
772+
773+ # If we attempt to use the cookie file while the browser is running
774+ # we will get errors. Only one process is allowed to use it at a time.
775+ # To get around this we can simply copy it to /tmp and then use it.
776+ tmp_cookie_file_path = os.path.join( "/" , "tmp" , os.path.basename(cookie_file_path) )
777+ shutil.copyfile(cookie_file_path, tmp_cookie_file_path)
778+
779+ # Epiphany cookie files are sqlite databases
780+ cookies = self.load_sqlite_cookie_file(tmp_cookie_file_path)
781+
782+ # If the load failed, then we've been given an invalid cookie file.
783+ if (not cookies):
784+ os.remove(tmp_cookie_file_path)
785+ return False
786+
787+ # Loop over all cookies and add them to our cookies list
788+ for cookie in cookies:
789+ self.cookies.append(cookie)
790+
791+ # Erase temporary cookie file.
792+ os.remove(tmp_cookie_file_path)
793+
794+ # We were able to read cookies from this file.
795+ self.found_valid_cookie_file = True
796+ return True
797+
798
799=== added file 'hugday_lib/cookie_lib/FirefoxCookieLoader.py'
800--- hugday_lib/cookie_lib/FirefoxCookieLoader.py 1970-01-01 00:00:00 +0000
801+++ hugday_lib/cookie_lib/FirefoxCookieLoader.py 2010-04-13 16:46:31 +0000
802@@ -0,0 +1,60 @@
803+#!/usr/bin/python
804+
805+import os, shutil
806+
807+from CookieLoader import CookieLoader
808+
809+# This is a subclass of CookieLoader. Please see CookieLoader for more details.
810+class FirefoxCookieLoader(CookieLoader):
811+
812+ # browser name
813+ browser_str = "firefox"
814+
815+ # List of directories to search for cookie files.
816+ cookie_file_search_path = [ os.path.join( "~" , ".mozilla" , "firefox" ) ]
817+
818+ # List of valid cookie file names.
819+ cookie_file_names = [ "cookies.sqlite" , "cookies.txt" ]
820+
821+ # attempts to load cookies from the given file.
822+ # If cookie_file_path points to a valid readable cookie file that can be
823+ # parsed then this function will return True. If it returns false then
824+ # the specified cookie file is not readable by this cookie finder.
825+ def load_cookies_from_file(self, cookie_file_path):
826+
827+ # If the given cookie file does not exist, give up.
828+ if (not os.path.exists(cookie_file_path)):
829+ return False
830+
831+ # If we attempt to use the cookie file while the browser is running
832+ # we will get errors. Only one process is allowed to use it at a time.
833+ # To get around this we can simply copy it to /tmp and then use it.
834+ tmp_cookie_file_path = os.path.join( "/" , "tmp" , os.path.basename(cookie_file_path) )
835+ shutil.copyfile(cookie_file_path, tmp_cookie_file_path)
836+
837+ # Firefox cookie files can be one of two formats:
838+ # old format - cookies.txt - Plain text cookie file
839+ # new format - cookies.sqlite - sqlite database file
840+
841+ # 1st, try to read that file as if it is an sqlite cookie file.
842+ cookies = self.load_sqlite_cookie_file(tmp_cookie_file_path)
843+
844+ # If that fails, try to read it as plain text
845+ if (not cookies): cookies = self.load_txt_cookie_file(tmp_cookie_file_path)
846+
847+ # If they both failed, then we've been given an invalid cookie file.
848+ if (not cookies):
849+ os.remove(tmp_cookie_file_path)
850+ return False
851+
852+ # Loop over all cookies and add them to our cookies list
853+ for cookie in cookies:
854+ self.cookies.append(cookie)
855+
856+ # Erase temporary cookie file.
857+ os.remove(tmp_cookie_file_path)
858+
859+ # We were able to read cookies from this file.
860+ self.found_valid_cookie_file = True
861+ return True
862+
863
864=== added file 'hugday_lib/cookie_lib/KonquerorCookieLoader.py'
865--- hugday_lib/cookie_lib/KonquerorCookieLoader.py 1970-01-01 00:00:00 +0000
866+++ hugday_lib/cookie_lib/KonquerorCookieLoader.py 2010-04-13 16:46:31 +0000
867@@ -0,0 +1,111 @@
868+ #!/usr/bin/python
869+
870+import os, shutil
871+
872+from CookieLoader import CookieLoader
873+
874+# This is a subclass of CookieLoader. Please see CookieLoader for more details.
875+class KonquerorCookieLoader(CookieLoader):
876+
877+ # browser name
878+ browser_str = "konqueror"
879+
880+ # List of directories to search for cookie files.
881+ cookie_file_search_path = [ os.path.join( "~" , ".kde" , "share" , "apps" , "kcookiejar") ]
882+
883+ # List of valid cookie file names.
884+ cookie_file_names = [ "cookies" ]
885+
886+ # attempts to load cookies from the given file.
887+ # If cookie_file_path points to a valid readable cookie file that can be
888+ # parsed then this function will return True. If it returns false then
889+ # the specified cookie file is not readable by this cookie finder.
890+ def load_cookies_from_file(self, cookie_file_path):
891+
892+ # If the given cookie file does not exist, give up.
893+ if (not os.path.exists(cookie_file_path)):
894+ return False
895+
896+ # Konqueror cookie files are in plain text format
897+ cookies = self.load_konqueror_cookie_file(cookie_file_path)
898+
899+ # If the load failed, then we've been given an invalid cookie file.
900+ if (not cookies):
901+ print "not cookies"
902+ return False
903+
904+ # Loop over all cookies and add them to our cookies list
905+ for cookie in cookies:
906+ self.cookies.append(cookie)
907+
908+ # We were able to read cookies from this file.
909+ return True
910+
911+
912+
913+ # Loads cookies from a Konqueror plain text cookie file and returns the list of cookies.
914+ # Returns list of cookies on success and None otherwise.
915+ def load_konqueror_cookie_file(self, cookie_file_path):
916+
917+ cookies = []
918+
919+ # Here is an example Konquerir cookie file:
920+ # # KDE Cookie File v2
921+ # #
922+ # # Host Domain Path Exp.date Prot Name Sec Value
923+ # [.ubuntu.com]
924+ # wiki.ubuntu.com ".wiki.ubuntu.com" "/" 1286259931 0 __utmz 4 230736537.1270491931.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none)
925+ # wiki.ubuntu.com ".wiki.ubuntu.com" "/" 1333564241 0 __utma 4 230736537.1618083871.1270491931.1270492211.1270492242.3
926+ # wiki.ubuntu.com ".wiki.ubuntu.com" "/" 1270494047 0 __utmb 4 230736537
927+ # wiki.ubuntu.com ".wiki.ubuntu.com" "/" 1333564247 0 __utmv 4 230736537.UbuntuWiki
928+ # wiki.ubuntu.com "" "/" 1585852247 0 MOIN_SESSION 4 249_4_7z1eln24s28t8ifido5k3cnwc_
929+ # [.launchpad.net]
930+ # login.launchpad.net "" "/" 1271701555 0 sessionid 5 9ebf071969502599c1d1f959d210c892
931+ # [.slashdot.org]
932+ # slashdot.org ".slashdot.org" "/" 1286260261 0 __utmz 4 9273847.1270492261.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
933+ # slashdot.org ".slashdot.org" "/" 1333564271 0 __utma 4 9273847.870340038.1270492261.1270492261.1270492261.1
934+ # slashdot.org ".slashdot.org" "/" 1270494071 0 __utmb 4 9273847.1.10.1270492261
935+ #
936+
937+ # Open cookie file
938+ cookie_file = open(cookie_file_path, "r")
939+
940+ # Ensure this is a Konqueror cookie file
941+ first_line = cookie_file.readline()
942+ if ( first_line.find("KDE Cookie File") == -1 ):
943+ print "Not cookie file!"
944+ return False
945+
946+ # Parse file line by line looking for cookies...
947+ for line in cookie_file:
948+
949+ # Throw away comments, host headers
950+ if ( line.startswith("#") ): continue
951+ if ( line.startswith("[") ): continue
952+
953+ # Split string on whitespace (Ignore lines that do not contain enough tokens)
954+ tokens = line.split()
955+ if ( len(tokens) < 8): continue
956+
957+ # Construct cookie
958+ new_cookie = {}
959+ new_cookie['domain'] = tokens[0]
960+ new_cookie['name'] = tokens[5]
961+ new_cookie['value'] = tokens[7]
962+ new_cookie['expire_time'] = tokens[3]
963+
964+ # Sanity check data & convert expire time to an integer
965+ if (new_cookie['expire_time'].isdigit()):
966+ new_cookie['expire_time'] = int(new_cookie['expire_time'])
967+ else: continue
968+
969+ # We have a good cookie :)
970+ cookies.append(new_cookie)
971+
972+ # Close file.
973+ cookie_file.close()
974+
975+ # Found cookie files :)
976+ self.found_valid_cookie_file = True
977+ return cookies
978+
979
980=== added file 'hugday_lib/cookie_lib/SeamonkeyCookieLoader.py'
981--- hugday_lib/cookie_lib/SeamonkeyCookieLoader.py 1970-01-01 00:00:00 +0000
982+++ hugday_lib/cookie_lib/SeamonkeyCookieLoader.py 2010-04-13 16:46:31 +0000
983@@ -0,0 +1,43 @@
984+#!/usr/bin/python
985+
986+import os, shutil
987+
988+from CookieLoader import CookieLoader
989+
990+# This is a subclass of CookieLoader. Please see CookieLoader for more details.
991+class SeamonkeyCookieLoader(CookieLoader):
992+
993+ # browser name
994+ browser_str = "seamonkey"
995+
996+ # List of directories to search for cookie files.
997+ cookie_file_search_path = [ os.path.join( "~" , ".mozilla" , "default" ) ]
998+
999+ # List of valid cookie file names.
1000+ cookie_file_names = [ "cookies.txt" ]
1001+
1002+ # attempts to load cookies from the given file.
1003+ # If cookie_file_path points to a valid readable cookie file that can be
1004+ # parsed then this function will return True. If it returns false then
1005+ # the specified cookie file is not readable by this cookie finder.
1006+ def load_cookies_from_file(self, cookie_file_path):
1007+
1008+ # If the given cookie file does not exist, give up.
1009+ if (not os.path.exists(cookie_file_path)):
1010+ return False
1011+
1012+ # Epiphany cookie files are sqlite databases
1013+ cookies = self.load_txt_cookie_file(cookie_file_path)
1014+
1015+ # If the load failed, then we've been given an invalid cookie file.
1016+ if (not cookies):
1017+ return False
1018+
1019+ # Loop over all cookies and add them to our cookies list
1020+ for cookie in cookies:
1021+ self.cookies.append(cookie)
1022+
1023+ # We were able to read cookies from this file.
1024+ self.found_valid_cookie_file = True
1025+ return True
1026+
1027
1028=== added file 'hugday_lib/cookie_lib/__init__.py'
1029=== modified file 'setup.py'
1030--- setup.py 2010-03-31 16:37:16 +0000
1031+++ setup.py 2010-04-13 16:46:31 +0000
1032@@ -28,5 +28,7 @@
1033 'dl-ubuntu-test-iso/iso-ripper',
1034 'hugday-tools/hugday',
1035 ],
1036- packages=[],
1037+ packages=['hugday_lib',
1038+ 'hugday_lib/cookie_lib'
1039+ ]
1040 )