|
3 | 3 | # its IDNA A-label (Punycode) form, and that it leaves an ASCII host name |
4 | 4 | # alone. Only the labels that are not ASCII get rewritten, so an address |
5 | 5 | # literal, an already-punycoded name, and a name that isn't a valid IDN all |
6 | | -# reach the resolver as typed. |
| 6 | +# reach the resolver as typed. A name typed with combining marks is normalized |
| 7 | +# on the way, so it converts the same as its precomposed spelling. |
7 | 8 | # |
8 | 9 | # Two daemon connection methods carry the host name out of rsync, so both are |
9 | 10 | # checked: |
@@ -55,6 +56,10 @@ def find_utf8_locale(): |
55 | 56 |
|
56 | 57 | idn_host = "\u010ci\u010dku.example" |
57 | 58 | ascii_host = "xn--iku-eqab.example" |
| 59 | +# The same name with each caron letter spelled as a plain "c" plus a combining |
| 60 | +# caron (U+030C). Unicode calls the two spellings equivalent, so both have to |
| 61 | +# come out as the same A-label; libidn2 is what normalizes them. |
| 62 | +nfd_host = "c\u030ci" "c\u030cku.example" |
58 | 63 |
|
59 | 64 | env = os.environ.copy() |
60 | 65 | env['LC_ALL'] = utf8_locale |
@@ -103,6 +108,9 @@ def check_rsh(url_host, want, what): |
103 | 108 | # A U-label becomes its A-label, case-folded by the IDNA mapping. An ASCII |
104 | 109 | # label is handed on byte for byte, case included, since DNS doesn't care. |
105 | 110 | check_rsh(idn_host, ascii_host, "a Unicode host") |
| 111 | +check_rsh(nfd_host, ascii_host, "a decomposed Unicode host") |
| 112 | +check_rsh("C\u030cI" "C\u030cKU.Example", "xn--iku-eqab.Example", |
| 113 | + "a decomposed mixed-case Unicode host") |
106 | 114 | check_rsh("ČIČKU.Example", "xn--iku-eqab.Example", |
107 | 115 | "a mixed-case Unicode host") |
108 | 116 | check_rsh(ascii_host, ascii_host, "an already-punycoded host") |
|
0 commit comments