A high performance, highly optimized multi-platform implementation of Punycode and IDNA (Internationalized Domain Names in Applications) as per RFC 5891 and friends.
- The only dependency of
swift-idnaisswift-collections, and it does not depend onFoundation. - Unit tests extensively run against 6400+ Unicode 17 test cases.
- The C code is all auto-generated from some Unicode files.
Initialize IDNA with your preferred configuration, then use toASCII(domainName:) and toUnicode(domainName:):
import SwiftIDNA
let idna = IDNA(configuration: .mostStrict)
/// Turn user input into a IDNA-compatible domain name using toASCII:
print(idna.toASCII(domainName: "新华网.中国"))
/// prints "xn--xkrr14bows.xn--fiqs8s"
/// Turn back an IDNA-compatible domain name to its Unicode representation using toUnicode:
print(idna.toUnicode(domainName: "xn--xkrr14bows.xn--fiqs8s"))
/// prints "新华网.中国"Domain names are inherently case-insensitive, and they will always be lowercased.
This package uses Unicode 17's IDNA test v2 suite with ~6400 test cases to ensure full compatibility.
Runs each test case extensively so each test case might even result in 2-3-4-5 test runs.
This testing facility enables the implementation to be highly optimized.
For example this packages uses unchecked element accessors everywhere, which do not do bounds checks.
This is only made possible thanks to the massive test suite: We expect the tests to reveal any implementation issues and incorrect element accesses.
swift-idna implements short-circuits in both toASCII and toUnicode functions to avoid IDNA conversions when possible.
The C code is all automatically generated using the 2 scripts in utils/:
IDNAMappingTableGenerator.swiftgenerates the IDNA mapping lookup table.IDNATestV2Generator.swiftgenerates the IDNA test v2 suite cases to use in tests to ensure full compatibility.
Current supported IDNA flags:
- checkHyphens
- checkBidi
- checkJoiners
- useSTD3ASCIIRules
- transitionalProcessing (deprecated, Unicode discourages support for this flag although it's trivial to support)
- verifyDnsLength
- ignoreInvalidPunycode
- replaceBadCharacters
- This last one is not a strict part of IDNA, and is only "recommended" to implement.
- To see up to date information about performance of this package, please go to this benchmarks list, and choose the most recent benchmark. You'll see a summary of the benchmark there.
- The results below are all reproducible by simply running
scripts/benchmark.bashon a machine of your own. - swift-foundation applies short-circuits of its own for ascii domain names so it should perform better than ICU (but likely still not as good as swift-idna).
Note
swift-idna wins the Malloc count benchmarks by far.
ICU wins the Non-ASCII Domain Names CPU time, by a bit.
swift-idna wins ASCII Domain Names CPU time by far.
| Benchmark | Foundation | swift-idna | Improv. Ratio |
|---|---|---|---|
| To_ASCII_Lax_öob_dot_se_CPU_300K | 80ms | 80ms | 1x |
| To_ASCII_Lax_生命之花_dot_中国_CPU_200K | 80ms | 120ms | 0.67x |
| To_Unicode_Lax_öob_dot_se_CPU_300K | 100ms | 80ms | 1.25x |
| To_Unicode_Lax_生命之花_dot_中国_CPU_200K | 110ms | 110ms | 1x |
| Benchmark | Foundation | swift-idna | Improv. Ratio |
|---|---|---|---|
| To_ASCII_Lax_öob_dot_se_Malloc | 2 | 1 | 2x |
| To_ASCII_Lax_生命之花_dot_中国_Malloc | 5 | 4 | 1.25x |
| To_Unicode_Lax_öob_dot_se_Malloc | 1 | 1 | 1x |
| To_Unicode_Lax_生命之花_dot_中国_Malloc | 4 | 3 | 1.33x |
| Benchmark | Foundation | swift-idna | Improv. Ratio |
|---|---|---|---|
| To_ASCII_Lowercased_app-analytics-services_dot_com_CPU_5M | 610ms | 140ms | 4.36x |
| To_ASCII_Lowercased_google_dot_com_CPU_8M | 650ms | 180ms | 3.61x |
| To_ASCII_Uppercased_app-analytics-services_dot_com_CPU_3M | 340ms | 180ms | 1.89x |
| To_ASCII_Uppercased_google_dot_com_CPU_5M | 380ms | 140ms | 2.71x |
| To_Unicode_Lowercased_app-analytics-services_dot_com_CPU_4M | 470ms | 170ms | 2.76x |
| To_Unicode_Lowercased_google_dot_com_CPU_8M | 650ms | 230ms | 2.83x |
| To_Unicode_Uppercased_app-analytics-services_dot_com_CPU_4M | 440ms | 250ms | 1.76x |
| To_Unicode_Uppercased_google_dot_com_CPU_5M | 380ms | 180ms | 2.11x |
| Benchmark | Foundation | swift-idna | Improv. Ratio |
|---|---|---|---|
| To_ASCII_Lowercased_app-analytics-services_dot_com_Malloc | 2 | 0 | ∞ |
| To_ASCII_Lowercased_google_dot_com_Malloc | 1 | 0 | ∞ |
| To_ASCII_Uppercased_app-analytics-services_dot_com_Malloc | 2 | 1 | 2x |
| To_ASCII_Uppercased_google_dot_com_Malloc | 1 | 0 | ∞ |
| To_Unicode_Lowercased_app-analytics-services_dot_com_Malloc | 2 | 0 | ∞ |
| To_Unicode_Lowercased_google_dot_com_Malloc | 1 | 0 | ∞ |
| To_Unicode_Uppercased_app-analytics-services_dot_com_Malloc | 2 | 1 | 2x |
| To_Unicode_Uppercased_google_dot_com_Malloc | 1 | 0 | ∞ |
To use the swift-idna library in a SwiftPM project,
add the following line to the dependencies in your Package.swift file:
.package(url: "https://github.com/mahdibm/swift-idna.git", from: "1.0.0-beta.15"),Include SwiftIDNA as a dependency for your targets:
.target(name: "<target>", dependencies: [
.product(name: "SwiftIDNA", package: "swift-idna"),
]),Finally, add import SwiftIDNA to your source code.
This package was initially a part of swift-dns which I decided to decouple from that project.
Currently it's used in swift-endpoint, which swift-dns relies on.