from __future__ import unicode_literals
import re
import sys
import logging
if sys.version_info < (3, 0, 0):
from urllib import quote, unquote
else:
from urllib.parse import quote, unquote
unichr = chr
basestring = str
from datetime import datetime, tzinfo, timedelta
from pytest import raises
from cookies import (
InvalidCookieError, InvalidCookieAttributeError,
Definitions,
Cookie, Cookies,
render_date, parse_date,
parse_string, parse_value, parse_domain, parse_path,
parse_one_response,
encode_cookie_value, encode_extension_av,
valid_value, valid_date, valid_domain, valid_path,
strip_spaces_and_quotes, _total_seconds,
)
class RFC1034:
digit = "[0-9]"
letter = "[A-Za-z]"
let_dig = "[0-9A-Za-z]"
let_dig_hyp = "[0-9A-Za-z\-]"
assert "\\" in let_dig_hyp
ldh_str = "%s+" % let_dig_hyp
label = "(?:%s|%s|%s)" % (
letter,
letter + let_dig,
letter + ldh_str + let_dig)
subdomain = "(?:%s\.)*(?:%s)" % (label, label)
domain = "( |%s)" % (subdomain)
def test_sanity(self):
"Basic smoke tests that definitions transcribed OK"
match = re.compile("^%s\Z" % self.domain).match
assert match("A.ISI.EDU")
assert match("XX.LCS.MIT.EDU")
assert match("SRI-NIC.ARPA")
assert not match("foo+bar")
assert match("foo.com")
assert match("foo9.com")
assert not match("9foo.com")
assert not match("26.0.0.73.COM")
assert not match(".woo.com")
assert not match("blop.foo.")
assert match("foo-bar.com")
assert not match("-foo.com")
assert not match("foo.com-")
class RFC1123:
label = "{let_dig}(?:(?:{let_dig_hyp}+)?{let_dig})?".format(
let_dig=RFC1034.let_dig, let_dig_hyp=RFC1034.let_dig_hyp)
subdomain = "(?:%s\.)*(?:%s)" % (label, label)
domain = "( |%s)" % (subdomain)
def test_sanity(self):
"Basic smoke tests that definitions transcribed OK"
match = re.compile("^%s\Z" % self.domain).match
assert match("A.ISI.EDU")
assert match("XX.LCS.MIT.EDU")
assert match("SRI-NIC.ARPA")
assert not match("foo+bar")
assert match("foo.com")
assert match("9foo.com")
assert match("3Com.COM")
assert match("3M.COM")
class RFC2616:
SEPARATORS = '()<>@,;:\\"/[]?={} \t'
class RFC5234:
CHAR = "".join([chr(i) for i in range(0, 127 + 1)])
CTL = "".join([chr(i) for i in range(0, 31 + 1)]) + "\x7f"
NONCTL = "".join([chr(i) for i in range(32, 127)])
TOKEN = "".join(sorted(set(NONCTL) - set(RFC2616.SEPARATORS)))
class FixedOffsetTz(tzinfo):
def __init__(self, offset):
minutes = round(offset / 60.0, 0)
self.__offset = timedelta(minutes=minutes)
def utcoffset(self, dt):
return self.__offset
def tzname(self, dt):
return "FixedOffsetTz" + str(self.__offset.seconds)
def dst(self, dt):
return timedelta(0)
class TestInvalidCookieError(object):
def test_simple(self):
"This be the test"
def exception(data):
"Gather an InvalidCookieError exception"
try:
raise InvalidCookieError(data)
except InvalidCookieError as exception:
return exception
return None
assert exception("no donut").data == "no donut"
e = exception("yay\x00whee")
assert "\x00" not in repr(e)
assert "\x00" not in str(e)
assert "yaywhee" not in repr(e)
assert "yaywhee" not in str(e)
assert "\n" not in repr(exception("foo\nbar"))
class TestInvalidCookieAttributeError(object):
def exception(self, *args, **kwargs):
"Generate an InvalidCookieAttributeError exception naturally"
try:
raise InvalidCookieAttributeError(*args, **kwargs)
except InvalidCookieAttributeError as exception:
return exception
return None
def test_simple(self):
e = self.exception("foo", "bar")
assert e.name == "foo"
assert e.value == "bar"
def test_junk_in_loggables(self):
e = self.exception("ya\x00y", "whee")
assert "\x00" not in repr(e)
assert "\x00" not in str(e)
assert "yay" not in repr(e)
assert "yay" not in str(e)
e = self.exception("whee", "ya\x00y")
assert "\x00" not in repr(e)
assert "\x00" not in str(e)
assert "yay" not in repr(e)
assert "yay" not in str(e)
assert "\n" not in repr(self.exception("yay", "foo\nbar"))
assert "\n" not in repr(self.exception("foo\nbar", "yay"))
def test_no_name(self):
e = self.exception(None, "stuff")
assert e.name == None
assert e.value == "stuff"
assert e.reason == None
assert 'stuff' in str(e)
class TestDefinitions(object):
def test_cookie_name(self, check_unicode=False):
regex = Definitions.COOKIE_NAME_RE
assert regex.match(RFC5234.TOKEN)
assert not regex.match(RFC5234.NONCTL)
for c in RFC5234.CTL:
assert not regex.match(c)
for c in RFC2616.SEPARATORS:
if c == ':':
continue
assert not regex.match(c)
if check_unicode:
for i in range(127, 0x10FFFF + 1):
assert not regex.match(unichr(i))
def test_cookie_octet(self):
match = re.compile("^[%s]+\Z" % Definitions.COOKIE_OCTET).match
for c in RFC5234.CTL:
assert not match(c)
assert not match("a%sb" % c)
assert not match(' ')
assert not match('"')
assert not match(',')
assert not match(';')
assert not match('\\')
assert match("=")
assert match(".")
assert match("-")
safe_cookie_octet = "".join(sorted(
set(RFC5234.NONCTL) - set(' ",;\\')))
assert match(safe_cookie_octet)
def test_set_cookie_header(self):
match = re.compile(Definitions.SET_COOKIE_HEADER).match
for case in HEADER_CASES:
arg, kwargs, request_result, expected = case
this_match = match(arg)
if expected and not isinstance(expected, type):
assert this_match, "should match as response: " + repr(arg)
else:
if not request_result:
assert not this_match, \
"should not match as response: " + repr(arg)
def test_cookie_cases(self):
match = re.compile(Definitions.COOKIE).match
for case in HEADER_CASES:
arg, kwargs, expected, response_result = case
this_match = match(arg)
if expected and not isinstance(expected, type):
assert this_match, "should match as request: " + repr(arg)
else:
if not response_result:
assert not this_match, \
"should not match as request: " + repr(arg)
def test_cookie_pattern(self):
match = re.compile(Definitions.COOKIE).match
assert match("foo").group('invalid')
assert match("foo=bar")
assert match("a=b=c")
assert match('foo="bar"')
regex = re.compile(Definitions.COOKIE)
correct = [
('foo', 'yar', ''),
('bar', 'eeg', ''),
('baz', 'wog', ''),
('frob', 'laz', '')]
def assert_correct(s):
result = regex.findall(s)
assert result == correct
assert_correct("foo=yar; bar=eeg; baz=wog; frob=laz")
assert_correct("foo=yar;bar=eeg;baz=wog;frob=laz")
assert_correct(" foo=yar; bar=eeg; baz=wog; frob=laz ")
assert regex.findall("foo=yar; bar=eeg; baz=wog; frob=l az") == [
('foo', 'yar', ''),
('bar', 'eeg', ''),
('baz', 'wog', ''),
('frob', 'l az', '')
]
result = regex.findall("foo=yarbar=eegbaz=wogfrob=laz")
assert result[0][0] == 'foo'
assert result[0][1] == 'yarbar=eegbaz=wogfrob=laz'
assert result[0][2] == ''
for c in '\x00",\\':
nasty = "foo=yar" + c + "bar"
result = regex.findall(nasty + "; baz=bam")
assert result[0][2] == nasty
assert result[1] == ('baz', 'bam', '')
result = regex.findall("baz=bam; " + nasty)
assert result[0] == ('baz', 'bam', '')
assert result[1][2] == ' ' + nasty
def test_extension_av(self, check_unicode=False):
extension_av_explicit = "".join(sorted(
set(RFC5234.CHAR) - set(RFC5234.CTL + ";")))
match = re.compile("^([%s]+)\Z" % Definitions.EXTENSION_AV).match
assert match(r']')
assert match(r'[')
assert match(r"'")
assert match(r'"')
assert match("\\")
assert match(extension_av_explicit)
assert not match(RFC5234.CHAR)
for c in RFC5234.CTL + ";":
assert not match(c)
if check_unicode:
for i in range(127, 0x10FFFF + 1):
assert not match(unichr(i))
def test_max_age_av(self):
"Smoke test Definitions.MAX_AGE_AV"
match = re.compile("^%s\Z" % Definitions.MAX_AGE_AV).match
assert not match("")
assert not match("Whiskers")
assert not match("Max-Headroom=992")
for c in "123456789":
assert not match(c)
assert match("Max-Age=%s" % c)
assert match("Max-Age=0")
for c in RFC5234.CHAR:
assert not match(c)
def test_label(self, check_unicode=False):
"Test label, as used in Domain attribute"
match = re.compile("^(%s)\Z" % Definitions.LABEL).match
for i in range(0, 10):
assert match(str(i))
assert not match(".")
assert not match(",")
for c in RFC5234.CTL:
assert not match("a%sb" % c)
assert not match("%sb" % c)
assert not match("a%s" % c)
if check_unicode:
for i in range(127, 0x10FFFF + 1):
assert not match(unichr(i))
def test_domain_av(self):
"Smoke test Definitions.DOMAIN_AV"
bad_domains = [
""
]
good_domains = [
"foobar.com",
"foo-bar.com",
"3Com.COM"
]
match = Definitions.DOMAIN_RE.match
for domain in bad_domains:
assert not match(domain)
for domain in good_domains:
assert match(domain)
match = re.compile("^%s\Z" % Definitions.DOMAIN_AV).match
for domain in bad_domains:
assert not match("Domain=%s" % domain)
for domain in good_domains:
assert not match(domain)
assert match("Domain=%s" % domain)
assert match("Domain=.foo.net")
def test_path_av(self):
"Smoke test PATH and PATH_AV"
bad_paths = [
""
]
good_paths = [
"/",
"/foo",
"/foo/bar"
]
match = Definitions.PATH_RE.match
for path in bad_paths:
assert not match(path)
for path in good_paths:
assert match(path)
match = re.compile("^%s\Z" % Definitions.PATH_AV).match
for path in bad_paths:
assert not match("Path=%s" % path)
for path in good_paths:
assert not match(path)
assert match("Path=%s" % path)
def test_months(self):
match = re.compile(Definitions.MONTH_SHORT).match
assert match("Jan")
assert match("Feb")
assert match("Mar")
assert match("Apr")
assert match("May")
assert match("Jun")
assert match("Jul")
assert match("Aug")
assert match("Sep")
assert match("Oct")
assert match("Nov")
assert match("Dec")
match = re.compile(Definitions.MONTH_LONG).match
assert match("January")
assert match("February")
assert match("March")
assert match("April")
assert match("May")
assert match("June")
assert match("July")
assert match("August")
assert match("September")
assert match("October")
assert match("November")
assert match("December")
def test_weekdays(self):
match = re.compile(Definitions.WEEKDAY_SHORT).match
assert match("Mon")
assert match("Tue")
assert match("Wed")
assert match("Thu")
assert match("Fri")
assert match("Sat")
assert match("Sun")
match = re.compile(Definitions.WEEKDAY_LONG).match
assert match("Monday")
assert match("Tuesday")
assert match("Wednesday")
assert match("Thursday")
assert match("Friday")
assert match("Saturday")
assert match("Sunday")
def test_day_of_month(self):
match = re.compile(Definitions.DAY_OF_MONTH).match
for day in ['01', '02', '03', '04', '05', '06', '07', '08', '09', ' 1',
' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' 8', ' 9', '1', '2', '3',
'4', '5', '6', '7', '8', '9'] \
+ [str(i) for i in range(10, 32)]:
assert match(day)
assert not match("0")
assert not match("00")
assert not match("000")
assert not match("111")
assert not match("99")
assert not match("41")
def test_expires_av(self):
"Smoke test the EXPIRES_AV regex pattern"
match = re.compile("^%s\Z" % Definitions.EXPIRES_AV).match
assert not match("")
assert not match("Expires=")
assert match("Expires=Tue, 15-Jan-2013 21:47:38 GMT")
assert match("Expires=Sun, 06 Nov 1994 08:49:37 GMT")
assert match("Expires=Sunday, 06-Nov-94 08:49:37 GMT")
assert match("Expires=Sun Nov 6 08:49:37 1994")
assert match("Expires=Mon, 13-Jun-93 10:00:00 GMT")
assert not match("Expires=S9n, 06 Nov 1994 08:49:37 GMT")
assert not match("Expires=Sun3ay, 06-Nov-94 08:49:37 GMT")
assert not match("Expires=S9n Nov 6 08:49:37 1994")
assert not match("Expires=Sun, A6 Nov 1994 08:49:37 GMT")
assert not match("Expires=Sunday, 0B-Nov-94 08:49:37 GMT")
assert not match("Expires=Sun No8 6 08:49:37 1994")
assert not match("Expires=Sun, 06 N3v 1994 08:49:37 GMT")
assert not match("Expires=Sunday, 06-N8v-94 08:49:37 GMT")
assert not match("Expires=Sun Nov A 08:49:37 1994")
assert not match("Expires=Sun, 06 Nov 1B94 08:49:37 GMT")
assert not match("Expires=Sunday, 06-Nov-C4 08:49:37 GMT")
assert not match("Expires=Sun Nov 6 08:49:37 1Z94")
def test_no_obvious_need_for_disjunctive_attr_pattern(self):
match = re.compile("^[%s]+\Z" % Definitions.EXTENSION_AV).match
assert match("Expires=Sun, 06 Nov 1994 08:49:37 GMT")
assert match("Expires=Sunday, 06-Nov-94 08:49:37 GMT")
assert match("Expires=Sun Nov 6 08:49:37 1994")
assert match("Max-Age=14658240962")
assert match("Domain=FoO.b9ar.baz")
assert match("Path=/flakes")
assert match("Secure")
assert match("HttpOnly")
def test_attr(self):
match = re.compile(Definitions.ATTR).match
def recognized(pattern):
"macro for seeing if ATTR recognized something"
this_match = match(pattern)
if not this_match:
return False
groupdict = this_match.groupdict()
if groupdict['unrecognized']:
return False
return True
for pattern in [
"Secure",
"HttpOnly",
"Max-Age=9523052",
"Domain=frobble.com",
"Domain=3Com.COM",
"Path=/",
"Expires=Wed, 09 Jun 2021 10:18:14 GMT",
]:
assert recognized(pattern)
assert not recognized("Frob=mugmannary")
assert not recognized("Fqjewp@1j5j510923")
assert not recognized(";aqjwe")
assert not recognized("ETJpqw;fjw")
assert not recognized("fjq;")
assert not recognized("Expires=\x00")
expires = "Wed, 09 Jun 2021 10:18:14 GMT"
m = match("Expires=%s" % expires)
assert m.group("expires") == expires
max_age = "233951698"
m = match("Max-Age=%s" % max_age)
assert m.group("max_age") == max_age
domain = "flarp"
m = match("Domain=%s" % domain)
assert m.group("domain") == domain
path = "2903"
m = match("Path=%s" % path)
assert m.group("path") == path
m = match("Secure")
assert m.group("secure")
assert not m.group("httponly")
m = match("HttpOnly")
assert not m.group("secure")
assert m.group("httponly")
def test_date_accepts_formats(self):
match = re.compile(Definitions.DATE).match
assert match("Mon, 20-Jan-1994 00:00:00 GMT")
assert match("Tuesday, 12-Feb-92 23:25:42 GMT")
assert match("Wed, 30 Mar 92 13:16:12 GMT")
assert match("Thu, 3 Apr 91 12:46 GMT")
assert match("13 Apr 91 12:46 GMT")
assert match("13 Apr 1991 12:46 GMT")
assert match("Wed, 13 Apr 1991 12:46 GMT")
assert match("Sun Nov 6 08:49:37 1994")
assert match("Sun Nov 26 08:49:37 1994")
assert match("Sun Nov 26 08:49:37 1994 GMT")
assert not match("")
assert not match(" ")
assert not match("wobbly")
assert not match("Mon")
assert not match("Mon, 20")
assert not match("Mon, 20 Jan")
assert not match("Mon, 20,Jan,1994 00:00:00 GMT")
assert not match("Tuesday, 12-Feb-992 23:25:42 GMT")
assert not match("Wed, 30 Mar 92 13:16:1210 GMT")
assert not match("Wed, 30 Mar 92 13:16:12:10 GMT")
assert not match("Thu, 3 Apr 91 12:461 GMT")
def test_eol(self):
split = Definitions.EOL.split
assert split("foo\nbar") == ["foo", "bar"]
assert split("foo\r\nbar") == ["foo", "bar"]
letters = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
assert split("\n".join(letters)) == letters
assert split("\r\n".join(letters)) == letters
def test_compiled(self):
re_type = type(re.compile(''))
def present(name):
"Macro for testing existence of an re in Definitions"
item = getattr(Definitions, name)
return item and isinstance(item, re_type)
assert present("COOKIE_NAME_RE")
assert present("COOKIE_RE")
assert present("SET_COOKIE_HEADER_RE")
assert present("ATTR_RE")
assert present("DATE_RE")
assert present("EOL")
def _test_init(cls, args, kwargs, expected):
"Core instance test function for test_init"
print("test_init", cls, args, kwargs)
try:
instance = cls(*args, **kwargs)
except Exception as exception:
if type(exception) == expected:
return
logging.error("expected %s, got %s", expected, repr(exception))
raise
if isinstance(expected, type) and issubclass(expected, Exception):
raise AssertionError("No exception raised; "
"expected %s for %s/%s" % (
expected.__name__,
repr(args),
repr(kwargs)))
for attr_name, attr_value in expected.items():
assert getattr(instance, attr_name) == attr_value
class TestCookie(object):
creation_cases = [
(("foo",), {}, TypeError),
(("a", "b", "c"), {}, TypeError),
(("ăŊĻ", "b"), {}, InvalidCookieError),
(("b", "ăŊĻ"), {}, {'name': 'b', 'value': "ăŊĻ"}),
(("foo", "bar"), {}, {'name': 'foo', 'value': 'bar'}),
(("baz", "bam"), {'max_age': 9},
{'name': 'baz', 'value': 'bam', 'max_age': 9}),
(("x", "y"), {'max_age': 9, 'comment': 'fruity'},
{'name': 'x', 'value': 'y',
'max_age': 9, 'comment': 'fruity'}),
(("w", "m"), {'max_age': 'loopy'}, InvalidCookieAttributeError),
(("w", "m"), {'max_age': -1}, InvalidCookieAttributeError),
(("w", "m"), {'max_age': 1.2}, InvalidCookieAttributeError),
(("w", "m"), {'expires': 0}, InvalidCookieAttributeError),
(("w", "m"), {'expires':
datetime(2010, 1, 1, tzinfo=FixedOffsetTz(600))},
InvalidCookieAttributeError),
(("w", "m"),
{'expires': datetime(2010, 1, 1)},
{'expires': datetime(2010, 1, 1)}),
(("w", "m"), {'domain': ''}, InvalidCookieAttributeError),
(("w", "m"), {'domain': '@'}, InvalidCookieAttributeError),
(("w", "m"), {'domain': '.foo.net'}, {'domain': '.foo.net'}),
(("w", "m"),
{'domain': 'foo.net'},
{'domain': 'foo.net'},),
(("w", "m"), {'path': ''}, InvalidCookieAttributeError),
(("w", "m"), {'path': '""'}, InvalidCookieAttributeError),
(("w", "m"), {'path': 'foo'}, InvalidCookieAttributeError),
(("w", "m"), {'path': '"/foo"'}, InvalidCookieAttributeError),
(("w", "m"), {'path': ' /foo '}, InvalidCookieAttributeError),
(("w", "m"), {'path': '/'},
{'path': '/'}),
(("w", "m"), {'path': '/axes'},
{'path': '/axes'}),
(("w", "m"), {'version': ''}, InvalidCookieAttributeError),
(("w", "m"), {'version': 'baa'}, InvalidCookieAttributeError),
(("w", "m"), {'version': -2}, InvalidCookieAttributeError),
(("w", "m"), {'version': 2.3}, InvalidCookieAttributeError),
(("w", "m"), {'version': 0}, {'version': 0}),
(("w", "m"), {'version': 1}, {'version': 1}),
(("w", "m"), {'version': 3042}, {'version': 3042}),
(("w", "m"), {'secure': ''}, InvalidCookieAttributeError),
(("w", "m"), {'secure': 0}, InvalidCookieAttributeError),
(("w", "m"), {'secure': 1}, InvalidCookieAttributeError),
(("w", "m"), {'secure': 'a'}, InvalidCookieAttributeError),
(("w", "m"), {'httponly': ''}, InvalidCookieAttributeError),
(("w", "m"), {'httponly': 0}, InvalidCookieAttributeError),
(("w", "m"), {'httponly': 1}, InvalidCookieAttributeError),
(("w", "m"), {'httponly': 'a'}, InvalidCookieAttributeError),
(("w", "m"), {'comment': 'a'}, {'comment': 'a'}),
((None, "m"), {}, InvalidCookieError),
(("", "m"), {}, InvalidCookieError),
(("ü", "m"), {}, InvalidCookieError),
(("w", None), {}, {'name': 'w'}),
(("w", "üm"), {}, {'value': "üm"}),
(('a', ','), {}, {'value': ','}),
(('a', ';'), {}, {'value': ';'}),
(('a', ' '), {}, {'value': ' '}),
]
def test_init(self):
creation_cases = self.creation_cases + [
(("a", "b"), {'frob': 10}, InvalidCookieAttributeError)
]
counter = 0
for args, kwargs, expected in creation_cases:
counter += 1
logging.error("counter %d, %s, %s, %s", counter, args, kwargs,
expected)
_test_init(Cookie, args, kwargs, expected)
def test_set_attributes(self):
for args, kwargs, expected in self.creation_cases:
if not kwargs:
continue
try:
cookie = Cookie("yarp", "flam")
for attr, value in kwargs.items():
setattr(cookie, attr, value)
if args:
cookie.name = args[0]
cookie.value = args[1]
except Exception as e:
if type(e) == expected:
continue
raise
if isinstance(expected, type) and issubclass(expected, Exception):
raise AssertionError("No exception raised; "
"expected %s for %s" % (
expected.__name__,
repr(kwargs)))
for attr_name, attr_value in expected.items():
assert getattr(cookie, attr_name) == attr_value
def test_get_defaults(self):
"Test that defaults are right for cookie attrs"
cookie = Cookie("foo", "bar")
for attr in (
"expires",
"max_age",
"domain",
"path",
"comment",
"version",
"secure",
"httponly"):
assert hasattr(cookie, attr)
assert getattr(cookie, attr) == None
for attr in ("foo", "bar", "baz"):
assert not hasattr(cookie, attr)
with raises(AttributeError):
getattr(cookie, attr)
names_values = [
("a", "b"),
("foo", "bar"),
("baz", "1234567890"),
("!!#po99!", "blah"),
("^_~`*", "foo"),
("%s+|-.&$", "snah"),
("lub", "!@#$%^&*()[]{}|/:'<>~.?`"),
("woah", "====+-_"),
]
def test_render_response(self):
"Test rendering Cookie object for Set-Cookie: header"
for name, value in self.names_values:
cookie = Cookie(name, value)
expected = "{name}={value}".format(
name=name, value=value)
assert cookie.render_response() == expected
for data, result in [
({'name': 'a', 'value': 'b'}, "a=b"),
({'name': 'foo', 'value': 'bar'}, "foo=bar"),
({'name': 'baz', 'value': 'bam'}, "baz=bam"),
({'name': 'baz', 'value': 'bam', 'max_age': 2},
"baz=bam; Max-Age=2"),
({'name': 'baz', 'value': 'bam',
'max_age': 2, 'comment': 'foobarbaz'},
"baz=bam; Max-Age=2; Comment=foobarbaz"),
({'name': 'baz', 'value': 'bam',
'max_age': 2,
'expires': datetime(1970, 1, 1),
},
"baz=bam; Max-Age=2; "
"Expires=Thu, 01 Jan 1970 00:00:00 GMT"),
({'name': 'baz', 'value': 'bam', 'path': '/yams',
'domain': '3Com.COM'},
"baz=bam; Domain=3Com.COM; Path=/yams"),
({'name': 'baz', 'value': 'bam', 'path': '/', 'secure': True,
'httponly': True},
"baz=bam; Path=/; Secure; HttpOnly"),
({'name': 'baz', 'value': 'bam', 'domain': '.domain'},
'baz=bam; Domain=domain'),
]:
cookie = Cookie(**data)
actual = sorted(cookie.render_response().split("; "))
ideal = sorted(result.split("; "))
assert actual == ideal
def test_render_encode(self):
cases = {
("x", "foo,bar;baz"): 'x=foo%2Cbar%3Bbaz',
("y", 'yap"bip'): 'y=yap%22bip',
}
for args, ideal in cases.items():
cookie = Cookie(*args)
assert cookie.render_response() == ideal
assert cookie.render_request() == ideal
def test_legacy_quotes(self):
cookie = Cookie.from_string(
'Set-Cookie: y="foo"; version="1"; Path="/foo"')
assert cookie.name == 'y'
assert cookie.value == 'foo'
assert cookie.version == 1
assert cookie.path == "/foo"
pieces = cookie.render_response().split("; ")
assert pieces[0] == 'y=foo'
assert set(pieces[1:]) == set([
'Path=/foo', 'Version=1'
])
def test_render_response_expires(self):
"Simple spot check of cookie expires rendering"
a = Cookie('a', 'blah')
a.expires = parse_date("Wed, 23-Jan-1992 00:01:02 GMT")
assert a.render_response() == \
'a=blah; Expires=Thu, 23 Jan 1992 00:01:02 GMT'
b = Cookie('b', 'blr')
b.expires = parse_date("Sun Nov 6 08:49:37 1994")
assert b.render_response() == \
'b=blr; Expires=Sun, 06 Nov 1994 08:49:37 GMT'
def test_eq(self):
"Smoke test equality/inequality with Cookie objects"
ref = Cookie('a', 'b')
assert ref == ref
assert not (ref != ref)
assert None != ref
assert not (None == ref)
assert ref != None
assert not (ref == None)
assert Cookie('a', 'b') is not ref
assert Cookie('a', 'b') == ref
assert Cookie('x', 'y') != ref
assert Cookie('a', 'y') != ref
assert Cookie('a', 'b', path='/') != ref
assert {'c': 'd'} != ref
assert ref != {'c': 'd'}
assert Cookie('a', 'b', path='/a') \
!= Cookie('a', 'b', path='/')
assert Cookie('x', 'y', max_age=3) != \
Cookie('x', 'y', path='/b')
assert Cookie('yargo', 'z', max_age=5) != \
Cookie('yargo', 'z', max_age=6)
assert ref != Cookie('a', 'b', domain='yab')
assert Cookie(b'a', 'b') == Cookie('a', 'b')
assert Cookie(b'a', 'b') == Cookie(b'a', 'b')
def test_manifest(self):
"Test presence of important stuff on Cookie class"
for name in ("attribute_names", "attribute_renderers",
"attribute_parsers", "attribute_validators"):
dictionary = getattr(Cookie, name)
assert dictionary
assert isinstance(dictionary, dict)
def test_simple_extension(self):
"Trivial example/smoke test of extending Cookie"
count_state = [0]
def call_counter(item=None):
count_state[0] += 1
return True if item else False
class Cookie2(Cookie):
"Example Cookie subclass with new behavior"
attribute_names = {
'foo': 'Foo',
'bar': 'Bar',
'baz': 'Baz',
'ram': 'Ram',
}
attribute_parsers = {
'foo': lambda s: "/".join(s),
'bar': call_counter,
'value': lambda s:
parse_value(s, allow_spaces=True),
}
attribute_validators = {
'foo': lambda item: True,
'bar': call_counter,
'baz': lambda item: False,
}
attribute_renderers = {
'foo': lambda s: "|".join(s) if s else None,
'bar': call_counter,
'name': lambda item: item,
}
cookie = Cookie2("a", "b")
for key in Cookie2.attribute_names:
assert hasattr(cookie, key)
assert getattr(cookie, key) == None
cookie.foo = "abc"
assert cookie.render_request() == "a=b"
assert cookie.render_response() == "a=b; Foo=a|b|c"
cookie.foo = None
assert cookie.render_response() == "a=b"
cookie.bar = "what"
assert cookie.bar == "what"
assert cookie.render_request() == "a=b"
assert cookie.render_response() == "a=b; Bar"
with raises(InvalidCookieAttributeError):
cookie.baz = "anything"
Cookie2('a', 'b fog')
Cookie2('a', ' b=fo g')
def test_from_string(self):
with raises(InvalidCookieError):
Cookie.from_string("")
with raises(InvalidCookieError):
Cookie.from_string("", ignore_bad_attributes=True)
assert Cookie.from_string("", ignore_bad_cookies=True) == None
def test_from_dict(self):
assert Cookie.from_dict({'name': 'a', 'value': 'b'}) == \
Cookie('a', 'b')
assert Cookie.from_dict(
{'name': 'a', 'value': 'b', 'duh': 'no'},
ignore_bad_attributes=True) == \
Cookie('a', 'b')
with raises(InvalidCookieError):
Cookie.from_dict({}, ignore_bad_attributes=True)
with raises(InvalidCookieError):
Cookie.from_dict({}, ignore_bad_attributes=False)
with raises(InvalidCookieError):
Cookie.from_dict({'name': ''}, ignore_bad_attributes=False)
with raises(InvalidCookieError):
Cookie.from_dict({'name': None, 'value': 'b'},
ignore_bad_attributes=False)
assert Cookie.from_dict({'name': 'foo'}) == Cookie('foo', None)
assert Cookie.from_dict({'name': 'foo', 'value': ''}) == \
Cookie('foo', None)
with raises(InvalidCookieAttributeError):
assert Cookie.from_dict(
{'name': 'a', 'value': 'b', 'duh': 'no'},
ignore_bad_attributes=False)
assert Cookie.from_dict({'name': 'a', 'value': 'b', 'expires': 2},
ignore_bad_attributes=True) == Cookie('a', 'b')
with raises(InvalidCookieAttributeError):
assert Cookie.from_dict({'name': 'a', 'value': 'b', 'expires': 2},
ignore_bad_attributes=False)
class Scone(object):
def __init__(self, name, value):
self.name = name
self.value = value
@classmethod
def from_dict(cls, cookie_dict):
instance = cls(cookie_dict['name'], cookie_dict['value'])
return instance
def __eq__(self, other):
if type(self) != type(other):
return False
if self.name != other.name:
return False
if self.value != other.value:
return False
return True
class Scones(Cookies):
DEFAULT_COOKIE_CLASS = Scone
class TestCookies(object):
creation_cases = [
((Cookie("a", "b"),), {}, 1),
(tuple(), {'a': 'b'}, 1),
(tuple(),
{'axl': 'bosk',
'x': 'y',
'foo': 'bar',
'baz': 'bam'}, 4),
((Cookie('a', 'b'),),
{'axl': 'bosk',
'x': 'y',
'foo': 'bar',
'baz': 'bam'}, 5),
((Cookie('a', 'b'),
Cookie('x', 'y')),
{'axl': 'bosk',
'x': 'y',
'foo': 'bar',
'baz': 'bam'}, 5),
]
def test_init(self):
def same(a, b):
keys = sorted(set(a.keys() + b.keys()))
for key in keys:
assert a[key] == b[key]
for args, kwargs, length in self.creation_cases:
cookies = Cookies(*args, **kwargs)
assert len(cookies) == length
rep = repr(cookies)
res = cookies.render_response()
req = cookies.render_request()
assert rep.count('=') == length
assert len(res) == length
assert [item.count('=') == 1 for item in res]
assert req.count('=') == length
assert len(req.split(";")) == length
pairs = [item.split("=") for item in req.split("; ")]
assert len(pairs) == length
for name, value in pairs:
cookie = cookies[name]
assert cookie.name == name
assert cookie.value == value
parsed = Cookies()
parsed.parse_request(req)
assert parsed == cookies
parsed = Cookies()
for item in res:
parsed.parse_response(item)
assert parsed == cookies
for cookie in args:
assert cookies[cookie.name] == cookie
for name, value in kwargs.items():
cookie = cookies[name]
assert cookie.name == name
assert cookie.value == value
assert name in rep
assert value in rep
for key in cookies:
cookies[key].max_age = 42
for line in cookies.render_response():
assert line.endswith("Max-Age=42")
assert cookies[key].max_age
del cookies[key].max_age
assert cookies[key].max_age is None
keys = [key for key in cookies.keys()]
for key in keys:
del cookies[key]
assert key not in cookies
def test_eq(self):
"Smoke test equality/inequality of Cookies objects"
ref = Cookies(a='b')
assert Cookies(a='b') == ref
assert Cookies(b='c') != ref
assert ref != Cookies(d='e')
assert Cookies(a='x') != ref
class Dummy(object):
"Just any old object"
pass
x = Dummy()
x.keys = True
with raises(TypeError):
assert ref != x
def test_add(self):
"Test the Cookies.add method"
for args, kwargs, length in self.creation_cases:
cookies = Cookies()
cookies.add(*args, **kwargs)
assert len(cookies) == length
for cookie in args:
assert cookies[cookie.name] == cookie
for name, value in kwargs.items():
cookie = cookies[name]
assert cookie.value == value
count = len(cookies)
assert 'w' not in cookies
cookies.add(w='m')
assert 'w' in cookies
assert count == len(cookies) - 1
assert cookies['w'].value == 'm'
def test_empty(self):
"Trivial test of behavior of empty Cookies object"
cookies = Cookies()
assert len(cookies) == 0
assert Cookies() == cookies
def test_parse_request(self):
def run(arg, **kwargs):
"run Cookies.parse_request on an instance"
cookies = Cookies()
result = runner(cookies.parse_request)(arg, **kwargs)
return result
for i, case in enumerate(HEADER_CASES):
arg, kwargs, expected, response_result = case
kwargs = kwargs.copy()
if 'ignore_bad_attributes' in kwargs:
del kwargs['ignore_bad_attributes']
def expect(arg, kwargs):
"repeated complex assertion"
result = run(arg, **kwargs)
assert result == expected \
or isinstance(expected, type) \
and type(result) == expected, \
"unexpected result for (%s): %s. should be %s" \
% (repr(arg), repr(result), repr(expected))
expect("Cookie: " + arg, kwargs)
expect(arg, kwargs)
other_result = run("Set-Cookie: " + arg, **kwargs)
assert other_result != expected
assert other_result != response_result
if expected == InvalidCookieError:
kwargs2 = kwargs.copy()
kwargs2['ignore_bad_cookies'] = True
cookies = Cookies()
cookies.parse_request(arg, **kwargs2)
assert not isinstance(run("Cookie: a=b"), InvalidCookieError)
assert isinstance(run("Set-Cookie: a=b"), InvalidCookieError)
def test_parse_response(self):
def run(arg, **kwargs):
"run parse_response method of a Cookies instance"
cookies = Cookies()
return runner(cookies.parse_response)(arg, **kwargs)
for case in HEADER_CASES:
arg, kwargs, request_result, expected = case
kwargs2 = kwargs.copy()
if expected == InvalidCookieError:
kwargs2['ignore_bad_cookies'] = True
assert not isinstance(
run(arg, **kwargs2),
Exception)
elif expected == InvalidCookieAttributeError:
kwargs2['ignore_bad_attributes'] = True
result = run(arg, **kwargs2)
if isinstance(result, InvalidCookieAttributeError):
raise AssertionError("InvalidCookieAttributeError "
"should have been silenced/logged")
else:
assert not isinstance(result, Exception)
sys.stdout.flush()
result = run(arg, **kwargs)
assert result == expected \
or isinstance(expected, type) \
and type(result) == expected, \
"unexpected result for (%s): %s. should be %s" \
% (repr(arg), repr(result), repr(expected))
result = run("Set-Cookie: " + arg, **kwargs)
assert result == expected \
or isinstance(expected, type) \
and type(result) == expected, \
"unexpected result for (%s): %s. should be %s" \
% (repr("Set-Cookie: " + arg),
repr(result), repr(expected))
other_result = run("Cookie: " + arg, **kwargs)
assert other_result != expected
assert other_result != request_result
assert not isinstance(run("Set-Cookie: a=b"), InvalidCookieError)
assert isinstance(run("Cookie: a=b"), InvalidCookieError)
def test_exercise_parse_one_response_asctime(self):
asctime = 'Sun Nov 6 08:49:37 1994'
line = "Set-Cookie: a=b; Expires=%s" % asctime
response_dict = parse_one_response(line)
assert response_dict == \
{'expires': 'Sun Nov 6 08:49:37 1994', 'name': 'a', 'value': 'b'}
assert Cookie.from_dict(response_dict) == \
Cookie('a', 'b', expires=parse_date(asctime))
def test_get_all(self):
cookies = Cookies.from_request('a=b; a=c; b=x')
assert cookies['a'].value == 'b'
assert cookies['b'].value == 'x'
values = [cookie.value for cookie in cookies.get_all('a')]
assert values == ['b', 'c']
def test_custom_cookie_class_on_instance(self):
cookies = Cookies(_cookie_class=Scone)
cookies.add(a="b")
assert cookies['a'] == Scone("a", "b")
def test_custom_cookie_class_on_subclass(self):
cookies = Scones()
cookies.add(a="b")
assert cookies['a'] == Scone("a", "b")
def test_custom_cookie_class_on_instance_parse_request(self):
cookies = Scones()
cookies.parse_request("Cookie: c=d")
assert cookies['c'] == Scone("c", "d")
def test_custom_cookie_class_on_instance_parse_response(self):
cookies = Scones()
cookies.parse_response("Set-Cookie: c=d")
assert cookies['c'] == Scone("c", "d")
def test_parse_date():
cases = [
("", None),
(" ", None),
("\t", None),
("\n", None),
("\x02\x03\x04", None),
("froppity", None),
("@@@@@%@#:%", None),
("foo bar baz", None),
("Sat, 10 Oct 2009 13:47:21 GMT", "Sat, 10 Oct 2009 13:47:21 GMT"),
(" Oct 2009 13:47:21 GMT", None),
("Fri, Oct 2009 13:47:21 GMT", None),
("Fri, 10 2009 13:47:21 GMT", None),
("Sat, 10 Oct 2009 :47:21 GMT", None),
("Sat, 10 Oct 2009 13::21 GMT", None),
("Sat, 10 Oct 2009 13:47: GMT", None),
("F i, 10 Oct 2009 13:47:21 GMT", None),
("Fr , 10 Oct 2009 13:47:21 GMT", None),
("Fri, 10 ct 2009 13:47:21 GMT", None),
("Fri, 10 O t 2009 13:47:21 GMT", None),
("Fri, 10 Oc 2009 13:47:21 GMT", None),
("Sat, 10 Oct 009 13:47:21 GMT", None),
("Sat, 10 Oct 2 09 13:47:21 GMT", None),
("Sat, 10 Oct 20 9 13:47:21 GMT", None),
("Sat, 10 Oct 200 13:47:21 GMT", None),
("Sat, 10 Oct 2009 1 :47:21 GMT", None),
("Sat, 10 Oct 2009 13 47:21 GMT", None),
("Sat, 10 Oct 2009 13: 7:21 GMT", None),
("Sat, 10 Oct 2009 13:4 :21 GMT", None),
("Sat, 10 Oct 2009 13:47 21 GMT", None),
("Sat, 10 Oct 2009 13:47: 1 GMT", None),
("Sat, 10 Oct 2009 13:47:2 GMT", None),
("Sat, 10 Oct 2009 13:47:21 MT", None),
("Sat, 10 Oct 2009 13:47:21 G T", None),
("Sat, 10 Oct 2009 13:47:21 GM ", None),
("Fri, Burp Oct 2009 13:47:21 GMT", None),
("Fri, 10 Tabalqplar 2009 13:47:21 GMT", None),
("Sat, 10 Oct Fruit 13:47:21 GMT", None),
("Sat, 10 Oct 2009 13:47:21 Fruits", None),
(", Dec 31 00:00:00 2003", None),
("T, Dec 31 00:00:00 2003", None),
("Tu, Dec 31 00:00:00 2003", None),
("Hi, Dec 31 00:00:00 2003", None),
("Heretounforeseen, Dec 31 00:00:00 2003", None),
("Wednesday2, Dec 31 00:00:00 2003", None),
("Mon\x00frobs, Dec 31 00:00:00 2003", None),
("Mon\x10day, Dec 31 00:00:00 2003", None),
("Fri, Oct 2009 13:47:21 GMT", None),
("Fri, 110 Oct 2009 13:47:21 GMT", None),
("Fri, 0 Oct 2009 13:47:21 GMT", None),
("Fri, 00 Oct 2009 13:47:21 GMT", None),
("Fri, 0 Oct 2009 13:47:21 GMT", None),
("Fri, 0 Oct 2009 13:47:21 GMT", None),
("Fri, 00 Oct 2009 13:47:21 GMT", None),
("Fri, 33 Oct 2009 13:47:21 GMT", None),
("Fri, 40 Oct 2009 13:47:21 GMT", None),
("Fri, A2 Oct 2009 13:47:21 GMT", None),
("Fri, 2\x00 Oct 2009 13:47:21 GMT", None),
("Fri, \t3 Oct 2009 13:47:21 GMT", None),
("Fri, 3\t Oct 2009 13:47:21 GMT", None),
("Fri, 10 2009 13:47:21 GMT", None),
("Fri, 10 O 2009 13:47:21 GMT", None),
("Fri, 10 Oc 2009 13:47:21 GMT", None),
("Sat, 10 Octuarial 2009 13:47:21 GMT", None),
("Sat, 10 Octuary 2009 13:47:21 GMT", None),
("Sat, 10 Octubre 2009 13:47:21 GMT", None),
("Sat, 10 Oct 009 13:47:21 GMT", None),
("Sat, 10 Oct 200 13:47:21 GMT", None),
("Sat, 10 Oct 209 13:47:21 GMT", None),
("Sat, 10 Oct 20 9 13:47:21 GMT", None),
("Sat, 10 Oct 2009 25:47:21 GMT", None),
("Sat, 10 Oct 2009 1@:47:21 GMT", None),
("Sat, 10 Oct 2009 13:71:21 GMT", None),
("Sat, 10 Oct 2009 13:61:21 GMT", None),
("Sat, 10 Oct 2009 13:60:21 GMT", None),
("Sat, 10 Oct 2009 24:01:00 GMT", None),
("Sat, 10 Oct 2009 13:47 GMT", "Sat, 10 Oct 2009 13:47:00 GMT"),
("Sat, 10 Oct 2009 13:47:00 GMT", "Sat, 10 Oct 2009 13:47:00 GMT"),
("Sat, 10 Oct 2009 24:00:01 GMT", None),
("Mon Dec 24 16:32:39 1977 GMT", "Sat, 24 Dec 1977 16:32:39 GMT"),
("Sat, 7 Dec 1991 13:56:05 GMT", "Sat, 07 Dec 1991 13:56:05 GMT"),
("Saturday, 8-Mar-2012 21:35:09 GMT", "Thu, 08 Mar 2012 21:35:09 GMT"),
("Sun, 1-Feb-1998 00:00:00 GMT", "Sun, 01 Feb 1998 00:00:00 GMT"),
("Thursday, 01-Jan-1983 01:01:01 GMT",
"Sat, 01 Jan 1983 01:01:01 GMT"),
("Tue, 15-Nov-1973 22:23:24 GMT", "Thu, 15 Nov 1973 22:23:24 GMT"),
("Wed, 09 Dec 1999 23:59:59 GMT", "Thu, 09 Dec 1999 23:59:59 GMT"),
("Mon, 12-May-05 20:25:03 GMT", "Thu, 12 May 2005 20:25:03 GMT"),
("Thursday, 01-Jan-12 09:00:00 GMT", "Sun, 01 Jan 2012 09:00:00 GMT"),
("Wed Mar 12 2007 08:25:07 GMT", None),
("Thu, 31 Dec 23:55:55 2107 GMT", None),
('Fri, 21-May-2004 10:40:51 GMT', "Fri, 21 May 2004 10:40:51 GMT"),
("Sat, 10 Oct 11 13:47:21 GMT", "Mon, 10 Oct 2011 13:47:21 GMT"),
("Sat, 10 Oct 09 13:47:22 GMT", "Sat, 10 Oct 2009 13:47:22 GMT"),
("Sat, 10 Oct 93 13:47:23 GMT", "Sun, 10 Oct 1993 13:47:23 GMT"),
("Sat, 10 Oct 85 13:47:24 GMT", "Thu, 10 Oct 1985 13:47:24 GMT"),
("Sat, 10 Oct 70 13:47:25 GMT", "Sat, 10 Oct 1970 13:47:25 GMT"),
("Sat, 10 Oct 69 13:47:26 GMT", "Thu, 10 Oct 2069 13:47:26 GMT"),
("Sat, 10 Oct 969 13:47:26 GMT", None),
("Sat, 10 Oct 9 13:47:26 GMT", None),
("Fri, 10 Oct 19691 13:47:26 GMT", None),
]
def change(string, position, new_value):
"Macro to change a string"
return string[:position] + new_value + string[position + 1:]
original = "Sat, 10 Oct 2009 13:47:21 GMT"
bad_chars = "/<>()\\*$#&=;\x00\b\f\n\r\"\'`?"
for pos in range(0, len(original)):
for bad_char in bad_chars:
cases.append((change(original, pos, bad_char), None))
letter_positions = [i for (i, c) in enumerate(original) \
if re.match("[A-Za-z]", c)]
for pos in letter_positions:
cases.append((change(original, pos, 'q'), None))
cases.append((change(original, pos, '0'), None))
cases.append((change(original, pos, '-'), None))
cases.append((change(original, pos, ''), None))
c = original[pos]
if c.isupper():
c = c.lower()
else:
c = c.upper()
cases.append((change(original, pos, c), original))
digit_positions = [i for (i, c) in enumerate(original) \
if c in "0123456789"]
for pos in digit_positions:
c = original[pos]
cases.append((change(original, pos, 'q'), None))
cases.append((change(original, pos, '-' + c), None))
cases.append((change(original, pos, '+' + c), None))
space_positions = [i for (i, c) in enumerate(original) \
if c in " \t\n\r"]
for pos in space_positions:
cases.append((change(original, pos, 'x'), None))
cases.append((change(original, pos, '\t'), None))
cases.append((change(original, pos, ' '), None))
cases.append((change(original, pos, ''), None))
colon_positions = [i for (i, c) in enumerate(original) \
if c == ":"]
for pos in colon_positions:
cases.append((change(original, pos, 'z'), None))
cases.append((change(original, pos, '0'), None))
cases.append((change(original, pos, ' '), None))
cases.append((change(original, pos, ''), None))
for data, ideal in cases:
actual = render_date(parse_date(data))
assert actual == ideal
def runner(function):
def run(*args, **kwargs):
"Function which collects result/exception"
actual_result, actual_exception = None, None
try:
actual_result = function(*args, **kwargs)
except Exception as exception:
actual_exception = exception
return actual_exception or actual_result
return run
HEADER_CASES = [
("", {},
InvalidCookieError,
InvalidCookieError),
('a', {},
InvalidCookieError,
InvalidCookieError),
(" ", {},
InvalidCookieError,
InvalidCookieError),
(";;;;;", {},
InvalidCookieError,
InvalidCookieError),
("qwejrkqlwjere", {},
InvalidCookieError,
InvalidCookieError),
('Cookie: ', {},
InvalidCookieError,
InvalidCookieError),
('Set-Cookie: ', {},
InvalidCookieError,
InvalidCookieError),
("foo=bar", {},
Cookies(foo='bar'),
Cookies(foo='bar')),
("SID=242d96421d4e", {},
Cookies(SID='242d96421d4e'),
Cookies(SID='242d96421d4e')),
("a=b; c=dx", {'ignore_bad_attributes': True},
Cookies(a='b', c='dx'),
Cookies(a='b')),
("a=b; c=d", {'ignore_bad_attributes': False},
Cookies(a='b', c='d'),
InvalidCookieAttributeError),
('g=h;j=k', {},
Cookies(g='h', j='k'),
Cookies(g='h')),
('a=b; brains', {},
InvalidCookieError,
Cookies(a='b')),
('A="BBB"', {},
Cookies(A='BBB'),
Cookies(A='BBB'),
),
('A= "BBB" ', {},
Cookies(A='BBB'),
Cookies(A='BBB'),
),
('foo=bar;', {},
Cookies(foo='bar'),
Cookies(foo='bar'),
),
('A="BBB";', {},
Cookies(A='BBB'),
Cookies(A='BBB'),
),
('A= "BBB" ;', {},
Cookies(A='BBB'),
Cookies(A='BBB'),
),
("lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT", {},
InvalidCookieError,
Cookies(
Cookie('lang', '',
expires=parse_date(
"Sun, 06 Nov 1994 08:49:37 GMT")))),
("frob=varvels; Expires=Wed, 09 Jun 2021 10:18:14 GMT", {},
InvalidCookieError,
Cookies(
Cookie('frob', 'varvels',
expires=parse_date(
"Wed, 09 Jun 2021 10:18:14 GMT"
)))),
("lang=en-US; Expires=Wed, 03 Jun 2019 10:18:14 GMT", {},
InvalidCookieError,
Cookies(
Cookie('lang', 'en-US',
expires=parse_date(
"Wed, 03 Jun 2019 10:18:14 GMT"
)))),
("CID=39b4d9be4d42; Path=/; Domain=example.com", {},
Cookies(CID="39b4d9be4d42", Path='/', Domain='example.com'),
Cookies(Cookie('CID', '39b4d9be4d42', path='/',
domain='example.com'))),
("lang=en-US; Path=/; Domain=example.com", {},
Cookies(lang='en-US', Path='/', Domain='example.com'),
Cookies(Cookie('lang', 'en-US',
path='/', domain='example.com'))),
("foo=bar; path=/; expires=Mon, 04-Dec-2001 12:43:00 GMT", {},
InvalidCookieError,
Cookies(
Cookie('foo', 'bar', path='/',
expires=parse_date("Mon, 04-Dec-2001 12:43:00 GMT")
))),
("SID=0fae49; Path=/; Secure; HttpOnly", {},
InvalidCookieError,
Cookies(Cookie('SID', '0fae49',
path='/', secure=True, httponly=True))),
('TMID=DQAAXKEaeo_aYp; Domain=mail.nauk.com; '
'Path=/accounts; Expires=Wed, 13-Jan-2021 22:23:01 GMT; '
'Secure; HttpOnly', {},
InvalidCookieError,
Cookies(
Cookie('TMID', 'DQAAXKEaeo_aYp',
domain='mail.nauk.com',
path='/accounts', secure=True, httponly=True,
expires=parse_date("Wed, 13-Jan-2021 22:23:01 GMT")
))),
("test=some_value; expires=Sat, 01-Jan-2000 00:00:00 GMT; "
"path=/;", {},
InvalidCookieError,
Cookies(
Cookie('test', 'some_value', path='/',
expires=parse_date('Sat, 01 Jan 2000 00:00:00 GMT')
))),
('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"; '
'Part_Number="Rocket_Launcher_0001"', {},
Cookies(Customer='WILE_E_COYOTE', Version='1', Path='/acme',
Part_Number='Rocket_Launcher_0001'),
Cookies(Cookie('Customer', 'WILE_E_COYOTE',
version=1, path='/acme'))),
('Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', {},
InvalidCookieError,
InvalidCookieError),
("lu=Qg3OHJZLehYLjVgAqiZbZbzo; Expires=Tue, 15-Jan-2013 "
"21:47:38 GMT; Path=/; Domain=.foo.com; HttpOnly", {},
InvalidCookieError,
Cookies(Cookie('lu', "Qg3OHJZLehYLjVgAqiZbZbzo",
expires=parse_date('Tue, 15 Jan 2013 21:47:38 GMT'),
path='/', domain='.foo.com', httponly=True,
))),
('ZQID=AYBEVnDKrdst; Domain=.nauk.com; Path=/; '
'Expires=Wed, 13-Jan-2021 22:23:01 GMT; HttpOnly', {},
InvalidCookieError,
Cookies(Cookie('ZQID', "AYBEVnDKrdst",
httponly=True, domain='.nauk.com', path='/',
expires=parse_date('Wed, 13 Jan 2021 22:23:01 GMT'),
))),
("OMID=Ap4PQQEq; Domain=.nauk.com; Path=/; "
'Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly', {},
InvalidCookieError,
Cookies(Cookie('OMID', "Ap4PQQEq",
path='/', domain='.nauk.com', secure=True, httponly=True,
expires=parse_date('Wed, 13 Jan 2021 22:23:01 GMT')
))),
('foo="?foo"; Path=/', {},
Cookies(foo='?foo', Path='/'),
Cookies(Cookie('foo', '?foo', path='/'))),
("a=b; Secure=true; HttpOnly=true;", {},
Cookies(a='b', Secure='true', HttpOnly='true'),
Cookies(Cookie('a', 'b', secure=True, httponly=True))),
('user=RJMmei IORqmD; expires=Wed, 3 Nov 2007 23:20:39 GMT; path=/',
{},
InvalidCookieError,
Cookies(
Cookie('user', 'RJMmei IORqmD', path='/',
expires=parse_date("Wed, 3 Nov 2007 23:20:39 GMT")))),
("x=!#$%&'()*+-./01", {},
Cookies(x="!#$%&'()*+-./01"),
Cookies(x="!#$%&'()*+-./01")),
('a=b; version="1"', {},
Cookies(a='b', version='1'),
Cookies(Cookie('a', 'b', version=1))),
('expires=foo', {},
Cookies(expires='foo'),
Cookies(expires='foo')),
('foo=bar; expires=Fri, 31-Dec-2010 23:59:59 GMT', {},
InvalidCookieError,
Cookies(
Cookie('foo', 'bar',
expires=datetime(2010, 12, 31, 23, 59, 59)))),
('a=Zm9vIGJhcg==', {},
Cookies(a='Zm9vIGJhcg=='),
Cookies(a='Zm9vIGJhcg==')),
('blah="Foo=2"', {},
Cookies(blah='Foo=2'),
Cookies(blah='Foo=2')),
('foo=33;foo=34', {},
Cookies(foo='33'),
Cookies(foo='33')),
('a:b=c', {},
Cookies(
Cookie('a:b', 'c')),
Cookies(
Cookie('a:b', 'c'))),
]
def _cheap_request_parse(arg1, arg2):
def crumble(arg):
"Break down string into pieces"
pieces = [piece.strip('\r\n ;') for piece in re.split("(\r\n|;)", arg)]
pieces = [piece for piece in pieces if piece and '=' in piece]
pieces = [tuple(piece.split("=", 1)) for piece in pieces]
pieces = [(name.strip(), value.strip('" ')) for name, value in pieces]
if len(pieces) > 1:
pieces = [pieces[0]] + sorted(pieces[1:])
return pieces
def dedupe(pieces):
"Eliminate duplicate pieces"
deduped = {}
for name, value in pieces:
if name in deduped:
continue
deduped[name] = value
return sorted(deduped.items(),
key=pieces.index)
return dedupe(crumble(arg1)), crumble(arg2)
def _cheap_response_parse(arg1, arg2):
def crumble(arg):
"Break down string into pieces"
lines = [line for line in arg if line]
done = []
for line in lines:
clauses = [clause for clause in line.split(';')]
import logging
logging.error("clauses %r", clauses)
name, value = re.split(" *= *", clauses[0], 1)
value = unquote(value.strip(' "'))
attrs = [re.split(" *= *", clause, 1) \
for clause in clauses[1:] if clause]
attrs = [attr for attr in attrs \
if attr[0] in Cookie.attribute_names]
attrs = [(k, v.strip(' "')) for k, v in attrs]
done.append((name, value, tuple(attrs)))
return done
result1 = crumble([arg1])
result2 = crumble(arg2)
return result1, result2
def test_render_request():
for case in HEADER_CASES:
arg, kwargs, cookies, _ = case
if isinstance(cookies, type) and issubclass(cookies, Exception):
continue
rendered = cookies.render_request()
expected, actual = _cheap_request_parse(arg, rendered)
assert set(actual) == set(expected)
def test_render_response():
def filter_attrs(items):
"Filter out the items which are Cookie attributes"
return [(name, value) for (name, value) in items \
if name.lower() in Cookie.attribute_names]
for case in HEADER_CASES:
arg, kwargs, _, cookies = case
if isinstance(cookies, type) and issubclass(cookies, Exception):
continue
rendered = cookies.render_response()
expected, actual = _cheap_response_parse(arg, rendered)
expected, actual = set(expected), set(actual)
assert actual == expected, \
"failed: %s -> %s | %s != %s" % (arg, repr(cookies), actual,
expected)
def test_backslash_roundtrip():
reference = Cookie('xx', '\\')
assert len(reference.value) == 1
reference_request = reference.render_request()
reference_response = reference.render_response()
assert '\\' not in reference_request
assert '\\' not in reference_response
assert '%5C' in reference_request
assert '%5C' in reference_response
raw_cookie = r'xx="\"'
parsed_cookies = [Cookie.from_string(raw_cookie),
Cookies.from_request(raw_cookie)['xx'],
Cookies.from_response(raw_cookie)['xx']]
for parsed_cookie in parsed_cookies:
assert parsed_cookie.name == reference.name
assert parsed_cookie.value == reference.value
request = parsed_cookie.render_request()
response = parsed_cookie.render_response()
assert request == reference_request
assert response == reference_response
rrequest = Cookies.from_request(request)['xx']
rresponse = Cookies.from_response(response)['xx']
assert rrequest.name == reference.name
assert rrequest.value == reference.value
assert rresponse.name == reference.name
assert rresponse.value == reference.value
def _simple_test(function, case_dict):
"Macro for making simple case-based tests for a function call"
def actual_test():
"Test generated by _simple_test"
for arg, expected in case_dict.items():
logging.info("case for %s: %s %s",
repr(function), repr(arg), repr(expected))
result = function(arg)
assert result == expected, \
"%s(%s) != %s, rather %s" % (
function.__name__,
repr(arg),
repr(expected),
repr(result))
actual_test.cases = case_dict
return actual_test
test_strip_spaces_and_quotes = _simple_test(strip_spaces_and_quotes, {
' ': '',
'""': '',
'"': '"',
"''": "''",
' foo ': 'foo',
'foo ': 'foo',
' foo': 'foo',
' "" ': '',
' " " ': ' ',
' " ': '"',
'foo bar': 'foo bar',
'"foo bar': '"foo bar',
'foo bar"': 'foo bar"',
'"foo bar"': 'foo bar',
'"dquoted"': 'dquoted',
' "dquoted"': 'dquoted',
'"dquoted" ': 'dquoted',
' "dquoted" ': 'dquoted',
})
test_parse_string = _simple_test(parse_string, {
None: None,
'': '',
b'': '',
})
test_parse_domain = _simple_test(parse_domain, {
' foo ': 'foo',
'"foo"': 'foo',
' "foo" ': 'foo',
'.foo': '.foo',
})
test_parse_path = _simple_test(parse_path, {
})
def test_render_date():
"Test date render routine directly with raw datetime objects"
cases = {
datetime(2001, 10, 11, tzinfo=FixedOffsetTz(60 * 60)):
AssertionError,
datetime(1970, 1, 1, 0, 0, 0):
'Thu, 01 Jan 1970 00:00:00 GMT',
datetime(2007, 9, 2, 13, 59, 49):
'Sun, 02 Sep 2007 13:59:49 GMT',
datetime(2007, 9, 2, 1, 59, 49):
"Sun, 02 Sep 2007 01:59:49 GMT",
datetime(2007, 9, 2, 1, 1, 49):
"Sun, 02 Sep 2007 01:01:49 GMT",
datetime(2007, 9, 2, 1, 1, 2):
"Sun, 02 Sep 2007 01:01:02 GMT",
datetime(1900, 9, 2, 1, 1, 2):
"Sun, 02 Sep 1900 01:01:02 GMT",
datetime(3000, 9, 2, 1, 1, 2):
"Tue, 02 Sep 3000 01:01:02 GMT"
}
for dt, expected in cases.items():
if isinstance(expected, type) and issubclass(expected, Exception):
try:
render_date(dt)
except expected:
continue
except Exception as exception:
raise AssertionError("expected %s, got %s"
% (expected, exception))
raise AssertionError("expected %s, got no exception"
% (expected))
else:
assert render_date(dt) == expected
def test_encoding_assumptions(check_unicode=False):
"Document and test assumptions underlying URL encoding scheme"
cookie_value_re = re.compile("[%s]" % Definitions.COOKIE_OCTET)
cookie_value_safe1 = set(chr(i) for i in range(0, 256) \
if cookie_value_re.match(chr(i)))
cookie_value_safe2 = set(unichr(i) for i in range(0, 256) \
if cookie_value_re.match(unichr(i)))
assert cookie_value_safe1 == cookie_value_safe2
safe_but_quoted = set(c for c in cookie_value_safe1
if quote(c, safe=b"") != c)
dont_quote = "".join(sorted(safe_but_quoted))
for c in dont_quote:
assert quote(c, safe="") != c
assert quote(c, safe=dont_quote) == c
for i in range(0, 255):
original = chr(i)
quoted = quote(original, safe=dont_quote)
if cookie_value_re.match(original):
assert original == quoted
else:
assert cookie_value_re.match(quoted)
assert set(dont_quote) == set("!#$%&'()*+/:<=>?@[]^`{|}~")
if check_unicode:
for c in (unichr(i) for i in range(0, 1114112)):
asc = c.encode('utf-8')
quoted = quote(asc, safe=dont_quote)
unquoted = unquote(asc)
unicoded = unquoted.decode('utf-8')
assert unicoded == c
extension_av_re = re.compile("[%s]" % Definitions.EXTENSION_AV)
extension_av_safe = set(chr(i) for i in range(0, 256) \
if extension_av_re.match(chr(i)))
safe_but_quoted = set(c for c in extension_av_safe \
if quote(c, safe="") != c)
dont_quote = "".join(sorted(safe_but_quoted))
for c in dont_quote:
assert quote(c, safe="") != c
assert quote(c, safe=dont_quote) == c
for i in range(0, 255):
original = chr(i)
quoted = quote(original, safe=dont_quote)
if extension_av_re.match(original):
assert original == quoted
else:
assert extension_av_re.match(quoted)
assert set(dont_quote) == set(' !"#$%&\'()*+,/:<=>?@[\\]^`{|}~')
test_encode_cookie_value = _simple_test(encode_cookie_value,
{
None: None,
' ': '%20',
'!': '!',
'#': '#',
'$': '$',
'%': '%',
'&': '&',
"'": "'",
'(': '(',
')': ')',
'*': '*',
'+': '+',
'/': '/',
':': ':',
'<': '<',
'=': '=',
'>': '>',
'?': '?',
'@': '@',
'[': '[',
']': ']',
'^': '^',
'`': '`',
'{': '{',
'|': '|',
'}': '}',
'~': '~',
' ': '%20',
'"': '%22',
',': '%2C',
'\\': '%5C',
'crud,': 'crud%2C',
})
test_encode_extension_av = _simple_test(encode_extension_av,
{
None: '',
'': '',
'foo': 'foo',
' ': ' ',
'"': '"',
',': ',',
'\\': '\\',
'yo\\b': 'yo\\b',
})
test_valid_value = _simple_test(valid_value,
{
None: False,
'': True,
'ಠ_ಠ': True,
'μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος': True,
'这事情得搞好啊': True,
'宮崎 駿': True,
'أم كلثوم': True,
'ედუარდ შევარდნაძე': True,
'Myötähäpeä': True,
'Pedro Almodóvar': True,
'Pedro Almodóvar'.encode('utf-8'): False,
})
test_valid_date = _simple_test(valid_date,
{
datetime(2011, 1, 1): True,
datetime(2011, 1, 1, tzinfo=FixedOffsetTz(1000)): False,
datetime(2011, 1, 1, tzinfo=FixedOffsetTz(0)): True,
})
test_valid_domain = _simple_test(valid_domain,
{
'': False,
' ': False,
'.': False,
'..': False,
'.foo': True,
'"foo"': False,
'foo': True,
})
test_valid_path = _simple_test(valid_path,
{
'': False,
' ': False,
'/': True,
'a': False,
'/a': True,
'\x00': False,
'/\x00': False,
})
def test_many_pairs():
from_request = Cookies.from_request
header = "a0=0"
for i in range(1, 100):
i_range = list(range(0, i))
cookies = from_request(header)
assert len(cookies) == i
for j in i_range:
key = 'a%d' % j
assert cookies[key].value == str(j * 10)
assert cookies[key].render_request() == \
"a%d=%d" % (j, j * 10)
cookies = Cookies()
cookies.parse_request(header)
assert len(cookies) == i
for j in i_range:
key = 'a%d' % j
assert cookies[key].value == str(j * 10)
assert cookies[key].render_request() == \
"a%d=%d" % (j, j * 10)
header += "; a%d=%d" % (i, i * 10)
def test_parse_value():
cases = {}
cases.update(test_strip_spaces_and_quotes.cases)
cases.update(test_parse_string.cases)
for inp, expected in cases.items():
print("case", inp, expected)
obtained = parse_value(inp, allow_spaces=True)
assert obtained == expected
if (isinstance(inp, bytes) and ' ' in inp.decode('utf-8').strip()) \
or (not isinstance(inp, bytes) and inp and ' ' in inp.strip()):
try:
obtained = parse_value(inp, allow_spaces=False)
except AssertionError:
pass
else:
raise AssertionError("parse_value(%s, allow_spaces=False) "
"did not raise" % repr(inp))
def test_total_seconds():
def basic_sanity(td_type):
assert _total_seconds(td_type(seconds=1)) == 1
assert _total_seconds(td_type(seconds=1, minutes=1)) == 1 + 60
assert _total_seconds(td_type(seconds=1, minutes=1, hours=1)) == \
1 + 60 + 60 * 60
basic_sanity(timedelta)
class FakeTimeDelta(object):
def __init__(self, days=0, hours=0, minutes=0, seconds=0,
microseconds=0):
self.days = days
self.seconds = seconds + minutes * 60 + hours * 60 * 60
self.microseconds = microseconds
assert not hasattr(FakeTimeDelta, "total_seconds")
basic_sanity(FakeTimeDelta)
FakeTimeDelta.total_seconds = lambda: None.missing_attribute
try:
_total_seconds(None)
except AttributeError as e:
assert 'total_seconds' not in str(e)
def test_valid_value_bad_quoter():
def bad_quote(s):
return "Frogs"
assert valid_value("eep", quote=bad_quote) == False