from datetime import timedelta import pytest from webob import cookies from webob.util import text_ def setup_module(module): cookies._should_raise = True def teardown_module(module): cookies._should_raise = False def test_cookie_empty(): c = cookies.Cookie() # empty cookie assert repr(c) == "" def test_cookie_one_value(): c = cookies.Cookie("dismiss-top=6") vals = list(c.values()) assert len(vals) == 1 assert vals[0].name == b"dismiss-top" assert vals[0].value == b"6" def test_cookie_one_value_with_trailing_semi(): c = cookies.Cookie("dismiss-top=6;") vals = list(c.values()) assert len(vals) == 1 assert vals[0].name == b"dismiss-top" assert vals[0].value == b"6" c = cookies.Cookie("dismiss-top=6;") def test_cookie_escaped_unquoted(): assert list(cookies.parse_cookie("x=\\040")) == [(b"x", b" ")] def test_cookie_complex(): c = cookies.Cookie( "dismiss-top=6; CP=null*, " 'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,"' ) def d(v): return v.decode("ascii") c_dict = {d(k): d(v.value) for k, v in c.items()} assert c_dict == { "a": "42,", "CP": "null*", "PHPSESSID": "0a539d42abc001cdc762809248d4beed", "dismiss-top": "6", } def test_cookie_complex_serialize(): c = cookies.Cookie( "dismiss-top=6; CP=null*, " 'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42"' ) assert ( c.serialize() == "CP=null*; PHPSESSID=0a539d42abc001cdc762809248d4beed;" " a=42; dismiss-top=6" ) def test_cookie_load_multiple(): c = cookies.Cookie("a=1; Secure=true") vals = list(c.values()) assert len(vals) == 1 assert c[b"a"][b"secure"] == b"true" def test_cookie_secure(): c = cookies.Cookie() c[text_("foo")] = b"bar" c[b"foo"].secure = True assert c.serialize() == "foo=bar; secure" def test_cookie_httponly(): c = cookies.Cookie() c["foo"] = b"bar" c[b"foo"].httponly = True assert c.serialize() == "foo=bar; HttpOnly" def test_cookie_samesite_strict(): c = cookies.Cookie() c[b"foo"] = b"bar" c[b"foo"].samesite = b"Strict" assert c.serialize() == "foo=bar; SameSite=Strict" def test_cookie_samesite_lax(): c = cookies.Cookie() c[b"foo"] = b"bar" c[b"foo"].samesite = b"Lax" assert c.serialize() == "foo=bar; SameSite=Lax" def test_cookie_samesite_none(): c = cookies.Cookie() c[b"foo"] = b"bar" c[b"foo"].samesite = b"None" c[b"foo"].secure = True assert c.serialize() == "foo=bar; secure; SameSite=None" def test_cookie_samesite_none_not_secure(): c = cookies.Cookie() c[b"foo"] = b"bar" c[b"foo"].samesite = b"None" with pytest.raises(ValueError): c.serialize() def test_cookie_samesite_future__default(): # ensure default behavior when unsupported values are provided c = cookies.Cookie() with pytest.raises(ValueError) as excinfo: c[b"foo"] = b"bar" c[b"foo"].samesite = b"Future" c.serialize() assert excinfo.value.args[0] == "SameSite must be 'strict', 'lax', or 'none'" def test_cookie_samesite_future__monkeypatched(monkeypatch): # disable validation so future args pass monkeypatch.setattr(cookies, "SAMESITE_VALIDATION", False) c = cookies.Cookie() c[b"foo"] = b"bar" c[b"foo"].samesite = b"Future" assert c.serialize() == "foo=bar; SameSite=Future" # ensure we can toggle it to True and re-achieve default behavior... monkeypatch.setattr(cookies, "SAMESITE_VALIDATION", True) with pytest.raises(ValueError) as excinfo: c[b"foo"] = b"bar" c[b"foo"].samesite = b"Future" c.serialize() assert excinfo.value.args[0] == "SameSite must be 'strict', 'lax', or 'none'" def test_cookie_reserved_keys(): c = cookies.Cookie("dismiss-top=6; CP=null*; $version=42; a=42") assert "$version" not in c c = cookies.Cookie("$reserved=42; a=$42") assert list(c.keys()) == [b"a"] def test_serialize_cookie_date(): """ Testing webob.cookies.serialize_cookie_date. Missing scenarios: * input value is an str, should be returned verbatim * input value is an int, should be converted to timedelta and we should continue the rest of the process """ date_one = cookies.serialize_cookie_date(b"Tue, 04-Jan-2011 13:43:50 GMT") assert date_one == b"Tue, 04-Jan-2011 13:43:50 GMT" date_two = cookies.serialize_cookie_date(text_("Tue, 04-Jan-2011 13:43:50 GMT")) assert date_two == b"Tue, 04-Jan-2011 13:43:50 GMT" assert cookies.serialize_cookie_date(None) is None cdate_delta = cookies.serialize_cookie_date(timedelta(seconds=10)) cdate_int = cookies.serialize_cookie_date(10) assert cdate_delta == cdate_int def test_serialize_samesite(): assert cookies.serialize_samesite(b"Lax") == b"Lax" assert cookies.serialize_samesite(b"Strict") == b"Strict" assert cookies.serialize_samesite(b"None") == b"None" with pytest.raises(ValueError): cookies.serialize_samesite(b"SomethingElse") def test_ch_unquote(): assert cookies._unquote(b'"hello world') == b'"hello world' assert cookies._unquote(b"hello world") == b"hello world" assert cookies._unquote(b'"hello world"') == b"hello world" # Spaces are not valid in cookies, we support getting them, but won't # support sending them with pytest.raises(ValueError): cookies._value_quote(b"hello world") # quotation mark escaped w/ backslash is unquoted correctly (support # pre webob 1.3 cookies) assert cookies._unquote(b'"\\""') == b'"' # we also are able to unquote the newer \\042 serialization of quotation # mark assert cookies._unquote(b'"\\042"') == b'"' # New cookies can not contain quotes. with pytest.raises(ValueError): cookies._value_quote(b'"') # backslash escaped w/ backslash is unquoted correctly (support # pre webob 1.3 cookies) assert cookies._unquote(b'"\\\\"') == b"\\" # we also are able to unquote the newer \\134 serialization of backslash assert cookies._unquote(b'"\\134"') == b"\\" # Cookies may not contain a backslash with pytest.raises(ValueError): cookies._value_quote(b"\\") # misc byte escaped as octal assert cookies._unquote(b'"\\377"') == b"\xff" with pytest.raises(ValueError): cookies._value_quote(b"\xff") # combination assert cookies._unquote(b'"a\\"\\377"') == b'a"\xff' with pytest.raises(ValueError): cookies._value_quote(b'a"\xff') def test_cookie_invalid_name(): c = cookies.Cookie() c["La Pe\xc3\xb1a"] = "1" assert len(c) == 0 def test_morsel_serialize_with_expires(): morsel = cookies.Morsel(b"bleh", b"blah") morsel.expires = b"Tue, 04-Jan-2011 13:43:50 GMT" result = morsel.serialize() assert result, "bleh=blah; expires=Tue, 04-Jan-2011 13:43:50 GMT" def test_serialize_max_age_timedelta(): import datetime val = datetime.timedelta(86400) result = cookies.serialize_max_age(val) assert result == b"7464960000" def test_serialize_max_age_int(): val = 86400 result = cookies.serialize_max_age(val) assert result == b"86400" def test_serialize_max_age_str(): val = "86400" result = cookies.serialize_max_age(val) assert result == b"86400" def test_parse_qmark_in_val(): v = r'x="\"\073\054\""; expires=Sun, 12-Jun-2011 23:16:01 GMT' c = cookies.Cookie(v) assert c[b"x"].value == b'";,"' assert c[b"x"].expires, b"Sun == 12-Jun-2011 23:16:01 GMT" def test_morsel_repr(): v = cookies.Morsel(b"a", b"b") result = repr(v) assert result == "" class TestRequestCookies: def _makeOne(self, environ): from webob.cookies import RequestCookies return RequestCookies(environ) def test_get_no_cache_key_in_environ_no_http_cookie_header(self): environ = {} inst = self._makeOne(environ) assert inst.get("a") is None parsed = environ["webob._parsed_cookies"] assert parsed == ({}, "") def test_get_no_cache_key_in_environ_has_http_cookie_header(self): header = "a=1; b=2" environ = {"HTTP_COOKIE": header} inst = self._makeOne(environ) assert inst.get("a") == "1" parsed = environ["webob._parsed_cookies"][0] assert parsed["a"] == "1" assert parsed["b"] == "2" assert environ["HTTP_COOKIE"] == header # no change def test_get_cache_key_in_environ_no_http_cookie_header(self): environ = {"webob._parsed_cookies": ({}, "")} inst = self._makeOne(environ) assert inst.get("a") is None parsed = environ["webob._parsed_cookies"] assert parsed == ({}, "") def test_get_cache_key_in_environ_has_http_cookie_header(self): header = "a=1; b=2" environ = {"HTTP_COOKIE": header, "webob._parsed_cookies": ({}, "")} inst = self._makeOne(environ) assert inst.get("a") == "1" parsed = environ["webob._parsed_cookies"][0] assert parsed["a"] == "1" assert parsed["b"] == "2" assert environ["HTTP_COOKIE"] == header # no change def test_get_missing_with_default(self): environ = {} inst = self._makeOne(environ) assert inst.get("a", "") == "" def test___setitem__name_not_string_type(self): inst = self._makeOne({}) with pytest.raises(TypeError): inst.__setitem__(None, 1) def test___setitem__name_not_encodeable_to_ascii(self): name = str(b"La Pe\xc3\xb1a", "utf-8") inst = self._makeOne({}) with pytest.raises(TypeError): inst.__setitem__(name, "abc") def test___setitem__name_not_rfc2109_valid(self): name = "$a" inst = self._makeOne({}) with pytest.raises(TypeError): inst.__setitem__(name, "abc") def test___setitem__value_not_string_type(self): inst = self._makeOne({}) with pytest.raises(ValueError): inst.__setitem__("a", None) def test___setitem__value_not_utf_8_decodeable(self): value = text_(b"La Pe\xc3\xb1a", "utf-8") value = value.encode("utf-16") inst = self._makeOne({}) with pytest.raises(ValueError): inst.__setitem__("a", value) def test__setitem__success_no_existing_headers(self): value = str(b"test_cookie", "utf-8") environ = {} inst = self._makeOne(environ) inst["a"] = value assert environ["HTTP_COOKIE"] == "a=test_cookie" def test__setitem__success_append(self): value = str(b"test_cookie", "utf-8") environ = {"HTTP_COOKIE": "a=1; b=2"} inst = self._makeOne(environ) inst["c"] = value assert environ["HTTP_COOKIE"] == "a=1; b=2; c=test_cookie" def test__setitem__success_replace(self): environ = {"HTTP_COOKIE": 'a=1; b="La Pe\\303\\261a"; c=3'} inst = self._makeOne(environ) inst["b"] = "abc" assert environ["HTTP_COOKIE"] == "a=1; b=abc; c=3" inst["c"] = "4" assert environ["HTTP_COOKIE"] == "a=1; b=abc; c=4" def test__delitem__fail_no_http_cookie(self): environ = {} inst = self._makeOne(environ) with pytest.raises(KeyError): inst.__delitem__("a") assert environ == {} def test__delitem__fail_with_http_cookie(self): environ = {"HTTP_COOKIE": ""} inst = self._makeOne(environ) with pytest.raises(KeyError): inst.__delitem__("a") assert environ == {"HTTP_COOKIE": ""} def test__delitem__success(self): environ = {"HTTP_COOKIE": "a=1"} inst = self._makeOne(environ) del inst["a"] assert environ["HTTP_COOKIE"] == "" assert inst._cache == {} def test_keys(self): environ = {"HTTP_COOKIE": 'a=1; b="La Pe\\303\\261a"; c=3'} inst = self._makeOne(environ) assert sorted(list(inst.keys())) == ["a", "b", "c"] def test_values(self): val = text_(b"La Pe\xc3\xb1a", "utf-8") environ = {"HTTP_COOKIE": 'a=1; b="La Pe\\303\\261a"; c=3'} inst = self._makeOne(environ) assert sorted(list(inst.values())) == ["1", "3", val] def test_items(self): val = text_(b"La Pe\xc3\xb1a", "utf-8") environ = {"HTTP_COOKIE": 'a=1; b="La Pe\\303\\261a"; c=3'} inst = self._makeOne(environ) assert sorted(list(inst.items())) == [("a", "1"), ("b", val), ("c", "3")] def test_iterkeys_py3(self): environ = {"HTTP_COOKIE": b'a=1; b="La Pe\\303\\261a"; c=3'.decode("utf-8")} inst = self._makeOne(environ) assert sorted(list(inst.keys())) == ["a", "b", "c"] def test_itervalues_py3(self): val = text_(b"La Pe\xc3\xb1a", "utf-8") environ = {"HTTP_COOKIE": b'a=1; b="La Pe\\303\\261a"; c=3'.decode("utf-8")} inst = self._makeOne(environ) sorted(list(inst.values())) == ["1", "3", val] def test_iteritems_py3(self): val = text_(b"La Pe\xc3\xb1a", "utf-8") environ = {"HTTP_COOKIE": b'a=1; b="La Pe\\303\\261a"; c=3'.decode("utf-8")} inst = self._makeOne(environ) assert sorted(list(inst.items())) == [("a", "1"), ("b", val), ("c", "3")] def test___contains__(self): environ = {"HTTP_COOKIE": "a=1"} inst = self._makeOne(environ) assert "a" in inst assert "b" not in inst def test___iter__(self): environ = {"HTTP_COOKIE": "a=1; b=2; c=3"} inst = self._makeOne(environ) assert sorted(list(iter(inst))) == ["a", "b", "c"] def test___len__(self): environ = {"HTTP_COOKIE": "a=1; b=2; c=3"} inst = self._makeOne(environ) assert len(inst) == 3 del inst["a"] assert len(inst) == 2 def test_clear(self): environ = {"HTTP_COOKIE": "a=1; b=2; c=3"} inst = self._makeOne(environ) inst.clear() assert environ["HTTP_COOKIE"] == "" assert inst.get("a") is None def test___repr__(self): environ = {"HTTP_COOKIE": "a=1; b=2; c=3"} inst = self._makeOne(environ) r = repr(inst) assert r.startswith("") class TestCookieMakeCookie: def makeOne(self, name, value, **kw): from webob.cookies import make_cookie return make_cookie(name, value, **kw) def test_make_cookie_max_age(self): cookie = self.makeOne("test_cookie", "value", max_age=500) assert "test_cookie=value" in cookie assert "Max-Age=500;" in cookie assert "expires" in cookie def test_make_cookie_max_age_timedelta(self): from datetime import timedelta cookie = self.makeOne("test_cookie", "value", max_age=timedelta(seconds=500)) assert "test_cookie=value" in cookie assert "Max-Age=500;" in cookie assert "expires" in cookie assert "expires=500" not in cookie def test_make_cookie_max_age_str_valid_int(self): cookie = self.makeOne("test_cookie", "value", max_age="500") assert "test_cookie=value" in cookie assert "Max-Age=500;" in cookie assert "expires" in cookie assert "expires=500" not in cookie def test_make_cookie_max_age_str_invalid_int(self): with pytest.raises(ValueError): self.makeOne("test_cookie", "value", max_age="test") def test_make_cookie_comment(self): cookie = self.makeOne("test_cookie", "value", comment="lolwhy") assert "test_cookie=value" in cookie assert "Comment=lolwhy" in cookie def test_make_cookie_path(self): cookie = self.makeOne("test_cookie", "value", path="/foo/bar/baz") assert "test_cookie=value" in cookie assert "Path=/foo/bar/baz" in cookie @pytest.mark.parametrize("samesite", ["Strict", "Lax", "None"]) def test_make_cookie_samesite(self, samesite): cookie = self.makeOne("test_cookie", "value", samesite=samesite, secure=True) assert "test_cookie=value" in cookie assert "SameSite=" + samesite in cookie class CommonCookieProfile: def makeDummyRequest(self, **kw): class Dummy: def __init__(self, **kwargs): self.__dict__.update(**kwargs) d = Dummy(**kw) d.response = Dummy() d.response.headerlist = list() return d def makeOneRequest(self): request = self.makeDummyRequest(environ=dict()) request.environ["HTTP_HOST"] = "www.example.net" request.cookies = dict() return request class TestCookieProfile(CommonCookieProfile): def makeOne(self, name="uns", **kw): if "request" in kw: request = kw["request"] del kw["request"] else: request = self.makeOneRequest() from webob.cookies import CookieProfile return CookieProfile(name, **kw)(request) def test_cookie_creation(self): cookie = self.makeOne() from webob.cookies import CookieProfile assert isinstance(cookie, CookieProfile) def test_cookie_name(self): cookie = self.makeOne() cookie_list = cookie.get_headers("test") for cookie in cookie_list: assert cookie[1].startswith("uns") assert 'uns="";' not in cookie[1] def test_cookie_no_request(self): from webob.cookies import CookieProfile cookie = CookieProfile("uns") with pytest.raises(ValueError): cookie.get_value() def test_get_value_serializer_raises_value_error(self): class RaisingSerializer: def loads(self, val): raise ValueError("foo") cookie = self.makeOne(serializer=RaisingSerializer()) assert cookie.get_value() is None def test_with_cookies(self): request = self.makeOneRequest() request.cookies["uns"] = "InRlc3Qi" cookie = self.makeOne(request=request) ret = cookie.get_value() assert ret == "test" def test_with_invalid_cookies(self): request = self.makeOneRequest() request.cookies["uns"] = "InRlc3Q" cookie = self.makeOne(request=request) ret = cookie.get_value() assert ret is None class TestSignedCookieProfile(CommonCookieProfile): def makeOne(self, secret="seekrit", salt="salty", name="uns", **kw): if "request" in kw: request = kw["request"] del kw["request"] else: request = self.makeOneRequest() from webob.cookies import SignedCookieProfile as CookieProfile return CookieProfile(secret, salt, name, **kw)(request) def test_cookie_name(self): cookie = self.makeOne() cookie_list = cookie.get_headers("test") for cookie in cookie_list: assert cookie[1].startswith("uns") assert 'uns="";' not in cookie[1] def test_cookie_expire(self): cookie = self.makeOne() cookie_list = cookie.get_headers(None, max_age=0) for cookie in cookie_list: assert "Max-Age=0;" in cookie[1] def test_cookie_max_age(self): cookie = self.makeOne() cookie_list = cookie.get_headers("test", max_age=60) for cookie in cookie_list: assert "Max-Age=60;" in cookie[1] assert "expires=" in cookie[1] def test_cookie_raw(self): cookie = self.makeOne() cookie_list = cookie.get_headers("test") assert isinstance(cookie_list, list) def test_set_cookie(self): request = self.makeOneRequest() cookie = self.makeOne(request=request) ret = cookie.set_cookies(request.response, "test") assert ret == request.response def test_no_cookie(self): cookie = self.makeOne() ret = cookie.get_value() assert ret is None def test_with_cookies(self): request = self.makeOneRequest() request.cookies["uns"] = ( "FLIoEwZcKG6ITQSqbYcUNnPljwOcGNs25JRVCSoZcx_uX-OA1AhssA-CNeVKpWksQ" "a0ktMhuQDdjzmDwgzbptiJ0ZXN0Ig" ) cookie = self.makeOne(request=request) ret = cookie.get_value() assert ret == "test" def test_with_bad_cookie_invalid_base64(self): request = self.makeOneRequest() request.cookies["uns"] = ( "gAJVBHRlc3RxAS4KjKfwGmCkliC4ba99rWUdpy_{}riHzK7MQFPsbSgYTgALHa" "SHrRkd3lyE8c4w5ruxAKOyj2h5oF69Ix7ERZv_" ) cookie = self.makeOne(request=request) val = cookie.get_value() assert val is None def test_with_bad_cookie_invalid_signature(self): request = self.makeOneRequest() request.cookies["uns"] = ( "InRlc3QiFLIoEwZcKG6ITQSqbYcUNnPljwOcGNs25JRVCSoZcx/uX+OA1AhssA" "+CNeVKpWksQa0ktMhuQDdjzmDwgzbptg==" ) cookie = self.makeOne(secret="sekrit!", request=request) val = cookie.get_value() assert val is None def test_with_domain(self): cookie = self.makeOne(domains=("testing.example.net",)) ret = cookie.get_headers("test") passed = False for cookie in ret: if "Domain=testing.example.net" in cookie[1]: passed = True assert passed assert len(ret) == 1 def test_with_domains(self): cookie = self.makeOne(domains=("testing.example.net", "testing2.example.net")) ret = cookie.get_headers("test") passed = 0 for cookie in ret: if "Domain=testing.example.net" in cookie[1]: passed += 1 if "Domain=testing2.example.net" in cookie[1]: passed += 1 assert passed == 2 assert len(ret) == 2 def test_flag_secure(self): cookie = self.makeOne(secure=True) ret = cookie.get_headers("test") for cookie in ret: assert "; secure" in cookie[1] def test_flag_http_only(self): cookie = self.makeOne(httponly=True) ret = cookie.get_headers("test") for cookie in ret: assert "; HttpOnly" in cookie[1] @pytest.mark.parametrize("samesite", [b"Strict", b"Lax", b"None"]) def test_with_samesite_bytes(self, samesite): cookie = self.makeOne(samesite=samesite, secure=True) ret = cookie.get_headers("test") for cookie in ret: assert "; SameSite=" + samesite.decode("ascii") in cookie[1] @pytest.mark.parametrize("samesite", ["Strict", "Lax", "None"]) def test_with_samesite(self, samesite): cookie = self.makeOne(samesite=samesite, secure=True) ret = cookie.get_headers("test") for cookie in ret: assert "; SameSite=" + samesite in cookie[1] def test_cookie_length(self): cookie = self.makeOne() longstring = "a" * 4096 with pytest.raises(ValueError): cookie.get_headers(longstring) def test_very_long_key(self): longstring = "a" * 1024 cookie = self.makeOne(secret=longstring) cookie.get_headers("test") def serialize(secret, salt, data): import base64 from hashlib import sha1 import hmac import json from webob.util import bytes_ salted_secret = bytes_(salt or "", "utf-8") + bytes_(secret, "utf-8") cstruct = bytes_(json.dumps(data)) sig = hmac.new(salted_secret, cstruct, sha1).digest() return base64.urlsafe_b64encode(sig + cstruct).rstrip(b"=") class TestSignedSerializer: def makeOne(self, secret, salt, hashalg="sha1", **kw): from webob.cookies import SignedSerializer return SignedSerializer(secret, salt, hashalg=hashalg, **kw) def test_serialize(self): ser = self.makeOne("seekrit", "salty") assert ser.dumps("test") == serialize("seekrit", "salty", "test") def test_deserialize(self): ser = self.makeOne("seekrit", "salty") assert ser.loads(serialize("seekrit", "salty", "test")) == "test" def test_with_highorder_secret(self): secret = b"\xce\xb1\xce\xb2\xce\xb3\xce\xb4".decode("utf-8") ser = self.makeOne(secret, "salty") assert ser.loads(serialize(secret, "salty", "test")) == "test" def test_with_highorder_salt(self): salt = b"\xce\xb1\xce\xb2\xce\xb3\xce\xb4".decode("utf-8") ser = self.makeOne("secret", salt) assert ser.loads(serialize("secret", salt, "test")) == "test" # bw-compat with webob <= 1.3.1 where secrets were encoded with latin-1 def test_with_latin1_secret(self): secret = b"La Pe\xc3\xb1a" ser = self.makeOne(secret.decode("latin-1"), "salty") assert ser.loads(serialize(secret, "salty", "test")), "test" # bw-compat with webob <= 1.3.1 where salts were encoded with latin-1 def test_with_latin1_salt(self): salt = b"La Pe\xc3\xb1a" ser = self.makeOne("secret", salt.decode("latin-1")) assert ser.loads(serialize("secret", salt, "test")) == "test"