class RFC1123: """Definitions from RFC 1123: "Requirements for Internet Hosts --
Application and Support" section 2.1, cited in RFC 6265 section
4.1.1 as an update to RFC 1034.
Here this is really just used for testing Domain attribute values. """ # Changed per 2.1 (similar to some changes in RFC 1101) # this implementation is a bit simpler... # n.b.: there are length limits in the real thing
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)
class RFC2616: """Definitions from RFC 2616 section 2.2, as cited in RFC 6265 4.1.1 """
SEPARATORS = '()<>@,;:\\"/[]?={} \t'
class RFC5234: """Basic definitions per RFC 5234: 'Augmented BNF for Syntax
Specifications' """
CHAR = "".join([chr(i) for i in range(0, 127 + 1)])
CTL = "".join([chr(i) for i in range(0, 31 + 1)]) + "\x7f" # this isn't in the RFC but it can be handy
NONCTL = "".join([chr(i) for i in range(32, 127)]) # this is what the RFC says about a token more or less verbatim
TOKEN = "".join(sorted(set(NONCTL) - set(RFC2616.SEPARATORS)))
class FixedOffsetTz(tzinfo): """A tzinfo subclass for attaching to datetime objects.
Used for various tests involving date parsing, since Python stdlib does not
obviously provide tzinfo subclasses and testing this module only requires
a very simple one. """ def __init__(self, offset): # tzinfo.utcoffset() throws an error for sub-minute amounts, # so round
minutes = round(offset / 60.0, 0)
self.__offset = timedelta(minutes=minutes)
class TestInvalidCookieError(object): """Exercise the trivial behavior of the InvalidCookieError exception. """ 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 # other exceptions will pass through returnNone assert exception("no donut").data == "no donut"
# Spot check for obvious junk in loggable representations.
e = exception("yay\x00whee") assert"\x00"notin repr(e) assert"\x00"notin str(e) assert"yaywhee"notin repr(e) assert"yaywhee"notin str(e) assert"\n"notin repr(exception("foo\nbar"))
class TestInvalidCookieAttributeError(object): """Exercise the trivial behavior of InvalidCookieAttributeError. """ def exception(self, *args, **kwargs): "Generate an InvalidCookieAttributeError exception naturally" try: raise InvalidCookieAttributeError(*args, **kwargs) except InvalidCookieAttributeError as exception: return exception returnNone
def test_junk_in_loggables(self): # Spot check for obvious junk in loggable representations. # This isn't completely idle: for example, nulls are ignored in # %-formatted text, and this could be very misleading
e = self.exception("ya\x00y", "whee") assert"\x00"notin repr(e) assert"\x00"notin str(e) assert"yay"notin repr(e) assert"yay"notin str(e)
def test_no_name(self): # not recommended to do this, but we want to handle it if people do
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): """Test the patterns in cookies.Definitions against specs. """ def test_cookie_name(self, check_unicode=False): """Check COOKIE_NAME against the token definition in RFC 2616 2.2 (as
cited in RFC 6265):
(Definitions.COOKIE_NAME is regex-ready while RFC5234.TOKEN is more
clearly related to the RFC; they should be functionally the same) """
regex = Definitions.COOKIE_NAME_RE assert regex.match(RFC5234.TOKEN) assertnot regex.match(RFC5234.NONCTL) for c in RFC5234.CTL: assertnot regex.match(c) for c in RFC2616.SEPARATORS: # Skip special case - some number of Java and PHP apps have used # colon in names, while this is dumb we want to not choke on this # by default since it may be the single biggest cause of bugs filed # against Python's cookie libraries if c == ':': continue assertnot regex.match(c) # Unicode over 7 bit ASCII shouldn't match, but this takes a while if check_unicode: for i in range(127, 0x10FFFF + 1): assertnot regex.match(unichr(i))
def test_cookie_octet(self): """Check COOKIE_OCTET against the definition in RFC 6265:
cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
; US-ASCII characters excluding CTLs,
; whitespace DQUOTE, comma, semicolon,
; and backslash """
match = re.compile("^[%s]+\Z" % Definitions.COOKIE_OCTET).match for c in RFC5234.CTL: assertnot match(c) assertnot match("a%sb" % c) # suspect RFC typoed 'whitespace, DQUOTE' as 'whitespace DQUOTE' assertnot match(' ') assertnot match('"') assertnot match(',') assertnot match(';') assertnot match('\\') # the spec above DOES include =.- assert match("=") assert match(".") assert match("-")
# Check that everything else in CHAR works.
safe_cookie_octet = "".join(sorted(
set(RFC5234.NONCTL) - set(' ",;\\'))) assert match(safe_cookie_octet)
def test_set_cookie_header(self): """Smoke test SET_COOKIE_HEADER (used to compile SET_COOKIE_HEADER_RE)
against HEADER_CASES. """ # should match if expectation is not an error, shouldn't match if it is # an error. set-cookie-header is for responses not requests, so use # response expectation rather than request expectation
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 andnot isinstance(expected, type): assert this_match, "should match as response: " + repr(arg) else: ifnot request_result: assertnot this_match, \ "should not match as response: " + repr(arg)
def test_cookie_cases(self): """Smoke test COOKIE_HEADER (used to compile COOKIE_HEADER_RE) against
HEADER_CASES. """ # should match if expectation is not an error, shouldn't match if it is # an error. cookie-header is for requests not responses, so use request # expectation rather than response expectation
match = re.compile(Definitions.COOKIE).match for case in HEADER_CASES:
arg, kwargs, expected, response_result = case
this_match = match(arg) if expected andnot isinstance(expected, type): assert this_match, "should match as request: " + repr(arg) else: ifnot response_result: assertnot this_match, \ "should not match as request: " + repr(arg)
def test_cookie_pattern(self): """Smoke test Definitions.COOKIE (used to compile COOKIE_RE) against
the grammar for cookie-header asin RFC 6265.
cookie-name and cookie-value are not broken apart for separate
testing, as the former is essentially just token and the latter
essentially just cookie-octet. """
match = re.compile(Definitions.COOKIE).match # cookie-pair behavior around = assert match("foo").group('invalid') assert match("foo=bar") # Looks dumb, but this is legal because "=" is valid for cookie-octet. assert match("a=b=c") # DQUOTE *cookie-octet DQUOTE - allowed assert match('foo="bar"')
# for testing on the contents of cookie name and cookie value, # see test_cookie_name and test_cookie_octet.
def assert_correct(s): #naive = re.findall(" *([^;]+)=([^;]+) *(?:;|\Z)", s)
result = regex.findall(s) assert result == correct # normal-looking case should work normally
assert_correct("foo=yar; bar=eeg; baz=wog; frob=laz") # forgive lack of whitespace as long as semicolons are explicit
assert_correct("foo=yar;bar=eeg;baz=wog;frob=laz") # forgive too much whitespace AROUND values
assert_correct(" foo=yar; bar=eeg; baz=wog; frob=laz ")
# Actually literal spaces are NOT allowed in cookie values per RFC 6265 # and it is UNWISE to put them in without escaping. But we want the # flexibility to let this pass with a warning, because this is the kind # of bad idea which is very common and results in loud complaining on # issue trackers on the grounds that PHP does it or something. So the # regex is weakened, but the presence of a space should still be at # least noted, and an exception must be raised if = is also used # - because that would often indicate the loss of cookies due to # forgotten separator, as in "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', ''), # handle invalid internal whitespace.
('frob', 'l az', '')
]
# Without semicolons or inside semicolon-delimited blocks, the part # before the first = should be interpreted as a name, and the rest as # a value (since = is not forbidden for cookie values). Thus:
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] == ''
# Make some bad values and see that it's handled reasonably. # (related to http://bugs.python.org/issue2988) # don't test on semicolon because the regexp stops there, reasonably. for c in'\x00",\\':
nasty = "foo=yar" + c + "bar"
result = regex.findall(nasty + "; baz=bam") # whole bad pair reported in the 'invalid' group (the third one) assert result[0][2] == nasty # kept on truckin' and got the other one just fine. assert result[1] == ('baz', 'bam', '') # same thing if the good one is first and the bad one second
result = regex.findall("baz=bam; " + nasty) assert result[0] == ('baz', 'bam', '') assert result[1][2] == ' ' + nasty
def test_extension_av(self, check_unicode=False): """Test Definitions.EXTENSION_AV against extension-av per RFC 6265.
extension-av = <any CHAR except CTLs or";"> """ # This is how it's defined in RFC 6265, just about verbatim.
extension_av_explicit = "".join(sorted(
set(RFC5234.CHAR) - set(RFC5234.CTL + ";"))) # ... that should turn out to be the same as Definitions.EXTENSION_AV
match = re.compile("^([%s]+)\Z" % Definitions.EXTENSION_AV).match # Verify I didn't mess up on escaping here first assert match(r']') assert match(r'[') assert match(r"'") assert match(r'"') assert match("\\") assert match(extension_av_explicit) # There should be some CHAR not matched assertnot match(RFC5234.CHAR) # Every single CTL should not match for c in RFC5234.CTL + ";": assertnot match(c) # Unicode over 7 bit ASCII shouldn't match, but this takes a while if check_unicode: for i in range(127, 0x10FFFF + 1): assertnot match(unichr(i))
def test_max_age_av(self): "Smoke test Definitions.MAX_AGE_AV" # Not a lot to this, it's just digits
match = re.compile("^%s\Z" % Definitions.MAX_AGE_AV).match assertnot match("") assertnot match("Whiskers") assertnot match("Max-Headroom=992") for c in"123456789": assertnot match(c) assert match("Max-Age=%s" % c) assert match("Max-Age=0") for c in RFC5234.CHAR: assertnot 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)) assertnot match(".") assertnot match(",") for c in RFC5234.CTL: assertnot match("a%sb" % c) assertnot match("%sb" % c) assertnot match("a%s" % c) # Unicode over 7 bit ASCII shouldn't match, but this takes a while if check_unicode: for i in range(127, 0x10FFFF + 1): assertnot match(unichr(i))
def test_domain_av(self): "Smoke test Definitions.DOMAIN_AV" # This is basically just RFC1123.subdomain, which has its own # assertions in the class definition
bad_domains = [ ""
]
good_domains = [ "foobar.com", "foo-bar.com", "3Com.COM"
]
# First test DOMAIN via DOMAIN_RE
match = Definitions.DOMAIN_RE.match for domain in bad_domains: assertnot match(domain) for domain in good_domains: assert match(domain)
# Now same tests through DOMAIN_AV
match = re.compile("^%s\Z" % Definitions.DOMAIN_AV).match for domain in bad_domains: assertnot match("Domain=%s" % domain) for domain in good_domains: assertnot match(domain) assert match("Domain=%s" % domain) # This is NOT valid and shouldn't be tolerated in cookies we create, # but it should be tolerated in existing cookies since people do it; # interpreted by stripping the initial . assert match("Domain=.foo.net")
def test_path_av(self): "Smoke test PATH and PATH_AV" # This is basically just EXTENSION_AV, see test_extension_av
bad_paths = [ ""
]
good_paths = [ "/", "/foo", "/foo/bar"
]
match = Definitions.PATH_RE.match for path in bad_paths: assertnot match(path) for path in good_paths: assert match(path)
match = re.compile("^%s\Z" % Definitions.PATH_AV).match for path in bad_paths: assertnot match("Path=%s" % path) for path in good_paths: assertnot match(path) assert match("Path=%s" % path)
def test_months(self): """Sanity checks on MONTH_SHORT and MONTH_LONG month name recognizers.
The RFCs set these in stone, they aren't locale-dependent. """
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")
def test_weekdays(self): """Sanity check on WEEKDAY_SHORT and WEEKDAY_LONG weekday
recognizers.
The RFCs set these in stone, they aren't locale-dependent. """
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")
def test_day_of_month(self): """Check that the DAY_OF_MONTH regex allows all actual days, but
excludes obviously wrong ones (so they are tossed in the first pass). """
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) assertnot match("0") assertnot match("00") assertnot match("000") assertnot match("111") assertnot match("99") assertnot match("41")
def test_expires_av(self): "Smoke test the EXPIRES_AV regex pattern" # Definitions.EXPIRES_AV is actually pretty bad because it's a disaster # to test three different date formats with lots of definition # dependencies, and odds are good that other implementations are loose. # so this parser is also loose. "liberal in what you accept, # conservative in what you produce"
match = re.compile("^%s\Z" % Definitions.EXPIRES_AV).match assertnot match("") assertnot 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") # attributed to Netscape in RFC 2109 10.1.2 assert match("Expires=Mon, 13-Jun-93 10:00:00 GMT")
assertnot match("Expires=S9n, 06 Nov 1994 08:49:37 GMT") assertnot match("Expires=Sun3ay, 06-Nov-94 08:49:37 GMT") assertnot match("Expires=S9n Nov 6 08:49:37 1994")
assertnot match("Expires=Sun, 06 N3v 1994 08:49:37 GMT") assertnot match("Expires=Sunday, 06-N8v-94 08:49:37 GMT") assertnot match("Expires=Sun Nov A 08:49:37 1994")
assertnot match("Expires=Sun, 06 Nov 1B94 08:49:37 GMT") assertnot match("Expires=Sunday, 06-Nov-C4 08:49:37 GMT") assertnot match("Expires=Sun Nov 6 08:49:37 1Z94")
def test_no_obvious_need_for_disjunctive_attr_pattern(self): """Smoke test the assumption that extension-av is a reasonable set of
chars for all attrs (and thus that there is no reason to use a fancy
disjunctive pattern in the findall that splits out the attrs, freeing
us to use EXTENSION_AV instead).
If this works, then ATTR should work """
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): """Smoke test ATTR, used to compile ATTR_RE. """
match = re.compile(Definitions.ATTR).match
def recognized(pattern): "macro for seeing if ATTR recognized something"
this_match = match(pattern) ifnot this_match: returnFalse
groupdict = this_match.groupdict() if groupdict['unrecognized']: returnFalse returnTrue
# Quickly test that a batch of attributes matching the explicitly # recognized patterns make it through without anything in the # 'unrecognized' catchall capture group. 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)
# Anything else is in extension-av and that's very broad; # see test_extension_av for that test. # This is only about the recognized ones. assertnot recognized("Frob=mugmannary") assertnot recognized("Fqjewp@1j5j510923") assertnot recognized(";aqjwe") assertnot recognized("ETJpqw;fjw") assertnot recognized("fjq;") assertnot recognized("Expires=\x00")
# Verify interface from regexp for extracting values isn't changed; # a little rigidity here is a good idea
expires = "Wed, 09 Jun 2021 10:18:14 GMT"
m = match("Expires=%s" % expires) assert m.group("expires") == expires
m = match("Secure") assert m.group("secure") assertnot m.group("httponly")
m = match("HttpOnly") assertnot m.group("secure") assert m.group("httponly")
def test_date_accepts_formats(self): """Check that DATE matches most formats used in Expires: headers, and explain what the different formats are about.
The value extraction of this regexp is more comprehensively exercised
by test_date_parsing(). """ # Date formats vary widely in the wild. Even the standards vary widely. # This series of tests does spot-checks with instances of formats that # it makes sense to support. In the following comments, each format is # discussed and the rationale for the overall regexp is developed.
match = re.compile(Definitions.DATE).match
# The most common formats, related to the old Netscape cookie spec # (NCSP), are supposed to follow this template: # # Wdy, DD-Mon-YYYY HH:MM:SS GMT # # (where 'Wdy' is a short weekday, and 'Mon' is a named month). assert match("Mon, 20-Jan-1994 00:00:00 GMT")
# Similarly, RFC 850 proposes this format: # # Weekday, DD-Mon-YY HH:MM:SS GMT # # (with a long-form weekday and a 2-digit year). assert match("Tuesday, 12-Feb-92 23:25:42 GMT")
# RFC 1036 obsoleted the RFC 850 format: # # Wdy, DD Mon YY HH:MM:SS GMT # # (shortening the weekday format and changing dashes to spaces). assert match("Wed, 30 Mar 92 13:16:12 GMT")
# RFC 6265 cites a definition from RFC 2616, which uses the RFC 1123 # definition but limits it to GMT (consonant with NCSP). RFC 1123 # expanded RFC 822 with 2-4 digit years (more permissive than NCSP); # RFC 822 left weekday and seconds as optional, and a day of 1-2 digits # (all more permissive than NCSP). Giving something like this: # # [Wdy, ][D]D Mon [YY]YY HH:MM[:SS] GMT # assert match("Thu, 3 Apr 91 12:46 GMT") # No weekday, two digit year. assert match("13 Apr 91 12:46 GMT")
# Similarly, there is RFC 2822: # # [Wdy, ][D]D Mon YYYY HH:MM[:SS] GMT # (which only differs in requiring a 4-digit year, where RFC 1123 # permits 2 or 3 digit years). assert match("13 Apr 1991 12:46 GMT") assert match("Wed, 13 Apr 1991 12:46 GMT")
# The generalized format given above encompasses RFC 1036 and RFC 2822 # and would encompass NCSP except for the dashes; allowing long-form # weekdays also encompasses the format proposed in RFC 850. Taken # together, this should cover something like 99% of Expires values # (see, e.g., https://bugzilla.mozilla.org/show_bug.cgi?id=610218)
# Finally, we also want to support asctime format, as mentioned in RFC # 850 and RFC 2616 and occasionally seen in the wild: # Wdy Mon DD HH:MM:SS YYYY # e.g.: Sun Nov 6 08:49:37 1994 assert match("Sun Nov 6 08:49:37 1994") assert match("Sun Nov 26 08:49:37 1994") # Reportedly someone has tacked 'GMT' on to the end of an asctime - # although this is not RFC valid, it is pretty harmless assert match("Sun Nov 26 08:49:37 1994 GMT")
# This test is not passed until it is shown that it wasn't trivially # because DATE was matching .* or similar. This isn't intended to be # a thorough test, just rule out the obvious reason. See test_date() # for a more thorough workout of the whole parse and render mechanisms assertnot match("") assertnot match(" ") assertnot match("wobbly") assertnot match("Mon") assertnot match("Mon, 20") assertnot match("Mon, 20 Jan") assertnot match("Mon, 20,Jan,1994 00:00:00 GMT") assertnot match("Tuesday, 12-Feb-992 23:25:42 GMT") assertnot match("Wed, 30 Mar 92 13:16:1210 GMT") assertnot match("Wed, 30 Mar 92 13:16:12:10 GMT") assertnot match("Thu, 3 Apr 91 12:461 GMT")
def test_eol(self): """Test that the simple EOL regex works basically as expected. """
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): """Check that certain patterns are present as compiled regexps """
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)
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): """Tests for the Cookie class. """ # Test cases exercising different constructor calls to make a new Cookie # from scratch. Each case is tuple: # args, kwargs, exception or dict of expected attribute values # this exercises the default validators as well.
creation_cases = [ # bad call gives TypeError
(("foo",), {}, TypeError),
(("a", "b", "c"), {}, TypeError), # give un-ascii-able name - raises error due to likely # compatibility problems (cookie ignored, etc.) # in value it's fine, it'll be encoded and not inspected anyway.
(("ăŊĻ", "b"), {}, InvalidCookieError),
(("b", "ăŊĻ"), {}, {'name': 'b', 'value': "ăŊĻ"}), # normal simple construction gives name and value
(("foo", "bar"), {}, {'name': 'foo', 'value': 'bar'}), # add a valid attribute and get it set
(("baz", "bam"), {'max_age': 9},
{'name': 'baz', 'value': 'bam', 'max_age': 9}), # multiple valid attributes
(("x", "y"), {'max_age': 9, 'comment': 'fruity'},
{'name': 'x', 'value': 'y', 'max_age': 9, 'comment': 'fruity'}), # invalid max-age
(("w", "m"), {'max_age': 'loopy'}, InvalidCookieAttributeError),
(("w", "m"), {'max_age': -1}, InvalidCookieAttributeError),
(("w", "m"), {'max_age': 1.2}, InvalidCookieAttributeError), # invalid expires
(("w", "m"), {'expires': 0}, InvalidCookieAttributeError),
(("w", "m"), {'expires':
datetime(2010, 1, 1, tzinfo=FixedOffsetTz(600))},
InvalidCookieAttributeError), # control: valid expires
(("w", "m"),
{'expires': datetime(2010, 1, 1)},
{'expires': datetime(2010, 1, 1)}), # invalid domain
(("w", "m"), {'domain': ''}, InvalidCookieAttributeError),
(("w", "m"), {'domain': '@'}, InvalidCookieAttributeError),
(("w", "m"), {'domain': '.foo.net'}, {'domain': '.foo.net'}), # control: valid domain
(("w", "m"),
{'domain': 'foo.net'},
{'domain': 'foo.net'},), # invalid path
(("w", "m"), {'path': ''}, InvalidCookieAttributeError),
(("w", "m"), {'path': '""'}, InvalidCookieAttributeError),
(("w", "m"), {'path': 'foo'}, InvalidCookieAttributeError),
(("w", "m"), {'path': '"/foo"'}, InvalidCookieAttributeError),
(("w", "m"), {'path': ' /foo '}, InvalidCookieAttributeError), # control: valid path
(("w", "m"), {'path': '/'},
{'path': '/'}),
(("w", "m"), {'path': '/axes'},
{'path': '/axes'}), # invalid version per RFC 2109/RFC 2965
(("w", "m"), {'version': ''}, InvalidCookieAttributeError),
(("w", "m"), {'version': 'baa'}, InvalidCookieAttributeError),
(("w", "m"), {'version': -2}, InvalidCookieAttributeError),
(("w", "m"), {'version': 2.3}, InvalidCookieAttributeError), # control: valid version
(("w", "m"), {'version': 0}, {'version': 0}),
(("w", "m"), {'version': 1}, {'version': 1}),
(("w", "m"), {'version': 3042}, {'version': 3042}), # invalid secure, httponly
(("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), # valid comment
(("w", "m"), {'comment': 'a'}, {'comment': 'a'}), # invalid names # (unicode cases are done last because they mess with pytest print)
((None, "m"), {}, InvalidCookieError),
(("", "m"), {}, InvalidCookieError),
(("ü", "m"), {}, InvalidCookieError), # invalid values
(("w", None), {}, {'name': 'w'}), # a control - unicode is valid value, just gets encoded on way out
(("w", "üm"), {}, {'value': "üm"}), # comma
(('a', ','), {}, {'value': ','}), # semicolons
(('a', ';'), {}, {'value': ';'}), # spaces
(('a', ' '), {}, {'value': ' '}),
]
def test_init(self): """Exercise __init__ and validators.
This is important both because it is a user-facing API, and also
because the parse/render tests depend heavily on it. """
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): """Exercise setting, validation and getting of attributes without
much involving __init__. Also sets value and name. """ for args, kwargs, expected in self.creation_cases: ifnot 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 # Verify that not every name is getting something for attr in ("foo", "bar", "baz"): assertnot hasattr(cookie, attr) with raises(AttributeError):
getattr(cookie, attr)
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"
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 elseNone, '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 # Setting it to None makes it drop from the listing assert cookie.render_response() == "a=b"
cookie.bar = "what" assert cookie.bar == "what" assert cookie.render_request() == "a=b" # bar's renderer returns a bool; if it's True we get Bar. # that's a special case for flags like HttpOnly. 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
class Scone(object): """Non-useful alternative to Cookie class for tests only. """ def __init__(self, name, value):
self.name = name
self.value = value
def __eq__(self, other): if type(self) != type(other): returnFalse if self.name != other.name: returnFalse if self.value != other.value: returnFalse returnTrue
class Scones(Cookies): """Non-useful alternative to Cookies class for tests only. """
DEFAULT_COOKIE_CLASS = Scone
class TestCookies(object): """Tests for the Cookies class. """
creation_cases = [ # Only args - simple
((Cookie("a", "b"),), {}, 1), # Only kwargs - simple
(tuple(), {'a': 'b'}, 1), # Only kwargs - bigger
(tuple(),
{'axl': 'bosk', 'x': 'y', 'foo': 'bar', 'baz': 'bam'}, 4), # Sum between args/kwargs
((Cookie('a', 'b'),),
{'axl': 'bosk', 'x': 'y', 'foo': 'bar', 'baz': 'bam'}, 5), # Redundant between args/kwargs
((Cookie('a', 'b'),
Cookie('x', 'y')),
{'axl': 'bosk', 'x': 'y', 'foo': 'bar', 'baz': 'bam'}, 5),
]
def test_init(self): """Create some Cookies objects with __init__, varying the constructor
arguments, and check on the results.
Exercises __init__, __repr__, render_request, render_response, and
simple cases of parse_response and parse_request. """ 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: # Make a Cookies object using the args.
cookies = Cookies(*args, **kwargs) assert len(cookies) == length
# Render into various text formats.
rep = repr(cookies)
res = cookies.render_response()
req = cookies.render_request()
# Very basic sanity check on renders, fail fast and in a simple way # if output is truly terrible 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
# Explicitly parse out the data (this can be simple since the # output should be in a highly consistent format)
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
# Parse the rendered output, check that result is equal to the # originally produced object.
parsed = Cookies()
parsed.parse_request(req) assert parsed == cookies
parsed = Cookies() for item in res:
parsed.parse_response(item) assert parsed == cookies
# Check that all the requested cookies were created correctly: # indexed with correct names in dict, also with correctly set name # and value attributes. 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
# Spot check that setting an attribute still works # with these particular parameters. Not a torture test. for key in cookies:
cookies[key].max_age = 42 for line in cookies.render_response(): assert line.endswith("Max-Age=42")
# Spot check cookie deletion
keys = [key for key in cookies.keys()] for key in keys: del cookies[key] assert key notin 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'notin 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): """Test Cookies.parse_request. """ 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
# parse_request doesn't take ignore_bad_attributes. remove it # without changing original kwargs for further tests
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))
# Check result - should be same with and without the prefix
expect("Cookie: " + arg, kwargs)
expect(arg, kwargs)
# But it should not match with the response prefix.
other_result = run("Set-Cookie: " + arg, **kwargs) assert other_result != expected assert other_result != response_result
# If case expects InvalidCookieError, verify that it is suppressed # by ignore_bad_cookies. if expected == InvalidCookieError:
kwargs2 = kwargs.copy()
kwargs2['ignore_bad_cookies'] = True
cookies = Cookies() # Let natural exception raise, easier to figure out
cookies.parse_request(arg, **kwargs2)
# Spot check that exception is raised for clearly wrong format assertnot isinstance(run("Cookie: a=b"), InvalidCookieError) assert isinstance(run("Set-Cookie: a=b"), InvalidCookieError)
def test_parse_response(self): """Test Cookies.parse_response. """ 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 # If we expect InvalidCookieError or InvalidCookieAttributeError, # telling the function to ignore those should result in no # exception.
kwargs2 = kwargs.copy() if expected == InvalidCookieError:
kwargs2['ignore_bad_cookies'] = True assertnot 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: assertnot isinstance(result, Exception) # Check result - should be same with and without the prefix
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)) # But it should not match with the request prefix.
other_result = run("Cookie: " + arg, **kwargs) assert other_result != expected assert other_result != request_result
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.