From 5c15fc00a49b5013d5de1c5c027470634869bf85 Mon Sep 17 00:00:00 2001 From: Thomas Roessler Date: Sat, 4 Sep 1999 04:55:58 +0000 Subject: [PATCH] Fix some more IMAP problems. From Brendan Cully. --- imap/BUGS | 16 ++ imap/browse.c | 13 +- imap/imap.c | 596 +------------------------------------------ imap/imap_private.h | 6 + imap/message.c | 602 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 644 insertions(+), 589 deletions(-) create mode 100644 imap/BUGS create mode 100644 imap/message.c diff --git a/imap/BUGS b/imap/BUGS new file mode 100644 index 00000000..b75eb413 --- /dev/null +++ b/imap/BUGS @@ -0,0 +1,16 @@ +* Mutt doesn't handle timeouts or dropped connections gracefully. + +* Mutt appends a '/' to $folder even when it is just '{server}', causing some + servers to look for folders off the root of the server instead of in the + home namespace. + +* Have a hard time when the home namespace isn't default "". + +* Mutt browses Cyrus IMAP servers poorly, because of Cyrus' feature where + folders can contain messages *and* subfolders, and because of the + namespace problem, which Cyrus uses. + +* The mutt_pretty routines don't work well when the delimiter isn't '/'. + +Brendan Cully +Updated 19990903 diff --git a/imap/browse.c b/imap/browse.c index adca6538..4567767c 100644 --- a/imap/browse.c +++ b/imap/browse.c @@ -157,15 +157,17 @@ int imap_init_browse (char *path, struct browser_state *state) n--; } - /* Find superiors to list */ + /* Find superiors to list + * Note: UW-IMAP servers return folder + delimiter when asked to list + * folder + delimiter. Cyrus servers don't. So we ask for folder, + * and tack on delimiter ourselves. */ for (n--; n >= 0 && mbox[n] != idata->delim ; n--); if (n > 0) /* "aaaa/bbbb/" -> "aaaa/" */ { - n++; ctmp = mbox[n]; mbox[n] = '\0'; /* List it to see if it can be selected */ - dprint (2, (debugfile, "imap_init_browse: listing %s\n", mbox)); + dprint (2, (debugfile, "imap_init_browse: listing possible parent %s\n", mbox)); imap_make_sequence (seq, sizeof (seq)); snprintf (buf, sizeof (buf), "%s %s \"\" \"%s\"\r\n", seq, list_cmd, mbox); @@ -266,6 +268,9 @@ static int add_list_result (CONNECTION *conn, const char *seq, const char *cmd, if (name) { imap_unquote_string (name); + /* Let a parent folder never be selectable for navigation */ + if (isparent) + noselect = 1; /* prune current folder from output */ if (isparent || strncmp (name, curfolder, strlen (name))) imap_add_folder (idata->delim, name, noselect, noinferiors, state, @@ -337,7 +342,7 @@ static void imap_add_folder (char delim, char *folder, int noselect, imap_qualify_path (tmp, sizeof (tmp), host, port, folder, trailing_delim); (state->entry)[state->entrylen].name = safe_strdup (tmp); - if (strlen (relpath) < sizeof (relpath) - 2) + if (!isparent && (strlen (relpath) < sizeof (relpath) - 2)) strcat (relpath, trailing_delim); (state->entry)[state->entrylen].desc = safe_strdup (relpath); diff --git a/imap/imap.c b/imap/imap.c index 51347f93..e4dcaacc 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -38,12 +38,6 @@ #include #include - -#ifdef _PGPPATH -#include "pgp.h" -#endif - - static char *Capabilities[] = {"IMAP4", "IMAP4rev1", "STATUS", "ACL", "NAMESPACE", "AUTH=CRAM-MD5", "AUTH=KERBEROS_V4", "AUTH=GSSAPI", "AUTH=LOGIN", "AUTH-LOGIN", "AUTH=PLAIN", "AUTH=SKEY", "IDLE", @@ -51,13 +45,8 @@ static char *Capabilities[] = {"IMAP4", "IMAP4rev1", "STATUS", "ACL", "THREAD=ORDEREDSUBJECT", "UIDPLUS", NULL}; /* imap forward declarations */ -static time_t imap_parse_date (char *s); -static int imap_parse_fetch (IMAP_HEADER_INFO *h, char *s); -static int imap_read_bytes (FILE *fp, CONNECTION *conn, long bytes); static int imap_wordcasecmp(const char *a, const char *b); static void imap_parse_capabilities (IMAP_DATA *idata, char *s); -static int get_literal_count(const char *buf, long *bytes); -static int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend); static int imap_reopen_mailbox (CONTEXT *ctx, int *index_hint); static int imap_get_delim (IMAP_DATA *idata, CONNECTION *conn); static int imap_check_acl (IMAP_DATA *idata); @@ -79,8 +68,8 @@ void imap_error (const char *where, const char *msg) mutt_error (_("imap_error(): unexpected response in %s: %s\n"), where, msg); } -/* date is of the form: DD-MMM-YYYY HH:MM:SS +ZZzz */ -static time_t imap_parse_date (char *s) +/* imap_parse_date: date is of the form: DD-MMM-YYYY HH:MM:SS +ZZzz */ +time_t imap_parse_date (char *s) { struct tm t; time_t tz; @@ -127,128 +116,6 @@ static time_t imap_parse_date (char *s) return (mutt_mktime (&t, 0) + tz); } -static int imap_parse_fetch (IMAP_HEADER_INFO *h, char *s) -{ - char tmp[SHORT_STRING]; - char *ptmp; - int state = 0; - int recent = 0; - - if (!s) - return (-1); - - h->old = 0; - - while (*s) - { - SKIPWS (s); - - switch (state) - { - case 0: - if (mutt_strncasecmp ("FLAGS", s, 5) == 0) - { - s += 5; - SKIPWS (s); - if (*s != '(') - { - dprint (1, (debugfile, "imap_parse_fetch(): bogus FLAGS entry: %s\n", s)); - return (-1); /* parse error */ - } - /* we're about to get a new set of headers, so clear the old ones. */ - h->deleted = 0; - h->flagged = 0; - h->replied = 0; - h->read = 0; - h->old = 0; - h->changed = 0; - recent = 0; - s++; - state = 1; - } - else if (mutt_strncasecmp ("INTERNALDATE", s, 12) == 0) - { - s += 12; - SKIPWS (s); - if (*s != '\"') - { - dprint (1, (debugfile, "imap_parse_fetch(): bogus INTERNALDATE entry: %s\n", s)); - return (-1); - } - s++; - ptmp = tmp; - while (*s && *s != '\"') - *ptmp++ = *s++; - if (*s != '\"') - return (-1); - s++; /* skip past the trailing " */ - *ptmp = 0; - h->received = imap_parse_date (tmp); - } - else if (mutt_strncasecmp ("RFC822.SIZE", s, 11) == 0) - { - s += 11; - SKIPWS (s); - ptmp = tmp; - while (isdigit (*s)) - *ptmp++ = *s++; - *ptmp = 0; - h->content_length += atoi (tmp); - } - else if (*s == ')') - s++; /* end of request */ - else if (*s) - { - /* got something i don't understand */ - imap_error ("imap_parse_fetch()", s); - return (-1); - } - break; - case 1: /* flags */ - if (*s == ')') - { - s++; - /* if a message is neither seen nor recent, it is OLD. */ - if (option (OPTMARKOLD) && !recent && !(h->read)) - h->old = 1; - state = 0; - } - else if (mutt_strncasecmp ("\\deleted", s, 8) == 0) - { - s += 8; - h->deleted = 1; - } - else if (mutt_strncasecmp ("\\flagged", s, 8) == 0) - { - s += 8; - h->flagged = 1; - } - else if (mutt_strncasecmp ("\\answered", s, 9) == 0) - { - s += 9; - h->replied = 1; - } - else if (mutt_strncasecmp ("\\seen", s, 5) == 0) - { - s += 5; - h->read = 1; - } - else if (mutt_strncasecmp ("\\recent", s, 5) == 0) - { - s += 7; - recent = 1; - } - else - { - while (*s && !ISSPACE (*s) && *s != ')') - s++; - } - break; - } - } - return 0; -} - void imap_quote_string (char *dest, size_t slen, const char *src) { char quote[] = "\"\\", *pt; @@ -312,7 +179,8 @@ void imap_unquote_string (char *s) *d = '\0'; } -static int imap_read_bytes (FILE *fp, CONNECTION *conn, long bytes) +/* imap_read_bytes: read bytes bytes from server into file */ +int imap_read_bytes (FILE *fp, CONNECTION *conn, long bytes) { long pos; long len; @@ -492,7 +360,9 @@ int imap_handle_untagged (IMAP_DATA *idata, char *s) return 0; } -static int get_literal_count(const char *buf, long *bytes) +/* imap_get_literal_count: write number of bytes in an IMAP literal into + * bytes, return 0 on success, -1 on failure. */ +int imap_get_literal_count(const char *buf, long *bytes) { char *pc; char *pn; @@ -508,200 +378,11 @@ static int get_literal_count(const char *buf, long *bytes) return (0); } -/* - * Changed to read many headers instead of just one. It will return the - * msgno of the last message read. It will return a value other than - * msgend if mail comes in while downloading headers (in theory). - */ -static int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) -{ - char buf[LONG_STRING],fetchbuf[LONG_STRING]; - char hdrreq[STRING]; - FILE *fp; - char tempfile[_POSIX_PATH_MAX]; - char seq[8]; - char *pc,*fpc,*hdr; - long ploc; - long bytes = 0; - int msgno,fetchlast; - IMAP_HEADER_INFO *h0,*h,*htemp; -#define WANT_HEADERS "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE IN-REPLY-TO REPLY-TO" - const char *want_headers = WANT_HEADERS; - int using_body_peek = 0; - fetchlast = 0; - - /* - * We now download all of the headers into one file. This should be - * faster on most systems. - */ - mutt_mktemp (tempfile); - if (!(fp = safe_fopen (tempfile, "w+"))) - { - return (-1); - } - - h0=safe_malloc(sizeof(IMAP_HEADER_INFO)); - h=h0; - for (msgno=msgbegin; msgno <= msgend ; msgno++) - { - snprintf (buf, sizeof (buf), _("Fetching message headers... [%d/%d]"), - msgno + 1, msgend + 1); - mutt_message (buf); - - if (msgno + 1 > fetchlast) - { - imap_make_sequence (seq, sizeof (seq)); - /* - * Make one request for everything. This makes fetching headers an - * order of magnitude faster if you have a large mailbox. - * - * If we get more messages while doing this, we make another - * request for all the new messages. - */ - if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4REV1)) - { - snprintf(hdrreq, sizeof (hdrreq), "BODY.PEEK[HEADER.FIELDS (%s)]", - want_headers); - using_body_peek = 1; - } - else if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4)) - { - snprintf(hdrreq, sizeof (hdrreq), "RFC822.HEADER.LINES (%s)", - want_headers); - } - else - { /* Unable to fetch headers for lower versions */ - mutt_error _("Unable to fetch headers from this IMAP server version."); - sleep (1); /* pause a moment to let the user see the error */ - return (-1); - } - snprintf (buf, sizeof (buf), - "%s FETCH %d:%d (FLAGS INTERNALDATE RFC822.SIZE %s)\r\n", - seq, msgno + 1, msgend + 1, hdrreq); - - mutt_socket_write (CTX_DATA->conn, buf); - fetchlast = msgend + 1; - } - - do - { - if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - return (-1); - } - - if (buf[0] == '*') - { - pc = buf; - pc = imap_next_word (pc); - h->number = atoi (pc); - dprint (1, (debugfile, "fetching message %d\n", h->number)); - pc = imap_next_word (pc); - if (mutt_strncasecmp ("FETCH", pc, 5) == 0) - { - if (!(pc = strchr (pc, '('))) - { - imap_error ("imap_read_headers()", buf); - return (-1); - } - pc++; - fpc=fetchbuf; - while (*pc != '\0' && *pc != ')') - { - if (using_body_peek) - hdr=strstr(pc,"BODY"); - else - hdr=strstr(pc,"RFC822.HEADER"); - if (!hdr) - { - imap_error ("imap_read_headers()", buf); - return (-1); - } - strncpy(fpc,pc,hdr-pc); - fpc += hdr-pc; - *fpc = '\0'; - pc=hdr; - /* get some number of bytes */ - if (get_literal_count(buf, &bytes) < 0) - { - imap_error ("imap_read_headers()", buf); - return (-1); - } - imap_read_bytes (fp, CTX_DATA->conn, bytes); - if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - return (-1); - } - pc = buf; - } - } - else if (imap_handle_untagged (CTX_DATA, buf) != 0) - return (-1); - } - } - while ((msgno + 1) >= fetchlast && mutt_strncmp (seq, buf, SEQLEN) != 0); - - h->content_length = -bytes; - if (imap_parse_fetch (h, fetchbuf) == -1) - return (-1); - - /* subtract the header length; the total message size will be - added to this */ - - /* in case we get new mail while fetching the headers */ - if (((IMAP_DATA *) ctx->data)->status == IMAP_NEW_MAIL) - { - msgend = ((IMAP_DATA *) ctx->data)->newMailCount - 1; - while ((msgend + 1) > ctx->hdrmax) - mx_alloc_memory (ctx); - ((IMAP_DATA *) ctx->data)->status = 0; - } - - h->next=safe_malloc(sizeof(IMAP_HEADER_INFO)); - h=h->next; - } - - rewind(fp); - h=h0; - - /* - * Now that we have all the header information, we can tell mutt about - * it. - */ - ploc=0; - for (msgno = msgbegin; msgno <= msgend;msgno++) - { - ctx->hdrs[ctx->msgcount] = mutt_new_header (); - ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; - - ctx->hdrs[msgno]->env = mutt_read_rfc822_header (fp, ctx->hdrs[msgno], 0); - ploc=ftell(fp); - ctx->hdrs[msgno]->read = h->read; - ctx->hdrs[msgno]->old = h->old; - ctx->hdrs[msgno]->deleted = h->deleted; - ctx->hdrs[msgno]->flagged = h->flagged; - ctx->hdrs[msgno]->replied = h->replied; - ctx->hdrs[msgno]->changed = h->changed; - ctx->hdrs[msgno]->received = h->received; - ctx->hdrs[msgno]->content->length = h->content_length; - - mx_update_context(ctx); /* increments ->msgcount */ - - htemp=h; - h=h->next; - safe_free((void **) &htemp); - } - fclose(fp); - unlink(tempfile); - - return (msgend); -} - /* reopen an imap mailbox. This is used when the server sends an * EXPUNGE message, indicating that some messages may have been deleted. * This is a heavy handed approach, as it reparses all of the headers, * but it should guarantee correctness. Later, we might implement - * something to actually only remove the messages taht are marked + * something to actually only remove the messages that are marked * EXPUNGE. */ static int imap_reopen_mailbox (CONTEXT *ctx, int *index_hint) @@ -1451,261 +1132,6 @@ int imap_open_mailbox_append (CONTEXT *ctx) return 0; } -int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) -{ - char seq[8]; - char buf[LONG_STRING]; - char path[_POSIX_PATH_MAX]; - char *pc; - long bytes; - int pos, len; - IMAP_CACHE *cache; - - /* see if we already have the message in our cache */ - cache = &CTX_DATA->cache[ctx->hdrs[msgno]->index % IMAP_CACHE_LEN]; - - if (cache->path) - { - if (cache->index == ctx->hdrs[msgno]->index) - { - /* yes, so just return a pointer to the message */ - if (!(msg->fp = fopen (cache->path, "r"))) - { - mutt_perror (cache->path); - return (-1); - } - return 0; - } - else - { - /* clear the previous entry */ - unlink (cache->path); - FREE (&cache->path); - } - } - - mutt_message _("Fetching message..."); - - cache->index = ctx->hdrs[msgno]->index; - mutt_mktemp (path); - cache->path = safe_strdup (path); - if (!(msg->fp = safe_fopen (path, "w+"))) - { - safe_free ((void **) &cache->path); - return (-1); - } - - imap_make_sequence (seq, sizeof (seq)); - snprintf (buf, sizeof (buf), "%s FETCH %d RFC822\r\n", seq, - ctx->hdrs[msgno]->index + 1); - mutt_socket_write (CTX_DATA->conn, buf); - do - { - if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - return (-1); - } - - if (buf[0] == '*') - { - pc = buf; - pc = imap_next_word (pc); - pc = imap_next_word (pc); - if (mutt_strncasecmp ("FETCH", pc, 5) == 0) - { - while (*pc) - { - pc = imap_next_word (pc); - if (pc[0] == '(') - pc++; - dprint (2, (debugfile, "Found FETCH word %s\n", pc)); - if (strncasecmp ("RFC822", pc, 6) == 0) - { - pc = imap_next_word (pc); - if (get_literal_count(pc, &bytes) < 0) - { - imap_error ("imap_fetch_message()", buf); - return (-1); - } - for (pos = 0; pos < bytes; ) - { - len = mutt_socket_read_line (buf, sizeof (buf), CTX_DATA->conn); - if (len < 0) - return (-1); - pos += len; - fputs (buf, msg->fp); - fputs ("\n", msg->fp); - } - if (mutt_socket_read_line (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - return (-1); - } - pc = buf; - } - } - } - else if (imap_handle_untagged (CTX_DATA, buf) != 0) - return (-1); - } - } - while (mutt_strncmp (buf, seq, SEQLEN) != 0) - ; - - if (!imap_code (buf)) - return (-1); - - /* Update the header information. Previously, we only downloaded a - * portion of the headers, those required for the main display. - */ - rewind (msg->fp); - mutt_free_envelope (&ctx->hdrs[msgno]->env); - ctx->hdrs[msgno]->env = mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno],0); - fgets (buf, sizeof (buf), msg->fp); - while (!feof (msg->fp)) - { - ctx->hdrs[msgno]->lines++; - fgets (buf, sizeof (buf), msg->fp); - } - - ctx->hdrs[msgno]->content->length = ftell (msg->fp) - - ctx->hdrs[msgno]->content->offset; - - /* This needs to be done in case this is a multipart message */ -#ifdef _PGPPATH - ctx->hdrs[msgno]->pgp = pgp_query (ctx->hdrs[msgno]->content); -#endif /* _PGPPATH */ - - mutt_clear_error(); - rewind (msg->fp); - - return 0; -} - -static void flush_buffer(char *buf, size_t *len, CONNECTION *conn) -{ - buf[*len] = '\0'; - mutt_socket_write(conn, buf); - *len = 0; -} - -int imap_append_message (CONTEXT *ctx, MESSAGE *msg) -{ - FILE *fp; - char buf[LONG_STRING]; - char host[SHORT_STRING]; - char mbox[LONG_STRING]; - char mailbox[LONG_STRING]; - char seq[16]; - char *pc; - int port; - size_t len; - int c, last; - - if (imap_parse_path (ctx->path, host, sizeof (host), &port, &pc)) - return (-1); - - imap_fix_path (CTX_DATA, pc, mailbox, sizeof (mailbox)); - - if ((fp = fopen (msg->path, "r")) == NULL) - { - mutt_perror (msg->path); - return (-1); - } - - for(last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c) - { - if(c == '\n' && last != '\r') - len++; - - len++; - } - rewind(fp); - - mutt_message _("Sending APPEND command ..."); - - imap_quote_string (mbox, sizeof (mbox), mailbox); - imap_make_sequence (seq, sizeof (seq)); - snprintf (buf, sizeof (buf), "%s APPEND %s {%d}\r\n", seq, mbox, len); - - mutt_socket_write (CTX_DATA->conn, buf); - - do - { - if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - fclose (fp); - return (-1); - } - - if (buf[0] == '*' && imap_handle_untagged (CTX_DATA, buf) != 0) - { - fclose (fp); - return (-1); - } - } - while ((mutt_strncmp (buf, seq, SEQLEN) != 0) && (buf[0] != '+')); - - if (buf[0] != '+') - { - char *pc; - - dprint (1, (debugfile, "imap_append_message(): command failed: %s\n", buf)); - - pc = buf + SEQLEN; - SKIPWS (pc); - pc = imap_next_word (pc); - mutt_error (pc); - sleep (1); - fclose (fp); - return (-1); - } - - mutt_message _("Uploading message ..."); - - for(last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c) - { - if(c == '\n' && last != '\r') - buf[len++] = '\r'; - - buf[len++] = c; - - if(len > sizeof(buf) - 3) - flush_buffer(buf, &len, CTX_DATA->conn); - } - - if(len) - flush_buffer(buf, &len, CTX_DATA->conn); - - - mutt_socket_write (CTX_DATA->conn, "\r\n"); - fclose (fp); - - do - { - if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - return (-1); - - if (buf[0] == '*' && imap_handle_untagged (CTX_DATA, buf) != 0) - return (-1); - } - while (mutt_strncmp (buf, seq, SEQLEN) != 0); - - if (!imap_code (buf)) - { - char *pc; - - dprint (1, (debugfile, "imap_append_message(): command failed: %s\n", buf)); - pc = buf + SEQLEN; - SKIPWS (pc); - pc = imap_next_word (pc); - mutt_error (pc); - sleep (1); - return (-1); - } - - return 0; -} - int imap_close_connection (CONTEXT *ctx) { char buf[LONG_STRING]; @@ -2078,9 +1504,9 @@ int imap_parse_list_response(CONNECTION *conn, char *buf, int buflen, ep = s; while (*ep && *ep != ')') ep++; do { - if (!strncmp (s, "\\NoSelect", 9)) + if (!strncasecmp (s, "\\NoSelect", 9)) *noselect = 1; - if (!strncmp (s, "\\NoInferiors", 12)) + if (!strncasecmp (s, "\\NoInferiors", 12)) *noinferiors = 1; if (*s != ')') s++; @@ -2103,7 +1529,7 @@ int imap_parse_list_response(CONNECTION *conn, char *buf, int buflen, { int len; - if (get_literal_count(buf, &bytes) < 0) + if (imap_get_literal_count(buf, &bytes) < 0) return (-1); len = mutt_socket_read_line (buf, buflen, conn); if (len < 0) diff --git a/imap/imap_private.h b/imap/imap_private.h index 54e49994..1dd8ba4b 100644 --- a/imap/imap_private.h +++ b/imap/imap_private.h @@ -167,16 +167,22 @@ int imap_exec (char *buf, size_t buflen, IMAP_DATA *idata, const char *seq, const char *cmd, int flags); char *imap_fix_path (IMAP_DATA *idata, char *mailbox, char *path, size_t plen); +int imap_get_literal_count (const char *buf, long *bytes); int imap_handle_untagged (IMAP_DATA *idata, char *s); void imap_make_sequence (char *buf, size_t buflen); char *imap_next_word (char *s); int imap_open_connection (IMAP_DATA *idata, CONNECTION *conn); +time_t imap_parse_date (char *s); int imap_parse_list_response(CONNECTION *conn, char *buf, int buflen, char **name, int *noselect, int *noinferiors, char *delim); +int imap_read_bytes (FILE *fp, CONNECTION *conn, long bytes); void imap_quote_string (char *dest, size_t slen, const char *src); void imap_unquote_string (char *s); /* imap_auth.c */ int imap_authenticate (IMAP_DATA *idata, CONNECTION *conn); +/* message.c */ +int imap_read_headers (CONTEXT* ctx, int msgbegin, int msgend); + #endif diff --git a/imap/message.c b/imap/message.c new file mode 100644 index 00000000..1d0d9959 --- /dev/null +++ b/imap/message.c @@ -0,0 +1,602 @@ +/* + * Copyright (C) 1996-9 Brandon Long + * Copyright (C) 1999 Brendan Cully + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* message parsing/updating functions */ + +#include +#include + +#include "mutt.h" +#include "imap.h" +#include "imap_private.h" +#include "mx.h" + +#ifdef _PGPPATH +#include "pgp.h" +#endif + +static void flush_buffer(char *buf, size_t *len, CONNECTION *conn); +static int parse_fetch (IMAP_HEADER_INFO *h, char *s); + +/* imap_read_headers: + * Changed to read many headers instead of just one. It will return the + * msgno of the last message read. It will return a value other than + * msgend if mail comes in while downloading headers (in theory). + */ +int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) +{ + char buf[LONG_STRING],fetchbuf[LONG_STRING]; + char hdrreq[STRING]; + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + char seq[8]; + char *pc,*fpc,*hdr; + long ploc; + long bytes = 0; + int msgno,fetchlast; + IMAP_HEADER_INFO *h0,*h,*htemp; +#define WANT_HEADERS "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE IN-REPLY-TO REPLY-TO" + const char *want_headers = WANT_HEADERS; + int using_body_peek = 0; + fetchlast = 0; + + /* + * We now download all of the headers into one file. This should be + * faster on most systems. + */ + mutt_mktemp (tempfile); + if (!(fp = safe_fopen (tempfile, "w+"))) + { + return (-1); + } + + h0=safe_malloc(sizeof(IMAP_HEADER_INFO)); + h=h0; + for (msgno=msgbegin; msgno <= msgend ; msgno++) + { + snprintf (buf, sizeof (buf), _("Fetching message headers... [%d/%d]"), + msgno + 1, msgend + 1); + mutt_message (buf); + + if (msgno + 1 > fetchlast) + { + imap_make_sequence (seq, sizeof (seq)); + /* + * Make one request for everything. This makes fetching headers an + * order of magnitude faster if you have a large mailbox. + * + * If we get more messages while doing this, we make another + * request for all the new messages. + */ + if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4REV1)) + { + snprintf(hdrreq, sizeof (hdrreq), "BODY.PEEK[HEADER.FIELDS (%s)]", + want_headers); + using_body_peek = 1; + } + else if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4)) + { + snprintf(hdrreq, sizeof (hdrreq), "RFC822.HEADER.LINES (%s)", + want_headers); + } + else + { /* Unable to fetch headers for lower versions */ + mutt_error _("Unable to fetch headers from this IMAP server version."); + sleep (1); /* pause a moment to let the user see the error */ + return (-1); + } + snprintf (buf, sizeof (buf), + "%s FETCH %d:%d (FLAGS INTERNALDATE RFC822.SIZE %s)\r\n", + seq, msgno + 1, msgend + 1, hdrreq); + + mutt_socket_write (CTX_DATA->conn, buf); + fetchlast = msgend + 1; + } + + do + { + if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) + { + return (-1); + } + + if (buf[0] == '*') + { + pc = buf; + pc = imap_next_word (pc); + h->number = atoi (pc); + dprint (1, (debugfile, "fetching message %d\n", h->number)); + pc = imap_next_word (pc); + if (mutt_strncasecmp ("FETCH", pc, 5) == 0) + { + if (!(pc = strchr (pc, '('))) + { + imap_error ("imap_read_headers()", buf); + return (-1); + } + pc++; + fpc=fetchbuf; + while (*pc != '\0' && *pc != ')') + { + if (using_body_peek) + hdr=strstr(pc,"BODY"); + else + hdr=strstr(pc,"RFC822.HEADER"); + if (!hdr) + { + imap_error ("imap_read_headers()", buf); + return (-1); + } + strncpy(fpc,pc,hdr-pc); + fpc += hdr-pc; + *fpc = '\0'; + pc=hdr; + /* get some number of bytes */ + if (imap_get_literal_count(buf, &bytes) < 0) + { + imap_error ("imap_read_headers()", buf); + return (-1); + } + imap_read_bytes (fp, CTX_DATA->conn, bytes); + if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) + { + return (-1); + } + pc = buf; + } + } + else if (imap_handle_untagged (CTX_DATA, buf) != 0) + return (-1); + } + } + while ((msgno + 1) >= fetchlast && mutt_strncmp (seq, buf, SEQLEN) != 0); + + h->content_length = -bytes; + if (parse_fetch (h, fetchbuf) == -1) + return (-1); + + /* subtract the header length; the total message size will be + added to this */ + + /* in case we get new mail while fetching the headers */ + if (((IMAP_DATA *) ctx->data)->status == IMAP_NEW_MAIL) + { + msgend = ((IMAP_DATA *) ctx->data)->newMailCount - 1; + while ((msgend + 1) > ctx->hdrmax) + mx_alloc_memory (ctx); + ((IMAP_DATA *) ctx->data)->status = 0; + } + + h->next=safe_malloc(sizeof(IMAP_HEADER_INFO)); + h=h->next; + } + + rewind(fp); + h=h0; + + /* + * Now that we have all the header information, we can tell mutt about + * it. + */ + ploc=0; + for (msgno = msgbegin; msgno <= msgend;msgno++) + { + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; + + ctx->hdrs[msgno]->env = mutt_read_rfc822_header (fp, ctx->hdrs[msgno], 0); + ploc=ftell(fp); + ctx->hdrs[msgno]->read = h->read; + ctx->hdrs[msgno]->old = h->old; + ctx->hdrs[msgno]->deleted = h->deleted; + ctx->hdrs[msgno]->flagged = h->flagged; + ctx->hdrs[msgno]->replied = h->replied; + ctx->hdrs[msgno]->changed = h->changed; + ctx->hdrs[msgno]->received = h->received; + ctx->hdrs[msgno]->content->length = h->content_length; + + mx_update_context(ctx); /* increments ->msgcount */ + + htemp=h; + h=h->next; + safe_free((void **) &htemp); + } + fclose(fp); + unlink(tempfile); + + return (msgend); +} + +int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) +{ + char seq[8]; + char buf[LONG_STRING]; + char path[_POSIX_PATH_MAX]; + char *pc; + long bytes; + int pos, len; + IMAP_CACHE *cache; + + /* see if we already have the message in our cache */ + cache = &CTX_DATA->cache[ctx->hdrs[msgno]->index % IMAP_CACHE_LEN]; + + if (cache->path) + { + if (cache->index == ctx->hdrs[msgno]->index) + { + /* yes, so just return a pointer to the message */ + if (!(msg->fp = fopen (cache->path, "r"))) + { + mutt_perror (cache->path); + return (-1); + } + return 0; + } + else + { + /* clear the previous entry */ + unlink (cache->path); + FREE (&cache->path); + } + } + + mutt_message _("Fetching message..."); + + cache->index = ctx->hdrs[msgno]->index; + mutt_mktemp (path); + cache->path = safe_strdup (path); + if (!(msg->fp = safe_fopen (path, "w+"))) + { + safe_free ((void **) &cache->path); + return (-1); + } + + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s FETCH %d RFC822\r\n", seq, + ctx->hdrs[msgno]->index + 1); + mutt_socket_write (CTX_DATA->conn, buf); + do + { + if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) + { + return (-1); + } + + if (buf[0] == '*') + { + pc = buf; + pc = imap_next_word (pc); + pc = imap_next_word (pc); + if (mutt_strncasecmp ("FETCH", pc, 5) == 0) + { + while (*pc) + { + pc = imap_next_word (pc); + if (pc[0] == '(') + pc++; + dprint (2, (debugfile, "Found FETCH word %s\n", pc)); + if (strncasecmp ("RFC822", pc, 6) == 0) + { + pc = imap_next_word (pc); + if (imap_get_literal_count(pc, &bytes) < 0) + { + imap_error ("imap_fetch_message()", buf); + return (-1); + } + for (pos = 0; pos < bytes; ) + { + len = mutt_socket_read_line (buf, sizeof (buf), CTX_DATA->conn); + if (len < 0) + return (-1); + pos += len; + fputs (buf, msg->fp); + fputs ("\n", msg->fp); + } + if (mutt_socket_read_line (buf, sizeof (buf), CTX_DATA->conn) < 0) + { + return (-1); + } + pc = buf; + } + } + } + else if (imap_handle_untagged (CTX_DATA, buf) != 0) + return (-1); + } + } + while (mutt_strncmp (buf, seq, SEQLEN) != 0) + ; + + if (!imap_code (buf)) + return (-1); + + /* Update the header information. Previously, we only downloaded a + * portion of the headers, those required for the main display. + */ + rewind (msg->fp); + mutt_free_envelope (&ctx->hdrs[msgno]->env); + ctx->hdrs[msgno]->env = mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno],0); + fgets (buf, sizeof (buf), msg->fp); + while (!feof (msg->fp)) + { + ctx->hdrs[msgno]->lines++; + fgets (buf, sizeof (buf), msg->fp); + } + + ctx->hdrs[msgno]->content->length = ftell (msg->fp) - + ctx->hdrs[msgno]->content->offset; + + /* This needs to be done in case this is a multipart message */ +#ifdef _PGPPATH + ctx->hdrs[msgno]->pgp = pgp_query (ctx->hdrs[msgno]->content); +#endif /* _PGPPATH */ + + mutt_clear_error(); + rewind (msg->fp); + + return 0; +} + +int imap_append_message (CONTEXT *ctx, MESSAGE *msg) +{ + FILE *fp; + char buf[LONG_STRING]; + char host[SHORT_STRING]; + char mbox[LONG_STRING]; + char mailbox[LONG_STRING]; + char seq[16]; + char *pc; + int port; + size_t len; + int c, last; + + if (imap_parse_path (ctx->path, host, sizeof (host), &port, &pc)) + return (-1); + + imap_fix_path (CTX_DATA, pc, mailbox, sizeof (mailbox)); + + if ((fp = fopen (msg->path, "r")) == NULL) + { + mutt_perror (msg->path); + return (-1); + } + + for(last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c) + { + if(c == '\n' && last != '\r') + len++; + + len++; + } + rewind(fp); + + mutt_message _("Sending APPEND command ..."); + + imap_quote_string (mbox, sizeof (mbox), mailbox); + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s APPEND %s {%d}\r\n", seq, mbox, len); + + mutt_socket_write (CTX_DATA->conn, buf); + + do + { + if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) + { + fclose (fp); + return (-1); + } + + if (buf[0] == '*' && imap_handle_untagged (CTX_DATA, buf) != 0) + { + fclose (fp); + return (-1); + } + } + while ((mutt_strncmp (buf, seq, SEQLEN) != 0) && (buf[0] != '+')); + + if (buf[0] != '+') + { + char *pc; + + dprint (1, (debugfile, "imap_append_message(): command failed: %s\n", buf)); + + pc = buf + SEQLEN; + SKIPWS (pc); + pc = imap_next_word (pc); + mutt_error (pc); + sleep (1); + fclose (fp); + return (-1); + } + + mutt_message _("Uploading message ..."); + + for(last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c) + { + if(c == '\n' && last != '\r') + buf[len++] = '\r'; + + buf[len++] = c; + + if(len > sizeof(buf) - 3) + flush_buffer(buf, &len, CTX_DATA->conn); + } + + if(len) + flush_buffer(buf, &len, CTX_DATA->conn); + + + mutt_socket_write (CTX_DATA->conn, "\r\n"); + fclose (fp); + + do + { + if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) + return (-1); + + if (buf[0] == '*' && imap_handle_untagged (CTX_DATA, buf) != 0) + return (-1); + } + while (mutt_strncmp (buf, seq, SEQLEN) != 0); + + if (!imap_code (buf)) + { + char *pc; + + dprint (1, (debugfile, "imap_append_message(): command failed: %s\n", buf)); + pc = buf + SEQLEN; + SKIPWS (pc); + pc = imap_next_word (pc); + mutt_error (pc); + sleep (1); + return (-1); + } + + return 0; +} + +/* parse_fetch: handle headers returned from header fetch */ +static int parse_fetch (IMAP_HEADER_INFO *h, char *s) +{ + char tmp[SHORT_STRING]; + char *ptmp; + int state = 0; + int recent = 0; + + if (!s) + return (-1); + + h->old = 0; + + while (*s) + { + SKIPWS (s); + + switch (state) + { + case 0: + if (mutt_strncasecmp ("FLAGS", s, 5) == 0) + { + s += 5; + SKIPWS (s); + if (*s != '(') + { + dprint (1, (debugfile, "parse_fetch(): bogus FLAGS entry: %s\n", s)); + return (-1); /* parse error */ + } + /* we're about to get a new set of headers, so clear the old ones. */ + h->deleted = 0; + h->flagged = 0; + h->replied = 0; + h->read = 0; + h->old = 0; + h->changed = 0; + recent = 0; + s++; + state = 1; + } + else if (mutt_strncasecmp ("INTERNALDATE", s, 12) == 0) + { + s += 12; + SKIPWS (s); + if (*s != '\"') + { + dprint (1, (debugfile, "parse_fetch(): bogus INTERNALDATE entry: %s\n", s)); + return (-1); + } + s++; + ptmp = tmp; + while (*s && *s != '\"') + *ptmp++ = *s++; + if (*s != '\"') + return (-1); + s++; /* skip past the trailing " */ + *ptmp = 0; + h->received = imap_parse_date (tmp); + } + else if (mutt_strncasecmp ("RFC822.SIZE", s, 11) == 0) + { + s += 11; + SKIPWS (s); + ptmp = tmp; + while (isdigit (*s)) + *ptmp++ = *s++; + *ptmp = 0; + h->content_length += atoi (tmp); + } + else if (*s == ')') + s++; /* end of request */ + else if (*s) + { + /* got something i don't understand */ + imap_error ("parse_fetch()", s); + return (-1); + } + break; + case 1: /* flags */ + if (*s == ')') + { + s++; + /* if a message is neither seen nor recent, it is OLD. */ + if (option (OPTMARKOLD) && !recent && !(h->read)) + h->old = 1; + state = 0; + } + else if (mutt_strncasecmp ("\\deleted", s, 8) == 0) + { + s += 8; + h->deleted = 1; + } + else if (mutt_strncasecmp ("\\flagged", s, 8) == 0) + { + s += 8; + h->flagged = 1; + } + else if (mutt_strncasecmp ("\\answered", s, 9) == 0) + { + s += 9; + h->replied = 1; + } + else if (mutt_strncasecmp ("\\seen", s, 5) == 0) + { + s += 5; + h->read = 1; + } + else if (mutt_strncasecmp ("\\recent", s, 5) == 0) + { + s += 7; + recent = 1; + } + else + { + while (*s && !ISSPACE (*s) && *s != ')') + s++; + } + break; + } + } + return 0; +} + +static void flush_buffer(char *buf, size_t *len, CONNECTION *conn) +{ + buf[*len] = '\0'; + mutt_socket_write(conn, buf); + *len = 0; +} -- 2.40.0