changeset: 6842:ad519d4b356c
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:33 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/ad519d4b356c

Compress patch from the neomutt repository.

With the following changes:
- po/de.po changes trimmed to just the compress additions.

- Move the sample muttrc to contrib, and add it to the Makefile.am so it is
  distributed.  Remove the sample vimrc.

- Remove extra fluff from manual.

Thanks to Roland Rosenfeld for the original patch, and to the NeoMutt
team for their work cleaning up the patch.

changeset: 6843:290631db373e
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:33 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/290631db373e

Compress: prefix external functions with "mutt_"

Also, include compress.h in compress.c so the mx_comp_ops doesn't need
to be redeclared.

changeset: 6844:c9c120d988a8
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:34 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/c9c120d988a8

Compress: remove buffy stats "improvisation".

Mutt doesn't support polling compressed mailboxes.  This code creates
a false impression and dirties mutt_save_message() in the process.  I
don't like it, so am taking it out.

changeset: 6845:d1ff983c9bcb
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:34 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/d1ff983c9bcb

Compress: fix several logic and memory bugs.

setup_paths leaks memory: realpath is already set in mx_open_mailbox()

restore_paths is unneeded.  mx_fastclose_mailbox() will free stuff,
and nothing is looking at the path once we are closing or aborting.

Make a copy of the hooks.  Otherwise 'unhook *' will leave dangling
pointers.

Add compress_info freeing inside mx_fastclose_mailbox().  Only free
inside compress.c when we want to prevent close() from doing anything.

close_mailbox() didn't preserve ctx->path on error.

execute_command() didn't return an error if the mutt_system() command
failed.

mx_open_mailbox_append() should check mutt_comp_can_append() only for
the case that the mailbox doesn't exist.  When it exists,
mx_get_magic() has already looked at the file contents before checking
for matching open_hooks.

In open_append_mailbox() if no append hook is defined, it should't
call ci->open() if the mailbox doesn't exist.  It should act just like
append and create a temporary file.

check_mailbox() needs more work.  For now, at least have it properly
close the mailbox on error.

changeset: 6846:7c055cc893dc
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:34 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/7c055cc893dc

Compress: add delegate calls to open_append and close mx_ops functions.

The open_append and close were partially duplicating mbox open_append
and close operations internally.  Change it to call the actual
delegate functions instead.

Inline the open_read() function inside open_mailbox().  Having it
split improved nothing and just complicated the code (i.e. added
error-handling checks in open_mailbox() that in reality could not
fail).

changeset: 6847:b1366f49c7b7
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:35 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/b1366f49c7b7

Compress: safe_fopen() the tempfile, to prevent tempfile attacks.

changeset: 6848:05f6bd8532ea
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:35 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/05f6bd8532ea

Compress: pull the lock/unlock operations into the open,close,sync operations.

Some operations, such as open_append and sync, need an exclusive lock
across a longer period than a single compress/decompress.  Remove it
from the execute_command and pull into the outer callers.  Store lock
information inside compress_info.

Sync and check_mailbox need more fixes, which will be addressed in
subsequent patches.

changeset: 6849:0a7054904b5b
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:35 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/0a7054904b5b

Create mx_ops.sync operation.  Refactor compress to use the mx_ops.sync.

Change compress.sync_mailbox() to lock the compressed mailbox around
both the tempfile sync and compress operations.  This will prevent
changes made inbetween the two syncs from being overwritten.

Thanks to Damien Riegel for his original patch refactoring
mx_ops.sync, which this patch is partially based upon.

changeset: 6850:a51f1c8a038a
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:35 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/a51f1c8a038a

Compress: fix check_mailbox and sync_mailbox.

Change check_mailbox to delegate to the child_ops->check_mailbox if
the compressed mailbox has changed.  This allows the mailbox to
properly recover if both the decompressed mailbox and compressed file
have changed.

Change sync_mailbox to call check_mailbox before attempting to sync.
This will prevent overwriting external changes to the compressed
mailbox.

changeset: 6851:cad0051417eb
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:36 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/cad0051417eb

Compress: escape single quotes when invoking the compress/decompress commands.

The format strings are placed in single quotes.  mutt_system() invokes
sh, so escape the single quotes using bourne-shell syntax: '\''

changeset: 6852:99a3ff6555d9
user:      Kevin McCarthy <[email protected]>
date:      Sun Nov 13 20:02:36 2016 -0800
link:      http://dev.mutt.org/hg/mutt/rev/99a3ff6555d9

Compress: check mailbox type if possible when appending.

If an append hook isn't defined, then mutt has to decompress the
mailbox.  In that case, we can check the type of the decompressed
mailbox instead of defaulting to DefaultMagic.

diffs (truncated from 1851 to 950 lines):

diff -r 42fee7585fae -r 99a3ff6555d9 Makefile.am
--- a/Makefile.am       Sun Nov 13 18:45:35 2016 -0800
+++ b/Makefile.am       Sun Nov 13 20:02:36 2016 -0800
@@ -54,7 +54,7 @@
 
 AM_CPPFLAGS=-I. -I$(top_srcdir) $(IMAP_INCLUDES) $(GPGME_CFLAGS) -Iintl
 
-EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \
+EXTRA_mutt_SOURCES = account.c bcache.c compress.c crypt-gpgme.c 
crypt-mod-pgp-classic.c \
        crypt-mod-pgp-gpgme.c crypt-mod-smime-classic.c \
        crypt-mod-smime-gpgme.c dotlock.c gnupgparse.c hcache.c md5.c \
        mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \
@@ -65,7 +65,7 @@
 
 EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \
        configure account.h \
-       attach.h buffy.h charset.h copy.h crypthash.h dotlock.h functions.h 
gen_defs \
+       attach.h buffy.h charset.h compress.h copy.h crypthash.h dotlock.h 
functions.h gen_defs \
        globals.h hash.h history.h init.h keymap.h mutt_crypt.h \
        mailbox.h mapping.h md5.h mime.h mutt.h mutt_curses.h mutt_menu.h \
        mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \
diff -r 42fee7585fae -r 99a3ff6555d9 compress.c
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/compress.c        Sun Nov 13 20:02:36 2016 -0800
@@ -0,0 +1,961 @@
+/* Copyright (C) 1997 Alain Penders <[email protected]>
+ * Copyright (C) 2016 Richard Russon <[email protected]>
+ *
+ *     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., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "mutt.h"
+#include "mailbox.h"
+#include "mutt_curses.h"
+#include "mx.h"
+#include "compress.h"
+
+/* Notes:
+ * Any references to compressed files also apply to encrypted files.
+ * ctx->path     == plaintext file
+ * ctx->realpath == compressed file
+ */
+
+/**
+ * struct COMPRESS_INFO - Private data for compress
+ *
+ * This object gets attached to the mailbox's CONTEXT.
+ */
+typedef struct
+{
+  const char *append;             /* append-hook command */
+  const char *close;              /* close-hook  command */
+  const char *open;               /* open-hook   command */
+  off_t size;                     /* size of the compressed file */
+  struct mx_ops *child_ops;       /* callbacks of de-compressed file */
+  int locked;                     /* if realpath is locked */
+  FILE *lockfp;                   /* fp used for locking */
+} COMPRESS_INFO;
+
+
+/**
+ * lock_realpath - Try to lock the ctx->realpath
+ * @ctx:  Mailbox to lock
+ * @excl: Lock exclusively?
+ *
+ * Try to (exclusively) lock the mailbox.  If we succeed, then we mark the
+ * mailbox as locked.  If we fail, but we didn't want exclusive rights, then
+ * the mailbox will be marked readonly.
+ *
+ * Returns:
+ *      1: Success (locked or readonly)
+ *      0: Error (can't lock the file)
+ */
+static int
+lock_realpath (CONTEXT *ctx, int excl)
+{
+  if (!ctx)
+    return 0;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return 0;
+
+  if (ci->locked)
+    return 1;
+
+  if (excl)
+    ci->lockfp = fopen (ctx->realpath, "a");
+  else
+    ci->lockfp = fopen (ctx->realpath, "r");
+  if (!ci->lockfp)
+  {
+    mutt_perror (ctx->realpath);
+    return 0;
+  }
+
+  int r = mx_lock_file (ctx->realpath, fileno (ci->lockfp), excl, 1, 1);
+
+  if (r == 0)
+    ci->locked = 1;
+  else if (excl == 0)
+  {
+    safe_fclose (&ci->lockfp);
+    ctx->readonly = 1;
+    return 1;
+  }
+
+  return (r == 0);
+}
+
+/**
+ * unlock_realpath - Unlock the ctx->realpath
+ * @ctx: Mailbox to unlock
+ *
+ * Unlock a mailbox previously locked by lock_mailbox().
+ */
+static void
+unlock_realpath (CONTEXT *ctx)
+{
+  if (!ctx)
+    return;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return;
+
+  if (!ci->locked)
+    return;
+
+  mx_unlock_file (ctx->realpath, fileno (ci->lockfp), 1);
+
+  ci->locked = 0;
+  safe_fclose (&ci->lockfp);
+}
+
+/**
+ * setup_paths - Set the mailbox paths
+ * @ctx: Mailbox to modify
+ *
+ * Save the compressed filename in ctx->realpath.
+ * Create a temporary filename and put its name in ctx->path.
+ * The temporary file is created to prevent symlink attacks.
+ *
+ * Returns:
+ *      0: Success
+ *      -1: Error
+ */
+static int
+setup_paths (CONTEXT *ctx)
+{
+  if (!ctx)
+    return -1;
+
+  char tmppath[_POSIX_PATH_MAX];
+  FILE *tmpfp;
+
+  /* Setup the right paths */
+  FREE(&ctx->realpath);
+  ctx->realpath = ctx->path;
+
+  /* We will uncompress to /tmp */
+  mutt_mktemp (tmppath, sizeof (tmppath));
+  ctx->path = safe_strdup (tmppath);
+
+  if ((tmpfp = safe_fopen (ctx->path, "w")) == NULL)
+    return -1;
+
+  safe_fclose (&tmpfp);
+  return 0;
+}
+
+/**
+ * get_size - Get the size of a file
+ * @path: File to measure
+ *
+ * Returns:
+ *      number: Size in bytes
+ *      0:      On error
+ */
+static int
+get_size (const char *path)
+{
+  if (!path)
+    return 0;
+
+  struct stat sb;
+  if (stat (path, &sb) != 0)
+    return 0;
+
+  return sb.st_size;
+}
+
+/**
+ * store_size - Save the size of the compressed file
+ * @ctx: Mailbox
+ *
+ * Save the compressed file size in the compress_info struct.
+ */
+static void
+store_size (const CONTEXT *ctx)
+{
+  if (!ctx)
+    return;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return;
+
+  ci->size = get_size (ctx->realpath);
+}
+
+/**
+ * find_hook - Find a hook to match a path
+ * @type: Type of hook, e.g. MUTT_CLOSEHOOK
+ * @path: Filename to test
+ *
+ * Each hook has a type and a pattern.
+ * Find a command that matches the type and path supplied. e.g.
+ *
+ * User config:
+ *      open-hook '\.gz$' "gzip -cd '%f' > '%t'"
+ *
+ * Call:
+ *      find_hook (MUTT_OPENHOOK, "myfile.gz");
+ *
+ * Returns:
+ *      string: Matching hook command
+ *      NULL:   No matches
+ */
+static const char *
+find_hook (int type, const char *path)
+{
+  if (!path)
+    return NULL;
+
+  const char *c = mutt_find_hook (type, path);
+  if (!c || !*c)
+    return NULL;
+
+  return c;
+}
+
+/**
+ * set_compress_info - Find the compress hooks for a mailbox
+ * @ctx: Mailbox to examine
+ *
+ * When a mailbox is opened, we check if there are any matching hooks.
+ *
+ * Returns:
+ *      COMPRESS_INFO: Hook info for the mailbox's path
+ *      NULL:          On error
+ */
+static COMPRESS_INFO *
+set_compress_info (CONTEXT *ctx)
+{
+  if (!ctx || !ctx->path)
+    return NULL;
+
+  if (ctx->compress_info)
+    return ctx->compress_info;
+
+  /* Open is compulsory */
+  const char *o = find_hook (MUTT_OPENHOOK,   ctx->path);
+  if (!o)
+    return NULL;
+
+  const char *c = find_hook (MUTT_CLOSEHOOK,  ctx->path);
+  const char *a = find_hook (MUTT_APPENDHOOK, ctx->path);
+
+  COMPRESS_INFO *ci = safe_calloc (1, sizeof (COMPRESS_INFO));
+  ctx->compress_info = ci;
+
+  ci->open   = safe_strdup (o);
+  ci->close  = safe_strdup (c);
+  ci->append = safe_strdup (a);
+
+  return ci;
+}
+
+/**
+ * mutt_free_compress_info - Frees the compress info members and structure.
+ * @ctx: Mailbox to free compress_info for.
+ */
+void
+mutt_free_compress_info (CONTEXT *ctx)
+{
+  COMPRESS_INFO *ci;
+
+  if (!ctx || !ctx->compress_info)
+    return;
+
+  ci = ctx->compress_info;
+  FREE (&ci->open);
+  FREE (&ci->close);
+  FREE (&ci->append);
+
+  unlock_realpath (ctx);
+
+  FREE (&ctx->compress_info);
+}
+
+/**
+ * escape_path - Escapes single quotes in a path for a command string.
+ * @src - the path to escape.
+ *
+ * Returns: a pointer to the escaped string.
+ */
+static char *
+escape_path (char *src)
+{
+  static char dest[HUGE_STRING];
+  char *destp = dest;
+  int destsize = 0;
+
+  if (!src)
+    return NULL;
+
+  while (*src && (destsize < sizeof(dest) - 1))
+  {
+    if (*src != '\'')
+    {
+      *destp++ = *src++;
+      destsize++;
+    }
+    else
+    {
+      /* convert ' into '\'' */
+      if (destsize + 4 < sizeof(dest))
+      {
+        *destp++ = *src++;
+        *destp++ = '\\';
+        *destp++ = '\'';
+        *destp++ = '\'';
+        destsize += 4;
+      }
+      else
+        break;
+    }
+  }
+  *destp = '\0';
+
+  return dest;
+}
+
+/**
+ * cb_format_str - Expand the filenames in the command string
+ * @dest:        Buffer in which to save string
+ * @destlen:     Buffer length
+ * @col:         Starting column, UNUSED
+ * @cols:        Number of screen columns, UNUSED
+ * @op:          printf-like operator, e.g. 't'
+ * @src:         printf-like format string
+ * @fmt:         Field formatting string, UNUSED
+ * @ifstring:    If condition is met, display this string, UNUSED
+ * @elsestring:  Otherwise, display this string, UNUSED
+ * @data:        Pointer to the mailbox CONTEXT
+ * @flags:       Format flags, UNUSED
+ *
+ * cb_format_str is a callback function for mutt_FormatString.  It understands
+ * two operators. '%f' : 'from' filename, '%t' : 'to' filename.
+ *
+ * Returns: src (unchanged)
+ */
+static const char *
+cb_format_str (char *dest, size_t destlen, size_t col, int cols, char op, 
const char *src,
+  const char *fmt, const char *ifstring, const char *elsestring,
+  unsigned long data, format_flag flags)
+{
+  if (!dest || (data == 0))
+    return src;
+
+  CONTEXT *ctx = (CONTEXT *) data;
+
+  switch (op)
+  {
+    case 'f':
+      /* Compressed file */
+      snprintf (dest, destlen, "%s", NONULL (escape_path (ctx->realpath)));
+      break;
+    case 't':
+      /* Plaintext, temporary file */
+      snprintf (dest, destlen, "%s", NONULL (escape_path (ctx->path)));
+      break;
+  }
+  return src;
+}
+
+/**
+ * expand_command_str - Expand placeholders in command string
+ * @ctx:    Mailbox for paths
+ * @buf:    Buffer to store the command
+ * @buflen: Size of the buffer
+ *
+ * This function takes a hook command and expands the filename placeholders
+ * within it.  The function calls mutt_FormatString() to do the replacement
+ * which calls our callback function cb_format_str(). e.g.
+ *
+ * Template command:
+ *      gzip -cd '%f' > '%t'
+ *
+ * Result:
+ *      gzip -dc '~/mail/abc.gz' > '/tmp/xyz'
+ */
+static void
+expand_command_str (const CONTEXT *ctx, const char *cmd, char *buf, int buflen)
+{
+  if (!ctx || !cmd || !buf)
+    return;
+
+  mutt_FormatString (buf, buflen, 0, buflen, cmd, cb_format_str, (unsigned 
long) ctx, 0);
+}
+
+/**
+ * execute_command - Run a system command
+ * @ctx:         Mailbox to work with
+ * @command:     Command string to execute
+ * @progress:    Message to show the user
+ *
+ * Run the supplied command, taking care of all the Mutt requirements,
+ * such as locking files and blocking signals.
+ *
+ * Returns:
+ *      1: Success
+ *      0: Failure
+ */
+static int
+execute_command (CONTEXT *ctx, const char *command, const char *progress)
+{
+  int rc = 1;
+  char sys_cmd[HUGE_STRING];
+
+  if (!ctx || !command || !progress)
+    return 0;
+
+  if (!ctx->quiet)
+    mutt_message (progress, ctx->realpath);
+
+  mutt_block_signals();
+  endwin();
+  fflush (stdout);
+
+  expand_command_str (ctx, command, sys_cmd, sizeof (sys_cmd));
+
+  if (mutt_system (sys_cmd) != 0)
+  {
+    rc = 0;
+    mutt_any_key_to_continue (NULL);
+    mutt_error (_("Error executing: %s\n"), sys_cmd);
+  }
+
+  mutt_unblock_signals();
+
+  return rc;
+}
+
+/**
+ * open_mailbox - Open a compressed mailbox
+ * @ctx: Mailbox to open
+ *
+ * Set up a compressed mailbox to be read.
+ * Decompress the mailbox and set up the paths and hooks needed.
+ * Then determine the type of the mailbox so we can delegate the handling of
+ * messages.
+ */
+static int
+open_mailbox (CONTEXT *ctx)
+{
+  if (!ctx || (ctx->magic != MUTT_COMPRESSED))
+    return -1;
+
+  COMPRESS_INFO *ci = set_compress_info (ctx);
+  if (!ci)
+    return -1;
+
+  /* If there's no close-hook, or the file isn't writable */
+  if (!ci->close || (access (ctx->path, W_OK) != 0))
+    ctx->readonly = 1;
+
+  if (setup_paths (ctx) != 0)
+    goto or_fail;
+  store_size (ctx);
+
+  if (!lock_realpath (ctx, 0))
+  {
+    mutt_error (_("Unable to lock mailbox!"));
+    goto or_fail;
+  }
+
+  int rc = execute_command (ctx, ci->open, _("Decompressing %s"));
+  if (rc == 0)
+    goto or_fail;
+
+  unlock_realpath (ctx);
+
+  ctx->magic = mx_get_magic (ctx->path);
+  if (ctx->magic == 0)
+  {
+    mutt_error (_("Can't identify the contents of the compressed file"));
+    goto or_fail;
+  }
+
+  ci->child_ops = mx_get_ops (ctx->magic);
+  if (!ci->child_ops)
+  {
+    mutt_error (_("Can't find mailbox ops for mailbox type %d"), ctx->magic);
+    goto or_fail;
+  }
+
+  return ci->child_ops->open (ctx);
+
+or_fail:
+  /* remove the partial uncompressed file */
+  remove (ctx->path);
+  mutt_free_compress_info (ctx);
+  return -1;
+}
+
+/**
+ * open_append_mailbox - Open a compressed mailbox for appending
+ * @ctx:   Mailbox to open
+ * @flags: e.g. Does the file already exist?
+ *
+ * To append to a compressed mailbox we need an append-hook (or both open- and
+ * close-hooks).
+ *
+ * Returns:
+ *       0: Success
+ *      -1: Failure
+ */
+static int
+open_append_mailbox (CONTEXT *ctx, int flags)
+{
+  if (!ctx)
+    return -1;
+
+  /* If this succeeds, we know there's an open-hook */
+  COMPRESS_INFO *ci = set_compress_info (ctx);
+  if (!ci)
+    return -1;
+
+  /* To append we need an append-hook or a close-hook */
+  if (!ci->append && !ci->close)
+  {
+    mutt_error (_("Cannot append without an append-hook or close-hook : %s"), 
ctx->path);
+    goto oa_fail1;
+  }
+
+  if (setup_paths (ctx) != 0)
+    goto oa_fail2;
+
+  /* Lock the realpath for the duration of the append.
+   * It will be unlocked in the close */
+  if (!lock_realpath (ctx, 1))
+  {
+    mutt_error (_("Unable to lock mailbox!"));
+    goto oa_fail2;
+  }
+
+  /* Open the existing mailbox, unless we are appending */
+  if (!ci->append && (get_size (ctx->realpath) > 0))
+  {
+    int rc = execute_command (ctx, ci->open, _("Decompressing %s"));
+    if (rc == 0)
+    {
+      mutt_error (_("Compress command failed: %s"), ci->open);
+      goto oa_fail2;
+    }
+    ctx->magic = mx_get_magic (ctx->path);
+  }
+  else
+    ctx->magic = DefaultMagic;
+
+  /* We can only deal with mbox and mmdf mailboxes */
+  if ((ctx->magic != MUTT_MBOX) && (ctx->magic != MUTT_MMDF))
+  {
+    mutt_error (_("Unsupported mailbox type for appending."));
+    goto oa_fail2;
+  }
+
+  ci->child_ops = mx_get_ops (ctx->magic);
+  if (!ci->child_ops)
+  {
+    mutt_error (_("Can't find mailbox ops for mailbox type %d"), ctx->magic);
+    goto oa_fail2;
+  }
+
+  if (ci->child_ops->open_append (ctx, flags) != 0)
+    goto oa_fail2;
+
+  return 0;
+
+oa_fail2:
+  /* remove the partial uncompressed file */
+  remove (ctx->path);
+oa_fail1:
+  /* Free the compress_info to prevent close from trying to recompress */
+  mutt_free_compress_info (ctx);
+
+  return -1;
+}
+
+/**
+ * close_mailbox - Close a compressed mailbox
+ * @ctx: Mailbox to close
+ *
+ * If the mailbox has been changed then re-compress the tmp file.
+ * Then delete the tmp file.
+ *
+ * Returns:
+ *       0: Success
+ *      -1: Failure
+ */
+static int
+close_mailbox (CONTEXT *ctx)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  ops->close (ctx);
+
+  /* sync has already been called, so we only need to delete some files */
+  if (!ctx->append)
+  {
+    /* If the file was removed, remove the compressed folder too */
+    if ((access (ctx->path, F_OK) != 0) && !option (OPTSAVEEMPTY))
+    {
+      remove (ctx->realpath);
+    }
+    else
+    {
+      remove (ctx->path);
+    }
+
+    return 0;
+  }
+
+  const char *append;
+  const char *msg;
+
+  /* The file exists and we can append */
+  if ((access (ctx->realpath, F_OK) == 0) && ci->append)
+  {
+    append = ci->append;
+    msg = _("Compressed-appending to %s...");
+  }
+  else
+  {
+    append = ci->close;
+    msg = _("Compressing %s...");
+  }
+
+  int rc = execute_command (ctx, append, msg);
+  if (rc == 0)
+  {
+    mutt_any_key_to_continue (NULL);
+    mutt_error (_(" %s: Error compressing mailbox!  Uncompressed one 
kept!\n"), ctx->path);
+  }
+  else
+    remove (ctx->path);
+
+  unlock_realpath (ctx);
+
+  return 0;
+}
+
+/**
+ * check_mailbox - Check for changes in the compressed file
+ * @ctx: Mailbox
+ *
+ * If the compressed file changes in size but the mailbox hasn't been changed
+ * in Mutt, then we can close and reopen the mailbox.
+ *
+ * If the mailbox has been changed in Mutt, warn the user.
+ *
+ * The return codes are picked to match mx_check_mailbox().
+ *
+ * Returns:
+ *      0:              Mailbox OK
+ *      MUTT_REOPENED:  The mailbox was closed and reopened
+ *      -1:             Mailbox bad
+ */
+static int
+check_mailbox (CONTEXT *ctx, int *index_hint)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  int size = get_size (ctx->realpath);
+  if (size == ci->size)
+    return 0;
+
+  if (!lock_realpath (ctx, 0))
+  {
+    mutt_error (_("Unable to lock mailbox!"));
+    return -1;
+  }
+
+  int rc = execute_command (ctx, ci->open, _("Decompressing %s"));
+  store_size (ctx);
+  unlock_realpath (ctx);
+  if (rc == 0)
+    return -1;
+
+  return ops->check (ctx, index_hint);
+}
+
+
+/**
+ * open_message - Delegated to mbox handler
+ */
+static int
+open_message (CONTEXT *ctx,  MESSAGE *msg, int msgno)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  /* Delegate */
+  return ops->open_msg (ctx, msg, msgno);
+}
+
+/**
+ * close_message - Delegated to mbox handler
+ */
+static int
+close_message (CONTEXT *ctx, MESSAGE *msg)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  /* Delegate */
+  return ops->close_msg (ctx, msg);
+}
+
+/**
+ * commit_message - Delegated to mbox handler
+ */
+static int
+commit_message (CONTEXT *ctx, MESSAGE *msg)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  /* Delegate */
+  return ops->commit_msg (ctx, msg);
+}
+
+/**
+ * open_new_message - Delegated to mbox handler
+ */
+static int
+open_new_message (MESSAGE *msg, CONTEXT *ctx, HEADER *hdr)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  /* Delegate */
+  return ops->open_new_msg (msg, ctx, hdr);
+}
+
+
+/**
+ * mutt_comp_can_append - Can we append to this path?
+ * @path: pathname of file to be tested
+ *
+ * To append to a file we can either use an 'append-hook' or a combination of
+ * 'open-hook' and 'close-hook'.
+ *
+ * A match means it's our responsibility to append to the file.
+ *
+ * Returns:
+ *      1: Yes, we can append to the file
+ *      0: No, appending isn't possible
+ */
+int
+mutt_comp_can_append (CONTEXT *ctx)
+{
+  if (!ctx)
+    return 0;
+
+  /* If this succeeds, we know there's an open-hook */
+  COMPRESS_INFO *ci = set_compress_info (ctx);
+  if (!ci)
+    return 0;
+
+  /* We have an open-hook, so to append we need an append-hook,
+   * or a close-hook. */
+  if (ci->append || ci->close)
+    return 1;
+
+  mutt_error (_("Cannot append without an append-hook or close-hook : %s"), 
ctx->path);
+  return 0;
+}
+
+/**
+ * mutt_comp_can_read - Can we read from this file?
+ * @path: Pathname of file to be tested
+ *
+ * Search for an 'open-hook' with a regex that matches the path.
+ *
+ * A match means it's our responsibility to open the file.
+ *
+ * Returns:
+ *      1: Yes, we can read the file
+ *      0: No, we cannot read the file
+ */
+int
+mutt_comp_can_read (const char *path)
+{
+  if (!path)
+    return 0;
+
+  if (find_hook (MUTT_OPENHOOK, path))
+    return 1;
+  else
+    return 0;
+}
+
+/**
+ * sync_mailbox - Save changes to the compressed mailbox file
+ * @ctx: Mailbox to sync
+ *
+ * Changes in Mutt only affect the tmp file.  Calling sync_mailbox()
+ * will commit them to the compressed file.
+ *
+ * Returns:
+ *       0: Success
+ *      -1: Failure
+ */
+static int
+sync_mailbox (CONTEXT *ctx, int *index_hint)
+{
+  if (!ctx)
+    return -1;
+
+  COMPRESS_INFO *ci = ctx->compress_info;
+  if (!ci)
+    return -1;
+
+  if (!ci->close)
+  {
+    mutt_error (_("Can't sync a compressed file without a close-hook"));
+    return -1;
+  }
+
+  struct mx_ops *ops = ci->child_ops;
+  if (!ops)
+    return -1;
+
+  if (!lock_realpath (ctx, 1))
+  {
+    mutt_error (_("Unable to lock mailbox!"));
+    return -1;
+  }
+
+  int rc = check_mailbox (ctx, index_hint);
+  if (rc != 0)
+    goto sync_cleanup;
+
+  rc = ops->sync (ctx, index_hint);
+  if (rc != 0)
+    goto sync_cleanup;
+
+  rc = execute_command (ctx, ci->close, _("Compressing %s"));
+  if (rc == 0)
+  {
+    rc = -1;
+    goto sync_cleanup;
+  }
+
+  rc = 0;
+
+sync_cleanup:
+  store_size (ctx);
+  unlock_realpath (ctx);
+  return rc;
+}
+
+/**
+ * mutt_comp_valid_command - Is this command string allowed?
+ * @cmd:  Command string
+ *

Reply via email to