#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct Readme;
mod ascii;
mod big5;
mod decoder;
mod encoder;
mod euc_jp;
mod euc_kr;
mod gb18030;
mod index;
mod iso_2022_jp;
mod replacement;
mod result;
mod shift_jis;
mod single_byte;
mod sink;
mod tables;
#[cfg(test)]
mod tests;
mod utf_16;
mod utf_8;
mod variant;
mod x_user_defined;
mod encodings;
#[cfg(feature = "serde")]
mod serde_impl;
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
pub use crate::decoder::{DECODER_MIN_BUFFER, Decoder, MalformedError};
pub use crate::encoder::{ENCODER_MIN_BUFFER, Encoder, UnmappableError};
pub use crate::encodings::*;
pub use crate::result::{CoderResult, DecoderResult, EncoderResult};
use crate::tables::labels::{ALL_ENCODINGS, LABELS};
use crate::variant::VariantEncoding;
const MAX_LABEL_LEN: usize = 24;
pub struct Encoding {
name: &'static str,
variant: VariantEncoding,
}
impl Encoding {
pub(crate) const fn new(name: &'static str, variant: VariantEncoding) -> Encoding {
Encoding { name, variant }
}
pub(crate) fn variant(&self) -> VariantEncoding {
self.variant
}
pub(crate) fn bom(&self) -> Option<&'static [u8]> {
match self.variant {
VariantEncoding::Utf8 => Some(&[0xEF, 0xBB, 0xBF]),
VariantEncoding::Utf16Be => Some(&[0xFE, 0xFF]),
VariantEncoding::Utf16Le => Some(&[0xFF, 0xFE]),
_ => None,
}
}
pub fn name(&self) -> &'static str {
self.name
}
pub fn all() -> &'static [&'static Encoding] {
&ALL_ENCODINGS
}
pub fn labels(&'static self) -> impl Iterator<Item = &'static str> {
LABELS
.iter()
.filter(move |(_, encoding)| *encoding == self)
.map(|(label, _)| *label)
}
pub fn for_label(label: &[u8]) -> Option<&'static Encoding> {
let trimmed = trim_ascii_whitespace(label);
if trimmed.is_empty() || trimmed.len() > MAX_LABEL_LEN {
return None;
}
let mut lowercased = [0u8; MAX_LABEL_LEN];
for (slot, byte) in lowercased.iter_mut().zip(trimmed) {
*slot = byte.to_ascii_lowercase();
}
let needle = &lowercased[..trimmed.len()];
LABELS
.binary_search_by(|(label, _)| label.as_bytes().cmp(needle))
.ok()
.map(|i| LABELS[i].1)
}
pub fn for_label_no_replacement(label: &[u8]) -> Option<&'static Encoding> {
match Encoding::for_label(label) {
Some(encoding) if encoding.variant == VariantEncoding::Replacement => None,
other => other,
}
}
pub fn for_bom(buffer: &[u8]) -> Option<(&'static Encoding, usize)> {
if buffer.starts_with(&[0xEF, 0xBB, 0xBF]) {
Some((UTF_8, 3))
} else if buffer.starts_with(&[0xFE, 0xFF]) {
Some((UTF_16BE, 2))
} else if buffer.starts_with(&[0xFF, 0xFE]) {
Some((UTF_16LE, 2))
} else {
None
}
}
pub fn output_encoding(&'static self) -> &'static Encoding {
match self.variant {
VariantEncoding::Replacement | VariantEncoding::Utf16Be | VariantEncoding::Utf16Le => {
UTF_8
}
_ => self,
}
}
pub fn is_single_byte(&self) -> bool {
self.variant.is_single_byte()
}
pub fn is_ascii_compatible(&self) -> bool {
self.variant.is_ascii_compatible()
}
pub fn new_decoder(&'static self) -> Decoder {
Decoder::new(self, true, false)
}
pub fn new_decoder_with_bom_removal(&'static self) -> Decoder {
Decoder::new(self, false, true)
}
pub fn new_decoder_without_bom_handling(&'static self) -> Decoder {
Decoder::new(self, false, false)
}
pub fn new_encoder(&'static self) -> Encoder {
Encoder::new(self.output_encoding())
}
pub fn decode<'a>(&'static self, bytes: &'a [u8]) -> (Cow<'a, str>, &'static Encoding, bool) {
let (encoding, bom_len) = Encoding::for_bom(bytes).unwrap_or((self, 0));
let (text, had_errors) = encoding.decode_without_bom_handling(&bytes[bom_len..]);
(text, encoding, had_errors)
}
pub fn decode_with_bom_removal<'a>(&'static self, bytes: &'a [u8]) -> (Cow<'a, str>, bool) {
let bom_len = match self.bom() {
Some(bom) if bytes.starts_with(bom) => bom.len(),
_ => 0,
};
self.decode_without_bom_handling(&bytes[bom_len..])
}
pub fn decode_without_bom_handling<'a>(&'static self, bytes: &'a [u8]) -> (Cow<'a, str>, bool) {
if let Some(borrowed) = self.borrow_as_str(bytes) {
return (Cow::Borrowed(borrowed), false);
}
let mut text = String::with_capacity(bytes.len());
let had_errors = self
.new_decoder_without_bom_handling()
.decode_to_string(bytes, &mut text, true);
(Cow::Owned(text), had_errors)
}
pub fn decode_without_bom_handling_and_without_replacement<'a>(
&'static self,
bytes: &'a [u8],
) -> Option<Cow<'a, str>> {
if let Some(borrowed) = self.borrow_as_str(bytes) {
return Some(Cow::Borrowed(borrowed));
}
let mut text = String::with_capacity(bytes.len());
self.new_decoder_without_bom_handling()
.decode_to_string_without_replacement(bytes, &mut text, true)
.ok()?;
Some(Cow::Owned(text))
}
pub fn encode<'a>(&'static self, string: &'a str) -> (Cow<'a, [u8]>, &'static Encoding, bool) {
let output = self.output_encoding();
if output.borrow_as_str(string.as_bytes()).is_some() {
return (Cow::Borrowed(string.as_bytes()), output, false);
}
let mut bytes = Vec::with_capacity(string.len());
let had_unmappable = output
.new_encoder()
.encode_from_str(string, &mut bytes, true);
(Cow::Owned(bytes), output, had_unmappable)
}
fn borrow_as_str<'a>(&self, bytes: &'a [u8]) -> Option<&'a str> {
let decodes_verbatim = match self.variant {
VariantEncoding::Utf8 => true,
_ if self.variant.is_ascii_compatible() => ascii::is_ascii(bytes),
_ => false,
};
if decodes_verbatim {
core::str::from_utf8(bytes).ok()
} else {
None
}
}
}
fn trim_ascii_whitespace(label: &[u8]) -> &[u8] {
let is_space = |b: u8| matches!(b, 0x09 | 0x0A | 0x0C | 0x0D | 0x20);
let start = label.iter().position(|&b| !is_space(b));
match start {
None => &[],
Some(start) => {
let end = label.iter().rposition(|&b| !is_space(b)).unwrap_or(start);
&label[start..=end]
}
}
}
impl PartialEq for Encoding {
fn eq(&self, other: &Encoding) -> bool {
core::ptr::eq(self, other) || self.name == other.name
}
}
impl Eq for Encoding {}
impl core::hash::Hash for Encoding {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl core::fmt::Debug for Encoding {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Encoding({})", self.name)
}
}
impl core::fmt::Display for Encoding {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name)
}
}