[web request] fix cookie 'expires' formating (closes #1953945) stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 23 Sep 2011 12:16:29 +0200
branchstable
changeset 7855 54283a5b7afc
parent 7854 d95a76df33a9
child 7856 51a3fb272bf3
[web request] fix cookie 'expires' formating (closes #1953945) This was because cookie.expires wasn't processed in cw.etwist.http, though this code should had never existed, instead proper twisted method should be called. Also, move on the way to headers handling simplification and rewrite cw request.set_cookie / remove_cookie to directly use the Cookie class in cw.web.http_headers rather than going back and forth simple cookie <-> raw string <-> http_headers.Cookie IMO more on this should be done.
devtools/fake.py
etwist/http.py
web/application.py
web/http_headers.py
web/request.py
web/views/basecontrollers.py
web/views/cwproperties.py
web/views/tabs.py
--- a/devtools/fake.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/devtools/fake.py	Fri Sep 23 12:16:29 2011 +0200
@@ -63,8 +63,8 @@
         self._session_data = {}
         self._headers_in = Headers()
 
-    def set_cookie(self, cookie, key, maxage=300, expires=None):
-        super(FakeRequest, self).set_cookie(cookie, key, maxage=300, expires=None)
+    def set_cookie(self, name, value, maxage=300, expires=None, secure=False):
+        super(FakeRequest, self).set_cookie(name, value, maxage, expires, secure)
         cookie = self.get_response_header('Set-Cookie')
         self._headers_in.setHeader('Cookie', cookie)
 
--- a/etwist/http.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/etwist/http.py	Fri Sep 23 12:16:29 2011 +0200
@@ -1,7 +1,7 @@
 """twisted server for CubicWeb web instances
 
 :organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:copyright: 2001-2011 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
@@ -25,25 +25,14 @@
     def _init_headers(self):
         if self._headers_out is None:
             return
-
-        # initialize cookies
-        cookies = self._headers_out.getHeader('set-cookie') or []
-        for cookie in cookies:
-            self._twreq.addCookie(cookie.name, cookie.value, cookie.expires,
-                                  cookie.domain, cookie.path, #TODO max-age
-                                  comment = cookie.comment, secure=cookie.secure)
-        self._headers_out.removeHeader('set-cookie')
-
-        # initialize other headers
-        for k, v in self._headers_out.getAllRawHeaders():
-            self._twreq.setHeader(k, v[0])
-
+        # initialize headers
+        for k, values in self._headers_out.getAllRawHeaders():
+            self._twreq.responseHeaders.setRawHeaders(k, values)
         # add content-length if not present
         if (self._headers_out.getHeader('content-length') is None
             and self._stream is not None):
            self._twreq.setHeader('content-length', len(self._stream))
 
-
     def _finalize(self):
         # we must set code before writing anything, else it's too late
         if self._code is not None:
--- a/web/application.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/web/application.py	Fri Sep 23 12:16:29 2011 +0200
@@ -236,12 +236,10 @@
 
     def open_session(self, req, allow_no_cnx=True):
         session = self.session_manager.open_session(req, allow_no_cnx=allow_no_cnx)
-        cookie = req.get_cookie()
         sessioncookie = self.session_cookie(req)
-        cookie[sessioncookie] = session.sessionid
-        if req.https and req.base_url().startswith('https://'):
-            cookie[sessioncookie]['secure'] = True
-        req.set_cookie(cookie, sessioncookie, maxage=None)
+        secure = req.https and req.base_url().startswith('https://')
+        req.set_cookie(sessioncookie, session.sessionid,
+                       maxage=None, secure=secure)
         if not session.anonymous_session:
             self.session_manager.postlogin(req)
         return session
@@ -251,8 +249,7 @@
         `AuthenticationError`
         """
         self.session_manager.close_session(req.session)
-        sessioncookie = self.session_cookie(req)
-        req.remove_cookie(req.get_cookie(), sessioncookie)
+        req.remove_cookie(self.session_cookie(req))
         raise LogOut(url=goto_url)
 
     # these are overridden by set_log_methods below
--- a/web/http_headers.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/web/http_headers.py	Fri Sep 23 12:16:29 2011 +0200
@@ -1354,9 +1354,25 @@
         raw_header.append(value)
         self._headers[name] = _RecalcNeeded
 
+    def addHeader(self, name, value):
+        """
+        Add a parsed representatoin to a header that may or may not already exist.
+        If it exists, add it as a separate header to output; do not
+        replace anything.
+        """
+        name=name.lower()
+        header = self._headers.get(name)
+        if header is None:
+            # No header yet
+            header = []
+            self._headers[name] = header
+        elif header is _RecalcNeeded:
+            header = self._toParsed(name)
+        header.append(value)
+        self._raw_headers[name] = _RecalcNeeded
+
     def removeHeader(self, name):
         """Removes the header named."""
-
         name=name.lower()
         if self._raw_headers.has_key(name):
             del self._raw_headers[name]
--- a/web/request.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/web/request.py	Fri Sep 23 12:16:29 2011 +0200
@@ -19,11 +19,12 @@
 
 __docformat__ = "restructuredtext en"
 
-import Cookie
 import hashlib
 import time
 import random
 import base64
+from Cookie import SimpleCookie
+from calendar import timegm
 from datetime import date
 from urlparse import urlsplit
 from itertools import count
@@ -42,7 +43,8 @@
 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
                           RequestError, StatusResponse)
-from cubicweb.web.http_headers import Headers
+from cubicweb.web.httpcache import GMTOFFSET
+from cubicweb.web.http_headers import Headers, Cookie
 
 _MARKER = object()
 
@@ -518,30 +520,44 @@
 
     def get_cookie(self):
         """retrieve request cookies, returns an empty cookie if not found"""
+        # XXX use http_headers implementation
         try:
-            return Cookie.SimpleCookie(self.get_header('Cookie'))
+            return SimpleCookie(self.get_header('Cookie'))
         except KeyError:
-            return Cookie.SimpleCookie()
+            return SimpleCookie()
 
-    def set_cookie(self, cookie, key, maxage=300, expires=None):
-        """set / update a cookie key
+    def set_cookie(self, name, value, maxage=300, expires=None, secure=False):
+        """set / update a cookie
 
         by default, cookie will be available for the next 5 minutes.
         Give maxage = None to have a "session" cookie expiring when the
         client close its browser
         """
-        morsel = cookie[key]
-        if maxage is not None:
-            morsel['Max-Age'] = maxage
-        if expires:
-            morsel['expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S %z')
+        if isinstance(name, SimpleCookie):
+            warn('[3.13] set_cookie now takes name and value as two first '
+                 'argument, not anymore cookie object and name',
+                 DeprecationWarning, stacklevel=2)
+            secure = name[value]['secure']
+            name, value = value, name[value].value
+        if maxage: # don't check is None, 0 may be specified
+            expires = maxage + time.time()
+            assert expires is None, 'both max age and expires cant be specified'
+        elif expires:
+            expires = timegm((expires + GMTOFFSET).timetuple())
+        else:
+            expires = None
         # make sure cookie is set on the correct path
-        morsel['path'] = self.base_url_path()
-        self.add_header('Set-Cookie', morsel.OutputString())
+        cookie = Cookie(name, value, self.base_url_path(), expires=expires,
+                        secure=secure)
+        self.headers_out.addHeader('Set-cookie', cookie)
 
-    def remove_cookie(self, cookie, key):
+    def remove_cookie(self, name, bwcompat=None):
         """remove a cookie by expiring it"""
-        self.set_cookie(cookie, key, maxage=0, expires=date(1970, 1, 1))
+        if bwcompat is not None:
+            warn('[3.13] remove_cookie now take only a name as argument',
+                 DeprecationWarning, stacklevel=2)
+            name = bwcompat
+        self.set_cookie(key, '', maxage=0, expires=date(1970, 1, 1))
 
     def set_content_type(self, content_type, filename=None, encoding=None):
         """set output content type for this request. An optional filename
--- a/web/views/basecontrollers.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/web/views/basecontrollers.py	Fri Sep 23 12:16:29 2011 +0200
@@ -535,24 +535,20 @@
         statename = treecookiename(treeid)
         treestate = cookies.get(statename)
         if treestate is None:
-            cookies[statename] = nodeeid
-            self._cw.set_cookie(cookies, statename)
+            self._cw.set_cookie(statename, nodeeid)
         else:
             marked = set(filter(None, treestate.value.split(':')))
             if nodeeid in marked:
                 marked.remove(nodeeid)
             else:
                 marked.add(nodeeid)
-            cookies[statename] = ':'.join(marked)
-            self._cw.set_cookie(cookies, statename)
+            self._cw.set_cookie(statename, ':'.join(marked))
 
     @jsonize
     @deprecated("[3.13] use jQuery.cookie(cookiename, cookievalue, {path: '/'}) in js land instead")
     def js_set_cookie(self, cookiename, cookievalue):
         cookiename, cookievalue = str(cookiename), str(cookievalue)
-        cookies = self._cw.get_cookie()
-        cookies[cookiename] = cookievalue
-        self._cw.set_cookie(cookies, cookiename)
+        self._cw.set_cookie(cookiename, cookievalue)
 
     # relations edition stuff ##################################################
 
--- a/web/views/cwproperties.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/web/views/cwproperties.py	Fri Sep 23 12:16:29 2011 +0200
@@ -102,8 +102,7 @@
         cookiename = self._cookie_name(group)
         cookie = cookies.get(cookiename)
         if cookie is None:
-            cookies[cookiename] = default
-            self._cw.set_cookie(cookies, cookiename, maxage=None)
+            self._cw.set_cookie(cookiename, default, maxage=None)
             status = default
         else:
             status = cookie.value
--- a/web/views/tabs.py	Fri Sep 23 12:17:12 2011 +0200
+++ b/web/views/tabs.py	Fri Sep 23 12:16:29 2011 +0200
@@ -93,8 +93,7 @@
         activetab = cookies.get(cookiename)
         if activetab is None:
             domid = uilib.domid(default)
-            cookies[cookiename] = domid
-            self._cw.set_cookie(cookies, cookiename)
+            self._cw.set_cookie(cookiename, domid)
             return domid
         return activetab.value