[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