Coverage for /pythoncovmergedfiles/medio/medio/src/turbohtml/tools/fuzz/idna_nfc_oracles.py: 70%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Canonical-equivalent labels need an independent target because stable wrong hosts satisfy idempotence."""
3from __future__ import annotations
5import re
6import unicodedata
7from typing import TYPE_CHECKING, Final
9from turbohtml.extract import normalize_url
11if TYPE_CHECKING:
12 import random
13 from collections.abc import Callable
16def idna_nfc_check(case: str, normalize: Callable[[str], str] = normalize_url) -> str | None:
17 """Pin equivalent spellings to an independent target instead of trusting their agreement."""
18 variant, separator, affix = case.partition(":")
19 if not separator or variant not in _LABELS or re.fullmatch(r"[a-z0-9]{0,8}", affix) is None:
20 raise UnsupportedIdnaNfcCaseError(case)
21 source: Final = affix + _LABELS[variant]
22 composed: Final = unicodedata.normalize("NFC", source)
23 expected: Final = f"https://xn--{composed.encode('punycode').decode('ascii')}.example/"
24 for label in (source, composed, unicodedata.normalize("NFD", source)):
25 try:
26 output = normalize(f"https://{label}.example/")
27 except ValueError:
28 return "rejects known-valid canonical host"
29 if output != expected:
30 return "canonical host differs from independent target"
31 try:
32 repeated: Final = normalize(expected)
33 except ValueError:
34 return "rejects canonical host output"
35 return None if repeated == expected else "canonical host changes on repeat"
38def idna_nfc_generate(rng: random.Random) -> str:
39 """Limit labels to established Unicode-16 mappings without unrelated IDNA policies."""
40 return f"{rng.choice(tuple(_LABELS))}:g{rng.randrange(10000)}"
43def idna_nfc_seeds() -> list[str]:
44 """Exercise composition and ordering families before random generation."""
45 return [f"{variant}:g{index}" for variant in _LABELS for index in range(20)]
48def idna_nfc_controls() -> dict[str, bool]:
49 """Reject stable wrong results as well as unstable canonical output."""
50 return {
51 "unchanged Unicode": idna_nfc_check("reorder:", lambda text: text) is not None,
52 "stable wrong target": idna_nfc_check("reorder:", lambda _text: "https://wrong.example/") is not None,
53 "changed repeat": idna_nfc_check("reorder:", lambda text: text + "x" if text.isascii() else normalize_url(text))
54 is not None,
55 }
58class UnsupportedIdnaNfcCaseError(ValueError):
59 """Keep unsupported fixture grammar distinct from rejection of valid hosts."""
62_LABELS: Final[dict[str, str]] = {
63 "reorder": "q\u0301\u0323",
64 "blocked": "a\u0305\u0301",
65 "successive-compose": "a\u030a\u0301",
66 "noncomposing-tail": "s\u0323\u0301",
67 "lower-class-intervening": "a\u0316\u0301",
68 "hangul-lv": "\u1100\u1161",
69 "hangul-lvt": "\u1100\u1161\u11a8",
70 "hangul-lv-plus-t": "\uac00\u11a8",
71}
73__all__ = [
74 "UnsupportedIdnaNfcCaseError",
75 "idna_nfc_check",
76 "idna_nfc_controls",
77 "idna_nfc_generate",
78 "idna_nfc_seeds",
79]