use std::borrow::Cow;
use crate::{
error::Error,
sql_read_bytes::SqlReadBytes,
tds::collation::{decode_oem, encode_oem, Charset, Collation},
VarLenType,
};
pub(crate) async fn decode<R>(
src: &mut R,
ty: VarLenType,
len: usize,
collation: Option<Collation>,
) -> crate::Result<Option<Cow<'static, str>>>
where
R: SqlReadBytes + Unpin,
{
use VarLenType::*;
let data = super::plp::decode(src, len).await?;
match (data, ty) {
(Some(buf), BigChar) | (Some(buf), BigVarChar) => {
let collation = collation
.as_ref()
.ok_or_else(|| Error::Protocol("varchar without a collation".into()))?;
Ok(Some(decode_varchar(buf, collation)?.into()))
}
(Some(buf), _) => Ok(Some(decode_nchar(&buf)?.into())),
_ => Ok(None),
}
}
pub(crate) fn decode_varchar(buf: Vec<u8>, collation: &Collation) -> crate::Result<String> {
let encoder = match collation.charset()? {
Charset::Encoding(encoder) => encoder,
Charset::Oem(table) => return Ok(decode_oem(table, &buf)),
};
match encoder.decode_without_bom_handling(&buf).0 {
Cow::Borrowed(_) => {
String::from_utf8(buf).map_err(|_| Error::Encoding("invalid sequence".into()))
}
Cow::Owned(s) => Ok(s),
}
}
pub(crate) fn decode_nchar(buf: &[u8]) -> crate::Result<String> {
if !buf.len().is_multiple_of(2) {
return Err(Error::Protocol(
format!("UTF-16 value of odd length {}", buf.len()).into(),
));
}
Ok(decode_utf16le(buf))
}
pub(crate) fn encode_varchar(s: &str, collation: &Collation) -> crate::Result<Vec<u8>> {
let (encoding, mut encoder) = match collation.charset()? {
Charset::Encoding(encoding) => (encoding, encoding.new_encoder()),
Charset::Oem(table) => return encode_oem(table, s),
};
let len = encoder
.max_buffer_length_from_utf8_without_replacement(s.len())
.ok_or_else(|| Error::Encoding("string too long to encode".into()))?;
let mut bytes = Vec::with_capacity(len);
let (res, _) = encoder.encode_from_utf8_to_vec_without_replacement(s, &mut bytes, true);
match res {
encoding_rs::EncoderResult::Unmappable(c) => Err(Error::Encoding(
format!("{c:?} has no representation in {}", encoding.name()).into(),
)),
_ => Ok(bytes),
}
}
pub(crate) fn decode_utf16le(buf: &[u8]) -> String {
let (pairs, _) = buf.as_chunks::<2>();
let units = pairs.iter().map(|&pair| u16::from_le_bytes(pair));
let mut utf8_len = 0usize;
let mut ascii = true;
for u in units.clone() {
utf8_len += match u {
0..=0x7f => 1,
0x80..=0x7ff | 0xd800..=0xdfff => 2,
_ => 3,
};
ascii &= u < 0x80;
}
if ascii {
let bytes: Vec<u8> = pairs.iter().map(|&[lo, _]| lo).collect();
return String::from_utf8(bytes).expect("ASCII is UTF-8");
}
let mut s = String::with_capacity(utf8_len);
s.extend(char::decode_utf16(units).map(|c| c.unwrap_or(char::REPLACEMENT_CHARACTER)));
s
}
#[cfg(test)]
mod tests {
use super::*;
fn utf16le(s: &str) -> Vec<u8> {
s.encode_utf16().flat_map(u16::to_le_bytes).collect()
}
#[test]
fn decode_utf16le_ascii_and_multibyte() {
assert_eq!(decode_utf16le(&utf16le("")), "");
assert_eq!(decode_utf16le(&utf16le("hello")), "hello");
let s = "åäö €𝄞 ascii";
let decoded = decode_utf16le(&utf16le(s));
assert_eq!(decoded, s);
assert_eq!(decoded.capacity(), s.len());
}
#[test]
fn decode_utf16le_replaces_unpaired_surrogates() {
let units = [0xd800, b'a' as u16, 0xdc00, 0xde00, 0xd83d, 0xd83d, 0xde00];
let buf: Vec<u8> = units.iter().flat_map(|u| u.to_le_bytes()).collect();
assert_eq!(decode_utf16le(&buf), "\u{FFFD}a\u{FFFD}\u{FFFD}\u{FFFD}😀");
}
#[tokio::test]
async fn decode_nvarchar_with_a_lone_surrogate() {
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::{BufMut, BytesMut};
let mut buf = BytesMut::new();
buf.put_u16_le(4);
buf.put_u16_le(b'x' as u16);
buf.put_u16_le(0xdbff);
let s = decode(
&mut buf.into_sql_read_bytes(),
VarLenType::NVarchar,
8000,
None,
)
.await
.unwrap();
assert_eq!(s.as_deref(), Some("x\u{FFFD}"));
}
#[test]
fn varchar_in_oem_code_pages() {
let cp437 = Collation::new(0x0409, 32); let cp850 = Collation::new(0x0409, 42); assert_eq!(
decode_varchar(vec![b'a', 0x80, 0xb3, 0xe3, 0xff], &cp437).unwrap(),
"aÇ│π\u{a0}"
);
assert_eq!(
decode_varchar(vec![b'a', 0x9d, 0xd0, 0xd5, 0xf2], &cp850).unwrap(),
"aØðı‗"
);
assert_eq!(
encode_varchar("aÇ│π\u{a0}", &cp437).unwrap(),
[b'a', 0x80, 0xb3, 0xe3, 0xff]
);
assert_eq!(
encode_varchar("aØðı‗", &cp850).unwrap(),
[b'a', 0x9d, 0xd0, 0xd5, 0xf2]
);
assert!(matches!(
encode_varchar("Ø", &cp437),
Err(Error::Encoding(_))
));
for c in [cp437, cp850] {
let all: Vec<u8> = (0..=255).collect();
let s = decode_varchar(all.clone(), &c).unwrap();
assert_eq!(s.chars().count(), 256);
assert_eq!(encode_varchar(&s, &c).unwrap(), all);
}
}
#[test]
fn varchar_in_a_utf8_collation() {
let utf8 = Collation::new(0x0409 | 0x0400_0000, 0);
assert_eq!(
decode_varchar("héllo 日😀".as_bytes().to_vec(), &utf8).unwrap(),
"héllo 日😀"
);
assert_eq!(encode_varchar("héllo", &utf8).unwrap(), "héllo".as_bytes());
}
#[test]
fn invalid_code_page_sequences_are_replaced() {
let sjis = Collation::new(0x0411, 0); let utf8 = Collation::new(0x0409 | 0x0400_0000, 0);
assert_eq!(
decode_varchar(vec![b'A', 0x82, 0x20, 0x82, 0xa0, 0x82], &sjis).unwrap(),
"A\u{FFFD} あ\u{FFFD}"
);
assert_eq!(
decode_varchar(vec![b'a', 0xff, 0xe6, 0x97], &utf8).unwrap(),
"a\u{FFFD}\u{FFFD}"
);
}
#[tokio::test]
async fn varchar_without_a_collation_is_an_error() {
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::{BufMut, BytesMut};
let mut buf = BytesMut::new();
buf.put_u16_le(1);
buf.put_u8(b'a');
let res = decode(
&mut buf.into_sql_read_bytes(),
VarLenType::BigVarChar,
10,
None,
)
.await;
assert!(matches!(res, Err(Error::Protocol(_))), "{res:?}");
}
#[test]
fn odd_length_utf16_is_an_error() {
assert_eq!(decode_nchar(&[b'h', 0, b'i', 0]).unwrap(), "hi");
assert!(matches!(
decode_nchar(&[b'h', 0, b'i']),
Err(Error::Protocol(_))
));
}
#[test]
fn decode_varchar_keeps_ascii_and_converts_the_rest() {
let collation = Collation::new(0x0409, 0); assert_eq!(
decode_varchar(b"plain".to_vec(), &collation).unwrap(),
"plain"
);
assert_eq!(
decode_varchar(vec![0xe5, 0xe4, 0xf6], &collation).unwrap(),
"åäö"
);
}
}