On 03/30/2010 02:08 PM, Daniel Stenberg wrote:
On Tue, 30 Mar 2010, Ben Greear wrote:

It seems that in general the imap code and perhaps pingpong logic
isn't well suited for handling imap stuff, where the data and headers
are all mixed up.

I agree that IMAP is a bit ugly in that regard and yes it makes things a
bit quirky within libcurl. But I'm convinced that we can work something
out that ends up decent!


Well, I don't know about decent..but I have at least some functionality
working now.  The attached patch is full of debugging code and needs
more testing, so this is just for comment unless you are feeling
very brave.

I am afraid I abused the pingpong logic quite a bit.  There is probably
a cleaner way, but not sure what that would be.

This patch is against a recent upstream pull..it includes all of my
previous curl imap patches.

Some things that should work with this patch:

* Enable header CURLOPT and mail headers will be requested and
  saved to the read callback.

* Select inbox and download two email messages with specified IDs:
   imap://tester:[email protected]/INBOX/;uid=2,1

  NOTE:  All headers come first, then body...this needs more work.

*  Get one message from INBOX:
  imap://tester:[email protected]/INBOX/;uid=2

* Get a listing of all folders:
  imap://tester:[email protected]/
  imap://tester:[email protected]


Still working on getting search working properly.


Thanks,
Ben


--
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com

diff --git a/lib/imap.c b/lib/imap.c
index 72ab8da..265f02a 100644
--- a/lib/imap.c
+++ b/lib/imap.c
@@ -108,6 +108,11 @@ static int imap_getsock(struct connectdata *conn,
 static CURLcode imap_doing(struct connectdata *conn,
                            bool *dophase_done);
 static CURLcode imap_setup_connection(struct connectdata * conn);
+static CURLcode imap_fetch(struct connectdata *conn);
+static int imap_process_resp_ln(const char* idstr, const char* h,
+                                size_t h_length,
+                                const char* good, size_t* consumed);
+static void hexprintf(const char* s, size_t ln, const char* desc);
 
 /*
  * IMAP protocol handler.
@@ -201,6 +206,27 @@ static const struct Curl_handler Curl_handler_imaps_proxy 
= {
 #endif
 #endif
 
+
+/**
+ * write text to output file
+ */
+#define imap_log(conn, pp, hdr, msg) _imap_log(conn, pp, hdr, msg, __FILE__, 
__LINE__, __FUNCTION__)
+static void _imap_log(struct connectdata* conn,
+                      struct pingpong* pp, const char* hdr, const char* msg,
+                      const char* file, int ln, const char* method) {
+  char buf[512];
+  snprintf(buf, sizeof(buf), "\nLOG: %s:%i(%s) msg: %s hdr: %s\n"
+           "  cache-len: %i nread_resp: %i ###\n\n",
+           file, ln, method, msg, hdr, pp?pp->cache_size:0xFFFFFFFF,
+           pp?pp->nread_resp:0xFFFFFFFF);
+  buf[sizeof(buf)-1] = 0;
+  printf("%s", buf);
+  fflush(stdout);
+  (void)conn;
+  /* Curl_client_write(conn, CLIENTWRITE_BODY, buf, strlen(buf)); */
+}
+
+
 /***********************************************************************
  *
  * imapsendf()
@@ -220,10 +246,14 @@ static CURLcode imapsendf(struct connectdata *conn,
   CURLcode res;
   struct imap_conn *imapc = &conn->proto.imapc;
   va_list ap;
-  va_start(ap, fmt);
 
-  imapc->idstr = idstr; /* this is the thing */
+  imap_log(conn, NULL, fmt, idstr);
+  
+  va_start(ap, fmt);
 
+  if (idstr)
+    imapc->idstr = idstr; /* this is the thing */
+  
   res = Curl_pp_vsendf(&imapc->pp, fmt, ap);
 
   va_end(ap);
@@ -272,7 +302,10 @@ static int imap_endofresp(struct pingpong *pp, int *resp)
       *resp = line[id_len+1]; /* O, N or B */
       return TRUE;
     }
-    else if((imapc->state == IMAP_FETCH) &&
+    else if(((imapc->state == IMAP_LIST) ||
+             (imapc->state == IMAP_SEARCH) ||
+             (imapc->state == IMAP_FETCH_HEADER) ||
+             (imapc->state == IMAP_FETCH_BODY)) &&
             !memcmp("* ", line, 2) ) {
       /* FETCH response we're interested in */
       *resp = '*';
@@ -282,9 +315,12 @@ static int imap_endofresp(struct pingpong *pp, int *resp)
   return FALSE; /* nothing for us */
 }
 
+
+#define state(a, b) _state(a, b, __FILE__, __LINE__)
+
 /* This is the ONLY way to change IMAP state! */
-static void state(struct connectdata *conn,
-                  imapstate newstate)
+static void _state(struct connectdata *conn,
+                  imapstate newstate, const char* file, int ln)
 {
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
   /* for debug purposes */
@@ -293,17 +329,23 @@ static void state(struct connectdata *conn,
     "SERVERGREET",
     "LOGIN",
     "STARTTLS",
+    "LIST",
+    "SEARCH",
     "SELECT",
-    "FETCH",
+    "FETCH_HEADER",
+    "FETCH_BODY",
     "LOGOUT",
     /* LAST */
   };
 #endif
   struct imap_conn *imapc = &conn->proto.imapc;
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-  if(imapc->state != newstate)
+  if(imapc->state != newstate) {
     infof(conn->data, "IMAP %p state change from %s to %s\n",
           imapc, names[imapc->state], names[newstate]);
+    printf("IMAP %p state change from %s to %s  %s:%i\n",
+           imapc, names[imapc->state], names[newstate], file, ln);
+  }
 #endif
   imapc->state = newstate;
 }
@@ -371,58 +413,453 @@ static CURLcode imap_state_login_resp(struct connectdata 
*conn,
   return result;
 }
 
-/* for the (first line of) FETCH BODY[TEXT] response */
-static CURLcode imap_state_fetch_resp(struct connectdata *conn,
-                                      int imapcode,
-                                      imapstate instate)
+/* for the (first line of) FETCH BODY[HEADER/TEXT] response */
+static CURLcode imap_state_fetch_gen_body_resp(struct connectdata *conn,
+                                               int imapcode,
+                                               imapstate instate,
+                                               bool is_body)
 {
   CURLcode result = CURLE_OK;
   struct SessionHandle *data = conn->data;
+  char *ptr = data->state.buffer;
+  size_t buflen = strlen(ptr);
   struct imap_conn *imapc = &conn->proto.imapc;
   struct FTP *imap = data->state.proto.imap;
   struct pingpong *pp = &imapc->pp;
-  const char *ptr = data->state.buffer;
-  (void)instate; /* no use for this yet */
+  int got_eor = 0;
+  
 
+  imap_log(conn, pp, ptr, "enter");
+  
+  (void)instate; /* no use for this yet */
   if('*' != imapcode) {
     Curl_pgrsSetDownloadSize(data, 0);
     state(conn, IMAP_STOP);
     return CURLE_OK;
   }
 
-  /* Something like this comes "* 1 FETCH (BODY[TEXT] {2021}\r" */
+  /* Something like this comes
+   *     "* 1 FETCH (FLAGS (\Seen) BODY[HEADER] {482}\r"
+   * Or: "* 1 FETCH (BODY[TEXT] {2021}\r"
+   */
   while(*ptr && (*ptr != '{'))
     ptr++;
 
   if(*ptr == '{') {
+    char* process_buffer;
+    bool do_cache_next = false;
+    size_t chunk;
+    size_t pb_len;
+    
     curl_off_t filesize = curlx_strtoofft(ptr+1, NULL, 10);
     if(filesize)
       Curl_pgrsSetDownloadSize(data, filesize);
 
-    infof(data, "Found %" FORMAT_OFF_TU " bytes to download\n", filesize);
+    printf("ptr: %p -:%s:-\n", ptr, ptr);
+    
+    /** Uggh, can have data after the newline in ptr, and can have more
+     * data and headers in the cache.
+     */
+    /* First, process rest of the header */
+    while (*ptr && (*ptr != '\n'))
+      ptr++;
+
+    printf("ptr: %p, *ptr: %c (0x%02hx) -:%s:-\n",
+           ptr, *ptr, (unsigned short)(*ptr), ptr);
+    
+    if ((*ptr == '\n') && (ptr[1])) {
+      ptr++;
+      process_buffer = ptr;
+      chunk = buflen - (ptr - data->state.buffer); /* nread - consumed */
+      do_cache_next = true;
+    }
+    else {
+      /* header is consumed, zero it out and process cache */
+      pp->nread_resp = 0;
+      data->state.buffer[0] = 0;
+      
+      process_buffer = pp->cache;
+      chunk = pp->cache_size;
+
+      printf("pb-cache len: %i  -:%s:-\n",
+             chunk, pp->cache);
+    }
+    pb_len = chunk;
 
-    if(pp->cache) {
-      /* At this point there is a bunch of data in the header "cache" that is
-         actually body content, send it as body and then skip it. Do note
-         that there may even be additional "headers" after the body. */
-      size_t chunk = pp->cache_size;
+    /* Do note that there may even be additional "headers" after the body. */
+    
+    while (process_buffer) {
 
       if(chunk > (size_t)filesize)
         /* the conversion from curl_off_t to size_t is always fine here */
         chunk = (size_t)filesize;
 
-      result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk);
+      result = Curl_client_write(conn, CLIENTWRITE_BODY, process_buffer, 
chunk);
+      
       if(result)
         return result;
 
       filesize -= chunk;
 
+      if (filesize == 0) {
+        /* Seems there is a closing ")\r\n" on the end of entries...eat this if
+         * it exists.
+         */
+        if (pb_len >= chunk +3) {
+          if ((process_buffer[chunk] == ')') &&
+              (process_buffer[chunk+1] == '\r') &&
+              (process_buffer[chunk+2] == '\n'))
+            chunk += 3;
+        }
+      }
+
+      /* we've now used parts of or the entire cache */
+      if(pb_len > chunk) {
+        /* See if we have the response code..if so, we are done, else, keep
+         * at it.
+         */
+
+        printf("pb: %p  chunk: %i -:%s:-\n",
+               process_buffer, chunk, process_buffer+chunk);
+        hexprintf(process_buffer + chunk, (int)(pb_len - chunk),
+                  "before eat white space");
+
+        if (process_buffer[chunk] && process_buffer[chunk] != '*') {
+          size_t orig_chunk = chunk;
+          hexprintf(process_buffer + chunk, pb_len - chunk, "next pb header");
+          if(imap_process_resp_ln(imapc->idstr, process_buffer + chunk,
+                                  pb_len - chunk, "OK", &chunk)) {
+            got_eor = 1; /* response is good */
+            imap_log(conn, NULL, process_buffer + chunk,
+                     "response is good, has newline");
+          }
+          else {
+            if(chunk != orig_chunk) {
+              imap_log(conn, NULL, process_buffer + chunk,
+                       "found newline, but had bad response. TODO: Tell 
user.");
+              got_eor = 1;
+            }
+            else {
+              imap_log(conn, NULL, process_buffer + chunk,
+                       "no newline, bad response. TODO: Tell user.");
+            }
+          }
+        }
+        
+        if (chunk < pb_len) {
+          if (process_buffer == pp->cache) {
+            /* part of, move the trailing data to the start and reduce the 
size */
+            memmove(pp->cache, pp->cache+chunk,
+                    pp->cache_size - chunk);
+            pp->cache_size -= chunk;
+            pp->cache[pp->cache_size] = 0; /* null terminate */
+          }
+          else {
+            /* consume a bit of the pingpong data */
+            memmove(data->state.buffer, data->state.buffer+chunk,
+                    pb_len - chunk);
+            buflen -= chunk;
+            pp->nread_resp = buflen;
+            data->state.buffer[pp->nread_resp] = 0;
+          }
+        }
+        else {
+          if (process_buffer == pp->cache) {          /* cache is drained */
+            free(pp->cache);
+            pp->cache = NULL;
+            pp->cache_size = 0;
+          }
+          else {
+            /* clear ping-pong data */
+            pp->nread_resp = 0;
+            data->state.buffer[0] = 0;
+          }
+        }
+      }
+
+      if (do_cache_next && !got_eor) {
+        process_buffer = pp->cache;
+        chunk = pp->cache_size;
+        pb_len = chunk;
+      }
+      else
+        break;
+    }
+
+    if(!filesize)
+      /* the entire data is already transfered! */
+      result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+    else
+      /* IMAP download */
+      result=Curl_setup_transfer(conn, FIRSTSOCKET, filesize, FALSE,
+                                 imap->bytecountp,
+                                 -1, NULL); /* no upload here */
+    
+    data->req.maxdownload = filesize;  
+  }
+  else {
+    /* We don't know how to parse this line */
+    result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
+  }
+  
+  if (got_eor) {
+    if(is_body)
+      state(conn, IMAP_STOP);
+    else {
+      /* Download the body of the email when we are done with headers. */
+      imap_fetch(conn);
+    }
+  }
+  return result;
+}
+
+/* for the (first line of) FETCH BODY[TEXT] response */
+static CURLcode imap_state_fetch_resp(struct connectdata *conn,
+                                      int imapcode,
+                                      imapstate instate)
+{
+  return imap_state_fetch_gen_body_resp(conn, imapcode, instate, true);
+}
+
+/* for the (first line of) FETCH BODY[HEADER] response */
+static CURLcode imap_state_fetch_hdr_resp(struct connectdata *conn,
+                                          int imapcode,
+                                          imapstate instate)
+{
+  return imap_state_fetch_gen_body_resp(conn, imapcode, instate, false);
+}
+
+/* Grabs lines until we find a line that does NOT start with 'starts'.  Returns
+ * length in bytes.
+ */
+static size_t get_until_nstarts(const char* str, size_t max,
+                                const char* starts)
+{
+  size_t i;
+  size_t slen = strlen(starts);
+  /* Grab lines until done with all 'starts' elements */
+  for (i = 0; i<max; i++) {
+    if (strncmp((str + i), starts, slen) == 0) {
+      /* grab entire line */
+      i += slen;
+      for ( ; i< max; i++) {
+        if (str[i] == '\n') {
+          break;
+        }
+      }
+    }
+    else {
+      break;
+    }
+  }
+  return i;
+}
+
+#if 0
+/** Returns next instance of 'n', with case-insensitive search.  If
+ * consumed is not NULL, then this will only search to the first newline
+ * and 'consumed' will be increased by the number of bytes it takes to
+ * consume the entire line, newline included.
+ */
+static const char* imap_strcasestr_ln(const char* h, const char* n,
+                                      size_t* consumed)
+{
+  size_t lnh = strlen(h);
+  size_t lnn = strlen(n);
+  size_t i;
+  for (i = 0; i<lnh - lnn; i++) {
+    if (strncasecmp(h+i, n, lnn) == 0) {
+      if (consumed) {
+        *consumed = i + lnn;
+        for (; *consumed<lnh; (*consumed)++) {
+          if (h[*consumed] == '\n')
+            break;
+        }
+      }
+      return h+i;
+    }
+  }
+  return NULL;
+}
+#endif
+
+/** Returns 1 if we find expected response, 0 otherwise.
+ * If newline is not found, then we return 0 and consume nothing.
+ */
+static int imap_process_resp_ln(const char* idstr, const char* h,
+                                size_t lnh,
+                                const char* good, size_t* consumed)
+{
+  size_t lng = strlen(good);
+  size_t i;
+  size_t lni = strlen(idstr);
+  int rv = 1;
+
+  /* If there is no newline, then return 0 w/out consuming anything. */
+  for (i = 0; i<lnh; i++) {
+    if (h[i] == '\n')
+      break;
+  }
+  if (i == lnh)
+    return 0; /* didn't find newline, don't consume anything */
+  
+  for (i = 0; i<lnh - lng; i++) {
+    /* skip any leading whitespace */
+    if (!ISSPACE(h[i]))
+      break;
+  }
+
+  if(strncmp(h+i, idstr, lni) == 0) {
+    i += lni;
+    if (!ISSPACE(h[i])) {
+      rv = 0;; /* should be a space after idstr, otherwise not a real match */
+      goto out_consume;
+    }
+
+    /* walk over space */
+    for ( ; i<lnh - lng; i++) {
+      if (!ISSPACE(h[i]))
+        break;
+    }
+
+    /* Next token is response.  Make sure it matches expected.  Assume
+     * case-insensitive match is OK.
+     */
+    if(! Curl_raw_nequal(h+i, good, lng))
+      rv = 0; /* didn't match expected response */
+  }
+  else
+    rv = 0; /* didn't match id str */
+
+out_consume:
+  for ( ; i<lnh; i++) {
+    if (h[i] == '\n') {
+      i++;
+      *consumed += i;
+      break;
+    }
+  }
+  return rv;
+}
+
+static void hexprintf(const char* s, size_t ln, const char* desc) {
+  size_t i;
+  printf("%s: ", desc);
+  for (i = 0; i<ln; i++) {
+    if (i % 16 == 15)
+      printf("%02hx\n", (unsigned short)(s[i]));
+    else
+      printf("%02hx ", (unsigned short)(s[i]));
+  }
+  printf("\n\n");
+}
+
+/* for the LIST response */
+static CURLcode imap_state_gen_listing_resp(struct connectdata *conn,
+                                            const char* list_str,
+                                            int imapcode,
+                                            imapstate instate)
+{
+  CURLcode result = CURLE_OK;
+  struct SessionHandle *data = conn->data;
+  struct imap_conn *imapc = &conn->proto.imapc;
+  struct FTP *imap = data->state.proto.imap;
+  struct pingpong *pp = &imapc->pp;
+  char *ptr = data->state.buffer;
+  size_t buflen = strlen(ptr);
+  size_t i;
+  int got_eor = 0;
+  (void)instate; /* no use for this yet */
+
+  imap_log(conn, pp, ptr, "enter");
+
+  if('*' != imapcode) {
+    Curl_pgrsSetDownloadSize(data, 0);
+    state(conn, IMAP_STOP);
+    return CURLE_OK;
+  }
+
+  printf("ptr: -:%s:-  buflen: %i\n", ptr, (int)(buflen));
+  hexprintf(ptr, buflen, "header on entry");
+  fflush(stdout);
+  
+  /* Something like this:
+   *       * LIST (\Flags) "/" "INBOX"
+   *  or   * SEARCH [uid-list]
+   */
+
+  /* If header doesn't end in a newline, but does end in a \r, then add
+   * a newline to make parsing easier.
+   */
+  if (ptr[buflen-1] == '\r') {
+    ptr[buflen] = '\n';
+    buflen++;
+    ptr[buflen] = 0;
+  }
+  
+  i = get_until_nstarts(ptr, buflen, list_str);
+
+  printf("after nstarts:  i: %i  buflen: %i\n", (int)(i), (int)(buflen));
+  hexprintf(ptr, i, "header remainder to consume");
+  
+  result = Curl_client_write(conn, CLIENTWRITE_BODY, ptr, i);
+  /* Clean out the header. */
+  if (buflen == i) {
+    pp->nread_resp = 0;
+    data->state.buffer[pp->nread_resp] = 0;
+  }
+  else {
+    /* shift unused bytes to front of buffer */
+    memmove(data->state.buffer, data->state.buffer+i, i);
+    buflen -= i;
+    pp->nread_resp = buflen;
+    data->state.buffer[pp->nread_resp] = 0;
+  }
+  
+  if(pp->cache) {
+    /* At this point there is a bunch of data in the header "cache" that is
+       actually body content, send it as body and then skip it. Do note
+       that there may even be additional "headers" after the body. */
+
+    hexprintf(pp->cache, pp->cache_size, "pp->cache");
+
+    i = get_until_nstarts(pp->cache, pp->cache_size, list_str);
+
+    printf("i: %i  cache_size: %i\n", (int)(i), (int)(pp->cache_size));
+    hexprintf(pp->cache, i, "cache to consume");
+  
+    if(i) {
+      result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, i);
+      if(result)
+        return result;
+
       /* we've now used parts of or the entire cache */
-      if(pp->cache_size > chunk) {
+      if(pp->cache_size > i) {
+        size_t orig_i = i;
+        if(imap_process_resp_ln(imapc->idstr, pp->cache + i,
+                                pp->cache_size - i, "OK", &i)) {
+          printf("got eor, sz: %i  i: %i orig_i: %i\n",
+                 (int)(pp->cache_size), (int)(i), (int)(orig_i));
+          got_eor = 1; /* response is good */
+        }
+        else {
+          if(i != orig_i) {
+            printf("found newline, but had bad response, sz: %i  i: %i orig_i: 
%i\n",
+                   (int)(pp->cache_size), (int)(i), (int)(orig_i));
+            /* found newline, but had bad response. TODO: Tell user. */
+            got_eor = 1;
+          }
+          else {
+            printf("didn't find newline, sz: %i  i: %i orig_i: %i\n",
+                   (int)(pp->cache_size), (int)(i), (int)(orig_i));
+          }
+        }
+      }
+      if(pp->cache_size > i) {
         /* part of, move the trailing data to the start and reduce the size */
-        memmove(pp->cache, pp->cache+chunk,
-                pp->cache_size - chunk);
-        pp->cache_size -= chunk;
+        memmove(pp->cache, pp->cache+i, pp->cache_size - i);
+        pp->cache_size -= i;
+        pp->cache[pp->cache_size] = 0; /* ensure null term */
       }
       else {
         /* cache is drained */
@@ -432,24 +869,79 @@ static CURLcode imap_state_fetch_resp(struct connectdata 
*conn,
       }
     }
 
-    infof(data, "Filesize left: %" FORMAT_OFF_T "\n", filesize);
-
-    if(!filesize)
+    /* Might should be case insensitive search ? */
+    if((!pp->cache) || got_eor) {
+      /* printf("Completed, cache:\n%s\n", pp->cache?pp->cache:"NULL"); */
       /* the entire data is already transfered! */
       result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
-    else
+    }
+    else {
+      /* printf("NOT completed, cache:\n%s\n", pp->cache); */
+      /* fflush(stdout); */
       /* IMAP download */
-      result=Curl_setup_transfer(conn, FIRSTSOCKET, filesize, FALSE,
+      result=Curl_setup_transfer(conn, FIRSTSOCKET, 0, FALSE,
                                  imap->bytecountp,
                                  -1, NULL); /* no upload here */
-
-    data->req.maxdownload = filesize;
+    }
+    data->req.maxdownload = 0; /* no idea */
   }
   else
     /* We don't know how to parse this line */
     result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
 
-  state(conn, IMAP_STOP);
+  if (got_eor && !pp->cache) {
+    /* TODO:  Assign rslts to uid and then go fetch it if user
+     * prefers, otherwise stop.
+     */
+    state(conn, IMAP_STOP);
+  }
+  return result;
+}
+
+static CURLcode imap_state_list_resp(struct connectdata *conn,
+                                     int imapcode,
+                                     imapstate instate)
+{
+  return imap_state_gen_listing_resp(conn, "* LIST ", imapcode, instate);
+}
+
+/* for the SEARCH response */
+static CURLcode imap_state_search_resp(struct connectdata *conn,
+                                       int imapcode,
+                                       imapstate instate)
+{
+  return imap_state_gen_listing_resp(conn, "* SEARCH ", imapcode, instate);
+}
+
+/* start the DO phase */
+static CURLcode imap_listing(struct connectdata *conn, const char* lst_args)
+{
+  CURLcode result = CURLE_OK;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  result = imapsendf(conn, str, "%s LIST \"\" \"%s\"", str, lst_args);
+  if(result)
+    return result;
+
+  state(conn, IMAP_LIST);
+  return result;
+}
+
+/* start the DO phase */
+static CURLcode imap_search(struct connectdata *conn, const char* search_args)
+{
+  CURLcode result = CURLE_OK;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  result = imapsendf(conn, str, "%s SEARCH \"%s\"", str, search_args);
+  if(result)
+    return result;
+
+  state(conn, IMAP_SEARCH);
   return result;
 }
 
@@ -463,7 +955,7 @@ static CURLcode imap_select(struct connectdata *conn)
   str = getcmdid(conn);
 
   result = imapsendf(conn, str, "%s SELECT %s", str,
-                     imapc->mailbox?imapc->mailbox:"");
+                     imapc->mbox?imapc->mbox:"");
   if(result)
     return result;
 
@@ -471,17 +963,39 @@ static CURLcode imap_select(struct connectdata *conn)
   return result;
 }
 
-static CURLcode imap_fetch(struct connectdata *conn)
+static CURLcode imap_fetch_hdr(struct connectdata *conn)
 {
   CURLcode result = CURLE_OK;
   const char *str;
 
   str = getcmdid(conn);
 
-  /* TODO: make this select the correct mail
-   * Use "1 body[text]" to get the full mail body of mail 1
+  result = imapsendf(conn, str, "%s FETCH %s BODY[HEADER]",
+                     str, conn->proto.imapc.uid);
+  if(result)
+    return result;
+
+  /*
+   * When issued, the server will respond with a single line similar to
+   * '* 1 FETCH (BODY[TEXT] {2021}'
+   *
+   * Identifying the fetch and how many bytes of contents we can expect. We
+   * must extract that number before continuing to "download as usual".
    */
-  result = imapsendf(conn, str, "%s FETCH 1 BODY[TEXT]", str);
+
+  state(conn, IMAP_FETCH_HEADER);
+  return result;
+}
+
+static CURLcode imap_fetch(struct connectdata *conn)
+{
+  CURLcode result = CURLE_OK;
+  const char *str;
+
+  str = getcmdid(conn);
+
+  result = imapsendf(conn, str, "%s FETCH %s BODY[TEXT]",
+                     str, conn->proto.imapc.uid);
   if(result)
     return result;
 
@@ -493,7 +1007,7 @@ static CURLcode imap_fetch(struct connectdata *conn)
    * must extract that number before continuing to "download as usual".
    */
 
-  state(conn, IMAP_FETCH);
+  state(conn, IMAP_FETCH_BODY);
   return result;
 }
 
@@ -504,14 +1018,28 @@ static CURLcode imap_state_select_resp(struct 
connectdata *conn,
 {
   CURLcode result = CURLE_OK;
   struct SessionHandle *data = conn->data;
+  struct imap_conn *imapc = &conn->proto.imapc;
   (void)instate; /* no use for this yet */
 
   if(imapcode != 'O') {
     failf(data, "Select failed");
     result = CURLE_LOGIN_DENIED;
   }
-  else
-    result = imap_fetch(conn);
+  else {
+    /* If we have a search defined, run the search..else attempt a fetch */
+    if (imapc->search && imapc->search[0])
+      imap_search(conn, imapc->search);
+    else {
+      if (imapc->uid && imapc->uid[0]) {
+        if(data->set.include_header)
+          result = imap_fetch_hdr(conn);
+        else
+          result = imap_fetch(conn);
+      }
+      else
+        state(conn, IMAP_STOP);
+    }
+  }
   return result;
 }
 
@@ -524,7 +1052,10 @@ static CURLcode imap_statemach_act(struct connectdata 
*conn)
   struct imap_conn *imapc = &conn->proto.imapc;
   struct pingpong *pp = &imapc->pp;
   size_t nread = 0;
+  size_t last_tot;
 
+  imap_log(conn, pp, data->state.buffer, "enter");  
+  
   if(pp->sendleft)
     return Curl_pp_flushsend(pp);
 
@@ -533,53 +1064,133 @@ static CURLcode imap_statemach_act(struct connectdata 
*conn)
   if(result)
     return result;
 
-  if(imapcode)
-  /* we have now received a full IMAP server response */
-  switch(imapc->state) {
-  case IMAP_SERVERGREET:
-    if(imapcode != 'O') {
-      failf(data, "Got unexpected imap-server response");
-      return CURLE_FTP_WEIRD_SERVER_REPLY;
-    }
-
-    if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
-      /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch
-         to TLS connection now */
-      const char *str;
-
-      str = getcmdid(conn);
-      result = imapsendf(conn, str, "%s STARTTLS", str);
-      state(conn, IMAP_STARTTLS);
-    }
-    else
-      result = imap_state_login(conn);
-    if(result)
-      return result;
-    break;
-
-  case IMAP_LOGIN:
-    result = imap_state_login_resp(conn, imapcode, imapc->state);
-    break;
-
-  case IMAP_STARTTLS:
-    result = imap_state_starttls_resp(conn, imapcode, imapc->state);
-    break;
-
-  case IMAP_FETCH:
-    result = imap_state_fetch_resp(conn, imapcode, imapc->state);
-    break;
-
-  case IMAP_SELECT:
-    result = imap_state_select_resp(conn, imapcode, imapc->state);
-    break;
-
-  case IMAP_LOGOUT:
-    /* fallthrough, just stop! */
-  default:
-    /* internal error */
-    state(conn, IMAP_STOP);
-    break;
-  }
+  imap_log(conn, pp, data->state.buffer, "after-pp-readresp");
+
+  if(imapcode) {
+    /* we have now received at least first line of an IMAP server response */
+    last_tot = nread + pp->cache_size;
+    while (true) {
+      switch(imapc->state) {
+      case IMAP_SERVERGREET:
+        if(imapcode != 'O') {
+          failf(data, "Got unexpected imap-server response");
+          return CURLE_FTP_WEIRD_SERVER_REPLY;
+        }
+
+        if(data->set.ftp_ssl && !conn->ssl[FIRSTSOCKET].use) {
+          /* We don't have a SSL/TLS connection yet, but SSL is requested. 
Switch
+             to TLS connection now */
+          const char *str;
+          
+          str = getcmdid(conn);
+          result = imapsendf(conn, str, "%s STARTTLS", str);
+          state(conn, IMAP_STARTTLS);
+        }
+        else
+          result = imap_state_login(conn);
+        if(result)
+          return result;
+        break;
+        
+      case IMAP_LOGIN:
+        result = imap_state_login_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_STARTTLS:
+        result = imap_state_starttls_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_FETCH_HEADER:
+        result = imap_state_fetch_hdr_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_FETCH_BODY:
+        result = imap_state_fetch_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_SELECT:
+        result = imap_state_select_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_LIST:
+        result = imap_state_list_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_SEARCH:
+        result = imap_state_search_resp(conn, imapcode, imapc->state);
+        break;
+        
+      case IMAP_LOGOUT:
+        /* fallthrough, just stop! */
+      default:
+        /* internal error */
+        state(conn, IMAP_STOP);
+        break;
+      }/* switch */
+
+      printf("state: %i  nread_resp: %i  cache_size: %i  last_tot: %i\n",
+             imapc->state, (int)(pp->nread_resp),
+             (int)(pp->cache_size), (int)(last_tot));
+      printf("buf: %s\n  cache: %s\n",
+             data->state.buffer, pp->cache?pp->cache:"NULL");
+      fflush(stdout);
+      
+      /* If we have data left to be consumed, run the state machine again */
+      if ((imapc->state == IMAP_FETCH_HEADER) ||
+          (imapc->state == IMAP_FETCH_BODY) ||
+          (imapc->state == IMAP_LIST) ||
+          (imapc->state == IMAP_SEARCH)) {
+        size_t this_tot = pp->nread_resp + pp->cache_size;
+        size_t to_move = pp->cache_size;
+
+        if (this_tot == 0)
+          break;
+        
+        if (this_tot == last_tot) {
+          printf("no progress, breaking out of loop\n");
+          fflush(stdout);
+          /* appear to be making no progress progress..bail out of loop */
+          break;
+        }
+
+        /* Merge cache into header */
+        if (to_move > (BUFSIZE - pp->nread_resp - 1))
+          to_move = (BUFSIZE - pp->nread_resp - 1);
+
+        printf("to-move: %i\n", (int)(to_move));
+        fflush(stdout);
+        
+        if (to_move > 0) {
+          memcpy(data->state.buffer + pp->nread_resp,
+                 pp->cache, to_move);
+          pp->nread_resp += to_move;
+          data->state.buffer[pp->nread_resp] = 0;
+          
+          if (to_move < pp->cache_size) {
+            memmove(pp->cache, pp->cache+to_move, to_move);
+            pp->cache_size -= to_move;
+            pp->cache[pp->cache_size] = 0;
+          }
+          else {
+            free(pp->cache);
+            pp->cache = NULL;
+            pp->cache_size = 0;
+          }
+        }
+        
+        printf("state: %i  nread_resp: %i  cache_size: %i  last_tot: %i\n",
+               imapc->state, (int)(pp->nread_resp),
+               (int)(pp->cache_size), (int)(last_tot));
+        fflush(stdout);
+
+        nread = pp->nread_resp;
+        last_tot = this_tot;
+      }
+      else
+        break;
+    }/* while */
+  }/* if imap code */
+  imap_log(conn, pp, data->state.buffer, "done");
 
   return result;
 }
@@ -790,8 +1401,15 @@ CURLcode imap_perform(struct connectdata *conn,
 
   *dophase_done = FALSE; /* not done yet */
 
-  /* start the first command in the DO phase */
-  result = imap_select(conn);
+  if (conn->proto.imapc.mbox && conn->proto.imapc.mbox[0]) {
+    /* start the first command in the DO phase */
+    result = imap_select(conn);
+  }
+  else {
+    /* Just a listing then */
+    result = imap_listing(conn, "*");
+  }
+  
   if(result)
     return result;
 
@@ -889,7 +1507,10 @@ static CURLcode imap_disconnect(struct connectdata *conn)
 
   Curl_pp_disconnect(&imapc->pp);
 
-  Curl_safefree(imapc->mailbox);
+  Curl_safefree(imapc->mbox);
+  Curl_safefree(imapc->uid);
+  Curl_safefree(imapc->validity);
+  Curl_safefree(imapc->search);
 
   return CURLE_OK;
 }
@@ -907,16 +1528,102 @@ static CURLcode imap_parse_url_path(struct connectdata 
*conn)
   struct imap_conn *imapc = &conn->proto.imapc;
   struct SessionHandle *data = conn->data;
   const char *path = data->state.path;
+  const char* tmp, *tmp2;
+  char* all_ue;
   int len;
-
-  if(!*path)
-    path = "INBOX";
+  size_t tmpi;
 
   /* url decode the path and use this mailbox */
-  imapc->mailbox = curl_easy_unescape(data, path, 0, &len);
-  if(!imapc->mailbox)
+  all_ue = curl_easy_unescape(data, path, 0, &len);
+  if(!all_ue)
     return CURLE_OUT_OF_MEMORY;
 
+  imapc->mbox = NULL;
+  imapc->uid = NULL;
+  imapc->validity = NULL;
+  imapc->search = NULL;
+  /* Deal with a few different types of URLs */
+  /* <imap://minbari.example.org/gray-council/;uid=20/;section=1.2> */
+  tmp = strstr(all_ue, "/;");
+  if (tmp) {
+    tmpi = (tmp - all_ue) + 1;
+    imapc->mbox = malloc(tmpi);
+    memcpy(imapc->mbox, all_ue, tmpi - 1);
+    imapc->mbox[tmpi-1] = 0;
+
+    printf("mbox -:%s:-\n", imapc->mbox);
+    hexprintf(imapc->mbox, tmpi - 1, "mbox");
+
+    /* look for options */
+    while (true) {
+      if (Curl_raw_nequal(tmp + 2, "uid=", 4)) {
+        tmp += 6; /* move past /;uid= */
+        tmp2 = strstr(tmp, "/;");
+        if (tmp2) {
+          tmpi = (tmp2 - tmp) + 1;
+        }
+        else {
+          /* Rest of the line is the UID then */
+          tmpi = strlen(tmp) + 1;
+        }
+        imapc->uid = malloc(tmpi);
+        memcpy(imapc->uid, tmp, tmpi - 1);
+        imapc->uid[tmpi-1] = 0;
+        
+        tmp += tmpi;
+        if (tmp2)
+          tmp += 2;
+        else
+          break;
+      }
+      if (Curl_raw_nequal(tmp + 2, "UIDVALIDITY=", 4)) {
+        tmp += 14; /* move past /;UIDVALIDITY= */
+        tmp2 = strstr(tmp, "/;");
+        if (tmp2) {
+          tmpi = (tmp2 - tmp) + 1;
+        }
+        else {
+          /* Rest of the line is the UIDVALIDITY then */
+          tmpi = strlen(tmp) + 1;
+        }
+        imapc->validity = malloc(tmpi);
+        memcpy(imapc->validity, tmp, tmpi - 1);
+        imapc->validity[tmpi-1] = 0;
+        
+        tmp += tmpi;
+        if (tmp2)
+          tmp += 2;
+        else
+          break;
+      }
+    }/* while */  
+  }
+  else {
+    /* maybe a query ? */
+    tmp = strstr(all_ue, "?");
+    if (tmp) {
+      tmpi = (tmp - all_ue) + 1;
+      imapc->mbox = malloc(tmpi);
+      memcpy(imapc->mbox, all_ue, tmpi - 1);
+      imapc->mbox[tmpi-1] = 0;
+
+      tmp += 1; /* move past ? */
+      tmpi = strlen(tmp) + 1;
+      imapc->search = malloc(tmpi);
+      memcpy(imapc->search, tmp, tmpi - 1);
+      imapc->search[tmpi-1] = 0;
+    }
+    else {
+      /* Assume they want listing of entire mbox and only specified mbox */
+      imapc->mbox = all_ue;
+      all_ue = NULL; /* don't free this */
+      imapc->search = strdup("*");
+    }
+  }
+
+  if (all_ue)
+    free(all_ue);
+  
   return CURLE_OK;
 }
 
diff --git a/lib/imap.h b/lib/imap.h
index 2f0b62a..7843644 100644
--- a/lib/imap.h
+++ b/lib/imap.h
@@ -33,8 +33,11 @@ typedef enum {
                        a connect */
   IMAP_LOGIN,
   IMAP_STARTTLS,
+  IMAP_LIST,
+  IMAP_SEARCH,
   IMAP_SELECT,
-  IMAP_FETCH,
+  IMAP_FETCH_HEADER,
+  IMAP_FETCH_BODY,
   IMAP_LOGOUT,
   IMAP_LAST  /* never used */
 } imapstate;
@@ -43,7 +46,13 @@ typedef enum {
    struct */
 struct imap_conn {
   struct pingpong pp;
-  char *mailbox;     /* what to FETCH */
+  
+  /* things we get from url */
+  char *mbox;
+  char *uid;
+  char *validity;
+  char *search;
+  
   imapstate state; /* always use imap.c:state() to change state! */
   int cmdid;       /* id number/index */
   const char *idstr; /* pointer to a string for which to wait for as id */
diff --git a/lib/pingpong.c b/lib/pingpong.c
index c6b6f2f..b8ba395 100644
--- a/lib/pingpong.c
+++ b/lib/pingpong.c
@@ -387,9 +387,9 @@ CURLcode Curl_pp_readresp(curl_socket_t sockfd,
           if(!conn->sec_complete)
 #endif
             if(data->set.verbose)
-            Curl_debug(data, CURLINFO_HEADER_IN,
-                       pp->linestart_resp, (size_t)perline, conn);
+              Curl_debug(data, CURLINFO_HEADER_IN,
+                         pp->linestart_resp, (size_t)perline, conn);
 
           /*
            * We pass all response-lines to the callback function registered
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to