diff options
author | David Lord <davidism@gmail.com> | 2023-05-01 07:12:47 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-01 07:12:47 -0700 |
commit | 53f571cf3a6aafc1f46a18d7f893652997dc9cb9 (patch) | |
tree | 711b08cf88525f119bab14fc03954ee7bda4d072 | |
parent | 942dab369fece304df9e8966fb4b33adc9fa6cf9 (diff) | |
parent | 9004c05320763e902927af9d98c1e4a1c651e776 (diff) | |
download | werkzeug-53f571cf3a6aafc1f46a18d7f893652997dc9cb9.tar.gz |
cookie sets `Path=/` everywhere by default again (#2680)
-rw-r--r-- | CHANGES.rst | 5 | ||||
-rw-r--r-- | src/werkzeug/http.py | 2 | ||||
-rw-r--r-- | src/werkzeug/sansio/response.py | 4 | ||||
-rw-r--r-- | src/werkzeug/test.py | 32 | ||||
-rw-r--r-- | tests/test_test.py | 19 | ||||
-rw-r--r-- | tests/test_wrappers.py | 2 |
6 files changed, 43 insertions, 21 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 9b83a5d8..075ca2bc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Version 2.3.3 Unreleased +- The cookie ``Path`` attribute is set to ``/`` by default again, to prevent clients + from falling back to RFC 6265's ``default-path`` behavior. :issue:`2672, 2679` + Version 2.3.2 ------------- @@ -14,8 +17,6 @@ Released 2023-04-28 - Parse the cookie ``Expires`` attribute correctly in the test client. :issue:`2669` - ``max_content_length`` can only be enforced on streaming requests if the server sets ``wsgi.input_terminated``. :issue:`2668` -- The cookie ``Path`` attribute is set to ``/`` by default again, to prevent clients - from falling back to RFC 6265's ``default-path`` behavior. :issue:`2672` Version 2.3.1 diff --git a/src/werkzeug/http.py b/src/werkzeug/http.py index 7edd1d3b..d38ca9a5 100644 --- a/src/werkzeug/http.py +++ b/src/werkzeug/http.py @@ -1388,7 +1388,7 @@ def dump_cookie( .. _`cookie`: http://browsercookielimits.squawky.net/ - .. versionchanged:: 2.3.2 + .. versionchanged:: 2.3.3 The ``path`` parameter is ``/`` by default. .. versionchanged:: 2.3.1 diff --git a/src/werkzeug/sansio/response.py b/src/werkzeug/sansio/response.py index fcbc03ea..387a4ae7 100644 --- a/src/werkzeug/sansio/response.py +++ b/src/werkzeug/sansio/response.py @@ -225,7 +225,7 @@ class Response: value: str = "", max_age: timedelta | int | None = None, expires: str | datetime | int | float | None = None, - path: str | None = None, + path: str | None = "/", domain: str | None = None, secure: bool = False, httponly: bool = False, @@ -276,7 +276,7 @@ class Response: def delete_cookie( self, key: str, - path: str | None = None, + path: str | None = "/", domain: str | None = None, secure: bool = False, httponly: bool = False, diff --git a/src/werkzeug/test.py b/src/werkzeug/test.py index 0f61d0ca..012ec30b 100644 --- a/src/werkzeug/test.py +++ b/src/werkzeug/test.py @@ -948,7 +948,7 @@ class Client: value = args[0] cookie = Cookie._from_response_header( - domain, dump_cookie(key, value, domain=domain, path=path, **kwargs) + domain, "/", dump_cookie(key, value, domain=domain, path=path, **kwargs) ) cookie.origin_only = origin_only @@ -1037,7 +1037,7 @@ class Client: environ.pop("HTTP_COOKIE", None) def _update_cookies_from_response( - self, server_name: str, headers: list[str] + self, server_name: str, path: str, headers: list[str] ) -> None: """If cookies are enabled, update the stored cookies from any ``Set-Cookie`` headers in the response. @@ -1050,7 +1050,7 @@ class Client: return for header in headers: - cookie = Cookie._from_response_header(server_name, header) + cookie = Cookie._from_response_header(server_name, path, header) if cookie._should_delete: self._cookies.pop(cookie._storage_key, None) @@ -1066,8 +1066,10 @@ class Client: """ self._add_cookies_to_wsgi(environ) rv = run_wsgi_app(self.application, environ, buffered=buffered) - server_name = urlsplit(get_current_url(environ)).hostname or "localhost" - self._update_cookies_from_response(server_name, rv[2].getlist("Set-Cookie")) + url = urlsplit(get_current_url(environ)) + self._update_cookies_from_response( + url.hostname or "localhost", url.path, rv[2].getlist("Set-Cookie") + ) return rv def resolve_redirect( @@ -1506,7 +1508,7 @@ class Cookie: return f"{self.key}={self.value}" @classmethod - def _from_response_header(cls, server_name: str, header: str) -> te.Self: + def _from_response_header(cls, server_name: str, path: str, header: str) -> te.Self: header, _, parameters_str = header.partition(";") key, _, value = header.partition("=") decoded_key, decoded_value = next(parse_cookie(header).items()) @@ -1514,21 +1516,21 @@ class Cookie: for item in parameters_str.split(";"): k, sep, v = item.partition("=") - params[k.strip()] = v.strip() if sep else None + params[k.strip().lower()] = v.strip() if sep else None return cls( key=key.strip(), value=value.strip(), decoded_key=decoded_key, decoded_value=decoded_value, - expires=parse_date(params.get("Expires")), - max_age=int(params["Max-Age"] or 0) if "Max-Age" in params else None, - domain=params.get("Domain", server_name) or server_name, - origin_only="Domain" not in params, - path=params.get("Path", "/") or "/", - secure="Secure" in params, - http_only="HttpOnly" in params, - same_site=params.get("SameSite"), + expires=parse_date(params.get("expires")), + max_age=int(params["max-age"] or 0) if "max-age" in params else None, + domain=params.get("domain") or server_name, + origin_only="domain" not in params, + path=params.get("path") or path.rpartition("/")[0] or "/", + secure="secure" in params, + http_only="httponly" in params, + same_site=params.get("samesite"), ) @property diff --git a/tests/test_test.py b/tests/test_test.py index e3a0fff8..a7390008 100644 --- a/tests/test_test.py +++ b/tests/test_test.py @@ -117,6 +117,25 @@ def test_cookie_for_different_path(): assert response.text == "test=test" +def test_cookie_default_path() -> None: + """When no path is set for a cookie, the default uses everything up to but not + including the first slash. + """ + + @Request.application + def app(request: Request) -> Response: + r = Response() + r.set_cookie("k", "v", path=None) + return r + + c = Client(app) + c.get("/nested/leaf") + assert c.get_cookie("k") is None + assert c.get_cookie("k", path="/nested") is not None + c.get("/nested/dir/") + assert c.get_cookie("k", path="/nested/dir") is not None + + def test_environ_builder_basics(): b = EnvironBuilder() assert b.content_type is None diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 2e257cea..8a91aefc 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -271,7 +271,7 @@ def test_base_response(): ("Content-Type", "text/plain; charset=utf-8"), ( "Set-Cookie", - "foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0", + "foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/", ), ] |