New N2VC interface + updated libjuju
[osm/N2VC.git] / modules / libjuju / juju / client / gocookies.py
diff --git a/modules/libjuju/juju/client/gocookies.py b/modules/libjuju/juju/client/gocookies.py
new file mode 100644 (file)
index 0000000..a8a0df8
--- /dev/null
@@ -0,0 +1,102 @@
+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