idna is a header-only library of internationalized domain names in C++20, one of the libraries of webcpp: Punycode (RFC 3492), Unicode’s UTS 46 processing, to ASCII and to Unicode, and the validity rules of IDNA2008 (RFC 5890 to 5893), over Unicode 17.0.0 tables generated into its headers. Every operation returns its error as a value, and every operation that writes into a caller’s buffer allocates nothing.
This page teaches idna concept by concept: why each exists, its smallest example, its variations and its rules. Every program it shows was compiled and run, natively and for wasm32-wasip2, wasm32-wasip3 and Emscripten, and printed the output shown under it. The reference that closes it lists every name the headers declare.
Introduction
The labels of a domain name were ASCII: letters, digits and the hyphen. An
internationalized domain name lets a label hold any script, bücher.example
or παράδειγμα.δοκιμή, and still reach the DNS, which stays ASCII: a label
that is not ASCII travels as an A-label, xn-- followed by the label’s code
points in Punycode, so bücher.example is looked up as
xn--bcher-kva.example.
Two standards say which labels are valid, and how a name a person typed becomes one:
-
IDNA2008, RFC 5890 to 5893, defines a valid label: the property of each code point, derived from Unicode (PVALID; CONTEXTJ and CONTEXTO, valid in a context the RFC states; DISALLOWED), Normalization Form C, the hyphens, and the Bidi rule of a name that mixes right-to-left and left-to-right labels. It maps nothing:
bücheris a valid label, andBücheris not. -
UTS 46, Unicode’s IDNA Compatibility Processing, first maps what was typed (case, compatibility forms, the full stops of other scripts), then normalizes it, then checks it, by rules close to IDNA2008’s but not the same: it accepts symbols such as
¡, which IDNA2008 disallows, and has no CONTEXTO rules at all.
idna implements both, and its default profile is the stricter union of the
two: UTS 46’s mapping, then IDNA2008’s validity on top of UTS 46’s. It is what
JSON Schema’s idn-hostname format asks for, and what no UTS 46 library
measured against that format’s tests gives alone: each accepts the labels
IDNA2008 refuses. idna::options{.idna2008 = false} is UTS 46 alone.
A first example
This program converts two names to their ASCII form, and an A-label back to its Unicode form, with the default options:
#include <webcpp/idna.hpp>
#include <array>
#include <iostream>
#include <string_view>
namespace idna = webcpp::idna;
int main() {
std::array<char, idna::max_domain_length> ascii{};
for (const std::string_view name : {"B\303\274cher.Example", "a\302\267b.example"}) {
const idna::result<std::size_t> converted = idna::to_ascii(ascii, name);
if (converted.has_error()) {
std::cout << name << " -> error: " << converted.error().message() << '\n';
continue;
}
std::cout << name << " -> " << std::string_view(ascii.data(), converted.value()) << '\n';
}
std::array<char, 1024> unicode{};
const idna::unicode_result shown = idna::to_unicode(unicode, "xn--bcher-kva.example");
std::cout << std::string_view(unicode.data(), shown.size)
<< (shown.error ? " (with an error)" : "") << '\n';
}
It prints:
Bücher.Example -> xn--bcher-kva.example a·b.example -> error: code point outside its allowed context bücher.example
Bücher.Example is mapped first, its capitals lowered, so it is valid, and
its first label, which is not ASCII, becomes an A-label. a·b.example is
refused: U+00B7 MIDDLE DOT is valid in IDNA2008 only between two l, a
CONTEXTO rule that UTS 46 alone does not have. to_unicode decodes the
A-label back, and reports no error.
Headers
<webcpp/idna.hpp> includes every public header; the names are in the
namespace webcpp::idna, and Punycode’s in webcpp::idna::punycode:
| Header | What it declares |
|---|---|
|
|
|
|
|
|
|
|
|
|
Requirements
-
A C++20 compiler. idna’s tests and examples are built and run on every native lane of webcpp’s continuous integration, for wasm32-wasip2 and wasm32-wasip3 with the Clang of wasi-sdk, and with Emscripten under Node.
-
Boost’s headers, 1.92 or newer: Config, System, Core and Assert. Nothing of Boost is built or linked for idna.
-
webcpp’s configuration, config, whose
<webcpp/config.hpp>every header of idna includes first. It is header-only too, and idna needs nothing else.
Punycode
Punycode, RFC 3492, writes a string of code points with the ASCII letters,
digits and hyphen alone, and reads it back: the code points below U+0080, the
basic ones, are copied first, then a hyphen, then numbers in base 36 that say
where each other code point goes. An A-label is xn-- followed by the
Punycode of its U-label. idna::punycode::encode and decode are that
encoding, without the prefix, which the conversions add and remove. Here
is RFC 3492 section 7.1’s sample (L), 3年B組金八先生:
// RFC 3492 section 7.1's sample (L).
const std::u32string_view sample = U"3\u5E74B\u7D44\u91D1\u516B\u5148\u751F";
std::array<char, 64> encoded{};
const idna::result<std::size_t> written = idna::punycode::encode(encoded, sample);
std::cout << std::string_view(encoded.data(), written.value()) << '\n';
decode gives the code points back, into a span of char32_t:
std::array<char32_t, 64> decoded{};
const idna::result<std::size_t> read =
idna::punycode::decode(decoded, "3B-ww4c5e180e575a65lsy2b");
std::cout << read.value() << " code points, the same: "
<< (std::u32string_view(decoded.data(), read.value()) == sample ? "yes" : "no")
<< '\n';
Both return the number of chars or code points written, or an error: a character that is no digit, a number cut short, a value that is not a Unicode scalar value, or arithmetic past 32 bits, which RFC 3492 section 6.4 refuses:
for (const std::string_view bad : {"-9uc", "bcher-k", "999999999999999999999"}) {
std::cout << bad << " -> " << idna::punycode::decode(decoded, bad).error().message()
<< '\n';
}
The program prints:
3B-ww4c5e180e575a65lsy2b 8 code points, the same: yes -9uc -> invalid Punycode bcher-k -> invalid Punycode 999999999999999999999 -> Punycode overflow
The digits are read in either case; the basic code points are copied as they
are, case included, as RFC 3492’s mixed-case annotation leaves them, and
encode writes its digits in lowercase. A hyphen at the very start is no
delimiter, since nothing precedes it, so -9uc is refused, as RFC 3492
says: it is the A-label xn---9uc, which a careless decoder would read as
xn--9uc. Each function has a second form that returns a std::pmr string
from a memory resource (Allocation). Neither form bounds the length
of its input: a pmr form’s time grows with the square of it, and a span
form’s at most with the input’s length times the span’s size, so a program
bounds input it does not trust before it hands it to a pmr form
(Punycode’s bound).
Converting names
idna::to_ascii and idna::to_unicode are UTS 46’s ToASCII and ToUnicode,
sections 4.2 and 4.3: the name is mapped, normalized to NFC, split into
labels at each full stop, and each label is converted and checked. The first
example (A first example) calls both. Each has two forms: one
that writes into the caller’s span and allocates nothing, and one that
returns a string from a memory resource (Allocation).
The processing
Each code point is looked up in UTS 46’s mapping table: a code point that is
valid stays, one that is mapped is replaced by its mapping (A by a,
fi by fi, U+3002 IDEOGRAPHIC FULL STOP by .), one that is ignored is
removed (U+00AD SOFT HYPHEN), a deviation stays unless the processing is
transitional (ß, ς, and the two joiners), and one that is disallowed
stays, to be refused when its label is checked. The result is normalized to
NFC and split at U+002E. A label that starts with xn-- is decoded from
Punycode and checked as the U-label it holds; any other is checked as it is.
The checks, in their order, are those of The rules.
idna does this one label at a time, in a working storage of one label’s code points, so a name of any length needs the same small storage (Limits).
To ASCII
to_ascii(output, domain, opts) writes the ASCII form of domain into the
caller’s span and returns the number of chars written: each label that is not
ASCII as xn-- and its Punycode, the others as they are, joined by .. A
name that fails any check has no ASCII form: the result is the first error
found, in the order of the labels, then bidi for the Bidi rule across the
labels, then domain_too_long, and output_too_small last, so a span too
small for a name that is too long anyway reports the name. With the DNS
lengths verified, the default, the ASCII form is at most
idna::max_domain_length, 253 chars, so a std::array<char,
idna::max_domain_length> always holds it.
to_ascii(domain, opts, resource) returns the same ASCII form as a
std::pmr::string. With the DNS lengths verified, it reports the same first
error as a span form large enough to hold the result, for every name, and it
is never output_too_small: a label that outgrows the span form’s working
storage is too long for the DNS anyway, and both forms refuse it there
(The working storage).
To Unicode
to_unicode(output, domain, opts) writes the Unicode form, in UTF-8, and
returns an idna::unicode_result: how many chars it wrote, and the first
error. As UTS 46 asks, the conversion goes on after an error, so that a name
can be shown even when it is not valid: each label is written as the
processing leaves it, mapped and normalized, an A-label decoded when its
Punycode decodes and kept as it is otherwise. A label that fails its checks
is written so too, as the toUnicode column of IdnaTestV2 records it, and no
U+FFFD is put in place of what failed: A_B.Example is written
a_b.example, with not_ldh. With the DNS lengths verified, a final empty
label, the DNS root of example., is no error in the Unicode form, as
UTS 46’s X4_2 allows; any other empty label is empty_label. When the span is
full, the writing stops at a code point boundary and the error is
output_too_small, which takes the place of any other: the caller needs a
larger span before anything else. Input that is not well-formed UTF-8 writes
nothing, and is invalid_utf8.
to_unicode(domain, opts, resource) returns an idna::unicode_string, the
Unicode form as a std::pmr::string and the first error. It is never
output_too_small, and the two forms differ only past the working storage,
which the Unicode form, checking no label’s maximum length, meets with the
default options: the span form stops at a label beyond it, and the pmr form
converts the label (The working storage).
Options
Every conversion and every check takes an idna::options, the flags of
UTS 46 section 4 and one of idna’s own, idna2008. The defaults are the
strict profile: every check on, nontransitional processing, the DNS lengths
verified, and IDNA2008’s validity on top. idna::options is an aggregate, so
a program names only what changes: idna::options{.idna2008 = false} is
UTS 46 alone, and idna::options{.verify_dns_length = false} lets labels of
any length through.
| Member | UTS 46’s flag | Default | When true |
|---|---|---|---|
|
CheckHyphens |
true |
No hyphen in both the third and the fourth position of a label (V2), none
at either end (V3). When false, only a label that starts with |
|
CheckBidi |
true |
Every label of a Bidi domain name, one that holds an R, AL or AN code point, satisfies the six rules of RFC 5893 section 2. |
|
CheckJoiners |
true |
U+200C ZERO WIDTH NON-JOINER and U+200D ZERO WIDTH JOINER stand only where RFC 5892 A.1 and A.2 allow them. |
|
UseSTD3ASCIIRules |
true |
The only ASCII code points of a label are |
|
VerifyDnsLength |
true |
Every label is 1 to 63 octets in its ASCII form, the name 1 to 253, and a final empty root label is refused (A4_1, A4_2). |
|
Transitional_Processing |
false |
The deviations are mapped: |
|
IgnoreInvalidPunycode |
false |
A label that starts with |
|
none |
true |
IDNA2008’s validity on top of UTS 46’s: a code point that UTS 46 marks NV8 or XV8, DISALLOWED in IDNA2008, is refused, and the CONTEXTO rules of RFC 5892 A.3 to A.9 apply. |
Validity
idna::check_label and idna::check_domain say whether a label, or a
domain name, is valid as it is given, by IDNA2008’s rules and UTS 46’s
validity criteria, without UTS 46’s mapping: nothing is lowercased,
normalized or replaced, so a label with a capital letter, or one not in NFC,
is refused. The one exception is an A-label, whose case does not matter
(below). They are the checks a program needs when a name must already be in
its final form, as a hostname’s A-labels must. is_valid_label and
is_valid_domain say the same as a bool.
// "\302\267" is U+00B7 MIDDLE DOT in UTF-8, which RFC 5892 allows only between two 'l'.
for (const std::string_view label :
{"example", "xn--bcher-kva", "l\302\267l", "a\302\267l", "ab--cd", "Example"}) {
const idna::result<void> checked = idna::check_label(label);
std::cout << label << ": " << (checked.has_value() ? "valid" : checked.error().message())
<< '\n';
}
An A-label’s xn-- and Punycode are read in either case, as RFC 5890
compares them. The label must be ASCII, and its Punycode must decode to a
label that holds a code point beyond ASCII (UTS 46’s P4). Its U-label is
then checked by the rules below, Normalization Form C included, and encoded
again: an A-label that is not the encoding of its U-label, its letters
compared in lowercase, is refused, as RFC 5891 section 5.4 asks. Each
failure of the A-label itself, a code point beyond ASCII, Punycode that does
not decode, P4 or the round trip, is invalid_punycode, or
punycode_overflow for a number past 32 bits. Under
ignore_invalid_punycode, a label whose Punycode does not decode is kept as
it is, and only its length is checked.
check_label refuses a U+002E inside its label as disallowed, and applies
the Bidi rule to its label as to a name of one label. check_domain splits
at U+002E alone: the other full stops, U+3002, U+FF0E and U+FF61, separate
labels only after UTS 46’s mapping, so as given they are code points UTS 46
maps, and disallowed. It applies the Bidi rule across the labels, and adds
the length of the whole name:
// "\327\220" is U+05D0 HEBREW LETTER ALEF in UTF-8, a right-to-left letter.
for (const std::string_view domain : {"a.\327\220", "0a.\327\220", "0a.b"}) {
std::cout << domain << ": " << (idna::is_valid_domain(domain) ? "valid" : "invalid")
<< '\n';
}
The same check under UTS 46 alone accepts what IDNA2008’s context rules refuse:
const idna::options uts46_alone{.idna2008 = false};
std::cout << "a\302\267l under UTS 46 alone: "
<< (idna::is_valid_label("a\302\267l", uts46_alone) ? "valid" : "invalid") << '\n';
The program prints:
example: valid xn--bcher-kva: valid l·l: valid a·l: code point outside its allowed context ab--cd: hyphens in the third and fourth positions Example: disallowed code point a.א: valid 0a.א: invalid 0a.b: valid a·l under UTS 46 alone: valid
The rules
A label is checked in this order, and the first rule it breaks is its error;
to_ascii and to_unicode apply the same rules to each label after the
mapping, with the NFC check only for a decoded A-label:
-
Normalization Form C (UTS 46 V1, RFC 5891 section 5.4):
not_nfc. -
Hyphens, under
check_hyphens: none in both the third and the fourth position (V2),hyphen_in_third_and_fourth, which keeps such labels free for a future prefix,xn--among them; none at either end (V3),hyphen_at_start_or_end. Withoutcheck_hyphens, a label that starts withxn--(V4),ace_prefix. -
No leading combining mark (V6, RFC 5891 section 5.4):
leading_combining_mark. -
UTS 46’s status (V7): every code point valid, or a deviation in nontransitional processing;
disallowed. -
STD3 ASCII, under
use_std3_ascii_rules(U1): ASCII onlyatoz,0to9and the hyphen;not_ldh. -
CONTEXTJ, under
check_joiners(RFC 5892 A.1, A.2): U+200C after a virama, or between a code point of Joining_Type L or D and one of R or D, with only T between; U+200D after a virama;contextj. -
IDNA2008, under
idna2008: no code point UTS 46 marks NV8 or XV8,not_idna2008; then the CONTEXTO rules,contexto: U+00B7 MIDDLE DOT between twol(A.3), U+0375 GREEK KERAIA before a Greek code point (A.4), U+05F3 and U+05F4, GERESH and GERSHAYIM, after a Hebrew one (A.5, A.6), U+30FB KATAKANA MIDDLE DOT in a label that holds Hiragana, Katakana or Han (A.7), and Arabic-Indic digits never beside extended ones (A.8, A.9).
Across the labels of a name, under check_bidi: when one label holds an R,
AL or AN code point, the name is a Bidi domain name, and then every label
must satisfy RFC 5893 section 2, bidi. 0a.א fails: its first label
starts with a digit, which rule 1 refuses in a Bidi domain name. Under
verify_dns_length, the lengths of the ASCII form: each label’s with the
label, an empty one as empty_label and, after its rules, one of more than
63 octets as label_too_long; the whole name’s last, after the Bidi rule,
domain_too_long. A label that outgrows the working storage of 256 code
points, which no label within the DNS lengths does, is refused as soon as it
does, before its rules: label_too_long under verify_dns_length,
storage_exhausted without it (Errors).
idna derives IDNA2008’s property of a code point from UTS 46’s table: PVALID, CONTEXTJ or CONTEXTO exactly when its status is valid or deviation and it bears no NV8 or XV8 mark. The generator checks this against IANA’s IDNA2008 tables each time it runs (Unicode and the tables).
Errors
Every operation of idna that can fail returns its error as a value: an
idna::result<T>, Boost.System’s result, or, for to_unicode, which
always converts what it can, a struct whose error is a
boost::system::error_code. idna throws nothing of its own. Its errors are
the values of idna::error, of the category webcpp.idna, one per family
of rules rather than one per status code of UTS 46: IdnaTestV2, Unicode’s
own conformance file, says an implementation "need only record that there
is an error". The first error found is the one reported, but for
output_too_small: to_ascii reports it last, after every label’s error,
bidi and domain_too_long, and the span form of to_unicode lets it
replace an earlier error (Converting names). The labels are processed in
order, and the checks of a label in the order UTS 46 section 4.1 lists
them, IDNA2008’s last.
| Value | Enumerator | Reports | Message |
|---|---|---|---|
1 |
|
input that is not well-formed UTF-8 (RFC 3629) |
invalid UTF-8 |
2 |
|
UTS 46 V7: a status other than valid, or deviation when nontransitional; V5: a U+002E inside one label |
disallowed code point |
3 |
|
a code point UTS 46 marks NV8 or XV8, DISALLOWED in IDNA2008 |
code point disallowed by IDNA2008 |
4 |
|
UTS 46 U1: ASCII other than |
ASCII code point other than a letter, a digit or a hyphen |
5 |
|
UTS 46 V1, RFC 5891 section 5.4 |
label not in Normalization Form C |
6 |
|
UTS 46 V2 |
hyphens in the third and fourth positions |
7 |
|
UTS 46 V3 |
hyphen at the start or end of a label |
8 |
|
UTS 46 V4: |
label starts with xn-- |
9 |
|
UTS 46 V6, RFC 5891 section 5.4 |
label starts with a combining mark |
10 |
|
IdnaTestV2 C1, C2: RFC 5892 A.1, A.2 |
joiner outside its allowed context |
11 |
|
RFC 5892 A.3 to A.9 |
code point outside its allowed context |
12 |
|
IdnaTestV2 B1 to B6: RFC 5893 section 2 |
Bidi rule violated |
13 |
|
RFC 3492 decoding; UTS 46 P4; an A-label not in its canonical form |
invalid Punycode |
14 |
|
RFC 3492 section 6.4; UTS 46 A3; a label’s Punycode past the conversions’ bound of 256 code points (Punycode’s bound) |
Punycode overflow |
15 |
|
UTS 46 A4_2 and X4_2 |
empty label |
16 |
|
UTS 46 A4_2: more than 63 octets |
label longer than 63 octets |
17 |
|
UTS 46 A4_1: more than 253 octets |
domain name longer than 253 octets |
18 |
|
the caller’s span cannot hold the result |
output too small |
19 |
|
a label beyond the inline working storage, with no memory resource |
label beyond the working storage |
The values are fixed: none changes, and none is reused for another meaning.
An idna::error converts to an error_code and compares with one,
result.error() == idna::error::contexto.
Allocation
idna allocates nothing it does not have to, and lets its user choose where what it does allocate comes from. Every conversion has two forms:
-
A span form writes into the caller’s buffer,
std::span<char>orstd::span<char32_t>, and allocates nothing, whatever its input. The validators,check_labelandcheck_domain, allocate nothing either. -
A pmr form returns a
std::pmr::stringorstd::pmr::u32stringfrom thestd::pmr::memory_resourceit is given, by defaultstd::pmr::get_default_resource(). It allocates from that resource alone: its result, and a label’s working storage beyond the inline capacity (Limits).
The span form of to_ascii needs no more than a buffer on the stack:
// A span form writes into the caller's buffer and allocates nothing.
std::array<char, idna::max_domain_length> buffer{};
const idna::result<std::size_t> written = idna::to_ascii(buffer, "b\303\274cher.example");
std::cout << std::string_view(buffer.data(), written.value()) << '\n';
Its pmr form, here with a std::pmr::monotonic_buffer_resource over a
buffer of the program’s own, which allocates nothing from the heap either:
// A pmr form takes its memory from the resource it is given, here a buffer on the stack.
std::array<std::byte, 16384> storage{};
std::pmr::monotonic_buffer_resource resource(storage.data(), storage.size(),
std::pmr::null_memory_resource());
const idna::result<std::pmr::string> owned =
idna::to_ascii("b\303\274cher.example", idna::options(), resource);
std::cout << owned.value() << '\n';
The two forms of to_ascii differ for a label longer than the inline
storage, which only a program that turns the DNS lengths off can give it:
the span form refuses it, and the pmr form holds it in its resource.
to_unicode, which checks no label’s maximum length, meets such a label
with the default options too (The working storage).
// Without the DNS lengths, a label of 300 code points is beyond the inline storage: the span
// form refuses it, and the pmr form holds it in its resource.
const std::string long_label(300, 'a');
const idna::options no_lengths{.verify_dns_length = false};
std::array<char, 512> wide{};
std::cout << idna::to_ascii(wide, long_label, no_lengths).error().message() << '\n';
std::cout << idna::to_ascii(long_label, no_lengths, resource).value().size() << " octets\n";
The program prints:
xn--bcher-kva.example xn--bcher-kva.example label beyond the working storage 300 octets
The test allocation replaces the global operator new for itself alone
and counts: no call in any span form or validator, over long names, long
labels and every kind of failure, with the strict profile, UTS 46 alone and
the DNS lengths off, and in the pmr forms, exactly the calls of their memory
resource (guarantee 1).
Limits
The DNS lengths
Under verify_dns_length, the default, a label is 1 to
idna::max_label_length, 63, octets in its ASCII form, and a name 1 to
idna::max_domain_length, 253, without a final root label, which is
refused: RFC 1034 section 3.1’s limits, as UTS 46 section 4.2 states them. A
U-label is measured by its A-label: 55 a and a ü make an A-label of 63
octets, and 56 make one of 64.
The working storage
The conversions and the checks hold one label at a time, in a working storage of 256 code points inside the call, about 1.3 KiB with the label’s ASCII form, and a second one while a label is checked for NFC the slow way. Every label the DNS lengths accept fits: an A-label decodes to at most 59 code points, and a code point decomposes to at most 4, U+1FAF among them, the longest canonical decomposition of Unicode 17.0.0, which the generator checks.
A label beyond 256 code points once decomposed is too long for the DNS, and what meets it depends on the length check:
-
Under
verify_dns_length,to_ascii, in both forms, and the validators refuse it aslabel_too_long, which it is, as they refuse a label whose NFC check’s copy outgrows the storage. -
Without it, the span form of
to_asciiand the validators returnstorage_exhausted, and the pmr form ofto_asciitakes the label’s storage from its memory resource. -
to_unicodechecks no label’s or name’s maximum length, and meets such a label with any options: its span form stops there withstorage_exhausted, and its pmr form converts the label, an A-label within Punycode’s bound (below). The difference is by design: a span form allocates nothing, and the label is no name the DNS can hold.
The storage fills while a label is gathered, before any of the label’s
checks, so a label beyond it reports that first: U+FFFF followed by 300 a
is label_too_long, not disallowed. In the span form of to_unicode, the
processing stops there, keeping an error recorded before it, and its output
ends with the last label written before the long one: a name of the labels
a_b, 300 a and c gives a_b, and not_ldh.
Punycode’s bound
Punycode’s work grows with the square of a label’s length: RFC 3492’s
encoder passes over the label once for each distinct code point, and its
decoder inserts each code point among those before it. The conversions bound
it at 256 code points, the working storage’s, and Punycode past the bound is
punycode_overflow whatever ignore_invalid_punycode says, since a limit of
the implementation is no invalid Punycode. to_ascii encodes, so a label of
more than 256 code points that is not ASCII is punycode_overflow there.
Both conversions decode, so an A-label whose Punycode decodes to more than
256 code points is punycode_overflow in either, and kept as it is in the
Unicode form. to_unicode encodes nothing: a long label that is not an
A-label is written as it is. Only a pmr form meets the bound, to_ascii
without the DNS lengths, and to_unicode, with any options, for an A-label,
since a span form refuses such a label as beyond its storage first. At the
bound, the worst label costs about 4 times one as long that Punycode handles
in linear time, measured at -O2.
punycode::encode and punycode::decode are the primitive, and have no
such bound: their pmr forms take input of any length, in time that grows
with its square, and their span forms stop when the caller’s span is full,
in time that grows at most with the input’s length times the span’s size. A
program bounds the length of input it does not trust before it hands it to
a pmr form.
The stack
The deepest call of idna, a check of a label, or a conversion of an
A-label, whose U-label holds more than 32 combining marks in a row, which the
slow NFC check sorts by counting, ran in about 11 KiB of stack, and the
conversions and checks of an ordinary name, such as
xn--bcher-kva.example, in about 8 KiB, a program’s start and its printf
included, against the 64 KiB a WebAssembly program gets by default. These
are measured figures, with no margin promised: a program built with
wasi-sdk 34 in a debug build (-O0 -g) for wasm32-wasip2 and wasm32-wasip3,
run by wasmtime 47 with a stack made smaller, 256 bytes at a time, until it
trapped. Another compiler or version may need more. idna does not
recurse.
Unicode and the tables
idna’s tables follow Unicode 17.0.0,
idna::unicode_version, and UTS 46 revision 35 for it. They
are generated from Unicode’s data files into
<webcpp/idna/detail/tables.hpp> as constexpr arrays of constant data,
never written: no table is built at run time, none is compressed, nothing is
allocated and nothing is shared mutable state, so any number of threads may
call idna at once. Each property is a sorted list of runs, looked up by
binary search.
| Table | From | Used by |
|---|---|---|
UTS 46 status, NV8 and XV8 mark, mapping |
|
the mapping, V7, IDNA2008’s property |
Bidi_Class |
|
RFC 5893 |
Joining_Type |
|
RFC 5892 A.1 |
Canonical_Combining_Class |
|
NFC, and the virama of A.1 and A.2 |
General_Category Mark |
|
V6 |
Script: Greek, Hebrew, Hiragana, Katakana, Han |
|
RFC 5892 A.4 to A.7 |
canonical decompositions, primary composites, NFC_Quick_Check |
|
NFC |
Hangul syllables are decomposed and composed by the arithmetic of Unicode section 3.12, and take no table.
The generator
tools/generate_tables.py, in idna’s repository, writes the tables, and
the vectors of the conformance tests, IdnaTestV2.txt and
NormalizationTest.txt as C++ data under test/generated/
(Testing). Its inputs are pinned in tools/unicode.json, each by
its URL under https://www.unicode.org/Public/17.0.0/ and its
SHA-256, with IANA’s IDNA2008 tables, idna-tables-12.0.0, read only by
its checks. A file its cache lacks is downloaded, every file is hashed, and
a digest that differs stops it, naming the file and both digests, before it
writes anything.
Before it writes, it checks what idna assumes of the data, and stops naming
the assumption that fails: every status of the mapping table is one of the
five it knows; IANA’s IDNA2008 property of every code point IANA assigns one
agrees with the derivation from UTS 46’s table, U+002E FULL STOP aside, which
IANA disallows and UTS 46 keeps as the separator no label holds; the
CONTEXTJ and CONTEXTO code points are those of RFC 5892 appendix A; no full
canonical decomposition is longer than 4 code points; no Hangul syllable has
a decomposition in UnicodeData.txt, since idna computes those; and every
property value a file holds is one it knows, either one the tables keep or
one it reads as other. It reads the test files as strictly: every line of
IdnaTestV2.txt has its seven fields, its status codes take the forms the
file’s header names, and its one escape is \uXXXX; every line of
NormalizationTest.txt has its five columns of code points; and each entry
of test/known_failures.json names a line of IdnaTestV2.txt by its number
and its source, once, with a reason, for the pinned version of Unicode. Two
runs write the same bytes. Its cache is bin/unicode/17.0.0/ of
the superproject, which it fills from
unicode.org and iana.org when a file is missing. The data are Unicode’s,
under the Unicode License v3, whose notice is in
the appendix "Unicode data".
The own lane tables runs the generator again, into the build directory, and
compares each of its outputs with the committed file, byte for byte, naming
each that differs; update-generated writes them in place, and is the one
writer of the generated files. The ordinary lanes never run the generator,
which may need the network; the CI runs the lane on its own.
b2 -a libs/idna/test//tables
b2 libs/idna/test//update-generated
What the tables cost
Only <webcpp/idna/detail/properties.hpp> includes the tables, and of the
public headers only uts46.hpp and validity.hpp reach it, with
<webcpp/idna.hpp>, which includes them: punycode.hpp, errors.hpp and
options.hpp cost nothing of them. The tables hold 147,324 bytes of data,
and a program links only those it reads. A translation unit that includes
<webcpp/idna.hpp> and calls to_ascii, compiled at -O2, takes about
0.7 s of processor time and makes an object of 188 KB natively with Apple
Clang 21, and about 0.8 s and 197 KB for wasm32-wasip2 with wasi-sdk 34,
against 0.14 s and 0.07 s, and under 1 KB, for the same program without
idna. Each time is the least of fifteen compilations, user and system time,
on an Apple M5 Max shared with other work.
Moving to another version of Unicode
A move is one change of its own, which updates every place that holds the version, in this order:
-
tools/unicode.json: itsversion, and theurland thesha256of each of Unicode’s files. IANA’sidna-tables-12.0.0stays as it is.test/known_failures.json: itsunicode, and no entry, since each names a line of the oldIdnaTestV2.txt. -
tools/generate_tables.py, run from the superproject’s root byb2 libs/idna/test//update-generated, which downloads the new files. A property value the new version adds, most often a script, stops it by name. The value then joins the generator’s list of the values it reads as other,SCRIPT_OTHERS,BIDI_OTHERS,JOINING_OTHERSorOTHER_CATEGORIES, once it is known not to matter to IDNA, or the values the tables keep,SCRIPTS,BIDI_CLASSESorJOINING_TYPES, with the code that reads them, when it does; the comment above those lists names the version they were read from. Any other check that fails is an assumption of idna’s that no longer holds, and is settled before the move goes on. Once every check passes,update-generatedwritesinclude/webcpp/idna/detail/tables.hppand the vectors again. -
include/webcpp/idna/options.hpp:unicode_version, whichdetail/properties.hpprefuses to compile while it differs from the tables’; the version thattest/options_test.cppandtest/properties_test.cpppin; and the counts and line numbers of the test files thattest/vectors_test.cpppins. -
doc/idna.adoc: the attributes:unicode-version:and:uts46-revision:, which every mention of the version and of UTS 46’s revision on this page reads. -
The text that names the version: the
descriptionofmeta/libraries.json, the first paragraph ofREADME.md, and the section "Unicode and the tables" ofAGENTS.md. -
Every lane, the own lane
tablesamong them, and the known failures oftest/known_failures.jsonbrought up to date, each with its reason, andupdate-generatedrun again, which writes each reason into its row. -
The changelog, in a line that names the new version.
Guarantees
Each guarantee keeps its number and its anchor; code, tests and comments cite them.
-
No span form of a conversion and no validator allocates, whatever its input; a pmr form allocates from its memory resource alone.
-
No conversion, validator or Punycode function throws but the allocation of the memory resource a pmr form was given. The category’s
message(int)returns astd::string, as Boost.System’serror_categorydeclares it, so it may throw what the globaloperator newthrows. The membermessage(char*, std::size_t)oferror_code, which calls the category’smessage(int, char*, std::size_t), copies the message into a caller’s buffer and throws nothing. -
The same input and options give the same output and the same error on every target.
-
With
verify_dns_length,to_asciireturns at mostidna::max_domain_lengthchars, and reports a name whose ASCII form is longer asdomain_too_long, unless an earlier error comes first in the order of the errors (To ASCII), and never asoutput_too_smallin a buffer of that size. After an error, the buffer of the span form ofto_ascii, as of a Punycode span form, holds nothing to read; the span form ofto_unicodegoes on after an error, as UTS 46 asks, and thesizechars of itsunicode_resultare the name as the processing left it. -
The tables are exactly what
tools/generate_tables.pywrites from the pinned Unicode 17.0.0 files.
Testing
How idna is tested
idna’s tests are lightweight_test programs, one per subject, under test/,
each declared in test/Jamfile with webcpp’s webcpp.run. Every vector of
the conformance files is compiled into its test, so no test reads a file,
and every test runs on every target.
| Test | What it checks |
|---|---|
|
The values of |
|
The strict profile’s defaults, the designated initializers of the aggregate, the DNS lengths and the version of Unicode. |
|
Strict UTF-8: the boundaries of every row of RFC 3629’s table of well-formed sequences, decoded and encoded back, every kind of ill-formed one, and the end of the text. |
|
RFC 3492 section 7.1’s samples, (A) to (S), both ways, and inputs that
tell apart what the samples leave alike in the bias; digits in either case
and the mixed-case annotation kept; a leading delimiter, a bad digit, a
number cut short, a value out of Unicode or a surrogate; each overflow of
RFC 3492 section 6.4, in the decoder and the encoder; |
|
One value of every generated table, through its lookup. |
|
The generated vectors: every line of both files, the blank fields of IdnaTestV2.txt resolved, its lone surrogate kept ill-formed, the lines of the strict pass. |
|
The verdict a conformance test gives a line of IdnaTestV2.txt, held
against |
|
NFC within a label’s storage against all 20,034 lines of NormalizationTest.txt, its quick check and its slow path, Hangul, and the storage, inline and growing: its edge after decomposition, at the capacity and one past it, canonical ordering against a stable sort, and a run of 10,000 non-starters normalized in less than 20 times the time of a text as long without one, which an insertion sort exceeds by far. |
|
Each rule of RFC 5892 appendix A and of RFC 5893 section 2, satisfied and broken, the joining scripts a hand-written list misses among them, and A.7 to A.9 over labels of thousands of code points in less than 1,000 times the time of a label as long without a CONTEXTO code point. |
|
The label driver in both forms on what ends a label early: an A-label that decodes to nothing, a label beyond the storage after an earlier error, gathered or in the NFC check’s copy, and the empty labels, the root label among them, an undecodable label kept, and the ASCII form as given; and that it throws only what a memory resource throws. Like every test, built with the standard library’s own checks on, so that a read past an empty label traps. |
|
|
|
All 6,391 lines of IdnaTestV2.txt, toUnicode, toAsciiN and
toAsciiT, in the span and the pmr forms, under UTS 46 alone as the file
assumes, then the strict profile over the lines whose toUnicode field
holds no NV8, XV8 or CONTEXTO code point; and the orders of the errors,
the same in both forms under the DNS lengths. Then the pmr forms’ bound on
a label’s Punycode, 256 code points: the decoder stops at it, so an
A-label of 300 code points followed by a character that is no digit is
|
|
The global |
Beside them, test/Jamfile compiles each of the 15 headers alone,
and config.hpp three times: as the build asks (config), with
WEBCPP_NO_EXCEPTIONS defined for every webcpp library (config_all_off),
and with WEBCPP_IDNA_NO_EXCEPTIONS defined by the developer
(config_developer_defined). example/Jamfile builds every program this
page shows, runs it, and compares what it printed with the .expected
beside it.
Conformance, and known failures
IdnaTestV2.txt is UTS 46’s own conformance file. idna passes every line of
it, which test/known_failures.json records as an empty list: a line listed
there must fail in the pass under UTS 46 alone, which the strict pass then
leaves out, and every other line must pass, so the list never lies. The one
kind of entry it may hold is a line that fails because of a difference
between two versions of Unicode, the file’s and the tables’, with a reason
that names both. The lines with a lone surrogate, which no UTF-8 string
holds, are refused as invalid_utf8, as the file allows. JSON Schema’s own
tests of hostname and idn-hostname run in webcpp::lexical, against the
validators it builds on idna.
Targets and exceptions
test/Jamfile and example/Jamfile declare webcpp.targets native wasip2
wasip3 emscripten, and b2 builds each program for those alone: natively
with any toolset, for wasm32-wasip2 and wasm32-wasip3 with the toolsets
clang-wasip2 and clang-wasip3 run by wasmtime, and with Emscripten run
by Node.
<webcpp/idna/config.hpp>, which every public header includes first,
defines WEBCPP_IDNA_NO_EXCEPTIONS when webcpp has no exceptions
(WEBCPP_HAS_EXCEPTIONS is not defined: the compiler has none, or the
program defined WEBCPP_NO_EXCEPTIONS), and a program may define it for
idna alone, as Boost.Asio’s BOOST_ASIO_NO_EXCEPTIONS. idna raises no
exception of its own (guarantee 2), so the macro changes nothing in
it: it exists so that a program can set it for every webcpp library alike.
The lint compiles every header, test and example a second time without
exceptions.
From the root of a webcpp checkout, natively:
b2 libs/idna/test libs/idna/example
builds the 34 programs of test/Jamfile, the tests above, the
two run-fail builds of known_failures, each header alone and config.hpp
three times, and runs every test among them; and it builds the 4
examples, runs each and compares its output. For wasm32-wasip2,
34 programs and 4 examples, run by wasmtime;
toolset=clang-wasip3 does the same for wasm32-wasip3:
b2 libs/idna/test libs/idna/example toolset=clang-wasip2 testing.launcher=wasmtime
With Emscripten, whose programs b2 runs with Node itself, 34 programs and 4 examples:
b2 libs/idna/test libs/idna/example toolset=emscripten
The own lane tables runs the generator again and compares its outputs with
the committed files (The generator):
b2 -a libs/idna/test//tables
The test matrix of every lane is at https://webcpporg.github.io/report/.
Reference
Every name idna declares, in the namespaces webcpp::idna and
webcpp::idna::punycode, from the Doc Comments of its headers: each
function, type, enumerator and constant with its declaration, what it does,
what it asks of its caller and how it fails. The other sections teach the
library; this one is where a name is looked up.
Macros
| Name | Description |
|---|---|
Defined when idna is built without exceptions: when webcpp has none (the compiler has none, or the developer defined |
webcpp::idna
Namespaces
| Name | Description |
|---|---|
Punycode (RFC 3492), the encoding of an A-label’s code points after its "xn--" prefix, which UTS 46 and IDNA2008 apply to one label at a time. |
Types
| Name | Description |
|---|---|
The flags of UTS 46 section 4, and IDNA2008’s stricter validity. |
|
What |
|
The string |
Type Aliases
| Name | Description |
|---|---|
What an operation that can fail returns: a |
Enums
| Name | Description |
|---|---|
The errors idna reports, as |
Functions
| Name | Description |
|---|---|
Checks a domain name, in UTF-8, as it is given: every label as |
|
Checks one label, in UTF-8, as it is given: a U-label, an A-label or an NR-LDH label. |
|
The category of every |
|
Whether |
|
Whether |
|
Makes the |
|
|
|
|
Variables
| Name | Description |
|---|---|
The longest domain name in its ASCII form, in octets, without a trailing root label. |
|
The longest label of a domain name in its ASCII form, in octets (RFC 1034 section 3.1). |
|
The version of Unicode whose data the tables were generated from. |
webcpp::idna::punycode
Punycode (RFC 3492), the encoding of an A-label’s code points after its "xn--" prefix, which UTS 46 and IDNA2008 apply to one label at a time.
See Also
"Punycode", in the guide.
webcpp::idna::punycode::decode
decode overloads
Synopses
Declared in <webcpp/idna/punycode.hpp>
Decodes Punycode into a string of code points of the given memory resource.
[[nodiscard]]
result<std::pmr::u32string>
decode(
std::string_view input,
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Decodes Punycode without its "xn--" prefix into code points, into a caller’s buffer (RFC 3492 section 6.2).
[[nodiscard]]
result<std::size_t>
decode(
std::span<char32_t> output,
std::string_view input) noexcept;
Return Value
-
The code points, or the error the span form returns but
error::output_too_small. -
The number of code points written, or
error::invalid_punycodefor a character that is not a digit, a non-basic code point before the delimiter, a truncated number, or a value that is basic, a surrogate or above U+10FFFF;error::punycode_overflow; orerror::output_too_small.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
input |
The Punycode to decode. |
resource |
Where the string’s memory comes from. |
output |
Where the code points are written; nothing is written past it. |
See Also
"Allocation", in the guide.
"Limits", in the guide.
"Punycode", in the guide.
webcpp::idna::punycode::decode
Decodes Punycode into a string of code points of the given memory resource.
Synopsis
Declared in <webcpp/idna/punycode.hpp>
[[nodiscard]]
result<std::pmr::u32string>
decode(
std::string_view input,
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Description
Nothing bounds the input’s length, and the decoding’s time grows with its square, since RFC 3492’s decoder inserts each code point among those before it: a program bounds the length of input it does not trust, as to_unicode bounds a label at 256 code points.
Return Value
The code points, or the error the span form returns but error::output_too_small.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
input |
The Punycode to decode. |
resource |
Where the string’s memory comes from. |
See Also
"Allocation", in the guide.
"Limits", in the guide.
webcpp::idna::punycode::decode
Decodes Punycode without its "xn--" prefix into code points, into a caller’s buffer (RFC 3492 section 6.2).
Synopsis
Declared in <webcpp/idna/punycode.hpp>
[[nodiscard]]
result<std::size_t>
decode(
std::span<char32_t> output,
std::string_view input) noexcept;
Description
The code points before the last delimiter are copied as they are, case included, as RFC 3492’s mixed-case annotation leaves them; the digits after it are read in either case. A delimiter at the very start consumes nothing. At most the size of output code points are decoded, so the decoding’s time grows at most with the input’s length times the size of output.
|
Note
|
A non-basic code point before the delimiter is found before anything is written, whatever the size of |
Return Value
The number of code points written, or error::invalid_punycode for a character that is not a digit, a non-basic code point before the delimiter, a truncated number, or a value that is basic, a surrogate or above U+10FFFF; error::punycode_overflow; or error::output_too_small.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
output |
Where the code points are written; nothing is written past it. |
input |
The Punycode to decode. |
See Also
"Punycode", in the guide.
webcpp::idna::punycode::encode
encode overloads
Synopses
Declared in <webcpp/idna/punycode.hpp>
Encodes code points as Punycode into a string of the given memory resource.
[[nodiscard]]
result<std::pmr::string>
encode(
std::u32string_view input,
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Encodes code points as Punycode, into a caller’s buffer, without the "xn--" prefix (RFC 3492 section 6.3).
[[nodiscard]]
result<std::size_t>
encode(
std::span<char> output,
std::u32string_view input) noexcept;
Return Value
-
The Punycode, or the error the span form returns but
error::output_too_small. -
The number of characters written, or
error::invalid_punycodefor a surrogate or a value above U+10FFFF,error::punycode_overflowwhen the arithmetic overflows 32 bits,error::output_too_smallwhenoutputcannot hold the result.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
input |
The code points to encode. |
resource |
Where the string’s memory comes from. |
output |
Where the characters are written; nothing is written past it. |
See Also
"Allocation", in the guide.
"Limits", in the guide.
"Punycode", in the guide.
webcpp::idna::punycode::encode
Encodes code points as Punycode into a string of the given memory resource.
Synopsis
Declared in <webcpp/idna/punycode.hpp>
[[nodiscard]]
result<std::pmr::string>
encode(
std::u32string_view input,
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Description
Nothing bounds the input’s length, and the encoding’s time grows with its square, since RFC 3492’s encoder passes over the input once for each distinct code point: a program bounds the length of input it does not trust, as to_ascii bounds a label at 256 code points.
Return Value
The Punycode, or the error the span form returns but error::output_too_small.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
input |
The code points to encode. |
resource |
Where the string’s memory comes from. |
See Also
"Allocation", in the guide.
"Limits", in the guide.
webcpp::idna::punycode::encode
Encodes code points as Punycode, into a caller’s buffer, without the "xn--" prefix (RFC 3492 section 6.3).
Synopsis
Declared in <webcpp/idna/punycode.hpp>
[[nodiscard]]
result<std::size_t>
encode(
std::span<char> output,
std::u32string_view input) noexcept;
Description
Basic code points, below U+0080, are copied as they are, case included, and then a delimiter - when there is any; the digits that follow are lowercase. The encoding stops when output is full, so its time grows at most with the input’s length times the size of output.
|
Note
|
A surrogate or a value above U+10FFFF is found before anything is written, whatever the size of |
Return Value
The number of characters written, or error::invalid_punycode for a surrogate or a value above U+10FFFF, error::punycode_overflow when the arithmetic overflows 32 bits, error::output_too_small when output cannot hold the result.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
output |
Where the characters are written; nothing is written past it. |
input |
The code points to encode. |
See Also
"Punycode", in the guide.
webcpp::idna::result
What an operation that can fail returns: a T, or the error_code of the failure.
Synopsis
Declared in <webcpp/idna/errors.hpp>
template<class T>
using result = boost::system::result<T>;
Description
It is Boost.System’s result. idna throws nothing of its own: every operation of it that can fail returns one, whose error is an error.
Template Parameters
| Name | Description |
|---|---|
T |
The type of the value; |
See Also
"Errors", in the guide.
webcpp::idna::options
The flags of UTS 46 section 4, and IDNA2008’s stricter validity.
Description
The defaults are the strict profile: every check of UTS 46 on, nontransitional processing, the DNS lengths verified, and IDNA2008’s validity on top. An aggregate, so a caller names only what changes: idna::options{.idna2008 = false} is UTS 46 alone.
Data Members
| Name | Description |
|---|---|
CheckBidi: every label of a Bidi domain name satisfies RFC 5893 section 2. |
|
CheckHyphens: no hyphens in the third and fourth positions, none at either end (UTS 46 V2, V3); when false, only the "xn--" prefix is refused (V4). |
|
CheckJoiners: the joiners U+200C and U+200D satisfy RFC 5892 A.1 and A.2. |
|
IDNA2008’s validity on top of UTS 46: a code point UTS 46 marks NV8 or XV8 is refused, and the CONTEXTO rules of RFC 5892 A.3 to A.9 apply. |
|
IgnoreInvalidPunycode: an "xn--" label that does not decode is kept as it is, without an error. |
|
Transitional_Processing, which UTS 46 deprecates: the deviations are mapped. |
|
UseSTD3ASCIIRules: the only ASCII code points are a-z, 0-9 and the hyphen (UTS 46 U1). |
|
VerifyDnsLength: labels of 1 to 63 octets, a name of 1 to 253, and no empty root label. |
See Also
"Converting names", in the guide.
webcpp::idna::options::check_bidi
CheckBidi: every label of a Bidi domain name satisfies RFC 5893 section 2.
webcpp::idna::options::check_hyphens
CheckHyphens: no hyphens in the third and fourth positions, none at either end (UTS 46 V2, V3); when false, only the "xn--" prefix is refused (V4).
webcpp::idna::options::check_joiners
CheckJoiners: the joiners U+200C and U+200D satisfy RFC 5892 A.1 and A.2.
webcpp::idna::options::idna2008
IDNA2008’s validity on top of UTS 46: a code point UTS 46 marks NV8 or XV8 is refused, and the CONTEXTO rules of RFC 5892 A.3 to A.9 apply.
webcpp::idna::options::ignore_invalid_punycode
IgnoreInvalidPunycode: an "xn--" label that does not decode is kept as it is, without an error.
webcpp::idna::options::transitional_processing
Transitional_Processing, which UTS 46 deprecates: the deviations are mapped.
webcpp::idna::options::use_std3_ascii_rules
UseSTD3ASCIIRules: the only ASCII code points are a-z, 0-9 and the hyphen (UTS 46 U1).
webcpp::idna::options::verify_dns_length
VerifyDnsLength: labels of 1 to 63 octets, a name of 1 to 253, and no empty root label.
webcpp::idna::unicode_result
What to_unicode wrote into a caller’s buffer, and the first error UTS 46 recorded.
Data Members
| Name | Description |
|---|---|
Empty when the processing succeeded; else its first |
|
The number of chars written. |
webcpp::idna::unicode_result::error
Empty when the processing succeeded; else its first error, or error::output_too_small when the buffer could not hold the whole result.
webcpp::idna::unicode_result::size
The number of chars written.
webcpp::idna::unicode_string
The string to_unicode produced into a memory resource, and the first error UTS 46 recorded.
webcpp::idna::unicode_string::error
Empty when the processing succeeded; else its first error.
webcpp::idna::unicode_string::value
The Unicode form, in UTF-8.
webcpp::idna::error
The errors idna reports, as boost::system::error_code values of the category error_category.
Description
One enumerator per rule family, not per UTS 46 status code: the comment of each names the steps of UTS 46, the status codes of IdnaTestV2 and the sections of the RFCs it reports. The values start at 1, so an error_code of value 0 is no error.
|
Note
|
The values are fixed: they are never reused for a different meaning, so a stored value keeps its meaning across versions. |
Members
| Name | Description |
|---|---|
The input is not well-formed UTF-8 (RFC 3629). |
|
A code point whose UTS 46 status is neither valid nor, in nontransitional processing, deviation (UTS 46 V7), or a U+002E FULL STOP inside a label (UTS 46 V5). |
|
A code point UTS 46 marks NV8 or XV8: DISALLOWED in IDNA2008 (options::idna2008). |
|
An ASCII code point outside a-z, 0-9 and the hyphen (UTS 46 U1, options::use_std3_ascii_rules). |
|
A label not in Normalization Form C (UTS 46 V1, RFC 5891 section 5.4). |
|
Hyphens in both the third and the fourth positions (UTS 46 V2, options::check_hyphens). |
|
A hyphen at the start or the end of a label (UTS 46 V3, options::check_hyphens). |
|
A label that starts with "xn--" while options::check_hyphens is off (UTS 46 V4). |
|
A label that starts with a combining mark (UTS 46 V6, RFC 5891 section 5.4). |
|
A joiner outside the context RFC 5892 A.1 and A.2 allow (IdnaTestV2 C1, C2, options::check_joiners). |
|
A code point outside the context RFC 5892 A.3 to A.9 allow (options::idna2008). |
|
A Bidi domain name with a label that breaks RFC 5893 section 2 (IdnaTestV2 B1 to B6, options::check_bidi). |
|
Punycode that does not decode, an A-label that decodes to nothing or to ASCII alone, or one not in its canonical form (RFC 3492, UTS 46 P4, RFC 5891 section 5.4). |
|
Punycode whose arithmetic overflows 32 bits (RFC 3492 section 6.4, UTS 46 A3), or, in the pmr forms of to_ascii and to_unicode, a label whose Punycode would hold more than 256 code points. |
|
An empty label (UTS 46 A4_2 and X4_2, options::verify_dns_length). |
|
A label longer than 63 octets in its ASCII form (UTS 46 A4_2, options::verify_dns_length). |
|
A domain name longer than 253 octets in its ASCII form (UTS 46 A4_1, options::verify_dns_length). |
|
The caller’s span cannot hold the result. |
|
A label beyond the inline working storage, with no memory resource to hold it. |
See Also
"Errors", in the guide.
webcpp::idna::error::disallowed
A code point whose UTS 46 status is neither valid nor, in nontransitional processing, deviation (UTS 46 V7), or a U+002E FULL STOP inside a label (UTS 46 V5).
webcpp::idna::error::not_idna2008
A code point UTS 46 marks NV8 or XV8: DISALLOWED in IDNA2008 (options::idna2008).
webcpp::idna::error::not_ldh
An ASCII code point outside a-z, 0-9 and the hyphen (UTS 46 U1, options::use_std3_ascii_rules).
webcpp::idna::error::hyphen_in_third_and_fourth
Hyphens in both the third and the fourth positions (UTS 46 V2, options::check_hyphens).
webcpp::idna::error::hyphen_at_start_or_end
A hyphen at the start or the end of a label (UTS 46 V3, options::check_hyphens).
webcpp::idna::error::ace_prefix
A label that starts with "xn--" while options::check_hyphens is off (UTS 46 V4).
webcpp::idna::error::leading_combining_mark
A label that starts with a combining mark (UTS 46 V6, RFC 5891 section 5.4).
webcpp::idna::error::contextj
A joiner outside the context RFC 5892 A.1 and A.2 allow (IdnaTestV2 C1, C2, options::check_joiners).
webcpp::idna::error::contexto
A code point outside the context RFC 5892 A.3 to A.9 allow (options::idna2008).
webcpp::idna::error::bidi
A Bidi domain name with a label that breaks RFC 5893 section 2 (IdnaTestV2 B1 to B6, options::check_bidi).
webcpp::idna::error::invalid_punycode
Punycode that does not decode, an A-label that decodes to nothing or to ASCII alone, or one not in its canonical form (RFC 3492, UTS 46 P4, RFC 5891 section 5.4).
webcpp::idna::error::punycode_overflow
Punycode whose arithmetic overflows 32 bits (RFC 3492 section 6.4, UTS 46 A3), or, in the pmr forms of to_ascii and to_unicode, a label whose Punycode would hold more than 256 code points.
webcpp::idna::error::label_too_long
A label longer than 63 octets in its ASCII form (UTS 46 A4_2, options::verify_dns_length).
webcpp::idna::error::domain_too_long
A domain name longer than 253 octets in its ASCII form (UTS 46 A4_1, options::verify_dns_length).
webcpp::idna::error::storage_exhausted
A label beyond the inline working storage, with no memory resource to hold it.
webcpp::idna::check_domain
Checks a domain name, in UTF-8, as it is given: every label as check_label checks it, the Bidi rule across the labels, and the DNS lengths.
Synopsis
Declared in <webcpp/idna/validity.hpp>
[[nodiscard]]
result<void>
check_domain(
std::string_view domain,
options const& opts = options()) noexcept;
Description
Labels are separated by U+002E alone. Nothing allocates.
Return Value
Nothing, or the first error found: the labels’ in their order, then error::bidi, then error::domain_too_long. error::storage_exhausted for a label beyond the inline storage when opts.verify_dns_length is off.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
domain |
The domain name. |
opts |
The checks to apply; the defaults are the strict profile. |
See Also
"Validity", in the guide.
"Limits", in the guide.
webcpp::idna::check_label
Checks one label, in UTF-8, as it is given: a U-label, an A-label or an NR-LDH label.
Synopsis
Declared in <webcpp/idna/validity.hpp>
[[nodiscard]]
result<void>
check_label(
std::string_view label,
options const& opts = options()) noexcept;
Description
An A-label, "xn--" in any ASCII case, is decoded, validated as a U-label, re-encoded and compared with its input, ASCII letters in either case. Nothing else is mapped, so a label not in NFC, or holding a code point UTS 46 would map (an ASCII capital letter outside an A-label among them), is refused. The Bidi rule is applied to the label as to a domain of one label. An empty label is refused under opts.verify_dns_length and accepted without it, as UTS 46 says. Nothing allocates.
Return Value
Nothing, or the first error found, in the order of the guide: error::disallowed for a U+002E inside label, error::storage_exhausted for a label beyond the inline storage when opts.verify_dns_length is off.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
label |
The label, without a separator. |
opts |
The checks to apply; the defaults are the strict profile. |
See Also
"Validity", in the guide.
webcpp::idna::error_category
The category of every error.
Synopsis
Declared in <webcpp/idna/errors.hpp>
boost::system::error_category const&
error_category() noexcept;
Description
Its name() is "webcpp.idna", and its message(value) the human message of the error, "invalid Punycode" for error::invalid_punycode, or "unknown webcpp.idna error" for a value error does not define.
Return Value
The category, a constant with a fixed identity.
See Also
"Errors", in the guide.
webcpp::idna::is_valid_domain
Whether check_domain finds no error in domain.
Synopsis
Declared in <webcpp/idna/validity.hpp>
[[nodiscard]]
bool
is_valid_domain(
std::string_view domain,
options const& opts = options()) noexcept;
Parameters
| Name | Description |
|---|---|
domain |
The domain name. |
opts |
The checks to apply. |
See Also
"Validity", in the guide.
webcpp::idna::is_valid_label
Whether check_label finds no error in label.
Synopsis
Declared in <webcpp/idna/validity.hpp>
[[nodiscard]]
bool
is_valid_label(
std::string_view label,
options const& opts = options()) noexcept;
Parameters
| Name | Description |
|---|---|
label |
The label. |
opts |
The checks to apply. |
See Also
"Validity", in the guide.
webcpp::idna::make_error_code
Makes the error_code of an error, as its implicit conversion does.
Synopsis
Declared in <webcpp/idna/errors.hpp>
boost::system::error_code
make_error_code(error e) noexcept;
Return Value
e as an error_code of the category error_category.
Parameters
| Name | Description |
|---|---|
e |
The error. |
webcpp::idna::to_ascii
to_ascii overloads
Synopses
Declared in <webcpp/idna/uts46.hpp>
Converts a domain name to its ASCII form into a string of the given memory resource, which also holds a label beyond the inline working storage.
[[nodiscard]]
result<std::pmr::string>
to_ascii(
std::string_view domain,
options const& opts = options(),
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Converts a domain name to its ASCII form (UTS 46 section 4.2), into a caller’s buffer.
[[nodiscard]]
result<std::size_t>
to_ascii(
std::span<char> output,
std::string_view domain,
options const& opts = options()) noexcept;
Return Value
-
The ASCII form, or the first
error, as the span form orders them. -
The number of chars written, or the first
errorfound, a label’s in their order, thenerror::bidi,error::domain_too_long, anderror::output_too_smalllast.error::storage_exhaustedfor a label beyond the inline storage whenopts.verify_dns_lengthis off.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
domain |
The domain name, in UTF-8. |
opts |
The processing’s flags. |
resource |
Where the string’s memory, and a long label’s, comes from. |
output |
Where the ASCII form is written. |
See Also
"Allocation", in the guide.
"Converting names", in the guide.
"Limits", in the guide.
webcpp::idna::to_ascii
Converts a domain name to its ASCII form into a string of the given memory resource, which also holds a label beyond the inline working storage.
Synopsis
Declared in <webcpp/idna/uts46.hpp>
[[nodiscard]]
result<std::pmr::string>
to_ascii(
std::string_view domain,
options const& opts = options(),
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Description
Under opts.verify_dns_length no label beyond the inline storage can pass, so such a label is refused as the span form refuses it, and both forms report the same error. Without it, a label of any length is converted, but a label of more than 256 code points that is not ASCII, or an A-label whose Punycode decodes to more, is error::punycode_overflow, whatever opts.ignore_invalid_punycode says, since Punycode’s work grows with the square of a label, and the bound keeps it a small multiple of the rest of the processing.
|
Note
|
Throws only what |
Return Value
The ASCII form, or the first error, as the span form orders them.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
domain |
The domain name, in UTF-8. |
opts |
The processing’s flags. |
resource |
Where the string’s memory, and a long label’s, comes from. |
See Also
"Allocation", in the guide.
webcpp::idna::to_ascii
Converts a domain name to its ASCII form (UTS 46 section 4.2), into a caller’s buffer.
Synopsis
Declared in <webcpp/idna/uts46.hpp>
[[nodiscard]]
result<std::size_t>
to_ascii(
std::span<char> output,
std::string_view domain,
options const& opts = options()) noexcept;
Description
The name is mapped, normalized to NFC, split into labels, and each label is validated and, when it is not ASCII, written as "xn--" and its Punycode. With opts.verify_dns_length, the default, the result never exceeds max_domain_length, so a std::array<char, max_domain_length> always holds it. Nothing allocates.
|
Note
|
After an error, the contents of |
Return Value
The number of chars written, or the first error found, a label’s in their order, then error::bidi, error::domain_too_long, and error::output_too_small last. error::storage_exhausted for a label beyond the inline storage when opts.verify_dns_length is off.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
output |
Where the ASCII form is written. |
domain |
The domain name, in UTF-8. |
opts |
The processing’s flags; the defaults are the strict profile. |
See Also
"Converting names", in the guide.
"Limits", in the guide.
webcpp::idna::to_unicode
to_unicode overloads
Synopses
Declared in <webcpp/idna/uts46.hpp>
Converts a domain name to its Unicode form into a string of the given memory resource, as far as it can.
[[nodiscard]]
unicode_string
to_unicode(
std::string_view domain,
options const& opts = options(),
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Converts a domain name to its Unicode form (UTS 46 section 4.3), into a caller’s buffer, as far as it can.
[[nodiscard]]
unicode_result
to_unicode(
std::span<char> output,
std::string_view domain,
options const& opts = options()) noexcept;
Return Value
-
The Unicode form, and the first error recorded.
-
What was written, and the first error recorded; a label beyond the inline storage stops the writing there with
error::storage_exhausted, after any earlier label’s error.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
domain |
The domain name, in UTF-8. |
opts |
The processing’s flags. |
resource |
Where the string’s memory, and a long label’s, comes from. |
output |
Where the Unicode form is written, in UTF-8; the writing stops at a code point boundary when it is full. |
See Also
"Allocation", in the guide.
"Converting names", in the guide.
"Limits", in the guide.
webcpp::idna::to_unicode
Converts a domain name to its Unicode form into a string of the given memory resource, as far as it can.
Synopsis
Declared in <webcpp/idna/uts46.hpp>
[[nodiscard]]
unicode_string
to_unicode(
std::string_view domain,
options const& opts = options(),
std::pmr::memory_resource& resource = *std::pmr::get_default_resource());
Description
A label of any length is converted, but an A-label whose Punycode decodes to more than 256 code points is kept as it is, with error::punycode_overflow whatever opts.ignore_invalid_punycode says, since the decoder’s work grows with the square of the label and a limit of the implementation is no invalid Punycode.
|
Note
|
Throws only what |
Return Value
The Unicode form, and the first error recorded.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
domain |
The domain name, in UTF-8. |
opts |
The processing’s flags. |
resource |
Where the string’s memory, and a long label’s, comes from. |
See Also
"Allocation", in the guide.
webcpp::idna::to_unicode
Converts a domain name to its Unicode form (UTS 46 section 4.3), into a caller’s buffer, as far as it can.
Synopsis
Declared in <webcpp/idna/uts46.hpp>
[[nodiscard]]
unicode_result
to_unicode(
std::span<char> output,
std::string_view domain,
options const& opts = options()) noexcept;
Description
As UTS 46 says, the conversion goes on after an error: each label is written as the processing leaves it, mapped and normalized, an A-label decoded when its Punycode decodes and kept as it is otherwise; no U+FFFD is inserted. Nothing allocates.
Return Value
What was written, and the first error recorded; a label beyond the inline storage stops the writing there with error::storage_exhausted, after any earlier label’s error.
|
Note
|
The return value should not be discarded. |
Parameters
| Name | Description |
|---|---|
output |
Where the Unicode form is written, in UTF-8; the writing stops at a code point boundary when it is full. |
domain |
The domain name, in UTF-8; ill-formed UTF-8 writes nothing. |
opts |
The processing’s flags; the defaults are the strict profile. |
See Also
"Converting names", in the guide.
"Limits", in the guide.
webcpp::idna::max_domain_length
The longest domain name in its ASCII form, in octets, without a trailing root label.
Synopsis
Declared in <webcpp/idna/options.hpp>
inline constexpr std::size_t max_domain_length = 253;
webcpp::idna::max_label_length
The longest label of a domain name in its ASCII form, in octets (RFC 1034 section 3.1).
webcpp::idna::unicode_version
The version of Unicode whose data the tables were generated from.
Synopsis
Declared in <webcpp/idna/options.hpp>
inline constexpr std::string_view unicode_version = "17.0.0";
WEBCPP_IDNA_NO_EXCEPTIONS
Defined when idna is built without exceptions: when webcpp has none (the compiler has none, or the developer defined WEBCPP_NO_EXCEPTIONS for every webcpp library), or when the developer defines it alone, to disable idna’s in a build that has them.
Description
idna raises no exception of its own: every operation that can fail returns its error, so the macro changes nothing in it. It is provided so that a program can set it for every webcpp library alike. An exception idna raised would go through boost::throw_exception, and an API that throws would be absent while this is defined.
See Also
"Testing", in the guide.
Appendix A: Unicode data
idna’s tables, <webcpp/idna/detail/tables.hpp>, and the conformance
vectors of its tests, test/generated/, are derived from the Unicode
Character Database and the files of UTS 46, version 17.0.0,
which Unicode, Inc. publishes under the following notice. idna’s code is
under the Boost Software License 1.0 alone.
UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE Copyright © 1991-2026 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. Permission is hereby granted, free of charge, to any person obtaining a copy of data files and any associated documentation (the "Data Files") or software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that either (a) this copyright and permission notice appear with all copies of the Data Files or Software, or (b) this copyright and permission notice appear in associated Documentation. THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder.
Appendix B: Changelog
What changed in idna in each version of webcpp, newest first, one line per change. "Unreleased" is what the next version brings. The release notes of every version are on webcpp’s index page.
Unreleased
webcpp 0.1.0
-
Added the library: internationalized domain names over Unicode 17.0.0 tables.
-
Added Punycode encoding and decoding, into a caller’s buffer or a pmr string.
-
Added UTS 46 ToASCII and ToUnicode, passing every line of IdnaTestV2.
-
Added IDNA2008 validity of labels and domains, with its contextual and Bidi rules.
-
Added errors as values with a fixed category, and options for every check.
-
Added the
WEBCPP_IDNA_NO_EXCEPTIONSconfiguration macro, whichWEBCPP_NO_EXCEPTIONSalso defines.