use std::io::Write;
use abnf_core::streaming::{dquote, sp};
use imap_types::{
command::CommandBody,
core::Vec1,
extensions::namespace::{Namespace, NamespaceResponseExtension, Namespaces},
response::Data,
};
use nom::{
branch::alt,
bytes::streaming::tag_no_case,
character::streaming::char,
combinator::{map, value},
multi::{many0, many1, separated_list1},
sequence::{delimited, preceded, tuple},
};
use crate::{
core::{nil, quoted_char, string},
decode::IMAPResult,
encode::{EncodeContext, EncodeIntoContext},
};
pub(crate) fn namespace_command(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
value(CommandBody::Namespace, tag_no_case(b"NAMESPACE"))(input)
}
pub(crate) fn namespace_response(input: &[u8]) -> IMAPResult<&[u8], Data> {
let mut parser = tuple((
tag_no_case("NAMESPACE "),
namespace,
preceded(sp, namespace),
preceded(sp, namespace),
));
let (remaining, (_, personal, other, shared)) = parser(input)?;
Ok((
remaining,
Data::Namespace {
personal,
other,
shared,
},
))
}
fn namespace(input: &[u8]) -> IMAPResult<&[u8], Namespaces> {
alt((
map(nil, |_| Vec::new()),
delimited(char('('), many1(namespace_descr), char(')')),
))(input)
}
fn namespace_descr(input: &[u8]) -> IMAPResult<&[u8], Namespace> {
map(
delimited(
char('('),
tuple((
string,
sp,
alt((
map(delimited(dquote, quoted_char, dquote), Some),
value(None, nil),
)),
many0(namespace_response_extension),
)),
char(')'),
),
|(prefix, _, delimiter, extensions)| Namespace {
prefix,
delimiter,
extensions,
},
)(input)
}
fn namespace_response_extension(input: &[u8]) -> IMAPResult<&[u8], NamespaceResponseExtension> {
map(
tuple((
preceded(sp, string),
preceded(
sp,
delimited(char('('), separated_list1(sp, string), char(')')),
),
)),
|(key, values)| NamespaceResponseExtension {
key,
values: Vec1::unvalidated(values),
},
)(input)
}
impl EncodeIntoContext for Namespace<'_> {
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
write!(ctx, "(")?;
self.prefix.encode_ctx(ctx)?;
write!(ctx, " ")?;
match &self.delimiter {
Some(delimiter_char) => {
write!(ctx, "\"")?;
delimiter_char.encode_ctx(ctx)?;
write!(ctx, "\"")?;
}
None => {
ctx.write_all(b"NIL")?;
}
}
for ext in &self.extensions {
ext.encode_ctx(ctx)?;
}
write!(ctx, ")")
}
}
impl EncodeIntoContext for NamespaceResponseExtension<'_> {
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
write!(ctx, " ")?;
self.key.encode_ctx(ctx)?;
write!(ctx, " (")?;
if let Some((last, head)) = self.values.as_ref().split_last() {
for value in head {
value.encode_ctx(ctx)?;
write!(ctx, " ")?;
}
last.encode_ctx(ctx)?;
}
write!(ctx, ")")
}
}
pub fn encode_namespaces(ctx: &mut EncodeContext, list: &Namespaces<'_>) -> std::io::Result<()> {
if list.is_empty() {
ctx.write_all(b"NIL")
} else {
ctx.write_all(b"(")?;
for desc in list {
desc.encode_ctx(ctx)?;
}
ctx.write_all(b")")
}
}
#[cfg(test)]
mod tests {
use super::namespace_response;
#[test]
fn parse_namespace_response() {
let tests = [
b"NAMESPACE ((\"0\" \"\\\"\")) NIL NIL\r\n".as_ref(),
#[cfg(feature = "ext_utf8")]
b"NAMESPACE ((\"^^\x00\" \"\x07\")) NIL NIL\r\n",
];
for test in tests.into_iter() {
namespace_response(test).unwrap();
}
}
}