# crate instead of \
`encoding_rs`."
)]
#![cfg_attr(
not(feature = "encoding"),
doc = "is backed by the Pure-Rust `oxixml-encoding` crate instead of `encoding_rs`."
)]
use std::borrow::Cow;
use std::str::Utf8Error;
#[cfg(feature = "encoding")]
use oxixml_encoding::Encoding;
pub(crate) const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
pub(crate) const UTF16_LE_BOM: &[u8] = &[0xFF, 0xFE];
pub(crate) const UTF16_BE_BOM: &[u8] = &[0xFE, 0xFF];
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncodingError {
Utf8(Utf8Error),
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
Other(Encoding),
}
impl From<Utf8Error> for EncodingError {
#[inline]
fn from(e: Utf8Error) -> Self {
Self::Utf8(e)
}
}
impl std::error::Error for EncodingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Utf8(e) => Some(e),
#[cfg(feature = "encoding")]
Self::Other(_) => None,
}
}
}
impl std::fmt::Display for EncodingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Utf8(e) => write!(f, "cannot decode input using UTF-8: {}", e),
#[cfg(feature = "encoding")]
Self::Other(encoding) => write!(f, "cannot decode input using {}", encoding.name()),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Decoder {
#[cfg(feature = "encoding")]
pub(crate) encoding: Encoding,
}
impl Decoder {
pub(crate) const fn utf8() -> Self {
Decoder {
#[cfg(feature = "encoding")]
encoding: Encoding::Utf8,
}
}
#[cfg(all(test, feature = "encoding", feature = "serialize"))]
pub(crate) const fn utf16() -> Self {
Decoder {
encoding: Encoding::Utf16Le,
}
}
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
pub const fn encoding(&self) -> Encoding {
self.encoding
}
pub fn decode<'b>(&self, bytes: &'b [u8]) -> Result<Cow<'b, str>, EncodingError> {
#[cfg(not(feature = "encoding"))]
{
Ok(Cow::Borrowed(std::str::from_utf8(bytes)?))
}
#[cfg(feature = "encoding")]
{
decode(bytes, self.encoding)
}
}
pub fn decode_into(&self, bytes: &[u8], buf: &mut String) -> Result<(), EncodingError> {
#[cfg(not(feature = "encoding"))]
{
buf.push_str(std::str::from_utf8(bytes)?);
}
#[cfg(feature = "encoding")]
{
decode_into(bytes, self.encoding, buf)?;
}
Ok(())
}
pub(crate) fn decode_cow<'b>(
&self,
bytes: &Cow<'b, [u8]>,
) -> Result<Cow<'b, str>, EncodingError> {
match bytes {
Cow::Borrowed(bytes) => self.decode(bytes),
Cow::Owned(bytes) => Ok(self.decode(bytes)?.into_owned().into()),
}
}
pub(crate) fn content<'b>(
&self,
bytes: &Cow<'b, [u8]>,
normalize_eol: impl Fn(&str) -> Cow<str>,
) -> Result<Cow<'b, str>, EncodingError> {
match bytes {
Cow::Borrowed(bytes) => {
let text = self.decode(bytes)?;
match normalize_eol(&text) {
Cow::Borrowed(_) => Ok(text),
Cow::Owned(s) => Ok(Cow::Owned(s)),
}
}
Cow::Owned(bytes) => {
let text = self.decode(bytes)?;
let text = normalize_eol(&text);
Ok(text.into_owned().into())
}
}
}
}
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
pub fn decode(bytes: &[u8], encoding: Encoding) -> Result<Cow<'_, str>, EncodingError> {
if matches!(encoding, Encoding::Utf8) {
return std::str::from_utf8(bytes)
.map(Cow::Borrowed)
.map_err(|_| EncodingError::Other(Encoding::Utf8));
}
let mut out = String::new();
let mut decoder = encoding.new_decoder();
decoder
.decode_to_string(bytes, &mut out, true)
.map_err(|_| EncodingError::Other(encoding))?;
Ok(Cow::Owned(out))
}
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
pub fn decode_into(
bytes: &[u8],
encoding: Encoding,
buf: &mut String,
) -> Result<(), EncodingError> {
if matches!(encoding, Encoding::Utf8) {
buf.push_str(std::str::from_utf8(bytes)?);
return Ok(());
}
let mut decoder = encoding.new_decoder();
decoder
.decode_to_string(bytes, buf, true)
.map_err(|_| EncodingError::Other(encoding))?;
Ok(())
}
pub fn detect_encoding(bytes: &[u8]) -> Option<DetectedEncoding> {
match bytes {
_ if bytes.starts_with(UTF16_BE_BOM) => Some(DetectedEncoding::Utf16BeBom),
_ if bytes.starts_with(UTF16_LE_BOM) => Some(DetectedEncoding::Utf16LeBom),
_ if bytes.starts_with(UTF8_BOM) => Some(DetectedEncoding::Utf8Bom),
_ if bytes.starts_with(&[0x00, b'<', 0x00, b'?']) => Some(DetectedEncoding::Utf16BeLike),
_ if bytes.starts_with(&[b'<', 0x00, b'?', 0x00]) => Some(DetectedEncoding::Utf16LeLike),
_ if bytes.starts_with(b"<?xm") => Some(DetectedEncoding::AsciiCompatible),
_ => None,
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DetectedEncoding {
AsciiCompatible,
Utf8Bom,
Utf16LeLike,
Utf16LeBom,
Utf16BeLike,
Utf16BeBom,
}
impl DetectedEncoding {
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
pub const fn encoding(&self) -> Encoding {
match self {
DetectedEncoding::AsciiCompatible | DetectedEncoding::Utf8Bom => Encoding::Utf8,
DetectedEncoding::Utf16LeLike | DetectedEncoding::Utf16LeBom => Encoding::Utf16Le,
DetectedEncoding::Utf16BeLike | DetectedEncoding::Utf16BeBom => Encoding::Utf16Be,
}
}
pub const fn bom_len(&self) -> usize {
match self {
DetectedEncoding::Utf8Bom => 3,
DetectedEncoding::Utf16LeBom | DetectedEncoding::Utf16BeBom => 2,
DetectedEncoding::AsciiCompatible
| DetectedEncoding::Utf16LeLike
| DetectedEncoding::Utf16BeLike => 0,
}
}
}
#[cfg(feature = "encoding")]
const PREFIX_CAP: usize = 64;
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
pub struct DecodingReader<R> {
inner: R,
decoder: oxixml_encoding::Decoder,
finished: bool,
out: String,
out_pos: usize,
prefix: Option<Box<Prefix>>,
inner_eof: bool,
}
#[cfg(feature = "encoding")]
struct Prefix {
buf: [u8; PREFIX_CAP],
len: usize,
detected: bool,
}
#[cfg(feature = "encoding")]
impl<R: std::fmt::Debug> std::fmt::Debug for DecodingReader<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DecodingReader")
.field("inner", &self.inner)
.field("encoding", &self.decoder.encoding())
.field("inner_eof", &self.inner_eof)
.field("prefix_active", &self.prefix.is_some())
.finish()
}
}
#[cfg(feature = "encoding")]
impl<R> DecodingReader<R> {
pub fn new(inner: R) -> Self {
Self {
inner,
decoder: Encoding::Utf8.new_decoder(),
finished: false,
out: String::new(),
out_pos: 0,
prefix: Some(Box::new(Prefix {
buf: [0; PREFIX_CAP],
len: 0,
detected: false,
})),
inner_eof: false,
}
}
pub const fn get_ref(&self) -> &R {
&self.inner
}
pub const fn get_mut(&mut self) -> &mut R {
&mut self.inner
}
pub fn into_inner(self) -> R {
self.inner
}
pub const fn encoding(&self) -> Encoding {
self.decoder.encoding()
}
pub fn set_encoding(&mut self, encoding: Encoding) -> bool {
if self.decoder.encoding() == encoding {
return true;
}
if self.prefix.is_none() {
return false;
}
self.decoder = encoding.new_decoder();
self.finished = false;
true
}
fn decode_chunk(&mut self, bytes: &[u8], last: bool) -> std::io::Result<()> {
let encoding = self.decoder.encoding();
match self.decoder.decode_to_string(bytes, &mut self.out, last) {
Ok(_) => Ok(()),
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
EncodingError::Other(encoding),
)),
}
}
}
#[cfg(feature = "encoding")]
impl<R: std::io::BufRead> std::io::BufRead for DecodingReader<R> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
if self.out_pos < self.out.len() {
return Ok(&self.out.as_bytes()[self.out_pos..]);
}
self.out.clear();
self.out_pos = 0;
if let Some(mut prefix) = self.prefix.take() {
if !prefix.detected {
prefix.detected = true;
while prefix.len < PREFIX_CAP {
match std::io::Read::read(&mut self.inner, &mut prefix.buf[prefix.len..]) {
Ok(0) => {
self.inner_eof = true;
break;
}
Ok(read) => prefix.len += read,
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
Err(error) => {
self.prefix = Some(prefix);
return Err(error);
}
}
}
if let Some(detected) = detect_encoding(&prefix.buf[..prefix.len]) {
let bom_len = detected.bom_len();
if bom_len > 0 {
prefix.buf.copy_within(bom_len..prefix.len, 0);
prefix.len -= bom_len;
}
let encoding = detected.encoding();
if encoding != Encoding::Utf8 {
self.decoder = encoding.new_decoder();
}
}
}
if prefix.len > 0 {
let len = prefix.len;
let mut bytes = [0; PREFIX_CAP];
bytes[..len].copy_from_slice(&prefix.buf[..len]);
prefix.len = 0;
self.prefix = Some(prefix);
self.decode_chunk(&bytes[..len], false)?;
if !self.out.is_empty() {
return Ok(self.out.as_bytes());
}
self.prefix = None;
}
}
if self.finished {
return Ok(&[]);
}
loop {
if self.inner_eof {
self.finished = true;
self.decode_chunk(&[], true)?;
return Ok(self.out.as_bytes());
}
let chunk = {
let available = self.inner.fill_buf()?;
if available.is_empty() {
self.inner_eof = true;
continue;
}
available.to_vec()
};
self.inner.consume(chunk.len());
self.decode_chunk(&chunk, false)?;
if !self.out.is_empty() {
return Ok(self.out.as_bytes());
}
}
}
fn consume(&mut self, amount: usize) {
self.out_pos = (self.out_pos + amount).min(self.out.len());
}
}
#[cfg(feature = "encoding")]
impl<R: std::io::BufRead> std::io::Read for DecodingReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let available = std::io::BufRead::fill_buf(self)?;
if available.is_empty() {
return Ok(0);
}
let len = available.len().min(buf.len());
buf[..len].copy_from_slice(&available[..len]);
std::io::BufRead::consume(self, len);
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utf8_decode_borrows() -> Result<(), EncodingError> {
let decoder = Decoder::utf8();
assert_eq!(decoder.decode(b"hello")?, Cow::Borrowed("hello"));
Ok(())
}
#[test]
fn utf8_decode_rejects_invalid() {
let decoder = Decoder::utf8();
#[cfg(not(feature = "encoding"))]
assert!(matches!(
decoder.decode(&[0xFF, 0xFE]),
Err(EncodingError::Utf8(_))
));
#[cfg(feature = "encoding")]
{
assert_eq!(
decoder.decode(&[0xFF, 0xFE]),
Err(EncodingError::Other(Encoding::Utf8))
);
let mut buf = String::new();
assert!(matches!(
decoder.decode_into(&[0xFF, 0xFE], &mut buf),
Err(EncodingError::Utf8(_))
));
}
}
#[test]
fn detect() {
assert_eq!(
detect_encoding(b"\xEF\xBB\xBF<?xml").map(|d| d.bom_len()),
Some(3)
);
assert_eq!(
detect_encoding(b"<?xml version").map(|d| d.bom_len()),
Some(0)
);
assert_eq!(detect_encoding(b"plain text"), None);
}
#[cfg(feature = "encoding")]
#[test]
fn non_utf8_decode() -> Result<(), EncodingError> {
let decoder = Decoder {
encoding: Encoding::Windows1252,
};
assert_eq!(decoder.decode(&[b'r', b'o', b's', 0xE9])?, "rosé");
Ok(())
}
#[cfg(feature = "encoding")]
#[test]
fn bare_utf16_label_does_not_override_the_boms_byte_order() {
use crate::events::Event;
use crate::reader::Reader;
for big_endian in [true, false] {
let mut bytes = if big_endian {
vec![0xFE, 0xFF]
} else {
vec![0xFF, 0xFE]
};
bytes.extend_from_slice(b"<?xml version=\"1.0\" encoding=\"UTF-16\"?><r>hi</r>");
let mut reader = Reader::from_reader(bytes.as_slice());
let expected = if big_endian {
Encoding::Utf16Be
} else {
Encoding::Utf16Le
};
match reader.read_event() {
Ok(Event::Decl(_)) => {}
other => panic!("big_endian={big_endian}: expected Decl, got {other:?}"),
}
assert_eq!(
reader.decoder().encoding(),
expected,
"big_endian={big_endian}: the BOM's byte order must survive the declaration",
);
loop {
match reader.read_event() {
Ok(Event::Eof) => break,
Ok(_) => {}
Err(e) => panic!("big_endian={big_endian}: unexpected error {e:?}"),
}
}
assert_eq!(
reader.decoder().encoding(),
expected,
"big_endian={big_endian}"
);
}
}
}
#[cfg(all(test, feature = "encoding"))]
mod decoding_reader {
use super::*;
use std::io::{BufRead, Read};
struct Chunked<'a> {
data: &'a [u8],
pos: usize,
chunk: usize,
}
impl std::io::Read for Chunked<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let available = self.fill_buf()?;
let len = available.len().min(buf.len());
buf[..len].copy_from_slice(&available[..len]);
self.pos += len;
Ok(len)
}
}
impl std::io::BufRead for Chunked<'_> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
let end = (self.pos + self.chunk).min(self.data.len());
Ok(&self.data[self.pos..end])
}
fn consume(&mut self, amount: usize) {
self.pos = (self.pos + amount).min(self.data.len());
}
}
fn utf16(text: &str, little_endian: bool, bom: bool) -> Vec<u8> {
let mut bytes = Vec::new();
if bom {
bytes.extend_from_slice(if little_endian {
UTF16_LE_BOM
} else {
UTF16_BE_BOM
});
}
for unit in text.encode_utf16() {
if little_endian {
bytes.extend_from_slice(&unit.to_le_bytes());
} else {
bytes.extend_from_slice(&unit.to_be_bytes());
}
}
bytes
}
fn decode_all(bytes: &[u8], chunk: usize) -> std::io::Result<String> {
let mut reader = DecodingReader::new(Chunked {
data: bytes,
pos: 0,
chunk,
});
let mut out = Vec::new();
reader.read_to_end(&mut out)?;
String::from_utf8(out)
.map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))
}
#[test]
fn utf8_passes_through() -> std::io::Result<()> {
assert_eq!(decode_all(b"<a>text</a>", 3)?, "<a>text</a>");
assert_eq!(decode_all(UTF8_BOM, 1)?, "");
Ok(())
}
#[test]
fn transcodes_utf16_at_every_chunk_size() -> std::io::Result<()> {
let text = "<?xml version='1.0' encoding='UTF-16'?><doc>текст</doc>";
for little_endian in [true, false] {
for bom in [true, false] {
for chunk in [1, 2, 3, 7, 64, 4096] {
let bytes = utf16(text, little_endian, bom);
assert_eq!(
decode_all(&bytes, chunk)?,
text,
"little_endian={little_endian} bom={bom} chunk={chunk}",
);
}
}
}
Ok(())
}
#[test]
fn reports_malformed_input() {
let mut bytes = utf16("<a/>", true, true);
bytes.extend_from_slice(&[0x00, 0xD8]);
let error = decode_all(&bytes, 4).expect_err("a truncated sequence must fail");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
fn exposes_and_replaces_the_encoding() -> std::io::Result<()> {
let mut bytes = b"<?xml version='1.0' encoding='ISO-8859-1'?><a>".to_vec();
bytes.extend_from_slice(&b"-".repeat(32));
bytes.extend_from_slice(&[0xE9]);
bytes.extend_from_slice(b"</a>");
let mut reader = DecodingReader::new(&bytes[..]);
assert_eq!(reader.encoding(), Encoding::Utf8);
assert_eq!(reader.get_ref().len(), bytes.len());
assert!(!reader.fill_buf()?.is_empty());
assert!(reader.set_encoding(Encoding::Latin1));
assert_eq!(reader.encoding(), Encoding::Latin1);
let mut out = String::new();
reader.read_to_string(&mut out)?;
assert!(out.ends_with("\u{e9}</a>"), "{out}");
assert!(reader.get_mut().is_empty());
assert!(reader.into_inner().is_empty());
Ok(())
}
#[test]
fn refuses_to_change_the_encoding_too_late() -> std::io::Result<()> {
let bytes =
b"<a>0123456789012345678901234567890123456789012345678901234567890123456789</a>";
let mut reader = DecodingReader::new(&bytes[..]);
let mut out = String::new();
reader.read_to_string(&mut out)?;
assert_eq!(out, std::str::from_utf8(bytes).expect("utf-8"));
assert!(reader.set_encoding(Encoding::Utf8));
assert!(!reader.set_encoding(Encoding::Utf16Le));
Ok(())
}
#[test]
fn parses_a_utf16_document_with_the_reader() -> crate::errors::Result<()> {
use crate::events::Event;
use crate::reader::Reader;
let bytes = utf16("<?xml version='1.0'?><doc>hi</doc>", true, true);
let mut reader = Reader::from_reader(DecodingReader::new(bytes.as_slice()));
let mut buf = Vec::new();
let mut texts = Vec::new();
loop {
buf.clear();
match reader.read_event_into(&mut buf)? {
Event::Eof => break,
Event::Text(text) => texts.push(text.decode()?.into_owned()),
_ => continue,
}
}
assert_eq!(texts, vec!["hi".to_owned()]);
Ok(())
}
}