Parcourir la source

imap: add status_response() parser

Manos Pitsidianakis il y a 5 ans
Parent
commit
c08ceae97c
2 fichiers modifiés avec 79 ajouts et 1 suppressions
  1. 19 1
      melib/src/backends/imap.rs
  2. 60 0
      melib/src/backends/imap/protocol_parser.rs

+ 19 - 1
melib/src/backends/imap.rs

@@ -300,7 +300,7 @@ impl MailBackend for ImapType {
                         .chain_err_summary(|| {
                         .chain_err_summary(|| {
                             format!("Could not select mailbox {}", mailbox_path)
                             format!("Could not select mailbox {}", mailbox_path)
                         })?;
                         })?;
-                    let examine_response = protocol_parser::select_response(&response)
+                    let mut examine_response = protocol_parser::select_response(&response)
                         .chain_err_summary(|| {
                         .chain_err_summary(|| {
                             format!(
                             format!(
                                 "Could not parse select response for mailbox {}",
                                 "Could not parse select response for mailbox {}",
@@ -345,6 +345,24 @@ impl MailBackend for ImapType {
                     let mut exists: usize = examine_response.exists;
                     let mut exists: usize = examine_response.exists;
                     /* reselecting the same mailbox with EXAMINE prevents expunging it */
                     /* reselecting the same mailbox with EXAMINE prevents expunging it */
                     conn.examine_mailbox(mailbox_hash, &mut response)?;
                     conn.examine_mailbox(mailbox_hash, &mut response)?;
+                    if examine_response.uidnext == 0 {
+                        /* UIDNEXT shouldn't be 0, since exists != 0 at this point */
+                        conn.send_command(
+                            format!("STATUS \"{}\" (UIDNEXT)", mailbox_path).as_bytes(),
+                        )?;
+                        conn.read_response(&mut response, RequiredResponses::STATUS)?;
+                        let (_, status) = protocol_parser::status_response(response.as_bytes())?;
+                        if let Some(uidnext) = status.uidnext {
+                            if uidnext == 0 {
+                                return Err(MeliError::new(
+                                    "IMAP server error: zero UIDNEXt with nonzero exists.",
+                                ));
+                            }
+                            examine_response.uidnext = uidnext;
+                        } else {
+                            return Err(MeliError::new("IMAP server did not reply with UIDNEXT"));
+                        }
+                    }
 
 
                     let mut tag_lck = uid_store.tag_index.write().unwrap();
                     let mut tag_lck = uid_store.tag_index.write().unwrap();
 
 

+ 60 - 0
melib/src/backends/imap/protocol_parser.rs

@@ -1402,6 +1402,66 @@ pub fn bodystructure_has_attachments(input: &[u8]) -> bool {
     input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some()
     input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some()
 }
 }
 
 
+#[derive(Debug, Default, Clone)]
+pub struct StatusResponse {
+    pub messages: Option<usize>,
+    pub recent: Option<usize>,
+    pub uidnext: Option<usize>,
+    pub uidvalidity: Option<usize>,
+    pub unseen: Option<usize>,
+}
+
+// status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")"
+// status-att = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" / "UNSEEN"
+pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
+    let (input, _) = tag("* STATUS ")(input)?;
+    let (input, _) = take_until(" (")(input)?;
+    let (input, _) = tag(" (")(input)?;
+    let (input, result) = permutation((
+        opt(preceded(
+            tag("MESSAGES "),
+            map_res(digit1, |s| {
+                usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
+            }),
+        )),
+        opt(preceded(
+            tag("RECENT "),
+            map_res(digit1, |s| {
+                usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
+            }),
+        )),
+        opt(preceded(
+            tag("UIDNEXT "),
+            map_res(digit1, |s| {
+                usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
+            }),
+        )),
+        opt(preceded(
+            tag("UIDVALIDITY "),
+            map_res(digit1, |s| {
+                usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
+            }),
+        )),
+        opt(preceded(
+            tag("UNSEEN "),
+            map_res(digit1, |s| {
+                usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
+            }),
+        )),
+    ))(input)?;
+    let (input, _) = tag(")\r\n")(input)?;
+    Ok((
+        input,
+        StatusResponse {
+            messages: result.0,
+            recent: result.1,
+            uidnext: result.2,
+            uidvalidity: result.3,
+            unseen: result.4,
+        },
+    ))
+}
+
 // mailbox = "INBOX" / astring
 // mailbox = "INBOX" / astring
 //           ; INBOX is case-insensitive. All case variants of
 //           ; INBOX is case-insensitive. All case variants of
 //           ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX
 //           ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX