use serde::{de, ser};
use std::error::Error;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::str::FromStr;
#[derive(Debug)]
pub struct CharSetError;
impl Error for CharSetError {}
impl fmt::Display for CharSetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
writeln!(f, "invalid charset")
}
}
pub trait CharSet {
const NAME: &'static str;
fn check(data: &[u8]) -> bool;
}
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct RestrictedString<C> {
data: Vec<u8>,
marker: PhantomData<C>,
}
impl<C: CharSet> RestrictedString<C> {
pub unsafe fn new_unchecked<V>(data: V) -> Self
where
V: Into<Vec<u8>>,
{
RestrictedString {
data: data.into(),
marker: PhantomData,
}
}
pub fn new<V>(data: V) -> Result<Self, CharSetError>
where
V: Into<Vec<u8>>,
{
let data = data.into();
if !C::check(&data) {
return Err(CharSetError);
};
Ok(RestrictedString {
data,
marker: PhantomData,
})
}
pub fn into_bytes(self) -> Vec<u8> {
self.data
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
}
impl<C: CharSet> Deref for RestrictedString<C> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<C: CharSet> AsRef<[u8]> for RestrictedString<C> {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
impl<C: CharSet> fmt::Debug for RestrictedString<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", C::NAME)?;
if let Ok(utf8) = std::str::from_utf8(&self.data) {
fmt::Debug::fmt(utf8, f)?;
} else {
write!(f, "0x")?;
self.data.iter().try_for_each(|byte| write!(f, "{byte:02X}"))?;
}
write!(f, ")")?;
Ok(())
}
}
impl<C: CharSet> fmt::Display for RestrictedString<C> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&String::from_utf8_lossy(&self.data), fmt)
}
}
impl<C: CharSet> From<RestrictedString<C>> for Vec<u8> {
fn from(rs: RestrictedString<C>) -> Self {
rs.into_bytes()
}
}
impl<'de, C> de::Deserialize<'de> for RestrictedString<C>
where
C: CharSet,
{
fn deserialize<D>(deserializer: D) -> Result<RestrictedString<C>, D::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor<C>(std::marker::PhantomData<C>);
impl<'de, C> de::Visitor<'de> for Visitor<C>
where
C: CharSet,
{
type Value = RestrictedString<C>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid buffer representing a restricted string")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_byte_buf(v.to_vec())
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: de::Error,
{
RestrictedString::new(v).map_err(|_| {
E::invalid_value(
de::Unexpected::Other("invalid charset"),
&"a buffer representing a string using the right charset",
)
})
}
}
deserializer.deserialize_byte_buf(Visitor(std::marker::PhantomData))
}
}
impl<C> ser::Serialize for RestrictedString<C> {
fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
where
S: ser::Serializer,
{
serializer.serialize_bytes(&self.data)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct NumericCharSet;
impl CharSet for NumericCharSet {
const NAME: &'static str = "NUMERIC";
fn check(data: &[u8]) -> bool {
for &c in data {
if c != b' ' && !c.is_ascii_digit() {
return false;
}
}
true
}
}
pub type NumericString = RestrictedString<NumericCharSet>;
impl NumericString {
pub fn from_string(s: String) -> Result<Self, CharSetError> {
Self::new(s.into_bytes())
}
pub fn as_utf8(&self) -> &str {
core::str::from_utf8(self.as_bytes()).expect("valid UTF-8 subset")
}
pub fn into_string(self) -> String {
String::from_utf8(self.into_bytes()).expect("valid UTF-8 subset")
}
}
impl FromStr for NumericString {
type Err = CharSetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.as_bytes())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PrintableCharSet;
impl CharSet for PrintableCharSet {
const NAME: &'static str = "PRINTABLE";
fn check(data: &[u8]) -> bool {
for &c in data {
if !(c.is_ascii_alphanumeric()
|| c == b' '
|| c == b'\''
|| c == b'('
|| c == b')'
|| c == b'+'
|| c == b','
|| c == b'-'
|| c == b'.'
|| c == b'/'
|| c == b':'
|| c == b'='
|| c == b'?')
{
return false;
}
}
true
}
}
pub type PrintableString = RestrictedString<PrintableCharSet>;
impl PrintableString {
pub fn from_string(s: String) -> Result<Self, CharSetError> {
Self::new(s.into_bytes())
}
pub fn as_utf8(&self) -> &str {
core::str::from_utf8(self.as_bytes()).expect("valid UTF-8 subset")
}
pub fn into_string(self) -> String {
String::from_utf8(self.into_bytes()).expect("valid UTF-8 subset")
}
}
impl FromStr for PrintableString {
type Err = CharSetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.as_bytes())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Utf8CharSet;
impl CharSet for Utf8CharSet {
const NAME: &'static str = "UTF8";
fn check(data: &[u8]) -> bool {
std::str::from_utf8(data).is_ok()
}
}
pub type Utf8String = RestrictedString<Utf8CharSet>;
impl Utf8String {
pub fn from_string(s: String) -> Result<Self, CharSetError> {
Self::new(s.into_bytes())
}
pub fn as_utf8(&self) -> &str {
core::str::from_utf8(self.as_bytes()).expect("valid UTF-8 subset")
}
pub fn into_string(self) -> String {
String::from_utf8(self.into_bytes()).expect("valid UTF-8 subset")
}
}
impl FromStr for Utf8String {
type Err = CharSetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.as_bytes())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Ia5CharSet;
impl CharSet for Ia5CharSet {
const NAME: &'static str = "IA5";
fn check(data: &[u8]) -> bool {
for &c in data {
if !c.is_ascii() {
return false;
}
}
true
}
}
pub type Ia5String = RestrictedString<Ia5CharSet>;
#[deprecated = "Use IA5String instead"]
pub use Ia5String as IA5String;
impl Ia5String {
pub fn from_string(s: String) -> Result<Self, CharSetError> {
Self::new(s.into_bytes())
}
pub fn as_utf8(&self) -> &str {
core::str::from_utf8(self.as_bytes()).expect("valid UTF-8 subset")
}
pub fn into_string(self) -> String {
String::from_utf8(self.into_bytes()).expect("valid UTF-8 subset")
}
}
impl FromStr for Ia5String {
type Err = CharSetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.as_bytes())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct BmpCharSet;
impl CharSet for BmpCharSet {
const NAME: &'static str = "BMP";
fn check(data: &[u8]) -> bool {
if data.len() % 2 != 0 {
return false;
}
let chunk_it = data.chunks_exact(2);
debug_assert!(chunk_it.remainder().is_empty());
let u16_it = chunk_it.map(|code_unit| u16::from_be_bytes([code_unit[0], code_unit[1]]));
let mut count = 0;
for res in char::decode_utf16(u16_it) {
if res.is_err() {
return false;
}
count += 1;
}
count == data.len() / 2
}
}
pub type BmpString = RestrictedString<BmpCharSet>;
#[deprecated = "Use BmpString instead"]
pub use BmpString as BMPString;
impl BmpString {
pub fn to_utf8(&self) -> String {
let chunk_it = self.as_bytes().chunks_exact(2);
debug_assert!(chunk_it.remainder().is_empty());
let u16_it = chunk_it.map(|code_unit| u16::from_be_bytes([code_unit[0], code_unit[1]]));
char::decode_utf16(u16_it)
.map(|res| res.expect("valid code point"))
.collect()
}
}
impl FromStr for BmpString {
type Err = CharSetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let data: Vec<u8> = s.encode_utf16().flat_map(|code_unit| code_unit.to_be_bytes()).collect();
Self::new(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_printable_string() {
PrintableString::from_str("29INRUSAET3snre?:=tanui83 9283019").expect("valid string");
}
#[test]
fn invalid_printable_string() {
assert!(PrintableString::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").is_err());
}
#[test]
fn valid_numeric_string() {
NumericString::from_str("2983 9283019").expect("valid string");
}
#[test]
fn invalid_numeric_string() {
assert!(NumericString::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").is_err());
}
#[test]
fn valid_ia5_string() {
Ia5String::from_str("BUeisuteurnt").expect("valid string");
}
#[test]
fn invalid_ia5_string() {
assert!(Ia5String::from_str("BUéisuteurnt").is_err());
}
#[test]
fn valid_utf8_string() {
Utf8String::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").expect("valid string");
}
#[test]
fn valid_bmp_string() {
assert_eq!(
BmpString::from_str("语言处理").expect("valid BMP string").to_utf8(),
"语言处理"
);
assert_eq!(
BmpString::new(vec![
0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00,
0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61,
0x00, 0x74, 0x00, 0x65,
])
.expect("valid BMP string")
.to_utf8(),
"CertificateTemplate"
);
assert_eq!(
BmpString::new(vec![0x00, 0x55, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72])
.expect("valid BMP string")
.to_utf8(),
"User"
);
}
#[test]
fn invalid_bmp_string() {
assert!(BmpString::new("1224na÷日本語はむずかちー−×—«BUeisuteurnt".as_bytes()).is_err())
}
}