2023-06-12 20:08:34 +00:00
|
|
|
use nom::{
|
|
|
|
IResult,
|
|
|
|
branch::alt,
|
|
|
|
bytes::complete::tag,
|
2023-06-16 10:07:17 +00:00
|
|
|
character::complete::{anychar, satisfy},
|
2023-06-12 20:08:34 +00:00
|
|
|
combinator::opt,
|
|
|
|
multi::many0,
|
|
|
|
sequence::{pair, preceded},
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::words::is_vchar;
|
2023-06-16 10:07:17 +00:00
|
|
|
use crate::whitespace::{fws, cfws, is_obs_no_ws_ctl};
|
2023-06-12 20:08:34 +00:00
|
|
|
|
|
|
|
/// Quoted pair
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
|
2023-06-16 10:07:17 +00:00
|
|
|
/// obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
|
2023-06-12 20:08:34 +00:00
|
|
|
/// ```
|
|
|
|
pub fn quoted_pair(input: &str) -> IResult<&str, char> {
|
2023-06-16 10:07:17 +00:00
|
|
|
preceded(tag("\\"), anychar)(input)
|
2023-06-12 20:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Allowed characters in quote
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// qtext = %d33 / ; Printable US-ASCII
|
|
|
|
/// %d35-91 / ; characters not including
|
|
|
|
/// %d93-126 / ; "\" or the quote character
|
|
|
|
/// obs-qtext
|
|
|
|
/// ```
|
2023-06-16 10:07:17 +00:00
|
|
|
fn is_restr_qtext(c: char) -> bool {
|
2023-06-12 20:08:34 +00:00
|
|
|
c == '\x21' || (c >= '\x23' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E')
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:07:17 +00:00
|
|
|
fn is_qtext(c: char) -> bool {
|
|
|
|
is_restr_qtext(c) || is_obs_no_ws_ctl(c)
|
|
|
|
}
|
|
|
|
|
2023-06-12 20:08:34 +00:00
|
|
|
/// Quoted pair content
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// qcontent = qtext / quoted-pair
|
|
|
|
/// ```
|
|
|
|
fn qcontent(input: &str) -> IResult<&str, char> {
|
|
|
|
alt((satisfy(is_qtext), quoted_pair))(input)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Quoted string
|
|
|
|
///
|
|
|
|
/// ```abnf
|
|
|
|
/// quoted-string = [CFWS]
|
|
|
|
/// DQUOTE *([FWS] qcontent) [FWS] DQUOTE
|
|
|
|
/// [CFWS]
|
|
|
|
/// ```
|
|
|
|
pub fn quoted_string(input: &str) -> IResult<&str, String> {
|
|
|
|
let (input, _) = opt(cfws)(input)?;
|
|
|
|
let (input, _) = tag("\"")(input)?;
|
|
|
|
let (input, content) = many0(pair(opt(fws), qcontent))(input)?;
|
|
|
|
|
|
|
|
// Rebuild string
|
|
|
|
let mut qstring = content.iter().fold(
|
|
|
|
String::with_capacity(16),
|
|
|
|
|mut acc, (maybe_wsp, c)| {
|
|
|
|
if let Some(wsp) = maybe_wsp {
|
|
|
|
acc.push(*wsp);
|
|
|
|
}
|
|
|
|
acc.push(*c);
|
|
|
|
acc
|
|
|
|
});
|
|
|
|
|
|
|
|
let (input, maybe_wsp) = opt(fws)(input)?;
|
|
|
|
if let Some(wsp) = maybe_wsp {
|
|
|
|
qstring.push(wsp);
|
|
|
|
}
|
|
|
|
|
|
|
|
let (input, _) = tag("\"")(input)?;
|
|
|
|
let (input, _) = opt(cfws)(input)?;
|
|
|
|
Ok((input, qstring))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_quoted_string() {
|
|
|
|
assert_eq!(quoted_string(" \"hello\\\"world\" "), Ok(("", "hello\"world".to_string())));
|
|
|
|
assert_eq!(quoted_string("\"hello\r\n world\""), Ok(("", "hello world".to_string())));
|
|
|
|
}
|
|
|
|
}
|