Skip to content

Commit 38efcb9

Browse files
[py] Align _cookie_matches domain and path matching with RFC 6265
1. In domain matching, strip any optional leading dot per RFC 6265 §5.1.3 so cookies with domain 'example.com' match subdomains (e.g. sub.example.com). 2. Canonicalize cookie domain and request hostname to lowercase for case-insensitive domain matching. 3. In path matching, handle cookie paths with trailing slashes (e.g. /docs/) so that subpaths (e.g. /docs/page) match correctly instead of attempting a prefix match against double slashes (/docs//).
1 parent 301eec3 commit 38efcb9

2 files changed

Lines changed: 31 additions & 11 deletions

File tree

‎py/selenium/webdriver/common/api_request_context.py‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,29 +119,33 @@ def _cookie_matches(cookie: dict, url: str, default_domain: str = "") -> bool:
119119
return False
120120

121121
parsed = urllib.parse.urlparse(url)
122-
hostname = parsed.hostname or ""
122+
hostname = (parsed.hostname or "").lower()
123123
path = parsed.path or "/"
124124
scheme = parsed.scheme or "http"
125125

126126
# Domain matching (RFC 6265 section 5.1.3)
127-
cookie_domain = cookie.get("domain", "")
127+
cookie_domain = (cookie.get("domain") or "").lower()
128128
if not cookie_domain:
129-
# Host-only cookie — must match the origin host exactly
130-
if not default_domain or hostname != default_domain:
131-
return False
132-
elif cookie_domain.startswith("."):
133-
# .example.com matches example.com and sub.example.com
134-
if not (hostname == cookie_domain[1:] or hostname.endswith(cookie_domain)):
129+
# Host-only cookie - must match the origin host exactly
130+
if not default_domain or hostname != default_domain.lower():
135131
return False
136132
else:
137-
if hostname != cookie_domain:
133+
# Strip optional leading dot per RFC 6265 section 5.1.3
134+
norm_domain = cookie_domain[1:] if cookie_domain.startswith(".") else cookie_domain
135+
if not (hostname == norm_domain or hostname.endswith("." + norm_domain)):
138136
return False
139137

140138
# Path matching (RFC 6265 section 5.1.4)
141-
cookie_path = cookie.get("path", "/")
139+
cookie_path = cookie.get("path") or "/"
142140
if cookie_path == "/":
143141
pass # root path matches everything
144-
elif path != cookie_path and not path.startswith(cookie_path + "/"):
142+
elif path == cookie_path:
143+
pass
144+
elif cookie_path.endswith("/") and path.startswith(cookie_path):
145+
pass
146+
elif not cookie_path.endswith("/") and path.startswith(cookie_path + "/"):
147+
pass
148+
else:
145149
return False
146150

147151
# Secure matching

‎py/test/unit/selenium/webdriver/common/api_request_context_tests.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,22 @@ def test_missing_path_defaults_root():
294294
assert _cookie_matches({"name": "a", "value": "1", "domain": "example.com"}, "http://example.com/anything")
295295

296296

297+
298+
299+
def test_domain_without_leading_dot_matches_subdomain():
300+
assert _cookie_matches({"name": "a", "value": "1", "domain": "example.com"}, "http://sub.example.com/")
301+
302+
303+
def test_domain_matching_is_case_insensitive():
304+
assert _cookie_matches({"name": "a", "value": "1", "domain": "EXAMPLE.COM"}, "http://example.com/")
305+
assert _cookie_matches({"name": "a", "value": "1", "domain": "example.com"}, "http://EXAMPLE.COM/")
306+
307+
308+
def test_path_with_trailing_slash_matches_subpath():
309+
assert _cookie_matches(
310+
{"name": "a", "value": "1", "domain": "example.com", "path": "/docs/"}, "http://example.com/docs/page"
311+
)
312+
297313
# --- Secure ---
298314

299315

0 commit comments

Comments
 (0)