| import datetime |
| import http.cookiejar as cookiejar |
| import json |
| import time |
| |
| import pyrfc3339 |
| |
| |
| class GoCookieJar(cookiejar.FileCookieJar): |
| '''A CookieJar implementation that reads and writes cookies |
| to the cookiejar format as understood by the Go package |
| github.com/juju/persistent-cookiejar.''' |
| def _really_load(self, f, filename, ignore_discard, ignore_expires): |
| '''Implement the _really_load method called by FileCookieJar |
| to implement the actual cookie loading''' |
| data = json.load(f) or [] |
| now = time.time() |
| for cookie in map(_new_py_cookie, data): |
| if not ignore_expires and cookie.is_expired(now): |
| continue |
| self.set_cookie(cookie) |
| |
| def save(self, filename=None, ignore_discard=False, ignore_expires=False): |
| '''Implement the FileCookieJar abstract method.''' |
| if filename is None: |
| if self.filename is not None: |
| filename = self.filename |
| else: |
| raise ValueError(cookiejar.MISSING_FILENAME_TEXT) |
| |
| # TODO: obtain file lock, read contents of file, and merge with |
| # current content. |
| go_cookies = [] |
| now = time.time() |
| for cookie in self: |
| if not ignore_discard and cookie.discard: |
| continue |
| if not ignore_expires and cookie.is_expired(now): |
| continue |
| go_cookies.append(_new_go_cookie(cookie)) |
| with open(filename, "w") as f: |
| f.write(json.dumps(go_cookies)) |
| |
| |
| def _new_py_cookie(go_cookie): |
| '''Convert a Go-style JSON-unmarshaled cookie into a Python cookie''' |
| expires = None |
| if go_cookie.get('Expires') is not None: |
| t = pyrfc3339.parse(go_cookie['Expires']) |
| expires = t.timestamp() |
| return cookiejar.Cookie( |
| version=0, |
| name=go_cookie['Name'], |
| value=go_cookie['Value'], |
| port=None, |
| port_specified=False, |
| # Unfortunately Python cookies don't record the original |
| # host that the cookie came from, so we'll just use Domain |
| # for that purpose, and record that the domain was specified, |
| # even though it probably was not. This means that |
| # we won't correctly record the CanonicalHost entry |
| # when writing the cookie file after reading it. |
| domain=go_cookie['Domain'], |
| domain_specified=not go_cookie['HostOnly'], |
| domain_initial_dot=False, |
| path=go_cookie['Path'], |
| path_specified=True, |
| secure=go_cookie['Secure'], |
| expires=expires, |
| discard=False, |
| comment=None, |
| comment_url=None, |
| rest=None, |
| rfc2109=False, |
| ) |
| |
| |
| def _new_go_cookie(py_cookie): |
| '''Convert a python cookie to the JSON-marshalable Go-style cookie form.''' |
| # TODO (perhaps): |
| # HttpOnly |
| # Creation |
| # LastAccess |
| # Updated |
| # not done properly: CanonicalHost. |
| go_cookie = { |
| 'Name': py_cookie.name, |
| 'Value': py_cookie.value, |
| 'Domain': py_cookie.domain, |
| 'HostOnly': not py_cookie.domain_specified, |
| 'Persistent': not py_cookie.discard, |
| 'Secure': py_cookie.secure, |
| 'CanonicalHost': py_cookie.domain, |
| } |
| if py_cookie.path_specified: |
| go_cookie['Path'] = py_cookie.path |
| if py_cookie.expires is not None: |
| unix_time = datetime.datetime.fromtimestamp(py_cookie.expires) |
| # Note: fromtimestamp bizarrely produces a time without |
| # a time zone, so we need to use accept_naive. |
| go_cookie['Expires'] = pyrfc3339.generate(unix_time, accept_naive=True) |
| return go_cookie |