use super::{DecodeError, Endian, Errors};
pub(super) fn decode_utf8(
data: &[u8],
errors: Errors,
out: &mut String,
) -> Result<(), DecodeError> {
match std::str::from_utf8(data) {
Ok(value) => {
out.push_str(value);
Ok(())
}
Err(_) if errors == Errors::Strict => Err(DecodeError::Invalid),
Err(_) => {
out.reserve(data.len());
for chunk in data.utf8_chunks() {
out.push_str(chunk.valid());
}
Ok(())
}
}
}
fn valid_utf16(data: &[u8], little: bool) -> bool {
if !data.len().is_multiple_of(2) {
return false;
}
let mut pending_high = false;
for pair in data.as_chunks::<2>().0 {
let unit = if little {
u16::from_le_bytes([pair[0], pair[1]])
} else {
u16::from_be_bytes([pair[0], pair[1]])
};
let low = (0xDC00..0xE000).contains(&unit);
if pending_high != low {
return false;
}
pending_high = (0xD800..0xDC00).contains(&unit);
}
!pending_high
}
pub(super) fn decode_utf16(
data: &[u8],
endian: Endian,
errors: Errors,
out: &mut String,
) -> Result<(), DecodeError> {
let (mut data, little) = match endian {
Endian::Little => (data, true),
Endian::Big => (data, false),
Endian::Detect => {
if let Some(rest) = data.strip_prefix(b"\xff\xfe") {
(rest, true)
} else if let Some(rest) = data.strip_prefix(b"\xfe\xff") {
(rest, false)
} else {
(data, cfg!(target_endian = "little"))
}
}
};
let unit = |bytes: &[u8]| {
if little {
u16::from_le_bytes([bytes[0], bytes[1]])
} else {
u16::from_be_bytes([bytes[0], bytes[1]])
}
};
if errors == Errors::Strict && !valid_utf16(data, little) {
return Err(DecodeError::Invalid);
}
out.reserve(data.len());
while !data.is_empty() {
if data.len() < 2 {
if errors == Errors::Strict {
return Err(DecodeError::Invalid);
}
break;
}
let first = unit(data);
if !(0xD800..0xE000).contains(&first) {
out.push(char::from_u32(u32::from(first)).ok_or(DecodeError::Invalid)?);
data = &data[2..];
continue;
}
if first < 0xDC00 && data.len() >= 4 {
let second = unit(&data[2..]);
if (0xDC00..0xE000).contains(&second) {
let value =
0x10000 + ((u32::from(first) - 0xD800) << 10) + (u32::from(second) - 0xDC00);
out.push(char::from_u32(value).ok_or(DecodeError::Invalid)?);
data = &data[4..];
continue;
}
}
if errors == Errors::Strict {
return Err(DecodeError::Invalid);
}
if first < 0xDC00 && data.len() < 4 {
break; }
data = &data[2..];
}
Ok(())
}
pub(super) fn decode_utf32(
data: &[u8],
endian: Endian,
errors: Errors,
out: &mut String,
) -> Result<(), DecodeError> {
let (mut data, little) = match endian {
Endian::Little => (data, true),
Endian::Big => (data, false),
Endian::Detect => {
if let Some(rest) = data.strip_prefix(b"\xff\xfe\x00\x00") {
(rest, true)
} else if let Some(rest) = data.strip_prefix(b"\x00\x00\xfe\xff") {
(rest, false)
} else {
(data, cfg!(target_endian = "little"))
}
}
};
if errors == Errors::Strict {
let valid = data.len() % 4 == 0
&& data.as_chunks::<4>().0.iter().all(|quad| {
let bytes = [quad[0], quad[1], quad[2], quad[3]];
let value = if little {
u32::from_le_bytes(bytes)
} else {
u32::from_be_bytes(bytes)
};
char::from_u32(value).is_some()
});
if !valid {
return Err(DecodeError::Invalid);
}
}
out.reserve(data.len());
while !data.is_empty() {
if data.len() < 4 {
if errors == Errors::Strict {
return Err(DecodeError::Invalid);
}
break;
}
let bytes = [data[0], data[1], data[2], data[3]];
let value = if little {
u32::from_le_bytes(bytes)
} else {
u32::from_be_bytes(bytes)
};
match char::from_u32(value) {
Some(character) => out.push(character),
None if errors == Errors::Strict => return Err(DecodeError::Invalid),
None => {}
}
data = &data[4..];
}
Ok(())
}
fn is_base64(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || byte == b'+' || byte == b'/'
}
fn from_base64(byte: u8) -> u32 {
match byte {
b'A'..=b'Z' => u32::from(byte - b'A'),
b'a'..=b'z' => u32::from(byte - b'a') + 26,
b'0'..=b'9' => u32::from(byte - b'0') + 52,
b'+' => 62,
_ => 63,
}
}
pub(super) fn decode_utf7(
data: &[u8],
errors: Errors,
out: &mut String,
) -> Result<(), DecodeError> {
out.reserve(data.len());
let mut position = 0usize;
let mut in_shift = false;
let mut shift_start = 0usize;
let mut bits = 0u32;
let mut buffer = 0u64;
let mut surrogate = 0u32;
let strict = errors == Errors::Strict;
macro_rules! fail {
() => {{
if strict {
return Err(DecodeError::Invalid);
}
}};
}
while position < data.len() {
let byte = data[position];
if in_shift {
if is_base64(byte) {
buffer = (buffer << 6) | u64::from(from_base64(byte));
bits += 6;
position += 1;
if bits >= 16 {
let unit = u32::try_from((buffer >> (bits - 16)) & 0xFFFF).unwrap_or_default();
bits -= 16;
buffer &= (1u64 << bits) - 1;
if surrogate != 0 {
if (0xDC00..0xE000).contains(&unit) {
let value = 0x10000 + ((surrogate - 0xD800) << 10) + (unit - 0xDC00);
out.push(char::from_u32(value).ok_or(DecodeError::Invalid)?);
surrogate = 0;
continue;
}
fail!(); surrogate = 0;
}
if (0xD800..0xDC00).contains(&unit) {
surrogate = unit;
} else if let Some(character) = char::from_u32(unit) {
out.push(character);
} else {
fail!(); }
}
} else {
in_shift = false;
if bits > 0 {
if bits >= 6 {
position += 1;
fail!(); continue;
} else if buffer != 0 {
position += 1;
fail!(); continue;
}
}
if surrogate != 0 && byte <= 127 && byte != b'+' {
fail!(); }
surrogate = 0;
if byte == b'-' {
position += 1;
}
}
} else if byte == b'+' {
shift_start = position;
position += 1;
if position < data.len() && data[position] == b'-' {
position += 1;
out.push('+');
} else if position < data.len() && !is_base64(data[position]) {
position += 1;
fail!(); } else {
in_shift = true;
surrogate = 0;
bits = 0;
buffer = 0;
}
} else if byte <= 127 {
position += 1;
out.push(byte as char);
} else {
position += 1;
fail!(); }
}
if in_shift && (surrogate != 0 || bits >= 6 || (bits > 0 && buffer != 0)) {
let _ = shift_start;
fail!();
}
Ok(())
}