charset

tape
import charset from "charset";

let text: string = charset.decode_label("iso-8859-1", body) or {
    charset.decode(charset.Encoding.Windows1252, body)
};

Tape’s text is UTF-8. charset turns text in other encodings into UTF-8: mail in ISO-8859-2, an old file in windows-1251, a Japanese message in Shift_JIS or ISO-2022-JP.

It has every encoding of the WHATWG Encoding Standard, the one browsers and mail readers use, with its names and labels, plus UTF-7 and IMAP’s mailbox-name encoding.

Decoding never fails. Bytes that aren’t valid in the encoding become U+FFFD (�), so a damaged message still shows.

Encodings

FamilyEncoding values
UnicodeUtf8, Utf16Le, Utf16Be, Utf7
Western and Central EuropeanWindows1252, Windows1250, Iso8859_2 … Iso8859_16, Macintosh
CyrillicWindows1251, Koi8R, Koi8U, Ibm866, Iso8859_5, XMacCyrillic
Greek, Turkish, Baltic, VietnameseWindows1253, Windows1254, Windows1257, Windows1258, Iso8859_7 …
Hebrew, Arabic, ThaiWindows1255, Iso8859_8, Iso8859_8I, Windows1256, Iso8859_6, Windows874
ChineseGbk, Gb18030, Big5
JapaneseShiftJis, EucJp, Iso2022Jp
KoreanEucKr
OtherReplacement, XUserDefined

"{e}" is an encoding’s standard name (windows-1252, Shift_JIS).

Labels

FunctionDoes
lookup(label) -> ?EncodingThe encoding a label names, or null
bom(data) -> ?EncodingThe encoding a byte order mark at the start announces, or null

A label is what a mail’s charset= or an HTTP header says. lookup accepts every label of the Standard, in any case and with white space around it: utf8, latin1, cp1251, gb2312, x-sjis, ks_c_5601-1987, and UTF-7’s utf-7.

As in browsers, us-ascii, iso-8859-1 and latin1 mean windows-1252: mail labelled ISO-8859-1 is very often windows-1252 (curly quotes, €).

An unknown label is null. What to do then is your choice; mail readers usually fall back to windows-1252.

Decoding

FunctionDoes
decode(enc, data) -> stringdata in encoding enc, as UTF-8
decode_label(label, data) -> string or CharsetErrorThe same by label; UnknownLabel for an unknown one
tape
let e: charset.Encoding = charset.lookup("Shift_JIS") or { return 1; };
io.println(charset.decode(e, data));

A byte order mark of the encoding itself is removed: EF BB BF before UTF-8, FF FE before UTF-16LE. The label decides the encoding, not the mark. To let a mark override the label, as browsers do, write charset.decode(charset.bom(data) or { enc }, data).

IMAP mailbox names

IMAP sends mailbox names in a modified UTF-7: Entwürfe is Entw&APw-rfe.

FunctionDoes
imap_utf7_decode(name) -> string or CharsetErrorA name from the server, as UTF-8
imap_utf7_encode(name) -> stringA UTF-8 name, as the server wants it
tape
let shown: string = charset.imap_utf7_decode("Entw&APw-rfe") or { return 1; };   // Entwürfe
let sent: string = charset.imap_utf7_encode("Entwürfe");                          // Entw&APw-rfe

imap_utf7_decode is strict: a malformed name is CharsetError.Malformed, not a guess, because the name goes back to the server to select the mailbox. A server that has enabled UTF8=ACCEPT sends plain UTF-8; don’t decode those.

Size

The tables (185 KB, most of it for Chinese, Japanese and Korean) are built into every program that imports charset, and into no other.

Last modified: