summaryrefslogtreecommitdiffstats
path: root/mh.c
diff options
context:
space:
mode:
authorThomas Roessler <roessler@does-not-exist.org>1998-06-08 09:16:03 +0000
committerThomas Roessler <roessler@does-not-exist.org>1998-06-08 09:16:03 +0000
commit1a5381e07e97fe482c2b3a7c75f99938f0b105d4 (patch)
treeb4fa4088bbbf5fc9217ee6f87ab60034175e6899 /mh.c
Initial revision
Diffstat (limited to 'mh.c')
-rw-r--r--mh.c765
1 files changed, 765 insertions, 0 deletions
diff --git a/mh.c b/mh.c
new file mode 100644
index 00000000..b4f088ad
--- /dev/null
+++ b/mh.c
@@ -0,0 +1,765 @@
+/*
+ * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
+ *
+ * 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.
+ */
+
+/*
+ * This file contains routines specific to MH and ``maildir'' style mailboxes
+ */
+
+#include "mutt.h"
+#include "mx.h"
+#include "mailbox.h"
+#include "copy.h"
+#include "buffy.h"
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <limits.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+
+void mh_parse_message (CONTEXT *ctx,
+ const char *subdir,
+ const char *fname,
+ int *count,
+ int isOld)
+{
+ char path[_POSIX_PATH_MAX];
+ char *p;
+ FILE *f;
+ HEADER *h;
+ struct stat st;
+
+ if (subdir)
+ snprintf (path, sizeof (path), "%s/%s/%s", ctx->path, subdir, fname);
+ else
+ snprintf (path, sizeof (path), "%s/%s", ctx->path, fname);
+
+ if ((f = fopen (path, "r")) != NULL)
+ {
+ (*count)++;
+
+ if (!ctx->quiet && ReadInc && ((*count % ReadInc) == 0 || *count == 1))
+ mutt_message ("Reading %s... %d", ctx->path, *count);
+
+ if (ctx->msgcount == ctx->hdrmax)
+ mx_alloc_memory (ctx);
+
+ h = ctx->hdrs[ctx->msgcount] = mutt_new_header ();
+
+ if (subdir)
+ {
+ snprintf (path, sizeof (path), "%s/%s", subdir, fname);
+ h->path = safe_strdup (path);
+ }
+ else
+ h->path = safe_strdup (fname);
+
+ h->env = mutt_read_rfc822_header (f, h);
+
+ fstat (fileno (f), &st);
+ fclose (f);
+
+ if (!h->received)
+ h->received = h->date_sent;
+
+ if (h->content->length <= 0)
+ h->content->length = st.st_size - h->content->offset;
+
+ /* index doesn't have a whole lot of meaning for MH and maildir mailboxes,
+ * but this is used to find the current message after a resort in
+ * the `index' event loop.
+ */
+ h->index = ctx->msgcount;
+
+ if (ctx->magic == M_MAILDIR)
+ {
+ /* maildir stores its flags in the filename, so ignore the flags in
+ * the header of the message
+ */
+
+ h->old = isOld;
+
+ if ((p = strchr (h->path, ':')) != NULL && strncmp (p + 1, "2,", 2) == 0)
+ {
+ p += 3;
+ while (*p)
+ {
+ switch (*p)
+ {
+ case 'F':
+
+ h->flagged = 1;
+ break;
+
+ case 'S': /* seen */
+
+ h->read = 1;
+ break;
+
+ case 'R': /* replied */
+
+ h->replied = 1;
+ break;
+ }
+ p++;
+ }
+ }
+ }
+
+ /* set flags and update context info */
+ mx_update_context (ctx);
+ }
+}
+
+/*
+ * Mark all the mails in ctx read.
+ */
+void tag_all_read (CONTEXT * ctx)
+{
+ int i;
+
+ ctx->new = 0;
+ if (ctx->hdrs == NULL)
+ return;
+ for (i = 0; i < ctx->msgcount; i++)
+ {
+ ctx->hdrs[i]->read = 1;
+ ctx->hdrs[i]->old = 1;
+ }
+}
+
+/*
+ * Mark one mail as unread
+ */
+int tag_unread (CONTEXT * ctx, char *name)
+{
+ int i;
+
+ if (ctx->hdrs == NULL)
+ return (1);
+ for (i = ctx->msgcount - 1; i >= 0; i--)
+ if (!strcmp (name, ctx->hdrs[i]->path))
+ {
+ ctx->hdrs[i]->read = 0;
+ ctx->hdrs[i]->old = 0;
+ return (1);
+ }
+ return (0);
+}
+
+/* Parse a .mh_sequences file. Only supports "unseen:" field.
+ */
+
+#define SKIP_BLANK(p) \
+ while ((*(p) == ' ') || (*(p) == '\t')) p++;
+
+#define SKIP_EOL(p) \
+{ \
+ while ((*p != '\0') && (*(p) != '\n') && (*(p) != '\r')) p++; \
+ while ((*(p) == '\n') || (*(p) == '\r')) p++; \
+}
+
+/* Parses and returns the next unsigned number in the string <cur>,
+ advancing the pointer to the character after the number ended.
+ */
+static unsigned parse_number (const char **cur)
+{
+ unsigned i = 0;
+ for (; (**cur >= '0') && (**cur <= '9'); (*cur)++)
+ {
+ i *= 10;
+ i += **cur - '0';
+ }
+ return i;
+}
+
+/* if context is NULL, it uses <folder> as the mailbox path, otherwise,
+ uses ctx->path. If context is not null, tags all the unread messages
+ and sets the position correctly.
+ returns the number of unread messages in the mailbox.
+ */
+int mh_parse_sequences (CONTEXT * ctx, const char *folder)
+{
+ FILE *mh_sequences;
+ struct stat sb;
+ char *content;
+ const char *cur;
+ int len;
+ int base, last;
+ char buf[_POSIX_PATH_MAX];
+ char filename[100];
+ int unread = 0;
+ static HASH *cache = 0;
+ int *h;
+
+ if (!cache) {
+ cache = hash_create (100); /* If this ain't enough, read less mail! */
+ if (!cache)
+ {
+ mutt_error ("mh_parse_sequences: Unable to allocate hash table!\n");
+ return -1;
+ }
+ }
+
+ if (ctx)
+ folder = ctx->path;
+
+ snprintf (buf, sizeof (buf), "%s/.mh_sequences", folder);
+ if (stat (buf, &sb) != 0)
+ return (-1);
+
+ if (ctx)
+ tag_all_read (ctx);
+
+ if (!ctx && sb.st_mtime < BuffyDoneTime && (h = hash_find (cache, folder))) /* woo! we win! */
+ return *h;
+
+ /*
+ * Parse the .mh_sequence to find the unread ones.
+ */
+
+ content = safe_malloc (sb.st_size + 100);
+ if (content == NULL)
+ {
+ mutt_perror ("malloc");
+ return (-1);
+ }
+ mh_sequences = fopen (buf, "r");
+ if (mh_sequences == NULL)
+ {
+ mutt_message ("Cannot open %s", buf);
+ safe_free ((void **) &content);
+ return (-1);
+ }
+
+ len = fread (content, 1, sb.st_size, mh_sequences);
+ content[len] = '\0';
+
+ fclose (mh_sequences);
+
+ cur = content;
+ SKIP_BLANK (cur);
+ while (*cur != '\0')
+ {
+ if (!strncmp (cur, "unseen", 6))
+ {
+ cur += 6;
+ SKIP_BLANK (cur);
+ if (*cur == ':')
+ {
+ cur++;
+ SKIP_BLANK (cur);
+ }
+ while ((*cur >= '0') && (*cur <= '9'))
+ {
+ base = parse_number (&cur);
+ SKIP_BLANK (cur);
+ if (*cur == '-')
+ {
+ cur++;
+ SKIP_BLANK (cur);
+ last = parse_number (&cur);
+ }
+ else
+ last = base;
+ if (ctx)
+ for (; base <= last; base++)
+ {
+ sprintf (filename, "%d", base);
+ if (tag_unread (ctx, filename))
+ unread++;
+ }
+ else
+ unread += last - base + 1;
+ SKIP_BLANK (cur);
+ }
+ }
+ SKIP_EOL (cur);
+ SKIP_BLANK (cur);
+ }
+ if (unread != 0)
+ {
+ mutt_message ("Folder %s : %d unread", folder, unread);
+ }
+ if (ctx)
+ ctx->new = unread;
+
+ safe_free ((void **) &content);
+
+ /* Cache the data so we only have to do 'stat's in the future */
+ if (!(h = hash_find (cache, folder)))
+ {
+ h = safe_malloc (sizeof (int));
+ if (!h) /* dear god, I hope not... */
+ return -1;
+ hash_insert (cache, folder, h, 1); /* every other time we just adjust the pointer */
+ }
+ *h = unread;
+
+ return unread;
+}
+
+/* Ignore the garbage files. A valid MH message consists of only
+ * digits. Deleted message get moved to a filename with a comma before
+ * it.
+ */
+int mh_valid_message (const char *s)
+{
+ for (; *s; s++)
+ {
+ if (!isdigit (*s))
+ return 0;
+ }
+ return 1;
+}
+
+/* Read a MH/maildir style mailbox.
+ *
+ * args:
+ * ctx [IN/OUT] context for this mailbox
+ * subdir [IN] NULL for MH mailboxes, otherwise the subdir of the
+ * maildir mailbox to read from
+ */
+int mh_read_dir (CONTEXT *ctx, const char *subdir)
+{
+ DIR *dirp;
+ struct dirent *de;
+ char buf[_POSIX_PATH_MAX];
+ int isOld = 0;
+ int count = 0;
+ struct stat st;
+ int has_mh_sequences = 0;
+
+ if (subdir)
+ {
+ snprintf (buf, sizeof (buf), "%s/%s", ctx->path, subdir);
+ isOld = (strcmp ("cur", subdir) == 0) && option (OPTMARKOLD);
+ }
+ else
+ strfcpy (buf, ctx->path, sizeof (buf));
+
+ if (stat (buf, &st) == -1)
+ return (-1);
+
+ if ((dirp = opendir (buf)) == NULL)
+ return (-1);
+
+ if (!subdir || (subdir && strcmp (subdir, "new") == 0))
+ ctx->mtime = st.st_mtime;
+
+ while ((de = readdir (dirp)) != NULL)
+ {
+ if (ctx->magic == M_MH)
+ {
+ if (!mh_valid_message (de->d_name))
+ {
+ if (!strcmp (de->d_name, ".mh_sequences"))
+ has_mh_sequences++;
+ continue;
+ }
+ }
+ else if (*de->d_name == '.')
+ {
+ /* Skip files that begin with a dot. This currently isn't documented
+ * anywhere, but it was a suggestion from the author of QMail on the
+ * mailing list.
+ */
+ continue;
+ }
+
+ mh_parse_message (ctx, subdir, de->d_name, &count, isOld);
+ }
+ {
+ int i;
+#define this_body ctx->hdrs[i]->content
+ ctx->size = 0;
+ for (i = 0; i < ctx->msgcount; i++)
+ ctx->size += this_body->length + this_body->offset -
+ this_body->hdr_offset;
+#undef this_body
+ }
+
+ /*
+ * MH style .
+ */
+ if (has_mh_sequences)
+ {
+ mh_parse_sequences (ctx, NULL);
+ }
+
+ closedir (dirp);
+ return 0;
+}
+
+/* read a maildir style mailbox */
+int maildir_read_dir (CONTEXT * ctx)
+{
+ /* maildir looks sort of like MH, except that there are two subdirectories
+ * of the main folder path from which to read messages
+ */
+ if (mh_read_dir (ctx, "new") == -1 || mh_read_dir (ctx, "cur") == -1)
+ return (-1);
+
+ return 0;
+}
+
+/* Open a new (unique) message in a maildir mailbox. In order to avoid the
+ * need for locks, the filename is generated in such a way that it is unique,
+ * even over NFS: <time>.<pid>_<count>.<hostname>. The _<count> part is
+ * optional, but required for programs like Mutt which do not change PID for
+ * each message that is created in the mailbox (otherwise you could end up
+ * creating only a single file per second).
+ */
+void maildir_create_filename (const char *path, HEADER *hdr, char *msg, char *full)
+{
+ char subdir[_POSIX_PATH_MAX];
+ char suffix[16];
+ struct stat sb;
+
+ /* the maildir format stores the status flags in the filename */
+ suffix[0] = 0;
+
+ if (hdr && (hdr->flagged || hdr->replied || hdr->read))
+ {
+ sprintf (suffix, ":2,%s%s%s",
+ hdr->flagged ? "F" : "",
+ hdr->replied ? "R" : "",
+ hdr->read ? "S" : "");
+ }
+
+ if (hdr && (hdr->read || hdr->old))
+ strfcpy (subdir, "cur", sizeof (subdir));
+ else
+ strfcpy (subdir, "new", sizeof (subdir));
+
+ FOREVER
+ {
+ snprintf (msg, _POSIX_PATH_MAX, "%s/%ld.%d_%d.%s%s",
+ subdir, time (NULL), getpid (), Counter++, Hostname, suffix);
+ snprintf (full, _POSIX_PATH_MAX, "%s/%s", path, msg);
+ if (stat (full, &sb) == -1 && errno == ENOENT) return;
+ }
+}
+
+/* save changes to a message to disk */
+static int mh_sync_message (CONTEXT *ctx, int msgno)
+{
+ HEADER *h = ctx->hdrs[msgno];
+ FILE *f;
+ FILE *d;
+ int rc = -1;
+ char oldpath[_POSIX_PATH_MAX];
+ char newpath[_POSIX_PATH_MAX];
+
+ snprintf (oldpath, sizeof (oldpath), "%s/%s", ctx->path, h->path);
+
+ mutt_mktemp (newpath);
+ if ((f = safe_fopen (newpath, "w")) == NULL)
+ {
+ dprint (1, (debugfile, "mh_sync_message: %s: %s (errno %d).\n",
+ newpath, strerror (errno), errno));
+ return (-1);
+ }
+
+ rc = mutt_copy_message (f, ctx, h, M_CM_UPDATE, CH_UPDATE | CH_UPDATE_LEN);
+
+ if (rc == 0)
+ {
+ /* replace the original version of the message with the new one */
+ if ((f = freopen (newpath, "r", f)) != NULL)
+ {
+ unlink (newpath);
+ if ((d = mx_open_file_lock (oldpath, "w")) != NULL)
+ {
+ mutt_copy_stream (f, d);
+ mx_unlock_file (oldpath, fileno (d));
+ fclose (d);
+ }
+ else
+ {
+ fclose (f);
+ return (-1);
+ }
+ fclose (f);
+ }
+ else
+ {
+ mutt_perror (newpath);
+ return (-1);
+ }
+
+ /*
+ * if the status of this message changed, the offsets for the body parts
+ * will be wrong, so free up the memory. This is ok since it will get
+ * parsed again the next time the user tries to view it.
+ */
+ mutt_free_body (&h->content->parts);
+ }
+ else
+ {
+ fclose (f);
+ unlink (newpath);
+ }
+
+ return (rc);
+}
+
+static int maildir_sync_message (CONTEXT *ctx, int msgno)
+{
+ HEADER *h = ctx->hdrs[msgno];
+ char newpath[_POSIX_PATH_MAX];
+ char partpath[_POSIX_PATH_MAX];
+ char fullpath[_POSIX_PATH_MAX];
+ char oldpath[_POSIX_PATH_MAX];
+ char *p;
+
+ if ((p = strchr (h->path, '/')) == NULL)
+ {
+ dprint (1, (debugfile, "maildir_sync_message: %s: unable to find subdir!\n",
+ h->path));
+ return (-1);
+ }
+ p++;
+ strfcpy (newpath, p, sizeof (newpath));
+
+ /* kill the previous flags */
+ if ((p = strchr (newpath, ':')) != NULL) *p = 0;
+
+ if (h->replied || h->read || h->flagged)
+ {
+ strcat (newpath, ":2,");
+ if (h->flagged) strcat (newpath, "F");
+ if (h->replied) strcat (newpath, "R");
+ if (h->read) strcat (newpath, "S");
+ }
+
+ /* decide which subdir this message belongs in */
+ strfcpy (partpath, (h->read || h->old) ? "cur" : "new", sizeof (partpath));
+ strcat (partpath, "/");
+ strcat (partpath, newpath);
+ snprintf (fullpath, sizeof (fullpath), "%s/%s", ctx->path, partpath);
+ snprintf (oldpath, sizeof (oldpath), "%s/%s", ctx->path, h->path);
+
+ if (strcmp (fullpath, oldpath) == 0 && !h->attach_del)
+ {
+ /* message hasn't really changed */
+ return 0;
+ }
+
+ /*
+ * At this point, this should work to actually delete the attachment
+ * without breaking anything else (mh_sync_message doesn't appear to
+ * make any mailbox assumptions except the one file per message, which
+ * is compatible with maildir
+ */
+ if (h->attach_del)
+ {
+ char tmppath[_POSIX_PATH_MAX];
+ char ftpath[_POSIX_PATH_MAX];
+
+ strfcpy (tmppath, "tmp/", sizeof (tmppath));
+ strcat (tmppath, newpath);
+
+ snprintf (ftpath, sizeof (ftpath), "%s/%s", ctx->path, tmppath);
+ dprint (1, (debugfile, "maildir_sync_message: deleting attachment!\n"));
+
+ if (rename (oldpath, ftpath) != 0)
+ {
+ mutt_perror ("rename");
+ return (-1);
+ }
+ safe_free ((void **)&h->path);
+ h->path = safe_strdup (tmppath);
+ dprint (1, (debugfile, "maildir_sync_message: deleting attachment2!\n"));
+ mh_sync_message (ctx, msgno);
+ dprint (1, (debugfile, "maildir_sync_message: deleting attachment3!\n"));
+ if (rename (ftpath, fullpath) != 0)
+ {
+ mutt_perror ("rename");
+ return (-1);
+ }
+ }
+ else
+ {
+ if (rename (oldpath, fullpath) != 0)
+ {
+ mutt_perror ("rename");
+ return (-1);
+ }
+ }
+ safe_free ((void **) &h->path);
+ h->path = safe_strdup (partpath);
+ return (0);
+}
+
+int mh_sync_mailbox (CONTEXT * ctx)
+{
+ char path[_POSIX_PATH_MAX], tmp[_POSIX_PATH_MAX];
+ int i, rc = 0;
+ FILE *mh_sequences;
+
+
+ for (i = 0; i < ctx->msgcount; i++)
+ {
+ if (ctx->hdrs[i]->deleted)
+ {
+ snprintf (path, sizeof (path), "%s/%s", ctx->path, ctx->hdrs[i]->path);
+ if (ctx->magic == M_MAILDIR)
+ unlink (path);
+ else
+ {
+ /* MH just moves files out of the way when you delete them */
+ snprintf (tmp, sizeof (tmp), "%s/,%s", ctx->path, ctx->hdrs[i]->path);
+ unlink (tmp);
+ rename (path, tmp);
+ }
+ }
+ else if (ctx->hdrs[i]->changed || ctx->hdrs[i]->attach_del)
+ {
+ if (ctx->magic == M_MAILDIR)
+ maildir_sync_message (ctx, i);
+ else
+ {
+ /* FOO - seems ok to ignore errors, but might want to warn... */
+ mh_sync_message (ctx, i);
+ }
+ }
+ }
+
+ snprintf (path, sizeof (path), "%s/%s", ctx->path, ".mh_sequences");
+ mh_sequences = fopen (path, "w");
+ if (mh_sequences == NULL)
+ {
+ mutt_message ("fopen %s failed", path);
+ }
+ else
+ {
+ fprintf (mh_sequences, "unseen: ");
+ for (i = 0; i < ctx->msgcount; i++)
+ if ((ctx->hdrs[i]->read == 0) && !(ctx->hdrs[i]->deleted))
+ fprintf (mh_sequences, "%s ", ctx->hdrs[i]->path);
+ fprintf (mh_sequences, "\n");
+ fclose (mh_sequences);
+ }
+
+ return (rc);
+}
+
+/* check for new mail */
+int mh_check_mailbox (CONTEXT * ctx, int *index_hint)
+{
+ DIR *dirp;
+ struct dirent *de;
+ char buf[_POSIX_PATH_MAX];
+ struct stat st;
+ LIST *lst = NULL, *tmp = NULL, *prev;
+ int i, lng = 0;
+ int count = 0;
+
+ /* MH users might not like the behavior of this function because it could
+ * take awhile if there are many messages in the mailbox.
+ */
+ if (!option (OPTCHECKNEW))
+ return (0); /* disabled */
+
+ if (ctx->magic == M_MH)
+ strfcpy (buf, ctx->path, sizeof (buf));
+ else
+ snprintf (buf, sizeof (buf), "%s/new", ctx->path);
+
+ if (stat (buf, &st) == -1)
+ return (-1);
+
+ if (st.st_mtime == ctx->mtime)
+ return (0); /* unchanged */
+
+ if ((dirp = opendir (buf)) == NULL)
+ return (-1);
+
+ while ((de = readdir (dirp)) != NULL)
+ {
+ if (ctx->magic == M_MH)
+ {
+ if (!mh_valid_message (de->d_name))
+ continue;
+ }
+ else /* maildir */
+ {
+ if (*de->d_name == '.')
+ continue;
+ }
+
+ if (tmp)
+ {
+ tmp->next = mutt_new_list ();
+ tmp = tmp->next;
+ }
+ else
+ lst = tmp = mutt_new_list ();
+ tmp->data = safe_strdup (de->d_name);
+ }
+ closedir (dirp);
+
+ ctx->mtime = st.st_mtime; /* save the time we scanned at */
+
+ if (!lst)
+ return 0;
+
+ /* if maildir, skip the leading "new/" */
+ lng = (ctx->magic == M_MAILDIR) ? 4 : 0;
+
+ for (i = 0; i < ctx->msgcount; i++)
+ {
+ if (ctx->magic == M_MAILDIR && ctx->hdrs[i]->old)
+ continue; /* only look at NEW messages */
+
+ prev = NULL;
+ tmp = lst;
+ while (tmp)
+ {
+ if (strcmp (tmp->data, ctx->hdrs[i]->path + lng) == 0)
+ {
+ if (prev)
+ prev->next = tmp->next;
+ else
+ lst = lst->next;
+ safe_free ((void **) &tmp->data);
+ safe_free ((void **) &tmp);
+ break;
+ }
+ else
+ {
+ prev = tmp;
+ tmp = tmp->next;
+ }
+ }
+ }
+
+ for (tmp = lst; tmp; tmp = lst)
+ {
+ mh_parse_message (ctx, (ctx->magic == M_MH ? NULL : "new"), tmp->data, &count, 0);
+
+ lst = tmp->next;
+ safe_free ((void **) &tmp->data);
+ safe_free ((void **) &tmp);
+ }
+
+ return (1);
+}