Skip to content

Commit

Permalink
Merge pull request #2 from zopefoundation/achapman-lenient-cookie-par…
Browse files Browse the repository at this point in the history
…sing

Lenient cookie parsing
  • Loading branch information
freddrake committed Jul 26, 2013
2 parents c946fd8 + 1e8f905 commit f9a67a9
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 17 deletions.
63 changes: 59 additions & 4 deletions src/zope/publisher/http.py
Expand Up @@ -167,6 +167,61 @@ def init_status_codes():
init_status_codes()


class LenientCookie(cookies.SimpleCookie):
"""Handler that drops individual cookies when invalid keys are used.
The reduces the number of cases for which a malformed Cookie: header
can cause all cookies to be lost.
"""

def __setitem__(self, key, value):
rval, cval = self.value_encode(value)
try:
self._BaseCookie__set(key, rval, cval)
except cookies.CookieError as e:
eventlog.warning(e)

def _BaseCookie__ParseString(self, str, patt=cookies._CookiePattern):
i = 0 # Our starting point
n = len(str) # Length of string
M = None # current morsel

while 0 <= i < n:
# Start looking for a cookie
match = patt.search(str, i)
if not match: break # No more cookies

K,V = match.group("key"), match.group("val")
i = match.end(0)

# Parse the key, value in case it's metainfo
if K[0] == "$":
# We ignore attributes which pertain to the cookie
# mechanism as a whole. See RFC 2109.
# (Does anyone care?)
if M:
try:
M[ K[1:] ] = V
except cookies.CookieError:
# We don't care.
pass
elif K.lower() in cookies.Morsel._reserved:
if M:
M[ K ] = cookies._unquote(V)
else:
rval, cval = self.value_decode(V)
try:
self._BaseCookie__set(K, rval, cval)
M = self[K]
except cookies.CookieError as e:
eventlog.warning(e)

def _BaseCookie__parse_string(self, str, patt=cookies._CookiePattern):
# Python 3.x support
self._BaseCookie__ParseString(str, patt)


class URLGetter(object):

__slots__ = "__request"
Expand Down Expand Up @@ -416,9 +471,9 @@ def _parseCookies(self, text, result=None):

# ignore cookies on a CookieError
try:
c = cookies.SimpleCookie(text)
c = LenientCookie(text)
except cookies.CookieError as e:
eventlog.warning(e)
eventlog.warn(e)
return result

for k,v in c.items():
Expand Down Expand Up @@ -926,9 +981,9 @@ def redirect(self, location, status=None, trusted=False):

def _cookie_list(self):
try:
c = cookies.SimpleCookie()
c = LenientCookie()
except cookies.CookieError as e:
eventlog.warning(e)
eventlog.warn(e)
return []
for name, attrs in self._cookies.items():
name = str(name)
Expand Down
24 changes: 11 additions & 13 deletions src/zope/publisher/tests/test_http.py
Expand Up @@ -450,7 +450,7 @@ def testRequestLocale(self):
def testCookies(self):
cookies = {
'HTTP_COOKIE':
'foo=bar; path=/; spam="eggs", this="Should be accepted"'
'foo=bar; path=/; spam="eggs", this="Should be fine"; $t="f"'
}
req = self._createRequest(extra_env=cookies)

Expand All @@ -460,24 +460,28 @@ def testCookies(self):
self.assertEqual(req.cookies[u'spam'], u'eggs')
self.assertEqual(req[u'spam'], u'eggs')

self.assertEqual(req.cookies[u'this'], u'Should be accepted')
self.assertEqual(req[u'this'], u'Should be accepted')
self.assertEqual(req.cookies[u'this'], u'Should be fine')
self.assertEqual(req[u'this'], u'Should be fine')

# Reserved key
self.assertFalse(req.cookies.has_key('path'))

self.assertFalse(req.cookies.has_key('t'))
self.assertFalse(req.cookies.has_key('$t'))

def testCookieErrorToLog(self):
# Cookies accompanying an invalid one shouldn't be trashed.
cookies = {
'HTTP_COOKIE':
'foo=bar; path=/; spam="eggs", ldap/OU="Williams"'
}
req = self._createRequest(extra_env=cookies)

self.assertFalse(req.cookies.has_key('foo'))
self.assertFalse(req.has_key('foo'))
self.assertEqual(req.cookies[u'foo'], u'bar')
self.assertEqual(req[u'foo'], u'bar')

self.assertFalse(req.cookies.has_key('spam'))
self.assertFalse(req.has_key('spam'))
self.assertEqual(req.cookies[u'spam'], u'eggs')
self.assertEqual(req[u'spam'], u'eggs')

self.assertFalse(req.cookies.has_key('ldap/OU'))
self.assertFalse(req.has_key('ldap/OU'))
Expand Down Expand Up @@ -908,12 +912,6 @@ def testSetCookie(self):
self.assertTrue((r'sign="\342\230\243";' in c) or
(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',
Expand Down

0 comments on commit f9a67a9

Please sign in to comment.