1use nom::{
8 branch::alt,
9 bytes::streaming::{tag, tag_no_case},
10 combinator::map,
11 multi::separated_list0,
12 sequence::tuple,
13 IResult,
14};
15use std::borrow::Cow;
16
17use crate::{parser::core::*, types::*};
18
19fn is_entry_component_char(c: u8) -> bool {
20 c < 0x80 && c > 0x19 && c != b'*' && c != b'%' && c != b'/'
21}
22
23enum EntryParseStage<'a> {
24 PrivateShared,
25 Admin(usize),
26 VendorComment(usize),
27 Path(usize),
28 Done(usize),
29 Fail(nom::Err<&'a [u8]>),
30}
31
32fn check_private_shared(i: &[u8]) -> EntryParseStage<'_> {
33 if i.starts_with(b"/private") {
34 EntryParseStage::VendorComment(8)
35 } else if i.starts_with(b"/shared") {
36 EntryParseStage::Admin(7)
37 } else {
38 EntryParseStage::Fail(nom::Err::Error(
39 b"Entry Name doesn't start with /private or /shared",
40 ))
41 }
42}
43
44fn check_admin(i: &[u8], l: usize) -> EntryParseStage<'_> {
45 if i[l..].starts_with(b"/admin") {
46 EntryParseStage::Path(l + 6)
47 } else {
48 EntryParseStage::VendorComment(l)
49 }
50}
51
52fn check_vendor_comment(i: &[u8], l: usize) -> EntryParseStage<'_> {
53 if i[l..].starts_with(b"/comment") {
54 EntryParseStage::Path(l + 8)
55 } else if i[l..].starts_with(b"/vendor") {
56 if i.len() < l + 9 || i[l + 7] != b'/' || !is_entry_component_char(i[l + 8]) {
58 EntryParseStage::Fail(nom::Err::Incomplete(nom::Needed::Unknown))
59 } else {
60 EntryParseStage::Path(l + 7)
61 }
62 } else {
63 EntryParseStage::Fail(nom::Err::Error(
64 b"Entry name is not continued with /admin, /vendor or /comment",
65 ))
66 }
67}
68
69fn check_path(i: &[u8], l: usize) -> EntryParseStage<'_> {
70 if i.len() == l || i[l] == b' ' || i[l] == b'\r' {
71 return EntryParseStage::Done(l);
72 } else if i[l] != b'/' {
73 return EntryParseStage::Fail(nom::Err::Error(b"Entry name path is corrupted"));
74 }
75 for j in 1..(i.len() - l) {
76 if !is_entry_component_char(i[l + j]) {
77 return EntryParseStage::Path(l + j);
78 }
79 }
80 EntryParseStage::Done(i.len())
81}
82
83fn check_entry_name(i: &[u8]) -> IResult<&[u8], &[u8]> {
84 let mut stage = EntryParseStage::PrivateShared;
85 loop {
86 match stage {
87 EntryParseStage::PrivateShared => {
88 stage = check_private_shared(i);
89 }
90 EntryParseStage::Admin(l) => {
91 stage = check_admin(i, l);
92 }
93 EntryParseStage::VendorComment(l) => {
94 stage = check_vendor_comment(i, l);
95 }
96 EntryParseStage::Path(l) => {
97 stage = check_path(i, l);
98 }
99 EntryParseStage::Done(l) => {
100 return Ok((&i[l..], &i[..l]));
101 }
102 EntryParseStage::Fail(nom::Err::Error(err_msg)) => {
103 return std::result::Result::Err(nom::Err::Error(nom::error::Error::new(
104 err_msg,
105 nom::error::ErrorKind::Verify,
106 )));
107 }
108 EntryParseStage::Fail(nom::Err::Incomplete(reason)) => {
109 return std::result::Result::Err(nom::Err::Incomplete(reason));
110 }
111 _ => panic!("Entry name verification failure"),
112 }
113 }
114}
115
116fn entry_name(i: &[u8]) -> IResult<&[u8], &[u8]> {
117 let astring_res = astring(i)?;
118 check_entry_name(astring_res.1)?;
119 Ok(astring_res)
120}
121
122fn slice_to_str(i: &[u8]) -> &str {
123 std::str::from_utf8(i).unwrap()
124}
125
126fn nil_value(i: &[u8]) -> IResult<&[u8], Option<String>> {
127 map(tag_no_case("NIL"), |_| None)(i)
128}
129
130fn string_value(i: &[u8]) -> IResult<&[u8], Option<String>> {
131 map(alt((quoted, literal)), |s| {
132 Some(slice_to_str(s).to_string())
133 })(i)
134}
135
136fn keyval_list(i: &[u8]) -> IResult<&[u8], Vec<Metadata>> {
137 parenthesized_nonempty_list(map(
138 tuple((
139 map(entry_name, slice_to_str),
140 tag(" "),
141 alt((nil_value, string_value)),
142 )),
143 |(key, _, value)| Metadata {
144 entry: key.to_string(),
145 value,
146 },
147 ))(i)
148}
149
150fn entry_list(i: &[u8]) -> IResult<&[u8], Vec<Cow<'_, str>>> {
151 separated_list0(tag(" "), map(map(entry_name, slice_to_str), Cow::Borrowed))(i)
152}
153
154fn metadata_common(i: &[u8]) -> IResult<&[u8], &[u8]> {
155 let (i, (_, mbox, _)) = tuple((tag_no_case("METADATA "), quoted, tag(" ")))(i)?;
156 Ok((i, mbox))
157}
158
159pub(crate) fn metadata_solicited(i: &[u8]) -> IResult<&[u8], Response<'_>> {
161 let (i, (mailbox, values)) = tuple((metadata_common, keyval_list))(i)?;
162 Ok((
163 i,
164 Response::MailboxData(MailboxDatum::MetadataSolicited {
165 mailbox: Cow::Borrowed(slice_to_str(mailbox)),
166 values,
167 }),
168 ))
169}
170
171pub(crate) fn metadata_unsolicited(i: &[u8]) -> IResult<&[u8], Response<'_>> {
173 let (i, (mailbox, values)) = tuple((metadata_common, entry_list))(i)?;
174 Ok((
175 i,
176 Response::MailboxData(MailboxDatum::MetadataUnsolicited {
177 mailbox: Cow::Borrowed(slice_to_str(mailbox)),
178 values,
179 }),
180 ))
181}
182
183pub(crate) fn resp_text_code_metadata_long_entries(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
188 let (i, (_, num)) = tuple((tag_no_case("METADATA LONGENTRIES "), number_64))(i)?;
189 Ok((i, ResponseCode::MetadataLongEntries(num)))
190}
191
192pub(crate) fn resp_text_code_metadata_max_size(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
197 let (i, (_, num)) = tuple((tag_no_case("METADATA MAXSIZE "), number_64))(i)?;
198 Ok((i, ResponseCode::MetadataMaxSize(num)))
199}
200
201pub(crate) fn resp_text_code_metadata_too_many(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
206 let (i, _) = tag_no_case("METADATA TOOMANY")(i)?;
207 Ok((i, ResponseCode::MetadataTooMany))
208}
209
210pub(crate) fn resp_text_code_metadata_no_private(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
215 let (i, _) = tag_no_case("METADATA NOPRIVATE")(i)?;
216 Ok((i, ResponseCode::MetadataNoPrivate))
217}
218
219#[cfg(test)]
220mod tests {
221 use super::{metadata_solicited, metadata_unsolicited};
222 use crate::types::*;
223 use std::borrow::Cow;
224
225 #[test]
226 fn test_solicited_fail_1() {
227 match metadata_solicited(b"METADATA \"\" (/asdfg \"asdf\")\r\n") {
228 Err(_) => {}
229 _ => panic!("Error required when entry name is not starting with /private or /shared"),
230 }
231 }
232
233 #[test]
234 fn test_solicited_fail_2() {
235 match metadata_solicited(b"METADATA \"\" (/shared/asdfg \"asdf\")\r\n") {
236 Err(_) => {}
237 _ => panic!(
238 "Error required when in entry name /shared \
239 is not continued with /admin, /comment or /vendor"
240 ),
241 }
242 }
243
244 #[test]
245 fn test_solicited_fail_3() {
246 match metadata_solicited(b"METADATA \"\" (/private/admin \"asdf\")\r\n") {
247 Err(_) => {}
248 _ => panic!(
249 "Error required when in entry name /private \
250 is not continued with /comment or /vendor"
251 ),
252 }
253 }
254
255 #[test]
256 fn test_solicited_fail_4() {
257 match metadata_solicited(b"METADATA \"\" (/shared/vendor \"asdf\")\r\n") {
258 Err(_) => {}
259 _ => panic!("Error required when vendor name is not provided."),
260 }
261 }
262
263 #[test]
264 fn test_solicited_success() {
265 match metadata_solicited(
266 b"METADATA \"mbox\" (/shared/vendor/vendorname \"asdf\" \
267 /private/comment/a \"bbb\")\r\n",
268 ) {
269 Ok((i, Response::MailboxData(MailboxDatum::MetadataSolicited { mailbox, values }))) => {
270 assert_eq!(mailbox, "mbox");
271 assert_eq!(i, b"\r\n");
272 assert_eq!(values.len(), 2);
273 assert_eq!(values[0].entry, "/shared/vendor/vendorname");
274 assert_eq!(
275 values[0]
276 .value
277 .as_ref()
278 .expect("None value is not expected"),
279 "asdf"
280 );
281 assert_eq!(values[1].entry, "/private/comment/a");
282 assert_eq!(
283 values[1]
284 .value
285 .as_ref()
286 .expect("None value is not expected"),
287 "bbb"
288 );
289 }
290 _ => panic!("Correct METADATA response is not parsed properly."),
291 }
292 }
293
294 #[test]
295 fn test_literal_success() {
296 match metadata_solicited(b"METADATA \"\" (/shared/vendor/vendor.coi/a {3}\r\nAAA)\r\n") {
298 Ok((i, Response::MailboxData(MailboxDatum::MetadataSolicited { mailbox, values }))) => {
299 assert_eq!(mailbox, "");
300 assert_eq!(i, b"\r\n");
301 assert_eq!(values.len(), 1);
302 assert_eq!(values[0].entry, "/shared/vendor/vendor.coi/a");
303 assert_eq!(
304 values[0]
305 .value
306 .as_ref()
307 .expect("None value is not expected"),
308 "AAA"
309 );
310 }
311 Err(e) => panic!("ERR: {e:?}"),
312 _ => panic!("Strange failure"),
313 }
314 }
315
316 #[test]
317 fn test_nil_success() {
318 match metadata_solicited(b"METADATA \"\" (/shared/comment NIL /shared/admin NIL)\r\n") {
319 Ok((i, Response::MailboxData(MailboxDatum::MetadataSolicited { mailbox, values }))) => {
320 assert_eq!(mailbox, "");
321 assert_eq!(i, b"\r\n");
322 assert_eq!(values.len(), 2);
323 assert_eq!(values[0].entry, "/shared/comment");
324 assert_eq!(values[0].value, None);
325 assert_eq!(values[1].entry, "/shared/admin");
326 assert_eq!(values[1].value, None);
327 }
328 Err(e) => panic!("ERR: {e:?}"),
329 _ => panic!("Strange failure"),
330 }
331 }
332
333 #[test]
334 fn test_unsolicited_success() {
335 match metadata_unsolicited(b"METADATA \"theBox\" /shared/admin/qwe /private/comment/a\r\n")
336 {
337 Ok((
338 i,
339 Response::MailboxData(MailboxDatum::MetadataUnsolicited { mailbox, values }),
340 )) => {
341 assert_eq!(i, b"\r\n");
342 assert_eq!(mailbox, "theBox");
343 assert_eq!(values.len(), 2);
344 assert_eq!(values[0], "/shared/admin/qwe");
345 assert_eq!(values[1], "/private/comment/a");
346 }
347 _ => panic!("Correct METADATA response is not parsed properly."),
348 }
349 }
350
351 #[test]
352 fn test_response_codes() {
353 use crate::parser::parse_response;
354
355 match parse_response(b"* OK [METADATA LONGENTRIES 123] Some entries omitted.\r\n") {
356 Ok((
357 _,
358 Response::Data {
359 status: Status::Ok,
360 code: Some(ResponseCode::MetadataLongEntries(123)),
361 information: Some(Cow::Borrowed("Some entries omitted.")),
362 },
363 )) => {}
364 rsp => panic!("unexpected response {rsp:?}"),
365 }
366
367 match parse_response(b"* NO [METADATA MAXSIZE 123] Annotation too large.\r\n") {
368 Ok((
369 _,
370 Response::Data {
371 status: Status::No,
372 code: Some(ResponseCode::MetadataMaxSize(123)),
373 information: Some(Cow::Borrowed("Annotation too large.")),
374 },
375 )) => {}
376 rsp => panic!("unexpected response {rsp:?}"),
377 }
378
379 match parse_response(b"* NO [METADATA TOOMANY] Too many annotations.\r\n") {
380 Ok((
381 _,
382 Response::Data {
383 status: Status::No,
384 code: Some(ResponseCode::MetadataTooMany),
385 information: Some(Cow::Borrowed("Too many annotations.")),
386 },
387 )) => {}
388 rsp => panic!("unexpected response {rsp:?}"),
389 }
390
391 match parse_response(b"* NO [METADATA NOPRIVATE] Private annotations not supported.\r\n") {
392 Ok((
393 _,
394 Response::Data {
395 status: Status::No,
396 code: Some(ResponseCode::MetadataNoPrivate),
397 information: Some(Cow::Borrowed("Private annotations not supported.")),
398 },
399 )) => {}
400 rsp => panic!("unexpected response {rsp:?}"),
401 }
402 }
403}