[Zope3-checkins] SVN: Zope3/branches/ZopeX3-3.0/src/zope/ Merged from trunk:

Jim Fulton jim at zope.com
Tue Aug 24 11:52:56 EDT 2004


Log message for revision 27245:
  Merged from trunk:
  
    r26545 | Zen | 2004-07-14 23:08:12 -0400 (Wed, 14 Jul 2004) | 3 lines
  
    Add tests and refactor cookie handling to use standard library Cookie.py 
    so that they pass
  
    r26546 | Zen | 2004-07-14 23:13:15 -0400 (Wed, 14 Jul 2004) | 5 lines
  
    Make functional test harnesses cookie aware. Multiple requests in a test
    handle cookies similar to a browser, implicitly resending them on each
    request. Cookies can either be set manually in the tests or from the 
    server responses.
  
  Also changed the tests for the test harness to use python
  expressions rather than the experimental <script>-tag support.
  
  


Changed:
  A   Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/publisher/http.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/publisher/tests/test_http.py


-=-
Copied: Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py (from rev 26546, Zope3/trunk/src/zope/app/ftests/test_functional.py)
===================================================================
--- Zope3/trunk/src/zope/app/ftests/test_functional.py	2004-07-15 03:13:15 UTC (rev 26546)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py	2004-08-24 15:52:55 UTC (rev 27245)
@@ -0,0 +1,125 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Functional tests for the functional test framework
+
+$Id: functional.py 26214 2004-07-08 19:00:07Z srichter $
+"""
+
+import unittest
+from zope.app.tests.functional import SampleFunctionalTest, BrowserTestCase
+
+class CookieFunctionalTest(BrowserTestCase):
+
+    """Functional tests should handle cookies like a web browser
+    
+    Multiple requests in the same test should acumulate cookies.
+    We also ensure that cookies with path values are only sent for
+    the correct URL's so we can test cookies don't 'leak'. Expiry,
+    secure and other cookie attributes are not being worried about
+    at the moment
+
+    """
+
+    def setUp(self):
+        super(CookieFunctionalTest, self).setUp()
+        self.assertEqual(
+                len(self.cookies.keys()), 0,
+                'cookies store should be empty'
+                )
+
+        root = self.getRootFolder()
+
+        from zope.app.zptpage.zptpage import ZPTPage
+
+        page = ZPTPage()
+        page.source = u'''<tal:tag tal:define="
+        cookies python:['%s=%s'%(k,v) for k,v in request.getCookies().items()]"
+        ><tal:tag tal:define="
+        ignored python:cookies.sort()"
+        /><span tal:replace="python:';'.join(cookies)" /></tal:tag>'''
+        root['getcookies'] = page
+
+        page = ZPTPage()
+        page.source = u'''<tal:tag tal:define="
+            ignored python:request.response.setCookie('bid','bval')" >
+            <h1 tal:condition="ignored" />
+            </tal:tag>'''
+        root['setcookie'] = page
+
+
+    def tearDown(self):
+        root = self.getRootFolder()
+        del root['getcookies']
+        del root['setcookie']
+        super(CookieFunctionalTest, self).tearDown()
+
+    def testDefaultCookies(self):
+        # By default no cookies are set
+        response = self.publish('/')
+        self.assertEquals(response.getStatus(), 200)
+        self.assert_(not response._request._cookies)
+
+    def testSimpleCookies(self):
+        self.cookies['aid'] = 'aval'
+        response = self.publish('/')
+        self.assertEquals(response.getStatus(), 200)
+        self.assertEquals(response._request._cookies['aid'], 'aval')
+
+    def testCookiePaths(self):
+        # We only send cookies if the path is correct
+        self.cookies['aid'] = 'aval'
+        self.cookies['aid']['Path'] = '/sub/folder'
+        self.cookies['bid'] = 'bval'
+        response = self.publish('/')
+
+        self.assertEquals(response.getStatus(), 200)
+        self.assert_(not response._request._cookies.has_key('aid'))
+        self.assertEquals(response._request._cookies['bid'], 'bval')
+
+    def testHttpCookieHeader(self):
+        # Passing an HTTP_COOKIE header to publish adds cookies
+        response = self.publish('/', env={
+            'HTTP_COOKIE': '$Version=1, aid=aval; $Path=/sub/folder, bid=bval'
+            })
+        self.assertEquals(response.getStatus(), 200)
+        self.failIf(response._request._cookies.has_key('aid'))
+        self.assertEquals(response._request._cookies['bid'], 'bval')
+
+    def testStickyCookies(self):
+        # Cookies should acumulate during the test
+        response = self.publish('/', env={'HTTP_COOKIE': 'aid=aval;'})
+        self.assertEquals(response.getStatus(), 200)
+
+        # Cookies are implicity passed to further requests in this test
+        response = self.publish('/getcookies')
+        self.assertEquals(response.getStatus(), 200)
+        self.assertEquals(response.getBody().strip(), 'aid=aval')
+
+        # And cookies set in responses also acumulate
+        response = self.publish('/setcookie')
+        self.assertEquals(response.getStatus(), 200)
+        response = self.publish('/getcookies')
+        self.assertEquals(response.getStatus(), 200)
+        self.assertEquals(response.getBody().strip(), 'aid=aval;bid=bval')
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(SampleFunctionalTest))
+    suite.addTest(unittest.makeSuite(CookieFunctionalTest))
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main()


Property changes on: Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py
___________________________________________________________________
Name: svn:keywords "LastChangedDate Author Id LastChangedRevision LastChangedBy HeadURL"
   + 
Name: svn:eol-style
   + native

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py	2004-08-24 15:22:57 UTC (rev 27244)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py	2004-08-24 15:52:55 UTC (rev 27245)
@@ -23,6 +23,7 @@
 import unittest
 
 from StringIO import StringIO
+from Cookie import SimpleCookie
 
 from transaction import get_transaction
 from ZODB.DB import DB
@@ -161,7 +162,13 @@
 class BrowserTestCase(FunctionalTestCase):
     """Functional test case for Browser requests."""
 
+    def setUp(self):
+        super(BrowserTestCase, self).setUp()
+        # Somewhere to store cookies between consecutive requests
+        self.cookies = SimpleCookie()
+
     def tearDown(self):
+        del self.cookies
         self.setSite(None)
         super(BrowserTestCase, self).tearDown()
 
@@ -191,7 +198,8 @@
         if outstream is None:
             outstream = HTTPTaskStub()
         environment = {"HTTP_HOST": 'localhost',
-                       "HTTP_REFERER": 'localhost'}
+                       "HTTP_REFERER": 'localhost',
+                       "HTTP_COOKIE": self.__http_cookie(path)}
         environment.update(env)
         app = FunctionalTestSetup().getApplication()
         request = app._request(path, '', outstream,
@@ -200,6 +208,12 @@
                                request=BrowserRequest)
         return request
 
+    def __http_cookie(self, path):
+        '''Return self.cookies as an HTTP_COOKIE environment format string'''
+        l = [m.OutputString() for m in self.cookies.values()
+                if path.startswith(m['path'])]
+        return '; '.join(l)
+
     def publish(self, path, basic=None, form=None, env={},
                 handle_errors=False):
         """Renders an object at a given location.
@@ -217,10 +231,27 @@
         outstream = HTTPTaskStub()
         old_site = self.getSite()
         self.setSite(None)
+        # A cookie header has been sent - ensure that future requests
+        # in this test also send the cookie, as this is what browsers do.
+        # We pull it apart and reassemble the header to block cookies
+        # with invalid paths going through, which may or may not be correct
+        if env.has_key('HTTP_COOKIE'):
+            self.cookies.load(env['HTTP_COOKIE'])
+            del env['HTTP_COOKIE'] # Added again in makeRequest
+
         request = self.makeRequest(path, basic=basic, form=form, env=env,
                                    outstream=outstream)
         response = ResponseWrapper(request.response, outstream, path)
+        if env.has_key('HTTP_COOKIE'):
+            self.cookies.load(env['HTTP_COOKIE'])
         publish(request, handle_errors=handle_errors)
+        # Urgh - need to play with the response's privates to extract
+        # cookies that have been set
+        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']
         self.setSite(old_site)
         return response
 

Modified: Zope3/branches/ZopeX3-3.0/src/zope/publisher/http.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/publisher/http.py	2004-08-24 15:22:57 UTC (rev 27244)
+++ Zope3/branches/ZopeX3-3.0/src/zope/publisher/http.py	2004-08-24 15:52:55 UTC (rev 27245)
@@ -19,6 +19,7 @@
 from urllib import quote, unquote, splitport
 from types import StringTypes, ClassType
 from cgi import escape
+from Cookie import SimpleCookie
 
 from zope.interface import implements
 
@@ -324,37 +325,16 @@
 
         return '%s://%s' % (protocol, host)
 
-    _cookieFormat = re.compile('[\x00- ]*'
-            # Cookie name
-            '([^\x00- ;,="]+)='
-            # Cookie value (either correct quoted or MSIE)
-            '(?:"([^"]*)"|([^\x00- ;,"]*))'
-            '(?:[\x00- ]*[;,])?[\x00- ]*')
-
     def _parseCookies(self, text, result=None):
         """Parse 'text' and return found cookies as 'result' dictionary."""
 
         if result is None:
             result = {}
 
-        cookieFormat = self._cookieFormat
+        c = SimpleCookie(text)
+        for k,v in c.items():
+            result[unicode(k, ENCODING)] = unicode(v.value, ENCODING)
 
-        pos = 0
-        ln = len(text)
-        while pos < ln:
-            match = cookieFormat.match(text, pos)
-            if match is None:
-                break
-
-            name  = unicode(match.group(1), ENCODING)
-            if name not in result:
-                value, ms_value = match.group(2, 3)
-                if value is None:
-                    value = ms_value
-                result[name] = unicode(value, ENCODING)
-
-            pos = match.end()
-
         return result
 
     def __setupCookies(self):
@@ -720,7 +700,7 @@
 
         for k, v in kw.items():
             if v is not None:
-                cookie[k] = v
+                cookie[k.lower()] = v
 
         cookie['value'] = value
 
@@ -829,36 +809,25 @@
         return location
 
     def _cookie_list(self):
-        cookie_list = []
+        c = SimpleCookie()
         for name, attrs in self._cookies.items():
+            name = str(name)
+            c[name] = attrs['value'].encode(ENCODING)
+            for k,v in attrs.items():
+                if k == 'value':
+                    continue
+                if k == 'secure':
+                    if v:
+                        c[name]['secure'] = True
+                    continue
+                if k == 'max_age':
+                    k = 'max-age'
+                elif k == 'comment':
+                    # Encode rather than throw an exception
+                    v = quote(v.encode('utf-8'), safe="/?:@&+")
+                c[name][k] = str(v)
+        return str(c).splitlines()
 
-            # Note that as of May 98, IE4 ignores cookies with
-            # quoted cookie attr values, so only the value part
-            # of name=value pairs may be quoted.
-
-            cookie='Set-Cookie: %s="%s"' % (name, attrs['value'])
-            for name, value in attrs.items():
-                name = name.lower()
-                if name == 'expires':
-                    cookie = '%s; Expires=%s' % (cookie,value)
-                elif name == 'domain':
-                    cookie = '%s; Domain=%s' % (cookie,value)
-                elif name == 'path':
-                    cookie = '%s; Path=%s' % (cookie,value)
-                elif name == 'max_age':
-                    cookie = '%s; Max-Age=%s' % (cookie,value)
-                elif name == 'comment':
-                    cookie = '%s; Comment=%s' % (cookie,value)
-                elif name == 'secure' and value:
-                    cookie = '%s; Secure' % cookie
-            cookie_list.append(cookie)
-
-        # TODO: Should really check size of cookies here!
-        # Why?
-
-        return cookie_list
-
-
     def getHeaderText(self, m):
         lst = ['Status: %s %s' % (self._status, self._reason)]
         items = m.items()

Modified: Zope3/branches/ZopeX3-3.0/src/zope/publisher/tests/test_http.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/publisher/tests/test_http.py	2004-08-24 15:22:57 UTC (rev 27244)
+++ Zope3/branches/ZopeX3-3.0/src/zope/publisher/tests/test_http.py	2004-08-24 15:52:55 UTC (rev 27245)
@@ -32,6 +32,7 @@
 from zope.interface.verify import verifyObject
 
 from StringIO import StringIO
+from Cookie import SimpleCookie, CookieError
 
 
 class UserStub(object):
@@ -212,7 +213,8 @@
 
     def testCookies(self):
         cookies = {
-            'HTTP_COOKIE': 'foo=bar; spam="eggs", this="Should be accepted"'
+            'HTTP_COOKIE':
+                'foo=bar; path=/; spam="eggs", this="Should be accepted"'
         }
         req = self._createRequest(extra_env=cookies)
 
@@ -225,6 +227,15 @@
         self.assertEquals(req.cookies[u'this'], u'Should be accepted')
         self.assertEquals(req[u'this'], u'Should be accepted')
 
+        # Reserved key
+        self.failIf(req.cookies.has_key('path'))
+
+    def testCookiesUnicode(self):
+        # Cookie values are assumed to be UTF-8 encoded
+        cookies = {'HTTP_COOKIE': r'key="\342\230\243";'}
+        req = self._createRequest(extra_env=cookies)
+        self.assertEquals(req.cookies[u'key'], u'\N{BIOHAZARD SIGN}')
+
     def testHeaders(self):
         headers = {
             'TEST_HEADER': 'test',
@@ -421,7 +432,15 @@
         headers = {}
         for line in hdrs_text.splitlines():
             key, val = line.split(":", 1)
-            headers[key.strip()] = val.strip()
+            key = key.strip()
+            val = val.strip()
+            if headers.has_key(key):
+                if type(headers[key]) == type([]):
+                    headers[key].append(val)
+                else:
+                    headers[key] = [headers[key], val]
+            else:
+                headers[key] = val
         return headers, body
 
     def _getResultFromResponse(self, body, charset=None, headers=None):
@@ -476,7 +495,68 @@
         eq("image/gif", headers["Content-Type"])
         eq("test", body)
 
+    def _getCookieFromResponse(self, cookies):
+        # Shove the cookies through request, parse the Set-Cookie header
+        # and spit out a list of headers for examination
+        response, stream = self._createResponse()
+        for name, value, kw in cookies:
+            response.setCookie(name, value, **kw)
+        response.setBody('test')
+        response.outputBody()
+        headers, body = self._parseResult(stream.getvalue())
+        c = SimpleCookie()
+        cookie_headers = headers["Set-Cookie"]
+        if type(cookie_headers) != type([]):
+            cookie_headers = [cookie_headers]
+        return cookie_headers
 
+    def testSetCookie(self):
+        c = self._getCookieFromResponse([
+                ('foo', 'bar', {}),
+                ])
+        self.failUnless('foo=bar;' in c, 'foo=bar not in %r' % c)
+
+        c = self._getCookieFromResponse([
+                ('foo', 'bar', {}),
+                ('alpha', 'beta', {}),
+                ])
+        self.failUnless('foo=bar;' in c)
+        self.failUnless('alpha=beta;' in c)
+
+        c = self._getCookieFromResponse([
+                ('sign', u'\N{BIOHAZARD SIGN}', {}),
+                ])
+        self.failUnless(r'sign="\342\230\243";' in c)
+
+        self.assertRaises(
+                CookieError,
+                self._getCookieFromResponse,
+                [('path', 'invalid key', {}),]
+                )
+
+        c = self._getCookieFromResponse([
+                ('foo', 'bar', {
+                    'Expires': 'Sat, 12 Jul 2014 23:26:28 GMT',
+                    'domain': 'example.com',
+                    'pAth': '/froboz',
+                    'max_age': 3600,
+                    'comment': u'blah;\N{BIOHAZARD SIGN}?',
+                    'seCure': True,
+                    }),
+                ])[0]
+        self.failUnless('foo=bar;' in c)
+        self.failUnless('expires=Sat, 12 Jul 2014 23:26:28 GMT;' in c, repr(c))
+        self.failUnless('Domain=example.com;' in c)
+        self.failUnless('Path=/froboz;' in c)
+        self.failUnless('Max-Age=3600;' in c)
+        self.failUnless('Comment=blah%3B%E2%98%A3?;' in c, repr(c))
+        self.failUnless('secure;' in c)
+
+        c = self._getCookieFromResponse([('foo', 'bar', {'secure': False})])[0]
+        self.failUnless('foo=bar;' in c)
+        self.failIf('secure' in c)
+
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ConcreteHTTPTests))



More information about the Zope3-Checkins mailing list