# HG changeset patch # User Sylvain Thénault # Date 1316772989 -7200 # Node ID 54283a5b7afcb8d8acf60216574c703db75d2fe4 # Parent d95a76df33a9967b56d28f591cce2cca58da008f [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. diff -r d95a76df33a9 -r 54283a5b7afc devtools/fake.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) diff -r d95a76df33a9 -r 54283a5b7afc etwist/http.py --- 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: diff -r d95a76df33a9 -r 54283a5b7afc web/application.py --- 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 diff -r d95a76df33a9 -r 54283a5b7afc web/http_headers.py --- 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] diff -r d95a76df33a9 -r 54283a5b7afc web/request.py --- 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 diff -r d95a76df33a9 -r 54283a5b7afc web/views/basecontrollers.py --- 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 ################################################## diff -r d95a76df33a9 -r 54283a5b7afc web/views/cwproperties.py --- 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 diff -r d95a76df33a9 -r 54283a5b7afc web/views/tabs.py --- 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