use std::borrow::Cow;
use std::hash::Hash;
use widestring::Utf16Str;
use crate::buf::BufMut;
pub unsafe trait CodesetDef {
fn encode_to_buf(&self, src: &str, dst: &mut BufMut<u8, 1>) -> Option<usize>;
fn decode_is_native(&self, src: &[u8]) -> Option<bool>;
fn decode_to_cow<'a>(&self, src: &'a [u8]) -> Option<Cow<'a, str>>;
fn decode_to_buf(&self, src: &[u8], dst: &mut BufMut<u8, 1>) -> Option<usize>;
}
pub unsafe trait WideCodesetDef {
fn encode_to_buf(&self, src: &Utf16Str, dst: &mut BufMut<u16, 1>) -> Option<usize>;
fn decode_is_native(&self, src: &[u16]) -> Option<bool>;
fn decode_to_cow<'a>(&self, src: &'a [u16]) -> Option<Cow<'a, Utf16Str>>;
fn decode_to_buf(&self, src: &[u16], dst: &mut BufMut<u16, 1>) -> Option<usize>;
}
pub trait AnyCodeset: Sized + Copy + Eq + Hash + 'static {
const NATIVE: Self;
const ALL: &[Self];
fn id(self) -> u32;
fn from_id(id: u32) -> Option<Self>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Codeset {
Ascii,
Utf8,
Iso8859_1,
Windows1252,
}
unsafe impl CodesetDef for Codeset {
fn encode_to_buf(&self, src: &str, dst: &mut BufMut<u8, 1>) -> Option<usize> {
match self {
Codeset::Ascii => Ascii.encode_to_buf(src, dst),
Codeset::Utf8 => Utf8.encode_to_buf(src, dst),
Codeset::Iso8859_1 => Iso8859_1.encode_to_buf(src, dst),
Codeset::Windows1252 => Windows1252.encode_to_buf(src, dst),
}
}
fn decode_is_native(&self, src: &[u8]) -> Option<bool> {
match self {
Codeset::Ascii => Ascii.decode_is_native(src),
Codeset::Utf8 => Utf8.decode_is_native(src),
Codeset::Iso8859_1 => Iso8859_1.decode_is_native(src),
Codeset::Windows1252 => Windows1252.decode_is_native(src),
}
}
fn decode_to_cow<'a>(&self, src: &'a [u8]) -> Option<Cow<'a, str>> {
match self {
Codeset::Ascii => Ascii.decode_to_cow(src),
Codeset::Utf8 => Utf8.decode_to_cow(src),
Codeset::Iso8859_1 => Iso8859_1.decode_to_cow(src),
Codeset::Windows1252 => Windows1252.decode_to_cow(src),
}
}
fn decode_to_buf(&self, src: &[u8], dst: &mut BufMut<u8, 1>) -> Option<usize> {
match self {
Codeset::Ascii => Ascii.decode_to_buf(src, dst),
Codeset::Utf8 => Utf8.decode_to_buf(src, dst),
Codeset::Iso8859_1 => Iso8859_1.decode_to_buf(src, dst),
Codeset::Windows1252 => Windows1252.decode_to_buf(src, dst),
}
}
}
impl AnyCodeset for Codeset {
const NATIVE: Self = Codeset::Utf8;
const ALL: &[Self] = &[
Self::Ascii,
Self::Utf8,
Self::Iso8859_1,
Self::Windows1252,
];
fn id(self) -> u32 {
match self {
Self::Ascii => 0x00010020,
Self::Utf8 => 0x05010001,
Self::Iso8859_1 => 0x00010001,
Self::Windows1252 => 0x100204E4,
}
}
fn from_id(id: u32) -> Option<Self> {
Some(match id {
0x00010020 => Self::Ascii,
0x05010001 => Self::Utf8,
0x00010001 => Self::Iso8859_1,
0x100204E4 => Self::Windows1252,
_ => return None
})
}
}
pub struct Utf8;
unsafe impl CodesetDef for Utf8 {
fn encode_to_buf(&self, src: &str, dst: &mut BufMut<u8, 1>) -> Option<usize> {
dst.extend_from_slice(src.as_bytes());
Some(src.len())
}
fn decode_is_native(&self, src: &[u8]) -> Option<bool> {
std::str::from_utf8(src).is_ok().then_some(true)
}
fn decode_to_cow<'a>(&self, src: &'a [u8]) -> Option<Cow<'a, str>> {
std::str::from_utf8(src).ok().map(Cow::Borrowed)
}
fn decode_to_buf(&self, src: &[u8], dst: &mut BufMut<u8, 1>) -> Option<usize> {
let src = std::str::from_utf8(src).ok()?;
dst.extend_from_slice(src.as_bytes());
Some(src.len())
}
}
pub struct Ascii;
unsafe impl CodesetDef for Ascii {
fn encode_to_buf(&self, src: &str, dst: &mut BufMut<u8, 1>) -> Option<usize> {
if !src.is_ascii() {
return None;
}
dst.extend_from_slice(src.as_bytes());
Some(src.len())
}
fn decode_is_native(&self, src: &[u8]) -> Option<bool> {
src.is_ascii().then_some(true)
}
fn decode_to_cow<'a>(&self, src: &'a [u8]) -> Option<Cow<'a, str>> {
as_ascii(src).map(Cow::Borrowed)
}
fn decode_to_buf(&self, src: &[u8], dst: &mut BufMut<u8, 1>) -> Option<usize> {
let src = as_ascii(src)?;
dst.extend_from_slice(src.as_bytes());
Some(src.len())
}
}
pub struct Iso8859_1;
unsafe impl CodesetDef for Iso8859_1 {
fn encode_to_buf(&self, src: &str, dst: &mut BufMut<u8, 1>) -> Option<usize> {
encode_simple_buf(src, dst, encode_iso8859_1)
}
fn decode_is_native(&self, src: &[u8]) -> Option<bool> {
Some(src.is_ascii())
}
fn decode_to_cow<'a>(&self, src: &'a [u8]) -> Option<Cow<'a, str>> {
Some(decode_simple_cow(src, 2, decode_iso8859_1))
}
fn decode_to_buf(&self, src: &[u8], dst: &mut BufMut<u8, 1>) -> Option<usize> {
Some(decode_simple_buf(src, dst, 2, decode_iso8859_1))
}
}
#[inline]
fn encode_iso8859_1(ch: char) -> Option<u8> {
u8::try_from(ch).ok()
}
#[inline]
fn decode_iso8859_1(b: u8) -> char {
b as char
}
pub struct Windows1252;
unsafe impl CodesetDef for Windows1252 {
fn encode_to_buf(&self, src: &str, dst: &mut BufMut<u8, 1>) -> Option<usize> {
encode_simple_buf(src, dst, encode_windows1252)
}
fn decode_is_native(&self, src: &[u8]) -> Option<bool> {
Some(src.is_ascii())
}
fn decode_to_cow<'a>(&self, src: &'a [u8]) -> Option<Cow<'a, str>> {
Some(decode_simple_cow(src, 3, decode_windows1252))
}
fn decode_to_buf(&self, src: &[u8], dst: &mut BufMut<u8, 1>) -> Option<usize> {
Some(decode_simple_buf(src, dst, 3, decode_windows1252))
}
}
#[inline]
fn encode_windows1252(ch: char) -> Option<u8> {
Some(match ch {
'\u{0000}'..='\u{007F}' |
'\u{00A0}'..='\u{00FF}' => ch as u8,
'\u{2000}'..='\u{20FF}' => {
let b = WINDOWS_1252_ENCODE_20XX[ch as u8 as usize];
if b == 0 { return None } else { b }
}
'\u{0192}' => 0x83,
'\u{02C6}' => 0x88,
'\u{0160}' => 0x8A,
'\u{0152}' => 0x8C,
'\u{017D}' => 0x8E,
'\u{02DC}' => 0x98,
'\u{2122}' => 0x99,
'\u{0161}' => 0x9A,
'\u{0153}' => 0x9C,
'\u{017E}' => 0x9E,
'\u{0178}' => 0x9F,
_ => return None
})
}
static WINDOWS_1252_ENCODE_20XX: [u8; 256] = const {
let mut table = [0; 256];
let mut i = 0;
while i < WINDOWS_1252_DECODE_8X_9X.len() {
let code = WINDOWS_1252_DECODE_8X_9X[i] as u32;
if let 0x2000..=0x20FF = code {
table[code as u8 as usize] = 0x80 + i as u8
}
i += 1;
}
table
};
#[inline]
fn decode_windows1252(b: u8) -> char {
match b {
0x80..=0x9F => WINDOWS_1252_DECODE_8X_9X[b as usize - 0x80],
_ => b as char,
}
}
static WINDOWS_1252_DECODE_8X_9X: [char; 32] = [
'€', '\0', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ', '\0', 'Ž', '\0',
'\0', '‘', '’', '“', '”', '•', '–', '—', '˜', '™', 'š', '›', 'œ', '\0', 'ž', 'Ÿ',
];
#[inline]
fn as_ascii(src: &[u8]) -> Option<&str> {
src.is_ascii().then(|| unsafe { std::str::from_utf8_unchecked(src) })
}
fn encode_simple_buf(src: &str, dst: &mut BufMut<u8, 1>, encode: impl Fn(char) -> Option<u8>) -> Option<usize> {
if src.is_ascii() {
dst.extend_from_slice(src.as_bytes());
return Some(src.len());
}
let start = dst.len();
dst.reserve(src.len());
for ch in src.chars() {
match encode(ch) {
Some(b) => dst.push(b),
None => {
dst.truncate(start);
return None;
}
}
}
Some(dst.len() - start)
}
fn decode_simple_cow(src: &[u8], max_char_len: usize, decode: impl Fn(u8) -> char) -> Cow<'_, str> {
if let Some(src) = as_ascii(src) {
return Cow::Borrowed(src);
}
let mut s = String::with_capacity(decode_simple_cap(src.len(), max_char_len));
s.extend(src.iter().map(|&b| decode(b)));
Cow::Owned(s)
}
fn decode_simple_buf(src: &[u8], dst: &mut BufMut<u8, 1>, max_char_len: usize, decode: impl Fn(u8) -> char) -> usize {
if src.is_ascii() {
dst.extend_from_slice(src);
return src.len();
}
let start = dst.len();
dst.reserve(decode_simple_cap(src.len(), max_char_len));
for &b in src {
dst.extend_from_slice(decode(b).encode_utf8(&mut [0; 4]).as_bytes());
}
dst.len() - start
}
#[inline]
fn decode_simple_cap(len: usize, max_char_len: usize) -> usize {
const WORST_CASE_UP_TO: usize = 64;
if len <= WORST_CASE_UP_TO { len * max_char_len } else { len + len / 4 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WideCodeset {
Utf16,
}
unsafe impl WideCodesetDef for WideCodeset {
fn encode_to_buf(&self, src: &Utf16Str, dst: &mut BufMut<u16, 1>) -> Option<usize> {
match self {
WideCodeset::Utf16 => Utf16.encode_to_buf(src, dst),
}
}
fn decode_is_native(&self, src: &[u16]) -> Option<bool> {
match self {
WideCodeset::Utf16 => Utf16.decode_is_native(src),
}
}
fn decode_to_cow<'a>(&self, src: &'a [u16]) -> Option<Cow<'a, Utf16Str>> {
match self {
WideCodeset::Utf16 => Utf16.decode_to_cow(src),
}
}
fn decode_to_buf(&self, src: &[u16], dst: &mut BufMut<u16, 1>) -> Option<usize> {
match self {
WideCodeset::Utf16 => Utf16.decode_to_buf(src, dst),
}
}
}
impl AnyCodeset for WideCodeset {
const NATIVE: Self = Self::Utf16;
const ALL: &[Self] = &[
Self::Utf16,
];
fn id(self) -> u32 {
match self {
Self::Utf16 => 0x00010109,
}
}
fn from_id(id: u32) -> Option<Self> {
Some(match id {
0x00010109 => Self::Utf16,
_ => return None
})
}
}
pub struct Utf16;
unsafe impl WideCodesetDef for Utf16 {
fn encode_to_buf(&self, src: &Utf16Str, dst: &mut BufMut<u16, 1>) -> Option<usize> {
dst.extend_from_slice(src.as_slice());
Some(src.len())
}
fn decode_is_native(&self, src: &[u16]) -> Option<bool> {
Utf16Str::from_slice(src).is_ok().then_some(true)
}
fn decode_to_cow<'a>(&self, src: &'a [u16]) -> Option<Cow<'a, Utf16Str>> {
Utf16Str::from_slice(src).ok().map(Cow::Borrowed)
}
fn decode_to_buf(&self, src: &[u16], dst: &mut BufMut<u16, 1>) -> Option<usize> {
let src = Utf16Str::from_slice(src).ok()?;
dst.extend_from_slice(src.as_slice());
Some(src.len())
}
}
pub fn negociate_with_server<C: AnyCodeset>(server_native_codeset: C, server_conversion_codesets: &[C]) -> Option<C> {
if C::NATIVE == server_native_codeset {
return Some(C::NATIVE);
}
if C::ALL.contains(&server_native_codeset) {
return Some(server_native_codeset);
}
if server_conversion_codesets.contains(&C::NATIVE) {
return Some(C::NATIVE);
}
for server_conversion_codeset in server_conversion_codesets {
if C::ALL.contains(server_conversion_codeset) {
return Some(*server_conversion_codeset);
}
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Codesets {
pub codeset: Codeset,
pub wide_codeset: WideCodeset,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn in_place() {
assert_eq!(Codeset::Utf8.decode_is_native("café".as_bytes()), Some(true));
assert_eq!(Codeset::Utf8.decode_is_native(b"caf\xE9"), None);
assert_eq!(Codeset::Ascii.decode_is_native(b"caf\xE9"), None);
assert_eq!(Codeset::Iso8859_1.decode_is_native(b"caf\xE9"), Some(false));
assert_eq!(Codeset::Windows1252.decode_is_native(b"caf\xE9"), Some(false));
for codeset in [Codeset::Ascii, Codeset::Iso8859_1, Codeset::Windows1252] {
assert_eq!(codeset.decode_is_native(b"cafe"), Some(true));
assert_ne!(codeset.decode_is_native("café".as_bytes()), Some(true));
assert!(matches!(codeset.decode_to_cow(b"cafe"), Some(Cow::Borrowed("cafe"))));
}
}
#[test]
fn decoding_cow() {
let decoded = Codeset::Iso8859_1.decode_to_cow(b"caf\xE9").unwrap();
assert_eq!(decoded, "café");
assert!(matches!(decoded, Cow::Owned(_)));
assert_eq!(Codeset::Windows1252.decode_to_cow(b"\x80 \xE9").unwrap(), "€ é");
assert_eq!(Codeset::Utf8.decode_to_cow(b"\xE9"), None);
assert_eq!(Codeset::Ascii.decode_to_cow(b"caf\xE9"), None);
}
#[test]
fn decoding_buf() {
let mut buf = BufMut::from(&b"> "[..]);
assert_eq!(Codeset::Iso8859_1.decode_to_buf(b"caf\xE9", &mut buf), Some(5));
assert_eq!(Codeset::Windows1252.decode_to_buf(b" \x80 ", &mut buf), Some(5));
assert_eq!(Codeset::Utf8.decode_to_buf("é".as_bytes(), &mut buf), Some(2));
assert_eq!(Codeset::Ascii.decode_to_buf(b" ok", &mut buf), Some(3));
assert_eq!(buf.as_slice(), "> café € é ok".as_bytes());
assert_eq!(Codeset::Utf8.decode_to_buf(b"\xE9", &mut buf), None);
assert_eq!(Codeset::Ascii.decode_to_buf(b"\xE9", &mut buf), None);
assert_eq!(buf.as_slice(), "> café € é ok".as_bytes());
}
#[test]
fn encoding() {
let mut buf = BufMut::from(&b"> "[..]);
assert_eq!(Codeset::Iso8859_1.encode_to_buf("café", &mut buf), Some(4));
assert_eq!(Codeset::Windows1252.encode_to_buf(" € ", &mut buf), Some(3));
assert_eq!(Codeset::Utf8.encode_to_buf("é", &mut buf), Some(2));
assert_eq!(buf.as_slice(), b"> caf\xE9 \x80 \xC3\xA9");
let len = buf.len();
assert_eq!(Codeset::Iso8859_1.encode_to_buf("café €", &mut buf), None);
assert_eq!(Codeset::Windows1252.encode_to_buf("café ✓", &mut buf), None);
assert_eq!(Codeset::Ascii.encode_to_buf("café", &mut buf), None);
assert_eq!(buf.len(), len);
}
#[test]
fn wide() {
let units = widestring::utf16str!("café").as_slice();
assert!(matches!(WideCodeset::Utf16.decode_to_cow(units), Some(Cow::Borrowed(s)) if s == "café"));
assert_eq!(WideCodeset::Utf16.decode_is_native(units), Some(true));
assert_eq!(WideCodeset::Utf16.decode_is_native(&[0xD800]), None);
let mut buf = BufMut::new();
assert_eq!(WideCodeset::Utf16.encode_to_buf(widestring::utf16str!("é€"), &mut buf), Some(2));
assert_eq!(WideCodeset::Utf16.decode_to_buf(&[0xD800], &mut buf), None);
assert_eq!(WideCodeset::Utf16.decode_to_buf(units, &mut buf), Some(4));
assert_eq!(buf.as_slice(), widestring::utf16str!("é€café").as_slice());
}
}