use std::{
fmt::{self, Write},
str,
};
use crate::{
exception_private::{ExcType, RunError, RunResult},
resource::ResourceTracker,
string_builder::StringBuilder,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Codec {
Utf8,
Ascii,
Utf16(Option<Endian>),
Utf32(Option<Endian>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Endian {
Little,
Big,
}
impl Codec {
pub(crate) fn find(name: &str) -> Option<Self> {
match name {
"utf-8" | "utf8" => Some(Self::Utf8),
"ascii" => Some(Self::Ascii),
"utf-16" => Some(Self::Utf16(None)),
"utf-32" => Some(Self::Utf32(None)),
_ => Self::find_normalized(name),
}
}
fn find_normalized(name: &str) -> Option<Self> {
match normalize_encoding(name).as_str() {
"utf_8" | "utf8" | "utf" | "u8" | "cp65001" | "utf8_ucs2" | "utf8_ucs4" => Some(Self::Utf8),
"ascii" | "646" | "us" | "us_ascii" | "cp367" | "ibm367" | "csascii" | "ansi_x3.4_1968"
| "ansi_x3_4_1968" | "ansi_x3.4_1986" | "iso646_us" | "iso_646.irv_1991" | "iso_ir_6" => Some(Self::Ascii),
"utf_16" | "u16" | "utf16" => Some(Self::Utf16(None)),
"utf_16_le" | "utf_16le" | "unicodelittleunmarked" => Some(Self::Utf16(Some(Endian::Little))),
"utf_16_be" | "utf_16be" | "unicodebigunmarked" => Some(Self::Utf16(Some(Endian::Big))),
"utf_32" | "u32" | "utf32" => Some(Self::Utf32(None)),
"utf_32_le" | "utf_32le" => Some(Self::Utf32(Some(Endian::Little))),
"utf_32_be" | "utf_32be" => Some(Self::Utf32(Some(Endian::Big))),
_ => None,
}
}
pub(crate) fn encode(self, s: &str, errors: &str, tracker: &impl ResourceTracker) -> RunResult<Vec<u8>> {
match self {
Self::Utf8 => Ok(s.as_bytes().to_vec()),
Self::Ascii => encode_ascii(s, errors, tracker),
Self::Utf16(endian) => Ok(encode_utf16(s, endian.unwrap_or(Endian::Little), endian.is_none())),
Self::Utf32(endian) => Ok(encode_utf32(s, endian.unwrap_or(Endian::Little), endian.is_none())),
}
}
pub(crate) fn decode(self, bytes: &[u8], errors: &str) -> RunResult<String> {
match self {
Self::Utf8 => decode_utf8(bytes, errors),
Self::Ascii => decode_ascii(bytes, errors),
Self::Utf16(Some(endian)) => decode_utf16(bytes, 0, endian, errors),
Self::Utf32(Some(endian)) => decode_utf32(bytes, 0, endian, errors),
Self::Utf16(None) => match bytes {
[0xFF, 0xFE, ..] => decode_utf16(bytes, 2, Endian::Little, errors),
[0xFE, 0xFF, ..] => decode_utf16(bytes, 2, Endian::Big, errors),
_ => decode_utf16(bytes, 0, Endian::Little, errors),
},
Self::Utf32(None) => match bytes {
[0xFF, 0xFE, 0x00, 0x00, ..] => decode_utf32(bytes, 4, Endian::Little, errors),
[0x00, 0x00, 0xFE, 0xFF, ..] => decode_utf32(bytes, 4, Endian::Big, errors),
_ => decode_utf32(bytes, 0, Endian::Little, errors),
},
}
}
}
fn normalize_encoding(name: &str) -> String {
let mut out = String::with_capacity(name.len());
let mut pending_sep = false;
for c in name.chars() {
if c.is_alphanumeric() || c == '.' {
if pending_sep && !out.is_empty() {
out.push('_');
}
pending_sep = false;
if c.is_ascii() {
out.push(c.to_ascii_lowercase());
}
} else {
pending_sep = true;
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ErrorHandler {
Strict,
Ignore,
Replace,
Backslashreplace,
Xmlcharrefreplace,
Namereplace,
Surrogateescape,
Surrogatepass,
}
impl ErrorHandler {
fn lookup(name: &str) -> RunResult<Self> {
match name {
"strict" => Ok(Self::Strict),
"ignore" => Ok(Self::Ignore),
"replace" => Ok(Self::Replace),
"backslashreplace" => Ok(Self::Backslashreplace),
"xmlcharrefreplace" => Ok(Self::Xmlcharrefreplace),
"namereplace" => Ok(Self::Namereplace),
"surrogateescape" => Ok(Self::Surrogateescape),
"surrogatepass" => Ok(Self::Surrogatepass),
_ => Err(ExcType::lookup_error_unknown_error_handler(name)),
}
}
}
struct LazyHandler<'a> {
name: &'a str,
cached: Option<ErrorHandler>,
}
impl<'a> LazyHandler<'a> {
fn new(name: &'a str) -> Self {
Self { name, cached: None }
}
fn get(&mut self) -> RunResult<ErrorHandler> {
if let Some(handler) = self.cached {
Ok(handler)
} else {
let handler = ErrorHandler::lookup(self.name)?;
self.cached = Some(handler);
Ok(handler)
}
}
}
fn handle_decode_error(
handler: ErrorHandler,
bad: &[u8],
out: &mut String,
is_surrogate: bool,
strict_err: impl FnOnce() -> RunError,
) -> RunResult<()> {
match handler {
ErrorHandler::Strict => Err(strict_err()),
ErrorHandler::Ignore => Ok(()),
ErrorHandler::Replace => {
out.push('\u{FFFD}');
Ok(())
}
ErrorHandler::Backslashreplace => {
for &byte in bad {
write!(out, "\\x{byte:02x}").expect("writing to a String is infallible");
}
Ok(())
}
ErrorHandler::Xmlcharrefreplace | ErrorHandler::Namereplace => Err(ExcType::type_error_decode_error_callback()),
ErrorHandler::Surrogateescape => Err(ExcType::not_implemented_surrogate_handler_decode("surrogateescape")),
ErrorHandler::Surrogatepass => {
if is_surrogate {
Err(ExcType::not_implemented_surrogate_handler_decode("surrogatepass"))
} else {
Err(strict_err())
}
}
}
}
fn encode_ascii(s: &str, errors: &str, tracker: &impl ResourceTracker) -> RunResult<Vec<u8>> {
if s.is_ascii() {
return Ok(s.as_bytes().to_vec());
}
let mut handler = LazyHandler::new(errors);
let mut out = StringBuilder::with_capacity(s.len(), tracker)?;
let mut chars = s.chars().enumerate().peekable();
while let Some((idx, c)) = chars.next() {
if c.is_ascii() {
out.push(c)?;
continue;
}
match handler.get()? {
ErrorHandler::Ignore => {}
ErrorHandler::Replace => out.push('?')?,
ErrorHandler::Backslashreplace => {
let _ = write_backslash_escape(&mut out, c);
}
ErrorHandler::Xmlcharrefreplace => {
let _ = write!(out, "&#{};", c as u32);
}
ErrorHandler::Namereplace => {
let _ = match unicode_names2::name(c) {
Some(name) => write!(out, "\\N{{{name}}}"),
None => write_backslash_escape(&mut out, c),
};
}
ErrorHandler::Strict | ErrorHandler::Surrogateescape | ErrorHandler::Surrogatepass => {
let mut end = idx + 1;
while let Some(&(_, next_c)) = chars.peek() {
if next_c.is_ascii() {
break;
}
chars.next();
end += 1;
}
return Err(ExcType::unicode_encode_error(
"ascii",
s,
c,
idx,
end,
"ordinal not in range(128)",
));
}
}
}
Ok(out.finish_raw()?.into_bytes())
}
fn write_backslash_escape(out: &mut impl fmt::Write, c: char) -> fmt::Result {
let code = c as u32;
if code <= 0xFF {
write!(out, "\\x{code:02x}")
} else if code <= 0xFFFF {
write!(out, "\\u{code:04x}")
} else {
write!(out, "\\U{code:08x}")
}
}
fn decode_ascii(bytes: &[u8], errors: &str) -> RunResult<String> {
if bytes.is_ascii() {
return Ok(str::from_utf8(bytes)
.expect("all-ASCII bytes are valid UTF-8")
.to_owned());
}
let mut handler = LazyHandler::new(errors);
let mut out = String::with_capacity(bytes.len());
for (idx, &byte) in bytes.iter().enumerate() {
if byte.is_ascii() {
out.push(byte as char);
} else {
handle_decode_error(handler.get()?, &bytes[idx..=idx], &mut out, false, || {
ExcType::unicode_decode_error("ascii", bytes, idx, idx + 1, "ordinal not in range(128)")
})?;
}
}
Ok(out)
}
fn decode_utf8(bytes: &[u8], errors: &str) -> RunResult<String> {
let mut handler = LazyHandler::new(errors);
let mut out = String::with_capacity(bytes.len());
let mut pos = 0;
while pos < bytes.len() {
match str::from_utf8(&bytes[pos..]) {
Ok(valid) => {
out.push_str(valid);
break;
}
Err(err) => {
let bad_start = pos + err.valid_up_to();
let bad_end = match err.error_len() {
Some(len) => bad_start + len,
None => bytes.len(),
};
out.push_str(str::from_utf8(&bytes[pos..bad_start]).expect("prefix validated by from_utf8"));
let reason = utf8_error_reason(bytes[bad_start], err.error_len());
let is_surrogate = is_cesu8_surrogate(&bytes[bad_start..]);
handle_decode_error(
handler.get()?,
&bytes[bad_start..bad_end],
&mut out,
is_surrogate,
|| ExcType::unicode_decode_error("utf-8", bytes, bad_start, bad_end, reason),
)?;
pos = bad_end;
}
}
}
Ok(out)
}
pub(crate) fn utf8_error_reason(first_bad_byte: u8, error_len: Option<usize>) -> &'static str {
if error_len.is_none() {
"unexpected end of data"
} else if (0xC2..=0xF4).contains(&first_bad_byte) {
"invalid continuation byte"
} else {
"invalid start byte"
}
}
fn is_cesu8_surrogate(rest: &[u8]) -> bool {
matches!(rest, [0xED, b1, b2, ..] if (0xA0..=0xBF).contains(b1) && (0x80..=0xBF).contains(b2))
}
fn encode_utf16(s: &str, endian: Endian, with_bom: bool) -> Vec<u8> {
let units: usize = s.chars().map(char::len_utf16).sum::<usize>() + usize::from(with_bom);
let mut out = Vec::with_capacity(units * 2);
if with_bom {
push_u16(&mut out, 0xFEFF, endian);
}
let mut buf = [0u16; 2];
for c in s.chars() {
for &unit in c.encode_utf16(&mut buf).iter() {
push_u16(&mut out, unit, endian);
}
}
out
}
fn encode_utf32(s: &str, endian: Endian, with_bom: bool) -> Vec<u8> {
let mut out = Vec::with_capacity((s.chars().count() + usize::from(with_bom)) * 4);
if with_bom {
push_u32(&mut out, 0xFEFF, endian);
}
for c in s.chars() {
push_u32(&mut out, c as u32, endian);
}
out
}
fn decode_utf16(bytes: &[u8], start: usize, endian: Endian, errors: &str) -> RunResult<String> {
let codec = match endian {
Endian::Little => "utf-16-le",
Endian::Big => "utf-16-be",
};
let mut handler = LazyHandler::new(errors);
let mut out = String::with_capacity(bytes.len());
let mut i = start;
while i < bytes.len() {
if bytes.len() - i == 1 {
handle_decode_error(handler.get()?, &bytes[i..], &mut out, false, || {
ExcType::unicode_decode_error(codec, bytes, i, i + 1, "truncated data")
})?;
break;
}
let unit = read_u16(bytes, i, endian);
if !(0xD800..0xE000).contains(&unit) {
out.push(char::from_u32(u32::from(unit)).expect("non-surrogate BMP code unit is a valid char"));
i += 2;
} else if unit >= 0xDC00 {
handle_decode_error(handler.get()?, &bytes[i..i + 2], &mut out, true, || {
ExcType::unicode_decode_error(codec, bytes, i, i + 2, "illegal encoding")
})?;
i += 2;
} else if bytes.len() - i < 4 {
let end = bytes.len();
handle_decode_error(handler.get()?, &bytes[i..end], &mut out, true, || {
ExcType::unicode_decode_error(codec, bytes, i, end, "unexpected end of data")
})?;
break;
} else {
let low = read_u16(bytes, i + 2, endian);
if (0xDC00..0xE000).contains(&low) {
let code = 0x10000 + ((u32::from(unit) - 0xD800) << 10) + (u32::from(low) - 0xDC00);
out.push(char::from_u32(code).expect("surrogate pair decodes to a valid char"));
i += 4;
} else {
handle_decode_error(handler.get()?, &bytes[i..i + 2], &mut out, true, || {
ExcType::unicode_decode_error(codec, bytes, i, i + 2, "illegal UTF-16 surrogate")
})?;
i += 2;
}
}
}
Ok(out)
}
fn decode_utf32(bytes: &[u8], start: usize, endian: Endian, errors: &str) -> RunResult<String> {
let codec = match endian {
Endian::Little => "utf-32-le",
Endian::Big => "utf-32-be",
};
let mut handler = LazyHandler::new(errors);
let mut out = String::with_capacity(bytes.len());
let mut i = start;
while i < bytes.len() {
if bytes.len() - i < 4 {
let end = bytes.len();
handle_decode_error(handler.get()?, &bytes[i..], &mut out, false, || {
ExcType::unicode_decode_error(codec, bytes, i, end, "truncated data")
})?;
break;
}
let code = read_u32(bytes, i, endian);
if let Some(c) = char::from_u32(code) {
out.push(c);
} else {
let (reason, is_surrogate) = if (0xD800..0xE000).contains(&code) {
("code point in surrogate code point range(0xd800, 0xe000)", true)
} else {
("code point not in range(0x110000)", false)
};
handle_decode_error(handler.get()?, &bytes[i..i + 4], &mut out, is_surrogate, || {
ExcType::unicode_decode_error(codec, bytes, i, i + 4, reason)
})?;
}
i += 4;
}
Ok(out)
}
fn push_u16(out: &mut Vec<u8>, unit: u16, endian: Endian) {
out.extend_from_slice(&match endian {
Endian::Little => unit.to_le_bytes(),
Endian::Big => unit.to_be_bytes(),
});
}
fn push_u32(out: &mut Vec<u8>, code: u32, endian: Endian) {
out.extend_from_slice(&match endian {
Endian::Little => code.to_le_bytes(),
Endian::Big => code.to_be_bytes(),
});
}
fn read_u16(bytes: &[u8], i: usize, endian: Endian) -> u16 {
let pair = [bytes[i], bytes[i + 1]];
match endian {
Endian::Little => u16::from_le_bytes(pair),
Endian::Big => u16::from_be_bytes(pair),
}
}
fn read_u32(bytes: &[u8], i: usize, endian: Endian) -> u32 {
let quad = [bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]];
match endian {
Endian::Little => u32::from_le_bytes(quad),
Endian::Big => u32::from_be_bytes(quad),
}
}