فهرست مقاله
فرض کنید یک ابزارِ آزمایشِ وب، برای همان دادهٔ ساختگی در دو نشانی نتیجهٔ متفاوتی میبیند: یک رکوردِ کوکی برای مسیرِ آزمایش نامزد میشود، اما برای مسیری با شروعِ ظاهراً مشابه نه. حدسِ «احتمالاً نشست خراب است» هنوز توضیحی قابل آزمون نیست. ابتدا میتوان بخشِ کوچکی از مسئله را جدا کرد: با میزبان، مسیر و چند پرچمِ ازپیشمشخصشده، کدام رکوردها از نظرِ دامنهٔ ارسال تطبیق دارند؟ این درس همین زیرمسئله را با پایتون و دادهٔ مصنوعی بررسی میکند؛ مرورگر یا شبکهای اجرا نمیشود.
۱. قرارداد مسئله؛ دامنهٔ ارسال با احراز هویت فرق دارد

هر رکورد یک شناسه، دامنه، پرچمِ host-only، مسیر و پرچمِ secure-only دارد. شناسه فقط برچسبِ همان ردیف در آزمایش است؛ نام و مقدارِ واقعیِ کوکی نیست و حکمِ کلیدِ ذخیرهسازیِ مرورگر را ندارد. هر درخواست نیز از یک میزبان، مسیر و مقدارِ بولیِ اعلامشده برای کانال تشکیل میشود. تابع برای هر درخواست، همهٔ شناسههای واجدِ سه شرط را با ترتیبِ ورودی برمیگرداند؛ خروجیِ تهی یعنی در همین قرارداد هیچ ردیفی تطبیق نکرده است.
پیش از این مرحله، فرض میکنیم رکوردها بهعنوانِ دادهٔ ذخیرهشده به ما داده شدهاند. پذیرشِ Set-Cookie، انقضا، سیاستِ شخصِ ثالث، SameSite، HttpOnly، تفکیکِ فضای ذخیرهسازی و تصمیمِ احراز هویت داخل تابع نیستند. حتی خروجیِ غیرتهی نیز مجوزِ انجامِ یک عمل نیست. در درسِ GET شرطی و ETag نیز مقایسهٔ نسخه از مجوز و انتخابِ بازنمایی جدا بود؛ اینجا بهجای نسخهٔ پاسخ، محدودهٔ رکوردِ همراهِ درخواست را بررسی میکنیم.
این جداسازی برای عیبیابی مفید است: اختلافِ دو نتیجه را میتوان به میزبان، مرزِ مسیر یا پرچمِ کانال نسبت داد، بدون آنکه رفتارِ یک مرورگرِ خاص را حدس بزنیم. رکوردهای مثال اعتبارنامه یا اطلاعاتِ شخصی ندارند. هدف نیز ساختِ یک بستهٔ کاملِ مدیریتِ نشست نیست؛ یک قراردادِ کوچک میسازیم که شاهدِ دستی، کد و مرجعِ مستقلِ آن کنار هم قابل خواندن باشند.
۲. میزبان، Domain و مرزِ host-only

در بخشِ انتخابِ کوکیِ RFC6265، پرچمِ host-only نقشِ روشنی دارد: در این حالت، میزبانِ درخواست باید با دامنهٔ رکورد برابر باشد. پس رکوردِ اختصاصیِ lab.test برای api.lab.test تطبیق نمیکند. اگر این پرچم خاموش باشد، برابری یا اشتراکِ دامنه در مرزِ کاملِ برچسب بررسی میشود. api.lab.test و x.api.lab.test میتوانند زیرِ lab.test قرار بگیرند، ولی evillab.test با وجودِ پایانِ شبیه، همان رابطه را ندارد.
ما نامها را از قبل به صورتِ حروفِ کوچکِ ASCII میگیریم. دستورِ ورودی، دو تا چهار برچسبِ یک تا دوازدهنویسهای دارد و به test ختم میشود؛ این پسوند، محدودهٔ مصنوعیِ آزمایش است. حروف و رقم و خطِ تیره درونِ برچسب مجازند، اما ابتدا و انتها باید حرف یا رقم باشد. نامِ خالی، نقطهٔ ابتدا یا انتها، IP، یونیکد و تبدیلِ IDNA پذیرفته نیستند. اینها محدودیتِ رابطِ خودماناند، نه فهرستِ همهٔ نامهای معتبر در اینترنت.
نقطهٔ ابتداییِ یک Domain خام را نیز این تابع تفسیر نمیکند. سندِ RFC دربارهٔ پردازشِ آن قاعده دارد، اما ورودیِ حاضر از مرحلهٔ تجزیه عبور کرده است. بنابراین ردکردنِ .lab.test در این API به معنیِ نامعتبر اعلامکردنِ همان ویژگی در تمامِ پیامهای HTTP نیست. فهرستِ پسوندهای عمومی و اجازهٔ ذخیرهکردنِ دامنه نیز جدا هستند؛ تابع فقط روی رکوردِ صریحِ دادهشده کار میکند.
۳. مسیر و کانال؛ چرا پیشوند کافی نیست؟

برای Path=/lab، درخواستِ /lab و /lab/run تطبیق دارند، اما /laboratory ندارد. تفاوت در نویسهٔ بعد از پیشوند است: اگر خودِ مسیرِ کوکی به / ختم نشده، ادامهٔ درخواست باید از مرزِ / عبور کند. برابریِ کامل نیز حالتِ جداگانهٔ مجاز است. به همین دلیل /lab/ برای درخواستِ کوتاهترِ /lab تطبیق نمیکند، هرچند /lab/run را پوشش میدهد. مسیرِ ریشهٔ / نیز برای مسیرهای پذیرفتهشدهٔ این آزمایش حالتِ قابل بررسیِ خودش را دارد.
مسیرها در این نمونه رشتههای صریحِ یک تا سیودو نویسهاند؛ با / آغاز میشوند و فقط حرفِ کوچک، رقم، /، خطِ تیره و زیرخط دارند. اسلشِ تکراری و انتهایی را حذف نمیکنیم. محاسبهٔ default-path از نشانیِ مبدأ، جداکردنِ query، درصدگشایی و رفعِ قطعههای نقطهدار اجرا نشدهاند. درسِ جفتهای پرسوجو و کدگذاریِ فرم نشان میدهد چرا بازکردنِ یک لایه، مسئلهای مستقل است؛ اینجا اصلاً مؤلفهٔ پرسوجو دریافت نمیکنیم.
شرطِ کانال را هم با شرطِ میزبان و مسیر مخلوط نمیکنیم. secure-only روشن، مقدارِ اعلامشدهٔ secure-transport را لازم دارد؛ خاموش بودنِ آن، کانالِ امنِ اعلامشده را ممنوع نمیکند. این مقدار فقط ورودیِ ساختگیِ ماست، نه نتیجهٔ مشاهدهٔ TLS. همچنین Path مرزِ قابل اتکای جداسازیِ امنیتی نیست و Secure بهتنهایی تضمینِ اصالت یا یکپارچگیِ نشست نمیسازد؛ این محدودیتها در همان سند توضیح داده شدهاند.
۴. برنامهٔ کامل و مرجع مستقلِ مرزها

رابط فقط tuple دقیق میپذیرد: حداکثر شش رکورد و دوازده درخواست. شناسهٔ صحیحِ دقیق از صفر تا سیویک است و تکرار نمیشود؛ bool جای شناسه و عدد جای پرچم قبول نیست. تمامِ ردیفها پیش از نخستین تطبیق بررسی میشوند، تا یک ردیفِ نامعتبرِ آخر پشتِ یک تطبیقِ زودهنگام پنهان نماند. این سقفها روی اشیایِ ازپیشدریافتشده اعمال میشوند؛ بودجهٔ دریافتِ شبکه یا اندازهگیریِ حافظهٔ فرایند نیستند.
مسیرِ اصلی، مرزِ نقطه و اسلش را با مقایسهٔ رشته بررسی میکند. مرجعِ مستقل بهجای تکرارِ همان تابع، رابطه میسازد: برچسبهای میزبان را جدا میکند و پسوندهای کاملِ برچسبی را نگه میدارد؛ از مسیرِ درخواست نیز برشهای پیش و پس از هر /، همراهِ رشتهٔ کامل، میسازد. سپس عضویتِ دامنه و مسیرِ کوکی در این مجموعهها را میسنجد. جدولِ دوحالتیِ کانال نیز جداست. مرجع برای شاهدهای معتبر است و اعتبارسنجِ دومِ ورودیِ خراب نیست.
کد را با نامِ cookie_domain_path_match.py ذخیره کنید. هدفِ اجرای این بسته CPython3.14.5 است و نسخهٔ واقعی در خروجی ثبت میشود؛ نسخهٔ مستنداتِ خواندهشده 3.14.9 است. مستندِ کاملِ http.cookiejar سیاستها و پروتکلهای قدیمیِ پشتیبانیشدهٔ آن را توضیح میدهد؛ این برنامه از CookieJar استفاده نمیکند و آزمونِ سازگاریِ آن یا مرورگر نیست. گذشتن از یک بررسیِ مقدماتیِ دامنه و مسیر نیز به معنیِ عبور از تمامِ سیاستهای بازگرداندنِ کوکی نیست.
"""Supplied stored-cookie scope only; no browser, HTTP parser or network."""
from itertools import product
import json
import platform
import sys
AUTHOR_EXECUTION_ENABLED = True
MAX_COOKIES = 6
MAX_REQUESTS = 12
MAX_LABEL_CHARS = 12
MAX_HOST_CHARS = 55
MAX_PATH_CHARS = 32
ALNUM = "abcdefghijklmnopqrstuvwxyz0123456789"
PATH_CHARS = ALNUM + "/_-"
def _host(value):
if type(value) is not str or not 1 <= len(value) <= MAX_HOST_CHARS:
raise ValueError("canonical_host_type_or_length")
labels = value.split(".")
if not 2 <= len(labels) <= 4 or labels[-1] != "test":
raise ValueError("declared_test_host")
for label in labels:
if not 1 <= len(label) <= MAX_LABEL_CHARS:
raise ValueError("host_label_length")
if label[0] not in ALNUM or label[-1] not in ALNUM:
raise ValueError("host_label_boundary")
if any(symbol not in ALNUM + "-" for symbol in label):
raise ValueError("canonical_ascii_host")
def _path(value):
if type(value) is not str or not 1 <= len(value) <= MAX_PATH_CHARS:
raise ValueError("supplied_path_type_or_length")
if value[0] != "/" or any(symbol not in PATH_CHARS for symbol in value):
raise ValueError("declared_literal_path")
def _admit(cookies, requests):
# Exact materialized tuples: no arbitrary iterators or coercion hooks.
if type(cookies) is not tuple or len(cookies) > MAX_COOKIES:
raise ValueError("cookies_type_or_count")
if type(requests) is not tuple or len(requests) > MAX_REQUESTS:
raise ValueError("requests_type_or_count")
seen = set()
for row in cookies:
if type(row) is not tuple or len(row) != 5:
raise ValueError("cookie_shape")
identifier, domain, host_only, cookie_path, secure_only = row
if type(identifier) is not int or not 0 <= identifier <= 31 or identifier in seen:
raise ValueError("unique_exact_cookie_ID")
seen.add(identifier)
if type(host_only) is not bool or type(secure_only) is not bool:
raise ValueError("exact_cookie_flags")
_host(domain)
_path(cookie_path)
for row in requests:
if type(row) is not tuple or len(row) != 3:
raise ValueError("request_shape")
host, path, secure_transport = row
if type(secure_transport) is not bool:
raise ValueError("exact_transport_flag")
_host(host)
_path(path)
def _cookie_matches(cookie, request):
_, domain, host_only, cookie_path, secure_only = cookie
host, request_path, secure_transport = request
domain_ok = host == domain or (not host_only and host.endswith("." + domain))
path_ok = request_path == cookie_path or (
request_path.startswith(cookie_path)
and (cookie_path.endswith("/")
or request_path[len(cookie_path):len(cookie_path) + 1] == "/"))
return domain_ok and path_ok and (not secure_only or secure_transport)
def matching_cookie_ids(cookies, requests):
"""Return every eligible ID in supplied order for each supplied request.
Only preclassified stored scope metadata is accepted. All input rows are
admitted before any matching, including rows after an earlier match.
This is not a Cookie header, winner, browser, parser or security approval.
"""
_admit(cookies, requests)
return tuple(tuple(cookie[0] for cookie in cookies
if _cookie_matches(cookie, request))
for request in requests)
def relation_reference(cookies, requests):
"""Known-valid fixture reference using label and slash-cut relations.
No main validator, prefix/suffix matcher, cookie helper or library jar is
used to construct expected values. It is not a malformed-input parser.
"""
result = []
transport_relation = {False: {False, True}, True: {True}}
for host, path, secure in requests:
labels = tuple(host.split("."))
host_scopes = {labels[index:] for index in range(len(labels) - 1)}
path_scopes = {path}
for index, symbol in enumerate(path):
if symbol == "/":
if index:
path_scopes.add(path[:index])
path_scopes.add(path[:index + 1])
eligible = []
for identifier, domain, host_only, cookie_path, secure_only in cookies:
wanted = tuple(domain.split("."))
allowed_hosts = {labels} if host_only else host_scopes
if (wanted in allowed_hosts and cookie_path in path_scopes
and secure in transport_relation[secure_only]):
eligible.append(identifier)
result.append(tuple(eligible))
return tuple(result)
def _rejects(cookies, requests):
try:
matching_cookie_ids(cookies, requests)
except ValueError:
return True
return False
def run_example():
checks = {}
def check(name, condition):
if type(condition) is not bool or not condition:
raise AssertionError(name)
checks[name] = condition
cookies = (
(10, "lab.test", True, "/lab", False),
(11, "lab.test", False, "/lab", False),
(12, "lab.test", False, "/lab/", True),
)
requests = (
("lab.test", "/lab", False),
("api.lab.test", "/lab/run", False),
("api.lab.test", "/lab/run", True),
("lab.test", "/laboratory", True),
("evillab.test", "/lab", True),
("lab.test", "/lab/", True),
)
expected = ((10, 11), (11,), (11, 12), (), (), (10, 11, 12))
check("manual_six_request_fixture",
matching_cookie_ids(cookies, requests) == expected
and relation_reference(cookies, requests) == expected)
exact_host = ((0, "lab.test", True, "/", False),)
check("host_only_requires_exact_canonical_host",
matching_cookie_ids(exact_host, (("lab.test", "/", False),
("api.lab.test", "/", False))) == ((0,), ()))
shared_domain = ((0, "lab.test", False, "/", False),)
check("domain_suffix_requires_a_whole_label_and_allows_nested_hosts",
matching_cookie_ids(shared_domain, (("lab.test", "/", False),
("x.api.lab.test", "/", False), ("evillab.test", "/", False),
("lab.test.other.test", "/", False))) == ((0,), (0,), (), ()))
scoped = ((0, "lab.test", True, "/lab", False),)
check("exact_supplied_path_matches",
matching_cookie_ids(scoped, (("lab.test", "/lab", False),)) == ((0,),))
check("plain_character_prefix_does_not_cross_a_path_boundary",
matching_cookie_ids(scoped, (("lab.test", "/lab/run", False),
("lab.test", "/laboratory", False),
("lab.test", "/lab-run", False))) == ((0,), (), ()))
trailing = ((0, "lab.test", True, "/lab/", False),)
check("trailing_slash_scope_is_asymmetric",
matching_cookie_ids(trailing, (("lab.test", "/lab", False),
("lab.test", "/lab/", False),
("lab.test", "/lab/run", False))) == ((), (0,), (0,)))
check("root_and_repeated_slashes_are_literal_supplied_paths",
matching_cookie_ids(exact_host, (("lab.test", "//lab", False),)) == ((0,),)
and matching_cookie_ids(trailing, (("lab.test", "/lab//run", False),)) == ((0,),)
and matching_cookie_ids(((0, "lab.test", True, "//lab", False),),
(("lab.test", "/lab", False),)) == ((),))
transport_rows = ((0, "lab.test", True, "/", False),
(1, "lab.test", True, "/", True))
check("secure_flag_is_a_separate_declared_transport_predicate",
matching_cookie_ids(transport_rows, (("lab.test", "/", False),
("lab.test", "/", True))) == ((0,), (0, 1)))
ordered = ((7, "lab.test", False, "/", False),
(2, "lab.test", False, "/", False),
(9, "lab.test", False, "/", False))
check("every_matching_ID_preserves_supplied_cookie_order",
matching_cookie_ids(ordered, (("lab.test", "/", True),)) == ((7, 2, 9),)
and matching_cookie_ids(tuple(reversed(ordered)),
(("lab.test", "/", True),)) == ((9, 2, 7),))
overlapping = ((5, "lab.test", False, "/", False),
(3, "api.lab.test", True, "/lab", False))
check("broad_and_narrow_scopes_coexist_without_a_winner",
matching_cookie_ids(overlapping, (("api.lab.test", "/lab/run", False),))
== ((5, 3),))
hosts = ("lab.test", "api.lab.test", "x.api.lab.test",
"evillab.test", "other.test", "a.lab.test")
paths = ("/", "/lab", "/lab/", "/lab/run", "/laboratory",
"//lab", "/lab//", "/a", "/ab")
queries = tuple(product(hosts, paths, (False, True)))
family_profiles = family_queries = family_API_batches = 0
for domain, host_only, path, secure_only in product(
("lab.test", "api.lab.test", "other.test"),
(False, True), paths, (False, True)):
fixture = ((0, domain, host_only, path, secure_only),)
for start in range(0, len(queries), MAX_REQUESTS):
batch = queries[start:start + MAX_REQUESTS]
if matching_cookie_ids(fixture, batch) != relation_reference(fixture, batch):
raise AssertionError("independent relation family")
family_queries += len(batch)
family_API_batches += 1
family_profiles += 1
check("independent_label_and_slash_cut_family_ONE_group",
family_profiles > 0 and family_queries > 0 and family_API_batches > 0)
class TupleChild(tuple):
pass
class StrChild(str):
def split(self, *args):
raise AssertionError("subclass hook must not run")
class IntChild(int):
pass
consumed = []
def lazy_requests():
consumed.append("pulled")
yield ("lab.test", "/", False)
one = ((0, "lab.test", True, "/", False),)
query = (("lab.test", "/", False),)
invalid = (
(None, query), ([], query), (TupleChild(one), query),
(one, None), (one, []), (one, TupleChild(query)), (one, lazy_requests()),
(((0, "lab.test", True, "/"),), query),
(([0, "lab.test", True, "/", False],), query),
(((True, "lab.test", True, "/", False),), query),
(((IntChild(0), "lab.test", True, "/", False),), query),
(one + one, query),
(((0, "lab.test", 1, "/", False),), query),
(((0, "lab.test", True, "/", 0),), query),
(((0, StrChild("lab.test"), True, "/", False),), query),
(((0, "LAB.test", True, "/", False),), query),
(((0, ".lab.test", True, "/", False),), query),
(((0, "lab.test.", True, "/", False),), query),
(((0, "a..test", True, "/", False),), query),
(((0, "-a.test", True, "/", False),), query),
(((0, "a-.test", True, "/", False),), query),
(((0, "a_b.test", True, "/", False),), query),
(((0, "127.0.0.1", True, "/", False),), query),
(((0, "lab.com", True, "/", False),), query),
(((0, "lab.test", True, "", False),), query),
(((0, "lab.test", True, "lab", False),), query),
(((0, "lab.test", True, "/a?b", False),), query),
(((0, "lab.test", True, "/a%2fb", False),), query),
(((0, "lab.test", True, "/a.b", False),), query),
(((0, "lab.test", True, StrChild("/"), False),), query),
(one, (("lab.test", "/", 1),)),
(one, ((StrChild("lab.test"), "/", False),)),
(one, (("lab.test", "/", False, "extra"),)),
(one, (["lab.test", "/", False],)),
(one + ((1, "lab.test", True, "/bad?", False),), query),
(one, query + (("lab.test", "/bad?", False),)),
)
original_matcher = globals()["_cookie_matches"]
entered = []
def forbidden_matcher(*args):
entered.append("entered")
raise AssertionError("complete admission must precede matching")
try:
globals()["_cookie_matches"] = forbidden_matcher
rejected = all(_rejects(a, b) for a, b in invalid)
finally:
globals()["_cookie_matches"] = original_matcher
check("whole_late_invalid_admission_precedes_all_matching_ONE_group",
rejected and entered == [] and consumed == [])
cap_cookies = tuple((i, "lab.test", True, "/", False)
for i in (0, 1, 2, 3, 4, 31))
cap_requests = query * MAX_REQUESTS
longest_host = ".".join(("a" * 12, "b" * 12, "c" * 12, "test"))
cap_bad = (
(cap_cookies + ((30, "lab.test", True, "/", False),), query),
(one, query * (MAX_REQUESTS + 1)),
(((32, "lab.test", True, "/", False),), query),
(((-1, "lab.test", True, "/", False),), query),
(((0, "a" * 13 + ".test", True, "/", False),), query),
(((0, "a.b.c.d.test", True, "/", False),), query),
(((0, "lab.test", True, "/" + "a" * 32, False),), query),
)
check("inclusive_resource_grammar_bounds_and_empty_batches",
matching_cookie_ids(cap_cookies, cap_requests) == ((0, 1, 2, 3, 4, 31),) * 12
and matching_cookie_ids(((31, longest_host, True, "/" + "a" * 31, False),),
((longest_host, "/" + "a" * 31, False),)) == ((31,),)
and len(longest_host) == 43
and matching_cookie_ids((), query) == ((),)
and matching_cookie_ids(one, ()) == ()
and all(_rejects(a, b) for a, b in cap_bad))
before = repr((cookies, requests))
first = matching_cookie_ids(cookies, requests)
matching_cookie_ids(shared_domain, (("other.test", "/", True),))
second = matching_cookie_ids(cookies, requests)
check("immutable_results_input_preservation_and_independent_calls",
first == second == expected and first is not second
and type(first) is tuple and all(type(row) is tuple for row in first)
and repr((cookies, requests)) == before)
assert len(checks) == 14 and all(type(x) is bool and x for x in checks.values())
return {
"runtime": {"python": platform.python_version(),
"implementation": platform.python_implementation()},
"scope": "Supplied canonical ASCII .test stored-cookie scope only",
"manual_cookies": cookies, "manual_requests": requests,
"manual_matching_IDs": first,
"output_order": "Every eligible ID in supplied cookie order; no winner",
"independent_family_ONE_group": {
"cookie_profiles": family_profiles, "request_queries": family_queries,
"API_batches": family_API_batches},
"invalid_profiles_ONE_group": len(invalid),
"invalid_bound_profiles_ONE_group": len(cap_bad),
"validation_probe_match_calls": len(entered),
"arbitrary_iterator_items_consumed": len(consumed),
"limits": {"cookies": MAX_COOKIES, "requests": MAX_REQUESTS,
"ID_inclusive_max": 31, "host_outer_character_cap": MAX_HOST_CHARS,
"host_label_count": [2, 4], "host_label_characters": MAX_LABEL_CHARS,
"largest_host_under_label_grammar": len(longest_host),
"path_characters": MAX_PATH_CHARS},
"unimplemented": [
"Cookie/Set-Cookie parsing, default paths, IDNA/IP/PSL and URL normalization",
"Expiry, SameSite, HttpOnly, partitioning, storage or Cookie-header ordering",
"Browser/network/TLS observation, authentication or complete security",
],
"groups_total": len(checks), "checks": checks,
}
if __name__ == "__main__":
if not AUTHOR_EXECUTION_ENABLED:
raise SystemExit("Unexecuted work draft; ROOT registration required")
sys.stdout.reconfigure(encoding="utf-8", newline="\n")
sys.stdout.write(json.dumps(run_example(), ensure_ascii=True,
indent=2, allow_nan=False) + "\n")
۵. نتیجهها، محدودیتها و چهار پرسشِ پاسخدار

در شاهدِ دستی، ردیفهای ده و یازده هر دو برای lab.test و /lab نامزد میشوند. برای api.lab.test و /lab/run روی کانالِ غیرامنِ اعلامشده، فقط یازده میماند؛ روشنکردنِ کانال، دوازده را نیز اضافه میکند. /laboratory و evillab.test هیچکدام از این سه ردیف را نمیگیرند. اینها خروجیِ تابعِ محدودند، نه سرآیندِ ارسالشده روی شبکه. ترتیبِ شناسهها عمداً ترتیبِ ورودی است و قاعدهٔ مرتبسازیِ Cookie header یا انتخابِ یک برنده را اجرا نمیکند.
چهارده گروهِ بولی، شاهدهای دستی، مرزهای میزبان و مسیر، کانال، چند تطبیق، اعتبارسنجی و استقلالِ فراخوانیها را در بر میگیرند. کلِ خانوادهٔ متناهیِ مقایسه با مرجع، فقط یک گروه است؛ تعدادِ نمایهها و درخواستهای واقعاً پیمودهشده جدا ثبت میشود. هیچ تعدادِ محدودی از این شاهدها را اثباتِ رفتارِ تمامِ مشتریانِ HTTP نمینامیم.
{
"runtime": {
"python": "3.14.5",
"implementation": "CPython"
},
"scope": "Supplied canonical ASCII .test stored-cookie scope only",
"manual_cookies": [
[
10,
"lab.test",
true,
"/lab",
false
],
[
11,
"lab.test",
false,
"/lab",
false
],
[
12,
"lab.test",
false,
"/lab/",
true
]
],
"manual_requests": [
[
"lab.test",
"/lab",
false
],
[
"api.lab.test",
"/lab/run",
false
],
[
"api.lab.test",
"/lab/run",
true
],
[
"lab.test",
"/laboratory",
true
],
[
"evillab.test",
"/lab",
true
],
[
"lab.test",
"/lab/",
true
]
],
"manual_matching_IDs": [
[
10,
11
],
[
11
],
[
11,
12
],
[],
[],
[
10,
11,
12
]
],
"output_order": "Every eligible ID in supplied cookie order; no winner",
"independent_family_ONE_group": {
"cookie_profiles": 108,
"request_queries": 11664,
"API_batches": 972
},
"invalid_profiles_ONE_group": 36,
"invalid_bound_profiles_ONE_group": 7,
"validation_probe_match_calls": 0,
"arbitrary_iterator_items_consumed": 0,
"limits": {
"cookies": 6,
"requests": 12,
"ID_inclusive_max": 31,
"host_outer_character_cap": 55,
"host_label_count": [
2,
4
],
"host_label_characters": 12,
"largest_host_under_label_grammar": 43,
"path_characters": 32
},
"unimplemented": [
"Cookie/Set-Cookie parsing, default paths, IDNA/IP/PSL and URL normalization",
"Expiry, SameSite, HttpOnly, partitioning, storage or Cookie-header ordering",
"Browser/network/TLS observation, authentication or complete security"
],
"groups_total": 14,
"checks": {
"manual_six_request_fixture": true,
"host_only_requires_exact_canonical_host": true,
"domain_suffix_requires_a_whole_label_and_allows_nested_hosts": true,
"exact_supplied_path_matches": true,
"plain_character_prefix_does_not_cross_a_path_boundary": true,
"trailing_slash_scope_is_asymmetric": true,
"root_and_repeated_slashes_are_literal_supplied_paths": true,
"secure_flag_is_a_separate_declared_transport_predicate": true,
"every_matching_ID_preserves_supplied_cookie_order": true,
"broad_and_narrow_scopes_coexist_without_a_winner": true,
"independent_label_and_slash_cut_family_ONE_group": true,
"whole_late_invalid_admission_precedes_all_matching_ONE_group": true,
"inclusive_resource_grammar_bounds_and_empty_batches": true,
"immutable_results_input_preservation_and_independent_calls": true
}
}
پرسش اول: آیا Domain=lab.test میزبانِ evillab.test را هم پوشش میدهد؟ پاسخ: خیر؛ آخرین برچسبهای کامل باید برابر باشند. api.lab.test مرزِ نقطه را دارد، اما شباهتِ پایانیِ evillab.test جای آن مرز را نمیگیرد. با host-only روشن حتی api.lab.test نیز کنار میرود.
پرسش دوم: آیا Path=/lab برای /laboratory کافی است؟ پاسخ: خیر؛ پس از /lab نه رشته تمام شده و نه / آمده است. /lab/run تطبیق دارد. این مثال نشان میدهد آزمونِ سادهٔ startswith بهتنهایی برای این قرارداد کافی نیست.
پرسش سوم: چرا /lab/ با /lab برابر نیست؟ پاسخ: رشتهٔ بلندترِ مسیرِ کوکی، پیشوندِ درخواستِ کوتاهتر نیست. اما برای /lab/run، همان پیشوند به اسلش ختم میشود و شرطِ مسیر برقرار است. تابع این تفاوت را با حذفِ خودکارِ اسلش اصلاح نمیکند.
پرسش چهارم: آیا موفقیتِ بررسیها امنیتِ نشست را تأیید میکند؟ پاسخ: خیر؛ فقط سه شرطِ انتخاب روی دادههای پذیرفتهشده آزموده شدهاند. انقضا، parsing، SameSite، HttpOnly، PSL، ذخیرهسازی، احراز هویت و رفتارِ واقعیِ مرورگر خارج از قراردادند. تاریخِ دسترسیِ منابع ۳۰ مهر ۱۴۰۵ است؛ انتشارِ RFC در آوریلِ ۲۰۱۱، نسخهٔ سندِ زنده و اجرای برنامه سه واقعیتِ جدا هستند. دو متنِ مرتبط نیز از نسخههای ذخیرهشدهٔ قبلاً منتشرشده خوانده شدهاند، نه مشاهدهٔ تازهٔ دسترسپذیریِ سایت.
منابع و مطالعهٔ بیشتر
- RFC6265؛ دامنه، مسیر، host-only و مرزِ تضمینها
- Python3.14 http.cookiejar؛ سیاستهای دریافت و بازگرداندن کوکی
بررسی منابع: ۳۰ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

