--- /dev/null
+/* Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
+ * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
+ *
+ * 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"
+
+/* 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 */
+} COMPRESS_INFO;
+
+
+/**
+ * lock_mailbox - Try to lock a mailbox (exclusively)
+ * @ctx: Mailbox to lock
+ * @fp: File pointer to the mailbox file
+ * @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_mailbox (CONTEXT *ctx, FILE *fp, int excl)
+{
+ if (!ctx || !fp)
+ return 0;
+
+ int r = mx_lock_file (ctx->realpath, fileno (fp), excl, 1, 1);
+
+ if (r == 0)
+ {
+ ctx->locked = 1;
+ }
+ else if (excl == 0)
+ {
+ ctx->readonly = 1;
+ return 1;
+ }
+
+ return (r == 0);
+}
+
+/**
+ * unlock_mailbox - Unlock a mailbox
+ * @ctx: Mailbox to unlock
+ * @fp: File pointer to mailbox file
+ *
+ * Unlock a mailbox previously locked by lock_mailbox().
+ */
+static void
+unlock_mailbox (CONTEXT *ctx, FILE *fp)
+{
+ if (!ctx || !fp)
+ return;
+
+ if (!ctx->locked)
+ return;
+
+ fflush (fp);
+
+ mx_unlock_file (ctx->realpath, fileno (fp), 1);
+ ctx->locked = 0;
+}
+
+/**
+ * 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.
+ *
+ * Note: The temporary file is NOT created.
+ * Note: ctx->path will be freed by restore_path()
+ */
+static void
+setup_paths (CONTEXT *ctx)
+{
+ if (!ctx)
+ return;
+
+ char tmppath[_POSIX_PATH_MAX];
+
+ /* Setup the right paths */
+ ctx->realpath = ctx->path;
+
+ /* We will uncompress to /tmp */
+ mutt_mktemp (tmppath, sizeof (tmppath));
+ ctx->path = safe_strdup (tmppath);
+}
+
+/**
+ * restore_path - Put back the original mailbox name
+ * @ctx: Mailbox to modify
+ *
+ * When we use a compressed mailbox, we change the CONTEXT to refer to the
+ * uncompressed file. We store the original name in ctx->realpath.
+ * ctx->path = "/tmp/mailbox"
+ * ctx->realpath = "mailbox.gz"
+ *
+ * When we have finished with a compressed mailbox, we put back the original
+ * name.
+ * ctx->path = "mailbox.gz"
+ * ctx->realpath = NULL
+ */
+static void
+restore_path (CONTEXT *ctx)
+{
+ if (!ctx)
+ return;
+
+ FREE(&ctx->path);
+ ctx->path = ctx->realpath;
+ ctx->realpath = NULL;
+}
+
+/**
+ * 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.
+ *
+ * Note: Caller must free the COMPRESS_INFO when done.
+ *
+ * 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 = o;
+ ci->close = c;
+ ci->append = a;
+
+ return ci;
+}
+
+/**
+ * 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", ctx->realpath);
+ break;
+ case 't':
+ /* Plaintext, temporary file */
+ snprintf (dest, destlen, "%s", 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
+ * @create_file: Should the tmp file be created?
+ * @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, int create_file, const char *progress)
+{
+ if (!ctx || !command || !progress)
+ return 0;
+
+ if (!ctx->quiet)
+ mutt_message (progress, ctx->realpath);
+
+ FILE *fp;
+ if (create_file)
+ fp = fopen (ctx->realpath, "a");
+ else
+ fp = fopen (ctx->realpath, "r");
+
+ if (!fp)
+ {
+ mutt_perror (ctx->realpath);
+ return 0;
+ }
+
+ mutt_block_signals();
+ /* If we're creating the file, lock it exclusively */
+ if (!lock_mailbox (ctx, fp, create_file))
+ {
+ safe_fclose (&fp);
+ mutt_unblock_signals();
+ mutt_error (_("Unable to lock mailbox!"));
+ return 0;
+ }
+
+ endwin();
+ fflush (stdout);
+
+ char sys_cmd[HUGE_STRING];
+
+ expand_command_str (ctx, command, sys_cmd, sizeof (sys_cmd));
+
+ int rc = mutt_system (sys_cmd);
+ if (rc != 0)
+ {
+ mutt_any_key_to_continue (NULL);
+ mutt_error (_("Error executing: %s\n"), sys_cmd);
+ }
+
+ unlock_mailbox (ctx, fp);
+ mutt_unblock_signals();
+ safe_fclose (&fp);
+
+ return 1;
+}
+
+/**
+ * open_read - Open a compressed mailbox for reading
+ * @ctx: Mailbox to open
+ *
+ * Decompress the mailbox and set up the paths and hooks needed.
+ *
+ * Note: The message handling will be delegated to the mbox code.
+ *
+ * Returns:
+ * 1: Success
+ * 0: Failure
+ */
+static int
+open_read (CONTEXT *ctx)
+{
+ if (!ctx)
+ return 0;
+
+ COMPRESS_INFO *ci = set_compress_info (ctx);
+ if (!ci)
+ {
+ ctx->magic = 0;
+ return 0;
+ }
+
+ /* If there's no close-hook, or the file isn't writable */
+ if (!ci->close || (access (ctx->path, W_OK) != 0))
+ ctx->readonly = 1;
+
+ setup_paths (ctx);
+ store_size (ctx);
+
+ int rc = execute_command (ctx, ci->open, 0, _("Decompressing %s"));
+ if (rc == 0)
+ {
+ goto or_fail;
+ }
+
+ 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 1;
+
+or_fail:
+ /* remove the partial uncompressed file */
+ remove (ctx->path);
+ restore_path (ctx);
+ return 0;
+}
+
+
+struct mx_ops mx_comp_ops;
+
+/**
+ * open_mailbox - Open a compressed mailbox
+ * @ctx: Mailbox to open
+ *
+ * Set up a compressed mailbox to be read.
+ * First call open_read() to decompress the file.
+ * 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;
+
+ if (!open_read (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 (ctx);
+}
+
+/**
+ * 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)
+ {
+ FREE(&ctx->compress_info);
+ mutt_error (_("Cannot append without an append-hook or close-hook : %s"), ctx->path);
+ return -1;
+ }
+
+ ctx->magic = DefaultMagic;
+ /* We can only deal with mbox and mmdf mailboxes */
+ if ((ctx->magic != MUTT_MBOX) && (ctx->magic != MUTT_MMDF))
+ return -1;
+
+ setup_paths (ctx);
+
+ ctx->mx_ops = &mx_comp_ops;
+ 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);
+ return -1;
+ }
+
+ if (ci->append)
+ {
+ /* Create an empty temporary file */
+ ctx->fp = safe_fopen (ctx->path, "w");
+ if (!ctx->fp)
+ {
+ mutt_perror (ctx->path);
+ goto oa_fail;
+ }
+ }
+ else
+ {
+ /* Open the existing mailbox */
+ int rc = execute_command (ctx, ci->open, 0, _("Decompressing %s"));
+ if (rc == 0)
+ {
+ mutt_error (_("Compress command failed: %s"), ci->open);
+ goto oa_fail;
+ }
+ ctx->fp = safe_fopen (ctx->path, "a");
+ if (!ctx->fp)
+ {
+ mutt_perror (ctx->path);
+ goto oa_fail;
+ }
+ }
+
+ return 0;
+
+oa_fail:
+ /* remove the partial uncompressed file */
+ remove (ctx->path);
+ restore_path (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;
+
+ safe_fclose (&ctx->fp);
+
+ /* 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);
+ }
+
+ restore_path (ctx);
+ FREE(&ctx->compress_info);
+ 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, 1, msg);
+ if (rc == 0)
+ {
+ mutt_any_key_to_continue (NULL);
+ mutt_error (_(" %s: Error compressing mailbox! Uncompressed one kept!\n"), ctx->path);
+ }
+
+ remove (ctx->path);
+ restore_path (ctx);
+ FREE(&ctx->compress_info);
+
+ 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;
+
+ int size = get_size (ctx->realpath);
+ if (size == ci->size)
+ return 0;
+
+ if (ctx->changed)
+ {
+ FREE(&ctx->compress_info);
+ restore_path (ctx);
+ mutt_error (_("Mailbox was corrupted!"));
+ return -1;
+ }
+
+ close_mailbox (ctx);
+
+ const char *path = ctx->path;
+ ctx->path = NULL;
+
+ mx_open_mailbox (path, 0, ctx);
+ FREE(&path);
+
+ return MUTT_REOPENED;
+}
+
+
+/**
+ * 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);
+}
+
+
+/**
+ * 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
+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;
+}
+
+/**
+ * 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
+comp_can_read (const char *path)
+{
+ if (!path)
+ return 0;
+
+ if (find_hook (MUTT_OPENHOOK, path))
+ return 1;
+ else
+ return 0;
+}
+
+/**
+ * comp_sync - Save changes to the compressed mailbox file
+ * @ctx: Mailbox to sync
+ *
+ * Changes in Mutt only affect the tmp file. Calling comp_sync() will commit
+ * them to the compressed file.
+ *
+ * Returns:
+ * 0: Success
+ * -1: Failure
+ */
+int
+comp_sync (CONTEXT *ctx)
+{
+ 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;
+ }
+
+ int rc = execute_command (ctx, ci->close, 1, _("Compressing %s"));
+ if (rc == 0)
+ return -1;
+
+ store_size (ctx);
+
+ return 0;
+}
+
+/**
+ * comp_valid_command - Is this command string allowed?
+ * @cmd: Command string
+ *
+ * A valid command string must have both "%f" (from file) and "%t" (to file).
+ * We don't check if we can actually run the command.
+ *
+ * Returns:
+ * 1: Valid command
+ * 0: "%f" and/or "%t" is missing
+ */
+int
+comp_valid_command (const char *cmd)
+{
+ if (!cmd)
+ return 0;
+
+ return (strstr (cmd, "%f") && strstr (cmd, "%t"));
+}
+
+
+/**
+ * mx_comp_ops - Mailbox callback functions
+ *
+ * Compress only uses open, close and check.
+ * The message functions are delegated to mbox.
+ */
+struct mx_ops mx_comp_ops =
+{
+ .open = open_mailbox,
+ .open_append = open_append_mailbox,
+ .close = close_mailbox,
+ .check = check_mailbox,
+ .open_msg = open_message,
+ .close_msg = close_message,
+ .commit_msg = commit_message,
+ .open_new_msg = open_new_message
+};
+
</sect2>
</sect1>
+<sect1 id="compress">
+ <title>Compressed Folders Feature</title>
+ <subtitle>Read from/write to compressed mailboxes</subtitle>
+
+ <sect2 id="compress-intro">
+ <title>Introduction</title>
+
+ <para>
+ The Compressed Folder patch allows Mutt to read mailbox files that are
+ compressed. But it isn't limited to compressed files. It works well
+ with encrypted files, too. In fact, if you can create a program/script
+ to convert to and from your format, then Mutt can read it.
+ </para>
+
+ <para>
+ The patch adds three hooks to Mutt: <literal>open-hook</literal>,
+ <literal>close-hook</literal> and <literal>append-hook</literal>. They
+ define commands to: uncompress a file; compress a file; append
+ messages to an already compressed file.
+ </para>
+
+ <para>
+ There are some examples of both compressed and encrypted files,
+ later. For now, the documentation will just concentrate on
+ compressed files.
+ </para>
+
+ </sect2>
+
+ <sect2 id="compress-commands">
+ <title>Commands</title>
+ <cmdsynopsis>
+ <command>open-hook</command>
+ <arg choice="plain">
+ <replaceable class="parameter">pattern</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">shell-command</replaceable>
+ </arg>
+ <command>close-hook</command>
+ <arg choice="plain">
+ <replaceable class="parameter">pattern</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">shell-command</replaceable>
+ </arg>
+ <command>append-hook</command>
+ <arg choice="plain">
+ <replaceable class="parameter">pattern</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">shell-command</replaceable>
+ </arg>
+ </cmdsynopsis>
+
+ <para>
+ The shell-command must contain two placeholders for filenames:
+ <literal>%f</literal> and <literal>%t</literal>. These represent
+ <quote>from</quote> and <quote>to</quote> filenames. It's a good idea to
+ put quotes around these placeholders.
+ </para>
+
+ <para>
+ If you need the exact string <quote>%f</quote> or <quote>%t</quote> in your
+ command, simply double up the <quote>%</quote> character, e.g.
+ <quote>%%f</quote> or <quote>%%t</quote>.
+ </para>
+
+ <table id="table-compress-optional">
+ <title>Not all Hooks are Required</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Open</entry>
+ <entry>Close</entry>
+ <entry>Append</entry>
+ <entry>Effect</entry>
+ <entry>Useful if</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Open</entry>
+ <entry>-</entry>
+ <entry>-</entry>
+ <entry>Folder is readonly</entry>
+ <entry>The folder is just a backup</entry>
+ </row>
+ <row>
+ <entry>Open</entry>
+ <entry>Close</entry>
+ <entry>-</entry>
+ <entry>Folder is read/write, but the entire folder must be
+ written if anything is changed</entry>
+ <entry>Your compression format doesn't support appending</entry>
+ </row>
+ <row>
+ <entry>Open</entry>
+ <entry>Close</entry>
+ <entry>Append</entry>
+ <entry>Folder is read/write and emails can be efficiently added
+ to the end</entry>
+ <entry>Your compression format supports appending</entry>
+ </row>
+ <row>
+ <entry>Open</entry>
+ <entry>-</entry>
+ <entry>Append</entry>
+ <entry>Folder is readonly, but can be appended to</entry>
+ <entry>You want to store emails, but never change them</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <note>
+ <para>The command:</para>
+ <itemizedlist>
+ <listitem><para>should return a non-zero exit status on failure</para></listitem>
+ <listitem><para>should not delete any files</para></listitem>
+ </itemizedlist>
+ </note>
+
+ <sect3 id="open-hook">
+ <title>Read from compressed mailbox</title>
+
+ <screen>open-hook regexp shell-command</screen>
+
+ <para>
+ If Mutt is unable to open a file, it then looks for
+ <literal>open-hook</literal> that matches the filename.
+ </para>
+
+ <para>
+ If your compression program doesn't have a well-defined extension,
+ then you can use <literal>.</literal> as the regexp.
+ </para>
+
+ <sect4 id="compress-open-hook-example">
+ <title>Example of open-hook</title>
+
+ <screen>open-hook '\.gz$' "gzip -cd '%f' > '%t'"</screen>
+
+ <itemizedlist>
+ <listitem><para>Mutt finds a file, <quote>example.gz</quote>,
+ that it can't read</para></listitem>
+ <listitem><para>Mutt has an <literal>open-hook</literal>
+ whose regexp matches the filename:
+ <literal>\.gz$</literal></para></listitem>
+ <listitem><para>Mutt uses the command <literal>gzip -cd</literal>
+ to create a temporary file that it <emphasis>can</emphasis>
+ read</para></listitem>
+ </itemizedlist>
+ </sect4>
+ </sect3>
+
+ <sect3 id="close-hook">
+ <title>Write to a compressed mailbox</title>
+
+ <screen>close-hook regexp shell-command</screen>
+
+ <para>
+ When Mutt has finished with a compressed mail folder, it will look
+ for a matching <literal>close-hook</literal> to recompress the file.
+ This hook is <link linkend="table-compress-optional">optional</link>.
+ </para>
+
+ <note>
+ <para>
+ If the folder has not been modified, the
+ <literal>close-hook</literal> will not be called.
+ </para>
+ </note>
+
+ <sect4 id="compress-close-hook-example">
+ <title>Example of close-hook</title>
+
+ <screen>close-hook '\.gz$' "gzip -c '%t' > '%f'"</screen>
+
+ <itemizedlist>
+ <listitem><para>Mutt has finished with a folder, <quote>example.gz</quote>,
+ that it opened with <literal>open-hook</literal></para></listitem>
+ <listitem><para>The folder has been modified</para></listitem>
+ <listitem><para>Mutt has a <literal>close-hook</literal> whose regexp
+ matches the filename: <literal>\.gz$</literal></para></listitem>
+ <listitem><para>Mutt uses the command <literal>gzip -c</literal>
+ to create a new compressed file</para></listitem>
+ </itemizedlist>
+ </sect4>
+ </sect3>
+
+ <sect3 id="append-hook">
+ <title>Append to a compressed mailbox</title>
+
+ <screen>append-hook regexp shell-command</screen>
+
+ <para>
+ When Mutt wants to append an email to a compressed mail folder, it
+ will look for a matching <literal>append-hook</literal>.
+ This hook is <link linkend="table-compress-optional">optional</link>.
+ </para>
+
+ <para>
+ Using the <literal>append-hook</literal> will save time, but
+ Mutt won't be able to determine the type of the mail folder
+ inside the compressed file.
+ </para>
+
+ <para>
+ Mutt will <emphasis>assume</emphasis> the type to be that of
+ the <literal>$mbox_type</literal> variable. Mutt also uses
+ this type for temporary files.
+ </para>
+
+ <para>
+ Mutt will only use the <literal>append-hook</literal> for existing files.
+ The <literal>close-hook</literal> will be used for empty, or missing files.
+ </para>
+
+ <note>
+ <para>
+ If your command writes to stdout, it is vital that you use
+ <literal>>></literal> in the <quote>append-hook</quote>.
+ If not, data will be lost.
+ </para>
+ </note>
+
+ <sect4 id="compress-append-hook-example">
+ <title>Example of append-hook</title>
+
+ <screen>append-hook '\.gz$' "gzip -c '%t' >> '%f'"</screen>
+
+ <itemizedlist>
+ <listitem><para>Mutt wants to append an email to a folder, <quote>example.gz</quote>,
+ that it opened with <literal>open-hook</literal></para></listitem>
+ <listitem><para>Mutt has an <literal>append-hook</literal> whose regexp matches
+ the filename: <literal>\.gz$</literal></para></listitem>
+ <listitem><para>Mutt knows the mailbox type from the <literal>$mbox</literal>
+ variable</para></listitem>
+ <listitem><para>Mutt uses the command <literal>gzip -c</literal>
+ to append to an existing compressed file</para></listitem>
+ </itemizedlist>
+ </sect4>
+
+ </sect3>
+
+ <sect3 id="compress-empty">
+ <title>Empty Files</title>
+
+ <para>
+ Mutt assumes that an empty file is not compressed. In this
+ situation, unset <link linkend="save-empty">$save_empty</link>, so
+ that the compressed file will be removed if you delete all of the
+ messages.
+ </para>
+ </sect3>
+
+ <sect3 id="compress-security">
+ <title>Security</title>
+
+ <para>
+ Encrypted files are decrypted into temporary files which are
+ stored in the <link linkend="tmpdir">$tmpdir</link> directory.
+ This could be a security risk.
+ </para>
+ </sect3>
+ </sect2>
+
+ <sect2 id="compress-known-bugs">
+ <title>Known Bugs</title>
+
+ <itemizedlist>
+ <listitem><para>The Compressed Folder hooks cannot deal with filenames that contains quotes/apostrophes.</para></listitem>
+ </itemizedlist>
+ </sect2>
+</sect1>
</chapter>
<chapter id="security">