use crate::common::check_buffer_boundaries;
use crate::error::{StunError, StunErrorType};
use crate::Encode;
use precis_core::profile::PrecisFastInvocation;
use precis_profiles::OpaqueString;
use quoted_string_parser::{QuotedStringParseLevel, QuotedStringParser};
use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt;
pub fn opaque_string_prepapre(s: &str) -> Result<Cow<'_, str>, precis_core::Error> {
OpaqueString::prepare(s)
}
pub fn opaque_string_enforce(s: &str) -> Result<Cow<'_, str>, precis_core::Error> {
OpaqueString::enforce(s)
}
fn is_removable_character(c: char) -> bool {
let cp = c as u32;
cp == 0x0d || cp == 0x0a || cp == 0x20 || cp == 0x09 || cp == 0x22
}
fn skip_trailing_characteres(text: &str) -> Option<usize> {
for (index, c) in text.chars().rev().enumerate() {
if !is_removable_character(c) {
return Some(index);
}
}
None
}
fn skip_starting_characteres(text: &str) -> Option<usize> {
for (index, c) in text.chars().enumerate() {
if !is_removable_character(c) {
return Some(index);
}
}
None
}
fn formatted_quoted_string_from(s: &str) -> Result<&str, StunError> {
if !QuotedStringParser::validate(QuotedStringParseLevel::QuotedText, s)
&& !QuotedStringParser::validate(QuotedStringParseLevel::QuotedString, s)
{
return Err(StunError::new(
StunErrorType::InvalidParam,
"The text does not meet the grammar for `quoted-string`",
));
}
let s = match skip_starting_characteres(s) {
Some(pos) => &s[pos..],
None => return Ok(&s[0..0]),
};
let mut res = s;
if let Some(pos) = skip_trailing_characteres(s) {
res = &s[..s.len() - pos];
}
Ok(res)
}
#[derive(Debug, PartialEq, Clone, Hash, Eq, PartialOrd, Ord)]
pub struct QuotedString(String);
impl QuotedString {
pub fn new<S>(value: S) -> Result<Self, StunError>
where
S: AsRef<str>,
{
let val = formatted_quoted_string_from(value.as_ref())?;
Ok(QuotedString(String::from(val)))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for QuotedString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl AsRef<str> for QuotedString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl AsRef<String> for QuotedString {
fn as_ref(&self) -> &String {
&self.0
}
}
impl TryFrom<&str> for QuotedString {
type Error = StunError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
QuotedString::new(value)
}
}
impl<'a> crate::Decode<'a> for QuotedString {
fn decode(raw_value: &'a [u8]) -> Result<(Self, usize), StunError> {
let str = std::str::from_utf8(raw_value)?;
let quoted = QuotedString::try_from(str)?;
if quoted.as_str() != str {
return Err(StunError::new(
StunErrorType::InvalidParam,
concat!(
"The text must be an unquoted sequence of `qdtext` or `quoted-pair`,",
" without the double quotes and their surrounding white spaces"
),
));
}
Ok((quoted, str.len()))
}
}
impl Encode for QuotedString {
fn encode(&self, raw_value: &mut [u8]) -> Result<usize, StunError> {
let len = self.as_str().len();
check_buffer_boundaries(raw_value, len)?;
raw_value[..len].clone_from_slice(self.as_str().as_bytes());
Ok(len)
}
}
#[cfg(test)]
mod tests {
use crate::strings::*;
#[test]
fn skip_trailing() {
assert_eq!(skip_trailing_characteres(""), None);
assert_eq!(skip_trailing_characteres("\u{0d}"), None);
assert_eq!(skip_trailing_characteres("\u{0d}a"), Some(0usize));
assert_eq!(skip_trailing_characteres("a\u{0d}"), Some(1usize));
assert_eq!(skip_trailing_characteres("a\u{0d}\u{0d}"), Some(2usize));
}
#[test]
fn skip_starting() {
assert_eq!(skip_starting_characteres(""), None);
assert_eq!(skip_starting_characteres("\u{0d}"), None);
assert_eq!(skip_starting_characteres("a\u{0d}"), Some(0usize));
assert_eq!(skip_starting_characteres("\u{0d}a"), Some(1usize));
}
#[test]
fn formatted_quoted_string_ok() {
assert_eq!(formatted_quoted_string_from(""), Ok(""));
assert_eq!(formatted_quoted_string_from(" "), Ok(""));
assert_eq!(formatted_quoted_string_from("\" \""), Ok(""));
assert_eq!(
formatted_quoted_string_from(" Hello world! "),
Ok("Hello world!")
);
assert_eq!(
formatted_quoted_string_from(" \" Hello world! \""),
Ok("Hello world!")
);
assert_eq!(
formatted_quoted_string_from("\" Test \\quoted \""),
Ok("Test \\quoted")
);
assert_eq!(
formatted_quoted_string_from(
"\u{0d}\u{0a} \" \u{0d}\u{0a} Test \\quoted \u{0d}\u{0a} \""
),
Ok("Test \\quoted")
);
assert_eq!(
formatted_quoted_string_from("\\abfg\\h\u{fd}\u{80}\u{81}\u{82}\u{83}\u{bf}"),
Ok("\\abfg\\h\u{fd}\u{80}\u{81}\u{82}\u{83}\u{bf}")
);
}
#[test]
fn formatted_quoted_string_error() {
assert_eq!(
formatted_quoted_string_from("\u{fd}\u{80}").expect_err("Error expected"),
StunErrorType::InvalidParam
);
assert_eq!(
formatted_quoted_string_from("\u{fd}\u{80}\u{81}\u{82}\u{83}")
.expect_err("Error expected"),
StunErrorType::InvalidParam
);
assert_eq!(
formatted_quoted_string_from("\u{0d}\u{0a}Miss white space after CR LF")
.expect_err("Error expected"),
StunErrorType::InvalidParam
);
}
#[test]
fn quoted_string() {
let val = QuotedString::try_from(" \" Hello world! \"").expect("Expected QuotedString");
assert_eq!(val.as_str(), "Hello world!");
assert_eq!(
QuotedString::try_from("\u{fd}\u{80}").expect_err("Error expected"),
StunErrorType::InvalidParam
);
}
}