A dependency-free, multiplatform implementation of Punycode and IDNA (Internationalized Domain Names in Applications) as per RFC 5891 and friends.
Initialize IDNA
with your preffered 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.
swift-idna
does short-circuit checks in both toASCII
and toUnicode
functions to avoid IDNA conversions when possible.
swift-idna
also provides public functions to check if a sequence of bytes will change at all after going through IDNA's ToASCII
conversion.
Note that these public functions are not sufficient to assert that a ToUnicode
conversion will also have no effect on the string.
For ToUnicode
, simply use the toUnicode
functions and they will automatically skip the conversion if not needed.
IDNA.performCharacterCheck(on: String)
IDNA.performCharacterCheck(on: Substring)
IDNA.performCharacterCheck(on: Span<UInt>)
IDNA.performCharacterCheck(onDNSWireFormatSpan: Span<UInt8>)
swift-idna
also provides public functions to turn an uppercased ASCII byte into lowercased, as well as a few more useful functions.
BinaryInteger.toLowercasedASCIILetter()
BinaryInteger._uncheckedToLowercasedASCIILetterAssumingUppercasedLetter()
BinaryInteger.isUppercasedASCIILetter
BinaryInteger.isIDNALabelSeparator
To use this on a Unicode.Scalar
, simply use them on Unicode.Scalar
's value
property.
You can use these function to implement short-circuits for any reason.
For example if you only have a sequence of bytes and don't want to decode them into a String
to provide to this library, considering this library only accepts Swift String
s as domain names.
Example usage:
import SwiftIDNA
let myBytes: [UInt8] = ...
switch IDNA.performCharacterCheck(on: myBytes.span) {
case .containsOnlyIDNANoOpCharacters:
/// `myBytes` is good
case .onlyNeedsLowercasingOfUppercasedASCIILetters:
myBytes = myBytes.map {
$0.toLowercasedASCIILetter()
}
/// `myBytes` is good now
case .mightChangeAfterIDNAConversion:
/// Need to go through IDNA conversion functions if needed
}
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.
The C code is all automatically generated using the 2 scripts in utils/
:
IDNAMappingTableGenerator.swift
generates the IDNA mapping lookup table.IDNATestV2Generator.swift
generates 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 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.5"),
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.