[Zope3-checkins] SVN: Zope3/branches/3.3/ Backport the
session-cookie-path-with-virtual-host changes from the trunk
(issue 679) including functional test fixes for handling cookies
Martijn Pieters
mj at zopatista.com
Wed Aug 2 10:16:05 EDT 2006
Log message for revision 69340:
Backport the session-cookie-path-with-virtual-host changes from the trunk (issue 679) including functional test fixes for handling cookies
Changed:
U Zope3/branches/3.3/doc/CHANGES.txt
U Zope3/branches/3.3/src/zope/app/session/configure.zcml
U Zope3/branches/3.3/src/zope/app/session/ftests.py
U Zope3/branches/3.3/src/zope/app/session/http.py
U Zope3/branches/3.3/src/zope/app/testing/functional.py
U Zope3/branches/3.3/src/zope/app/testing/tests.py
U Zope3/branches/3.3/src/zope/publisher/DEPENDENCIES.cfg
U Zope3/branches/3.3/src/zope/publisher/http.py
U Zope3/branches/3.3/src/zope/publisher/interfaces/http.py
U Zope3/branches/3.3/src/zope/publisher/tests/test_http.py
-=-
Modified: Zope3/branches/3.3/doc/CHANGES.txt
===================================================================
--- Zope3/branches/3.3/doc/CHANGES.txt 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/doc/CHANGES.txt 2006-08-02 14:16:04 UTC (rev 69340)
@@ -137,6 +137,10 @@
- Fixed issue 632: "bin/zopectl run" drops into python shell on errors.
+ - Fixed issue 679: using a new IHTTPVirtualHostChangedEvent, session's
+ cliend id cookies fix up the set cookie path whenever the virtual host
+ information changes during a request.
+
Notes
If you register a new default view name for a particular layer, and this
Modified: Zope3/branches/3.3/src/zope/app/session/configure.zcml
===================================================================
--- Zope3/branches/3.3/src/zope/app/session/configure.zcml 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/app/session/configure.zcml 2006-08-02 14:16:04 UTC (rev 69340)
@@ -70,6 +70,11 @@
for="zope.app.appsetup.IDatabaseOpenedEvent"
handler=".bootstrap.bootStrapSubscriber"
/>
+
+ <subscriber
+ for="zope.publisher.interfaces.http.IHTTPVirtualHostChangedEvent"
+ handler=".http.notifyVirtualHostChanged"
+ />
<include file="browser.zcml" />
Modified: Zope3/branches/3.3/src/zope/app/session/ftests.py
===================================================================
--- Zope3/branches/3.3/src/zope/app/session/ftests.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/app/session/ftests.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -16,9 +16,14 @@
$Id: tests.py 26427 2004-07-12 16:05:02Z Zen $
"""
import unittest
+from zope.component import provideHandler, getGlobalSiteManager
+from zope.app.folder import Folder
+from zope.app.publication.interfaces import IBeforeTraverseEvent
from zope.app.testing.functional import BrowserTestCase
from zope.app.zptpage.zptpage import ZPTPage
+from interfaces import ISession
+
class ZPTSessionTest(BrowserTestCase):
content = u'''
<div tal:define="
@@ -58,10 +63,50 @@
response3 = self.fetch()
self.failUnlessEqual(response3, u'3')
-
+class VirtualHostSessionTest(BrowserTestCase):
+ def setUp(self):
+ super(VirtualHostSessionTest, self).setUp()
+ page = ZPTPage()
+ page.source = (u'<div '
+ u'tal:define="session request/session:products.foo"/>')
+ page.evaluateInlineCode = True
+ root = self.getRootFolder()
+ root['folder'] = Folder()
+ root['folder']['page'] = page
+ self.commit()
+
+ provideHandler(self.accessSessionOnTraverse, (IBeforeTraverseEvent,))
+
+ def tearDown(self):
+ getGlobalSiteManager().unregisterHandler(self.accessSessionOnTraverse,
+ (IBeforeTraverseEvent,))
+
+ def accessSessionOnTraverse(self, event):
+ session = ISession(event.request)
+
+ def assertCookiePath(self, path):
+ cookie = self.cookies.values()[0]
+ self.assertEqual(cookie['path'], path)
+
+ def testShortendPath(self):
+ self.publish(
+ '/++skin++Rotterdam/folder/++vh++http:localhost:80/++/page')
+ self.assertCookiePath('/')
+
+ def testLongerPath(self):
+ self.publish(
+ '/folder/++vh++http:localhost:80/foo/bar/++/page')
+ self.assertCookiePath('/foo/bar')
+
+ def testDifferentHostname(self):
+ self.publish(
+ '/folder/++vh++http:foo.bar:80/++/page')
+ self.assertCookiePath('/')
+
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(ZPTSessionTest),
+ unittest.makeSuite(VirtualHostSessionTest),
))
if __name__ == '__main__':
Modified: Zope3/branches/3.3/src/zope/app/session/http.py
===================================================================
--- Zope3/branches/3.3/src/zope/app/session/http.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/app/session/http.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -24,8 +24,9 @@
from cStringIO import StringIO
from persistent import Persistent
-from zope import schema
+from zope import schema, component
from zope.interface import implements
+from zope.publisher.interfaces.http import IHTTPRequest
from zope.publisher.interfaces.http import IHTTPApplicationRequest
from zope.annotation.interfaces import IAttributeAnnotatable
@@ -273,3 +274,53 @@
path=request.getApplicationURL(path_only=True)
)
+def notifyVirtualHostChanged(event):
+ """Adjust cookie paths when IVirtualHostRequest information changes.
+
+ Given a event, this method should call a CookieClientIdManager's
+ setRequestId if a cookie is present in the response for that manager. To
+ demonstrate we create a dummy manager object and event:
+
+ >>> class DummyManager(object):
+ ... implements(ICookieClientIdManager)
+ ... namespace = 'foo'
+ ... request_id = None
+ ... def setRequestId(self, request, id):
+ ... self.request_id = id
+ ...
+ >>> manager = DummyManager()
+ >>> component.provideUtility(manager, IClientIdManager)
+ >>> from zope.publisher.http import HTTPRequest
+ >>> class DummyEvent (object):
+ ... request = HTTPRequest(StringIO(''), {}, None)
+ >>> event = DummyEvent()
+
+ With no cookies present, the manager should not be called:
+
+ >>> notifyVirtualHostChanged(event)
+ >>> manager.request_id is None
+ True
+
+ However, when a cookie *has* been set, the manager is called so it can
+ update the cookie if need be:
+
+ >>> event.request.response.setCookie('foo', 'bar')
+ >>> notifyVirtualHostChanged(event)
+ >>> manager.request_id
+ 'bar'
+
+ Cleanup of the utility registration:
+
+ >>> import zope.component.testing
+ >>> zope.component.testing.tearDown()
+
+ """
+ # the event sends us a IHTTPApplicationRequest, but we need a
+ # IHTTPRequest for the response attribute, and so does the cookie-
+ # manager.
+ request = IHTTPRequest(event.request, None)
+ manager = component.queryUtility(IClientIdManager)
+ if manager and request and ICookieClientIdManager.providedBy(manager):
+ cookie = request.response.getCookie(manager.namespace)
+ if cookie:
+ manager.setRequestId(request, cookie['value'])
Modified: Zope3/branches/3.3/src/zope/app/testing/functional.py
===================================================================
--- Zope3/branches/3.3/src/zope/app/testing/functional.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/app/testing/functional.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -264,7 +264,7 @@
def httpCookie(self, path):
"""Return self.cookies as an HTTP_COOKIE environment value."""
- l = [m.OutputString() for m in self.cookies.values()
+ l = [m.OutputString().split(';')[0] for m in self.cookies.values()
if path.startswith(m['path'])]
return '; '.join(l)
@@ -275,11 +275,14 @@
"""Save cookies from the response."""
# Urgh - need to play with the response's privates to extract
# cookies that have been set
+ # TODO: extend the IHTTPRequest interface to allow access to all
+ # cookies
+ # TODO: handle cookie expirations
for k,v in response._cookies.items():
k = k.encode('utf8')
self.cookies[k] = v['value'].encode('utf8')
- if self.cookies[k].has_key('Path'):
- self.cookies[k]['Path'] = v['Path']
+ if v.has_key('path'):
+ self.cookies[k]['path'] = v['path']
class BrowserTestCase(CookieHandler, FunctionalTestCase):
Modified: Zope3/branches/3.3/src/zope/app/testing/tests.py
===================================================================
--- Zope3/branches/3.3/src/zope/app/testing/tests.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/app/testing/tests.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -181,12 +181,59 @@
self.assert_(IRequest.implementedBy(request_class))
self.assert_(IPublication.implementedBy(publication_class))
+class DummyCookiesResponse(object):
+ # Ugh, this simulates the *internals* of a HTTPResponse object
+ # TODO: expand the IHTTPResponse interface to give access to all cookies
+ _cookies = None
+
+ def __init__(self, cookies=None):
+ if not cookies:
+ cookies = {}
+ self._cookies = cookies
+
+class CookieHandlerTestCase(unittest.TestCase):
+ def setUp(self):
+ self.handler = functional.CookieHandler()
+
+ def test_saveCookies(self):
+ response = DummyCookiesResponse(dict(
+ spam=dict(value='eggs', path='/foo', comment='rest is ignored'),
+ monty=dict(value='python')))
+ self.handler.saveCookies(response)
+ self.assertEqual(len(self.handler.cookies), 2)
+ self.assertEqual(self.handler.cookies['spam'].OutputString(),
+ 'spam=eggs; Path=/foo;')
+ self.assertEqual(self.handler.cookies['monty'].OutputString(),
+ 'monty=python;')
+
+ def test_httpCookie(self):
+ cookies = self.handler.cookies
+ cookies['spam'] = 'eggs'
+ cookies['spam']['path'] = '/foo'
+ cookies['bar'] = 'baz'
+ cookies['bar']['path'] = '/foo/baz'
+ cookies['monty'] = 'python'
+
+ cookieHeader = self.handler.httpCookie('/foo/bar')
+ parts = cookieHeader.split('; ')
+ parts.sort()
+ self.assertEqual(parts, ['monty=python', 'spam=eggs'])
+
+ cookieHeader = self.handler.httpCookie('/foo/baz')
+ parts = cookieHeader.split('; ')
+ parts.sort()
+ self.assertEqual(parts, ['bar=baz', 'monty=python', 'spam=eggs'])
+
+ # There is no test for CookieHandler.loadCookies because it that method
+ # only passes the arguments on to Cookie.BaseCookie.load, which the
+ # standard library has tests for (we hope).
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(FunctionalHTTPDocTest),
unittest.makeSuite(AuthHeaderTestCase),
unittest.makeSuite(HTTPCallerTestCase),
+ unittest.makeSuite(CookieHandlerTestCase),
))
if __name__ == '__main__':
Modified: Zope3/branches/3.3/src/zope/publisher/DEPENDENCIES.cfg
===================================================================
--- Zope3/branches/3.3/src/zope/publisher/DEPENDENCIES.cfg 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/publisher/DEPENDENCIES.cfg 2006-08-02 14:16:04 UTC (rev 69340)
@@ -1,4 +1,5 @@
zope.component
+zope.event
zope.exceptions
zope.i18n
zope.interface
Modified: Zope3/branches/3.3/src/zope/publisher/http.py
===================================================================
--- Zope3/branches/3.3/src/zope/publisher/http.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/publisher/http.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -25,7 +25,7 @@
import logging
from tempfile import TemporaryFile
-from zope import component, interface
+from zope import component, interface, event
from zope.deprecation import deprecation
from zope.publisher import contenttype
@@ -33,6 +33,7 @@
from zope.publisher.interfaces.http import IHTTPRequest
from zope.publisher.interfaces.http import IHTTPApplicationRequest
from zope.publisher.interfaces.http import IHTTPPublisher
+from zope.publisher.interfaces.http import IHTTPVirtualHostChangedEvent
from zope.publisher.interfaces import Redirect
from zope.publisher.interfaces.http import IHTTPResponse
@@ -73,6 +74,14 @@
dict['PATH_INFO'] = dict['PATH_INFO'].decode('utf-8')
return dict
+class HTTPVirtualHostChangedEvent(object):
+ interface.implements(IHTTPVirtualHostChangedEvent)
+
+ request = None
+
+ def __init__(self, request):
+ self.request = request
+
# Possible HTTP status responses
status_reasons = {
100: 'Continue',
@@ -546,6 +555,7 @@
if port and str(port) != DEFAULT_PORTS.get(proto):
host = '%s:%s' % (host, port)
self._app_server = '%s://%s' % (proto, host)
+ event.notify(HTTPVirtualHostChangedEvent(self))
def shiftNameToApplication(self):
"""Add the name being traversed to the application name
@@ -556,6 +566,7 @@
"""
if len(self._traversed_names) == 1:
self._app_names.append(self._traversed_names.pop())
+ event.notify(HTTPVirtualHostChangedEvent(self))
return
raise ValueError("Can only shift leading traversal "
@@ -565,6 +576,7 @@
del self._traversed_names[:]
self._vh_root = self._last_obj_traversed
self._app_names = list(names)
+ event.notify(HTTPVirtualHostChangedEvent(self))
def getVirtualHostRoot(self):
return self._vh_root
Modified: Zope3/branches/3.3/src/zope/publisher/interfaces/http.py
===================================================================
--- Zope3/branches/3.3/src/zope/publisher/interfaces/http.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/publisher/interfaces/http.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -392,3 +392,12 @@
Note that this function can be only requested once, since it is
constructed from the result.
"""
+
+class IHTTPVirtualHostChangedEvent(Interface):
+ """The host, port and/or the application path have changed.
+
+ The request referred to in this event implements at least the
+ IHTTPAppliationRequest interface.
+ """
+ request = Attribute(u'The application request whose virtual host info has '
+ u'been altered')
Modified: Zope3/branches/3.3/src/zope/publisher/tests/test_http.py
===================================================================
--- Zope3/branches/3.3/src/zope/publisher/tests/test_http.py 2006-08-02 13:38:06 UTC (rev 69339)
+++ Zope3/branches/3.3/src/zope/publisher/tests/test_http.py 2006-08-02 14:16:04 UTC (rev 69340)
@@ -18,6 +18,7 @@
"""
import unittest
+import zope.event
from zope.interface import implements
from zope.publisher.interfaces.logginginfo import ILoggingInfo
from zope.publisher.http import HTTPRequest, HTTPResponse
@@ -367,6 +368,8 @@
self.assertEqual(r.method, 'EGGS')
def test_setApplicationServer(self):
+ events = []
+ zope.event.subscribers.append(events.append)
req = self._createRequest()
req.setApplicationServer('foo')
self.assertEquals(req._app_server, 'http://foo')
@@ -384,22 +387,36 @@
self.assertEquals(req._app_server, 'http://foo')
req.setApplicationServer('foo', proto='telnet', port=80)
self.assertEquals(req._app_server, 'telnet://foo:80')
+ zope.event.subscribers.pop()
+ self.assertEquals(len(events), 8)
+ for event in events:
+ self.assertEquals(event.request, req)
def test_setApplicationNames(self):
+ events = []
+ zope.event.subscribers.append(events.append)
req = self._createRequest()
names = ['x', 'y', 'z']
req.setVirtualHostRoot(names)
self.assertEquals(req._app_names, ['x', 'y', 'z'])
names[0] = 'muahahahaha'
self.assertEquals(req._app_names, ['x', 'y', 'z'])
+ zope.event.subscribers.pop()
+ self.assertEquals(len(events), 1)
+ self.assertEquals(events[0].request, req)
def test_setVirtualHostRoot(self):
+ events = []
+ zope.event.subscribers.append(events.append)
req = self._createRequest()
req._traversed_names = ['x', 'y']
req._last_obj_traversed = object()
req.setVirtualHostRoot()
self.failIf(req._traversed_names)
self.assertEquals(req._vh_root, req._last_obj_traversed)
+ zope.event.subscribers.pop()
+ self.assertEquals(len(events), 1)
+ self.assertEquals(events[0].request, req)
def test_getVirtualHostRoot(self):
req = self._createRequest()
More information about the Zope3-Checkins
mailing list