@@ -35,6 +35,43 @@ public enum Punycode {
35
35
/// Delimiter used in Punycode to separate basic and encoded code points.
36
36
private static let labelDelimiter : Character = " - "
37
37
38
+ /// Encodes a full domain name (e.g. `bücher.com` or `🍿.com`) using IDNA rules.
39
+ ///
40
+ /// This function splits the domain into labels, encodes each with Punycode if needed,
41
+ /// and prepends "xn--" to any encoded label as per IDNA.
42
+ ///
43
+ /// - Parameter domain: A full domain name (may include Unicode).
44
+ /// - Returns: The encoded domain (e.g. "xn--bcher-kva.com")
45
+ public static func encodeDomain( _ domain: String ) throws -> String {
46
+ return try domain
47
+ . split ( separator: " . " )
48
+ . map { label in
49
+ if label. allSatisfy ( \. isASCII) {
50
+ return String ( label)
51
+ } else {
52
+ let encoded = try Punycode . encode ( String ( label) )
53
+ return " xn-- " + encoded
54
+ }
55
+ } . joined ( separator: " . " )
56
+ }
57
+
58
+ /// Decodes a full Punycode-based domain back to its Unicode representation.
59
+ ///
60
+ /// - Parameter domain: A domain name with potential "xn--" labels.
61
+ /// - Returns: A Unicode-decoded domain name.
62
+ public static func decodeDomain( _ domain: String ) throws -> String {
63
+ return try domain
64
+ . split ( separator: " . " )
65
+ . map { label in
66
+ if label. hasPrefix ( " xn-- " ) {
67
+ let punycode = String ( label. dropFirst ( 4 ) )
68
+ return try Punycode . decode ( punycode)
69
+ } else {
70
+ return String ( label)
71
+ }
72
+ } . joined ( separator: " . " )
73
+ }
74
+
38
75
/// Decodes a Punycode string into a Unicode string.
39
76
///
40
77
/// - Parameter punycode: The Punycode-encoded string (must be ASCII only).
0 commit comments