[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