diff options
Diffstat (limited to 'mh.c')
-rw-r--r-- | mh.c | 765 |
1 files changed, 765 insertions, 0 deletions
@@ -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); +} |