package extract import ( "bytes" "strings" "unicode/utf8" "golang.org/x/net/html/charset" "golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/unicode" "golang.org/x/text/unicode/norm" ) func hasUTF16BOM(b []byte) bool { return len(b) >= 2 && ((b[0] == 0xFF && b[1] == 0xFE) || (b[0] == 0xFE && b[1] == 0xFF)) } // decodeText turns a text file into UTF-8, NFC. // // UTF-8 is taken as it is (its BOM dropped); UTF-16 by its BOM, which is how // Windows' Notepad writes "Unicode". Anything else is a single-byte code // page, and which one cannot be known from the bytes; Windows-1254 is // assumed, since it is what a Turkish Windows writes and it agrees with // Windows-1252 on every letter but six (ğĞıİşŞ in place of ðÐýÝþÞ) — which // are rare in the western languages 1252 serves. func decodeText(b []byte) string { var s string switch { case bytes.HasPrefix(b, []byte{0xEF, 0xBB, 0xBF}): s = string(b[3:]) case hasUTF16BOM(b): out, err := unicode.UTF16(unicode.LittleEndian, unicode.ExpectBOM).NewDecoder().Bytes(b) if err != nil { return "" } s = string(out) case utf8.Valid(b): s = string(b) default: out, err := charmap.Windows1254.NewDecoder().Bytes(b) if err != nil { return "" } s = string(out) } return norm.NFC.String(strings.ReplaceAll(s, "\r\n", "\n")) } // decodeCharset decodes bytes declared to be in a charset, as mail and web // pages declare theirs, by any of the WHATWG labels; an unknown or missing // one is treated as decodeText would. func decodeCharset(b []byte, cs string) string { cs = strings.ToLower(strings.TrimSpace(cs)) if cs == "" || cs == "utf-8" || cs == "utf8" || cs == "us-ascii" { return decodeText(b) } enc, _ := charset.Lookup(cs) if enc == nil { return decodeText(b) } out, err := enc.NewDecoder().Bytes(b) if err != nil { return decodeText(b) } return norm.NFC.String(strings.ReplaceAll(string(out), "\r\n", "\n")) } // codePage is the decoder for an RTF \ansicpgN or a Windows code page number. func codePage(n int) *charmap.Charmap { switch n { case 1250: return charmap.Windows1250 case 1251: return charmap.Windows1251 case 1252: return charmap.Windows1252 case 1253: return charmap.Windows1253 case 1254: return charmap.Windows1254 case 1255: return charmap.Windows1255 case 1256: return charmap.Windows1256 case 1257: return charmap.Windows1257 case 1258: return charmap.Windows1258 case 850: return charmap.CodePage850 case 437: return charmap.CodePage437 } return charmap.Windows1252 }