My own corner of monopam
README.md

ocaml-punycode #

Punycode (RFC 3492) and IDNA 2008 domain name conversion for OCaml.

The DNS carries only ASCII, so a name such as münchen.example.com has to be turned into xn--mnchen-3ya.example.com before it is looked up, and back again before it is shown to a user. Punycode is the encoding that does this for one label, and IDNA is the protocol around it that normalises the name, splits it into labels and adds the xn-- prefix. The punycode library implements the Bootstring algorithm of RFC 3492, including the optional mixed-case annotation that preserves letter case through a round trip. The punycode.idna library implements the ToASCII and ToUnicode operations of RFC 5891, with NFC normalisation through uunf and conversions for Domain_name.t values from the domain-name library. Punycode failures raise an exception that carries the byte offset and character index of the offending input.

Usage #

Basic Punycode Encoding/Decoding #

(* Encode a UTF-8 string to Punycode *)
let encoded = Punycode.encode_utf8 "münchen"
(* = "mnchen-3ya" *)

(* Decode Punycode back to UTF-8 *)
let decoded = Punycode.decode_utf8 "mnchen-3ya"
(* = "münchen" *)
(* Raises Punycode.Error on invalid input *)

Domain Label Operations #

(* Encode a domain label with ACE prefix *)
let label = Punycode.encode_label "münchen"
(* = "xn--mnchen-3ya" *)

(* Decode an ACE-prefixed label *)
let original = Punycode.decode_label "xn--mnchen-3ya"
(* = "münchen" *)

IDNA Domain Name Conversion #

(* Convert internationalized domain to ASCII for DNS lookup *)
let ascii_domain = Punycode_idna.to_ascii "münchen.example.com"
(* = "xn--mnchen-3ya.example.com" *)

(* Convert ASCII domain back to Unicode for display *)
let unicode_domain = Punycode_idna.to_unicode "xn--mnchen-3ya.example.com"
(* = "münchen.example.com" *)

Working with Unicode Code Points #

(* Encode an array of Unicode code points *)
let codepoints = [| Uchar.of_int 0x4ED6; Uchar.of_int 0x4EEC |]
let encoded = Punycode.encode codepoints
(* Result is Punycode string *)

(* Decode to code points *)
let decoded = Punycode.decode "ihqwcrb4cv8a8dqg056pqjye"
(* Result is Uchar.t array *)

Integration with domain-name Library #

(* Convert a Domain_name.t to ASCII *)
let domain = Domain_name.of_string_exn "münchen.example.com"
let ascii = Punycode_idna.ascii_of_domain domain
(* = Domain_name for "xn--mnchen-3ya.example.com" *)

(* Convert back to Unicode *)
let unicode = Punycode_idna.unicode_of_domain ascii
(* = original domain *)

Installation #

Install with opam:

$ opam install punycode

If opam cannot find the package, it may not yet be released in the public opam-repository. Add the overlay repository, then install it:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install punycode

Limitations #

Two IDNA 2008 checks are not yet implemented: the bidirectional text rules of RFC 5893 for right-to-left scripts, and the contextual rules of RFC 5892 Appendix A.1 for the zero-width joiner and non-joiner. Names in European and CJK scripts do not depend on them.

References #

  • RFC 3492 - Punycode: A Bootstring encoding of Unicode for IDNA
  • RFC 5891 - Internationalized Domain Names in Applications (IDNA): Protocol
  • RFC 5892 - Unicode Code Points and IDNA
  • RFC 5893 - Right-to-Left Scripts for IDNA
  • RFC 1035 - Domain Names Implementation and Specification

Licence #

ISC. See LICENSE.md.