idna2008-1.0.0.0: README.md
# idna2008
A Haskell library for parsing and validating internationalized
domain names: domain names that may contain characters from
non-Latin scripts (Greek, Hebrew, Arabic, CJK, ...) alongside the
conventional letters, digits, and hyphens.
## What it does
Given a domain name as a string (with whatever mix of ASCII and
non-ASCII characters the user typed), the library:
* Checks that every label (the parts between dots) is allowed.
* Encodes valid non-ASCII Unicode IDN labels (U-labels) to their
ACE-prefixed (`xn--...`) ASCII (A-label) forms, suitable for
inclusion in zone files or use in DNS queries.
* Tells the caller what kind of label each one is (see below),
and lets the caller pick which kinds are accepted in the first
place — strict IDN, hostname-shaped, every form a DNS zone
file might carry plus U-labels, or anything in between.
* Optionally normalises display-form input (case folding, NFC,
full-width to ASCII, alternate label separators) before parsing.
* Optionally renders the parsed name back to display form
(Unicode where possible, ASCII where not).
## Per-label classification
A single domain name often mixes different kinds of labels. The
library reports each label as one of:
| Class | What it is |
|-------------|---------------------------------------------------------|
| `LDH` | A valid label consisting of letters, digits and hyphens.|
| `RLDH` | Legacy reserved labels with `--` at positions 3-4. |
| `FAKEA` | An ACE-prefixed label that isn't a valid A-label. |
| `ALABEL` | An ACE-prefixed label that encodes a valid IDN label. |
| `ULABEL` | A non-ASCII label that can be part of a valid IDN. |
| `ATTRLEAF` | An underscore-prefixed label (e.g. `_25._tcp`). |
| `OCTET` | A label with characters outside the LDH alphabet. |
| `WILDLABEL` | The DNS wildcard label `*`. |
| `LAXULABEL` | A U-label that fails strict IDN validation. |
A name like `_25._tcp.müllers.example.de` parses cleanly with
five labels in three different classes (`ATTRLEAF`, `ULABEL`,
`LDH`). Most existing IDNA libraries don't make these
distinctions; they typically support only LDH + ALABEL + ULABEL.
The caller controls which classes are admitted via a
`LabelFormSet`. Pre-built sets cover the common policies:
* `idnLabelForms` — strict IDN: `LDH` + `ALABEL` + `ULABEL`.
* `hostnameLabelForms` — the IDN set plus `RLDH` and `FAKEA`,
for hostname-shaped names from the wild where unusual but
syntactically valid LDH labels do appear.
* `allLabelForms` — every label class a DNS zone file might
carry (`LDH`, `RLDH`, `FAKEA`, `ALABEL`, `ATTRLEAF`, `OCTET`,
`WILDLABEL`) plus `ULABEL`. Zone files are 8-bit and contain
no U-labels in practice, but admitting U-labels alongside
the on-the-wire forms matches what this library is for —
parsing presentation-form input that may carry either.
`LAXULABEL` is excluded from every pre-built set: admitting a
U-label that fails strict IDN validation is a deliberate choice
the caller makes by writing it in, e.g. `idnLabelForms <+>
LAXULABEL`.
## What's distinctive
* **Strict.** Some browsers and language standard libraries use
a more permissive variant of the IDNA standard that accepts
characters strict IDNA2008 rejects. This library does not use
that variant; if a name is admitted, it's by-the-book valid.
* **Bidirectional-text rules in two layers.** When right-to-left
scripts (Hebrew, Arabic) appear in a domain name, special rules
prevent visual confusion with neighbouring left-to-right text.
The library splits these rules into a per-label check (does the
label make sense on its own?) and a cross-label check (do the
labels make sense together?), each independently configurable.
An ASCII-fallback option lets display code show a safe ASCII
spelling when the cross-label check would otherwise reject the
name.
* **Up-to-date Unicode coverage.** The Unicode Consortium
publishes new versions of its character database every year or
so; this library derives its tables directly from those
publications and stays current.
* **Conformance test vectors.** Test cases are published as
JSON, reusable by ports to other programming languages.
## Status
Initial public release (`1.0.0.0`). The conformance suite in
`tests/` carries 186 JSON test vectors with a documented schema
so ports to other languages can reuse the fixtures.
## Demo
Given the below `demo.hs`:
```haskell
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
module Main(main) where
import qualified Data.Text.IO as T
import Text.IDNA2008
-- Strict default: idnLabelForms + defaultIdnaFlags.
ex1 :: Domain
ex1 = $$(dnLit mkDomain "αβγ.gr")
-- Enable mappings via @(parseDomainOpts forms flags)@:
ex2 :: Domain
ex2 = $$(let forms = idnLabelForms
flags = defaultIdnaFlags <> allIdnaMappings
parser = parseDomainOpts forms flags
in dnLit (fmap fst . parser) "ΑβΓ.GR")
main :: IO()
main = do
-- Print A-label form
ascOut ex1
-- Print U-label form
uniOut ex1
-- Print A-label + U-label forms and label types:
mapM_ dump $ parseDomain allLabelForms "_25._tcp.*.\\097bc.αβγ.gr"
-- An invalid domain, with code point 95 ('_') in the second label.
-- Only LDH ASCII characters can appear in a U-label. The offset
-- within that label is non-specific because it may have gone
-- through some "mappings" that mask the real byte offset.
print $ parseDomain idnLabelForms "foo.αβ_γδ.gr"
where
ascOut, uniOut :: Domain -> IO ()
ascOut = T.putStrLn . domainToAscii
uniOut = T.putStrLn . domainToUnicode
dump (dom, inf) = do
ascOut dom
uniOut dom
print inf
```
Compiling and running it we get the below output:
```
xn--mxacd.gr
αβγ.gr
_25._tcp.*.abc.xn--mxacd.gr
_25._tcp.*.abc.αβγ.gr
[ATTRLEAF,ATTRLEAF,WILDLABEL,OCTET,ULABEL,LDH]
Left (ErrLabelInvalid 1 (DisallowedCodepoint 95))
```
## License
BSD-3-Clause.