From eaf46595ff9e841d5ca5d692d525361ea740ae9f Mon Sep 17 00:00:00 2001
From: Luca Di Rocco <lucadirocco@gmail.com>
Date: Thu, 14 Mar 2019 21:59:28 +0100
Subject: [PATCH] Added fetch of a message via custom command (-X opt)

---
 lib/imap.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 1 deletion(-)

diff --git a/lib/imap.c b/lib/imap.c
index 075b3ad20..8b4330576 100644
--- a/lib/imap.c
+++ b/lib/imap.c
@@ -110,6 +110,13 @@ static CURLcode imap_continue_authenticate(struct connectdata *conn,
                                            const char *resp);
 static void imap_get_message(char *buffer, char **outptr);
 
+
+#define IMAP_CUSTOM_FETCH_BODY 1
+
+#ifdef IMAP_CUSTOM_FETCH_BODY
+static bool isCustomFetchBody(const char * imap_cmd, const char * imap_params);
+#endif
+
 /*
  * IMAP protocol handler.
  */
@@ -337,6 +344,116 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len,
   return FALSE; /* Nothing for us */
 }
 
+
+#ifdef IMAP_CUSTOM_FETCH_BODY
+/*****************************************************************************
+ *
+ * isCustomFetchBody()
+ *
+ * Analyzes the cmd part and the parameters part of the custom imap command,
+ * in order to identify one of the following formats (corresponding to the
+ * fetch of the body of a single message):
+ *
+ * FETCH <id> (BODY[<section>]<<partial>>)
+ * FETCH <id> (BODY.PEEK[<section>]<<partial>>)
+ * UID FETCH <uid> (BODY[<section>]<<partial>>)
+ * UID FETCH <uid> (BODY.PEEK[<section>]<<partial>>)
+ *
+ * where <id> and <uid> are mandatory numbers, while <section>,
+ * <<partial>> and ( ) are optional.
+ *
+ * The function doesn't check the <section> and and <<partial>>
+ * parts in details, and accepts BODY[*]<*> and BODY.PEEK[*]<*>.
+ *
+ * Possible syntax errors in the <section> and/or <<partial>> parts
+ * will be detected by the imap server.
+ *
+ */
+static bool isCustomFetchBody(const char * imap_cmd,const char * imap_params){
+  int round_brackets=0;   /* count round brackets (in this impl, 0 or 1)*/
+  size_t token_len = 0;
+  const char * token = NULL;
+
+  if (!imap_cmd || !imap_params) return FALSE;
+
+  /* imap_cmd must be either "FETCH" or "UID". */
+  /* if imap_cmd is "UID",the first token of imap_params must be "FETCH" */
+  if ( ! strcasecompare(imap_cmd,"FETCH") ) {
+    if ( !strcasecompare(imap_cmd,"UID") ) return FALSE;
+
+    /* get a token, skipping initial blanks and using blank as delimiter */
+    token=imap_params+strspn(imap_params," ");  /* skip initial blanks */
+    token_len = strcspn(token," "); /* include all chars until ' ' excluded */
+    imap_params = token + token_len; /* skip current token in imap_params */
+
+    /* "FETCH" is expected */
+    if ( token_len != strlen("FETCH") ||
+    !strncasecompare(token,"FETCH",token_len) ) return FALSE;
+  }
+
+  /* get next token, skipping initial blanks and using blank as delimiter */
+  token=imap_params+strspn(imap_params," "); /* skip initial blanks */
+  token_len = strcspn(token," "); /* include all chars until ' ' excluded */
+  imap_params = token + token_len; /* skip current token in imap_params */
+
+  /* a number (either index or uid) is expected after FETCH. Ranges N:M are
+     rejected, because we look for a fetch of a single message */
+  /* make sure the found token contains digits only */
+  if (token_len == 0) return FALSE;
+  do { if (!isdigit(*token++)) return FALSE; } while (--token_len > 0);
+
+  /* skip all blanks, and check for an optional open round bracket */
+  token=imap_params+strspn(imap_params," "); /* skips initial blanks */
+  if (*token == '(') {    /* optional open round bracket found */
+    round_brackets++;     /* a closed round bracket will be expected later */
+    token++;              /* skip '(' */
+    token+=strspn(token," ");  /* skip all blanks following the found "(" */
+  }
+
+  /* we expect either BODY[ or BODY.PEEK[ */
+  /* get next token (initial blanks already skipped) using "[" as delimiter */
+  token_len = strcspn(token, "["); /* consider all chars until "[" excluded */
+  imap_params = token + token_len;  /* skip current token in params */
+
+  /* make sure token is either BODY or BODY.PEEK */
+  if (  ( token_len != strlen("BODY") ||
+             !strncasecompare(token,"BODY",token_len) ) &&
+        ( token_len != strlen("BODY.PEEK") ||
+             !strncasecompare(token,"BODY.PEEK",token_len) )  ) return FALSE;
+
+  /* make sure an open square bracket follow BODY (or BODY.PEEK) */
+  if (*imap_params != '[') return FALSE;
+  /* skip the optional <section> between '[' and ']' */
+  imap_params += strcspn(imap_params, "]"); /* skip all chars until ']' */
+  if (*imap_params != ']') return FALSE;  /* a ']' is expected here */
+  imap_params++;   /* skip ']' */
+
+  /* check for an optional open angle bracket following the closed square */
+  if (*imap_params == '<') {
+    /* skip the <partial>  between '<' and '>' */
+    imap_params += strcspn(imap_params, ">"); /* skip all chars until '>' */
+    if (*imap_params != '>') return FALSE; /* '>' is expected here */
+    imap_params++; /* skip '>' */
+  }
+
+  /* skip all blanks, and, if there was an optional open round bracket,
+     check for the mandatory related closed round bracket */
+  token=imap_params+strspn(imap_params," "); /* skip initial blanks */
+  if (round_brackets > 0) {
+    if (*token != ')') return FALSE; /* ')' is expected here */
+    round_brackets--; /* not used any further, decreased for consistency */
+    token++;    /* skip ')' */
+    token+=strspn(token," "); /* skip all blanks following the found ')' */
+  }
+
+  /* no more tokens expected, next token should be emtpy */
+  token_len = strcspn(token, " ");
+
+  return (token_len) ? FALSE : TRUE;
+}
+#endif /* IMAP_CUSTOM_FETCH_BODY */
+
+
 /***********************************************************************
  *
  * imap_get_message()
@@ -1081,8 +1198,17 @@ static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode,
       /* Note the currently opened mailbox on this connection */
       imapc->mailbox = strdup(imap->mailbox);
 
-      if(imap->custom)
+      if(imap->custom) {
         result = imap_perform_list(conn);
+#ifdef IMAP_CUSTOM_FETCH_BODY
+        if (result == CURLE_OK)
+          if (isCustomFetchBody(imap->custom,imap->custom_params)) {
+            DEBUGF("imap_state_select_resp: Extended command is a fetch body."
+                   "Changing state to IMAP_FETCH.\n");
+            state(conn, IMAP_FETCH);
+          }
+#endif
+      }
       else if(imap->query)
         result = imap_perform_search(conn);
       else
-- 
2.21.0.windows.1

