mutt stable branch with some hacks
1/* Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
2 * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#if HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include <errno.h>
24#include <string.h>
25#include <sys/stat.h>
26#include <unistd.h>
27
28#include "mutt.h"
29#include "mailbox.h"
30#include "mutt_curses.h"
31#include "mx.h"
32#include "compress.h"
33
34/* Notes:
35 * Any references to compressed files also apply to encrypted files.
36 * ctx->path == plaintext file
37 * ctx->realpath == compressed file
38 */
39
40/**
41 * struct COMPRESS_INFO - Private data for compress
42 *
43 * This object gets attached to the mailbox's CONTEXT.
44 */
45typedef struct
46{
47 const char *append; /* append-hook command */
48 const char *close; /* close-hook command */
49 const char *open; /* open-hook command */
50 off_t size; /* size of the compressed file */
51 struct mx_ops *child_ops; /* callbacks of de-compressed file */
52 int locked; /* if realpath is locked */
53 FILE *lockfp; /* fp used for locking */
54} COMPRESS_INFO;
55
56
57/**
58 * lock_realpath - Try to lock the ctx->realpath
59 * @ctx: Mailbox to lock
60 * @excl: Lock exclusively?
61 *
62 * Try to (exclusively) lock the mailbox. If we succeed, then we mark the
63 * mailbox as locked. If we fail, but we didn't want exclusive rights, then
64 * the mailbox will be marked readonly.
65 *
66 * Returns:
67 * 1: Success (locked or readonly)
68 * 0: Error (can't lock the file)
69 */
70static int
71lock_realpath (CONTEXT *ctx, int excl)
72{
73 if (!ctx)
74 return 0;
75
76 COMPRESS_INFO *ci = ctx->compress_info;
77 if (!ci)
78 return 0;
79
80 if (ci->locked)
81 return 1;
82
83 if (excl)
84 ci->lockfp = fopen (ctx->realpath, "a");
85 else
86 ci->lockfp = fopen (ctx->realpath, "r");
87 if (!ci->lockfp)
88 {
89 mutt_perror (ctx->realpath);
90 return 0;
91 }
92
93 int r = mx_lock_file (ctx->realpath, fileno (ci->lockfp), excl, 1, 1);
94
95 if (r == 0)
96 ci->locked = 1;
97 else if (excl == 0)
98 {
99 safe_fclose (&ci->lockfp);
100 ctx->readonly = 1;
101 return 1;
102 }
103
104 return (r == 0);
105}
106
107/**
108 * unlock_realpath - Unlock the ctx->realpath
109 * @ctx: Mailbox to unlock
110 *
111 * Unlock a mailbox previously locked by lock_mailbox().
112 */
113static void
114unlock_realpath (CONTEXT *ctx)
115{
116 if (!ctx)
117 return;
118
119 COMPRESS_INFO *ci = ctx->compress_info;
120 if (!ci)
121 return;
122
123 if (!ci->locked)
124 return;
125
126 mx_unlock_file (ctx->realpath, fileno (ci->lockfp), 1);
127
128 ci->locked = 0;
129 safe_fclose (&ci->lockfp);
130}
131
132/**
133 * setup_paths - Set the mailbox paths
134 * @ctx: Mailbox to modify
135 *
136 * Save the compressed filename in ctx->realpath.
137 * Create a temporary filename and put its name in ctx->path.
138 * The temporary file is created to prevent symlink attacks.
139 *
140 * Returns:
141 * 0: Success
142 * -1: Error
143 */
144static int
145setup_paths (CONTEXT *ctx)
146{
147 if (!ctx)
148 return -1;
149
150 char tmppath[_POSIX_PATH_MAX];
151 FILE *tmpfp;
152
153 /* Setup the right paths */
154 FREE(&ctx->realpath);
155 ctx->realpath = ctx->path;
156
157 /* We will uncompress to /tmp */
158 mutt_mktemp (tmppath, sizeof (tmppath));
159 ctx->path = safe_strdup (tmppath);
160
161 if ((tmpfp = safe_fopen (ctx->path, "w")) == NULL)
162 return -1;
163
164 safe_fclose (&tmpfp);
165 return 0;
166}
167
168/**
169 * get_size - Get the size of a file
170 * @path: File to measure
171 *
172 * Returns:
173 * number: Size in bytes
174 * 0: On error
175 */
176static int
177get_size (const char *path)
178{
179 if (!path)
180 return 0;
181
182 struct stat sb;
183 if (stat (path, &sb) != 0)
184 return 0;
185
186 return sb.st_size;
187}
188
189/**
190 * store_size - Save the size of the compressed file
191 * @ctx: Mailbox
192 *
193 * Save the compressed file size in the compress_info struct.
194 */
195static void
196store_size (const CONTEXT *ctx)
197{
198 if (!ctx)
199 return;
200
201 COMPRESS_INFO *ci = ctx->compress_info;
202 if (!ci)
203 return;
204
205 ci->size = get_size (ctx->realpath);
206}
207
208/**
209 * find_hook - Find a hook to match a path
210 * @type: Type of hook, e.g. MUTT_CLOSEHOOK
211 * @path: Filename to test
212 *
213 * Each hook has a type and a pattern.
214 * Find a command that matches the type and path supplied. e.g.
215 *
216 * User config:
217 * open-hook '\.gz$' "gzip -cd '%f' > '%t'"
218 *
219 * Call:
220 * find_hook (MUTT_OPENHOOK, "myfile.gz");
221 *
222 * Returns:
223 * string: Matching hook command
224 * NULL: No matches
225 */
226static const char *
227find_hook (int type, const char *path)
228{
229 if (!path)
230 return NULL;
231
232 const char *c = mutt_find_hook (type, path);
233 if (!c || !*c)
234 return NULL;
235
236 return c;
237}
238
239/**
240 * set_compress_info - Find the compress hooks for a mailbox
241 * @ctx: Mailbox to examine
242 *
243 * When a mailbox is opened, we check if there are any matching hooks.
244 *
245 * Returns:
246 * COMPRESS_INFO: Hook info for the mailbox's path
247 * NULL: On error
248 */
249static COMPRESS_INFO *
250set_compress_info (CONTEXT *ctx)
251{
252 if (!ctx || !ctx->path)
253 return NULL;
254
255 if (ctx->compress_info)
256 return ctx->compress_info;
257
258 /* Open is compulsory */
259 const char *o = find_hook (MUTT_OPENHOOK, ctx->path);
260 if (!o)
261 return NULL;
262
263 const char *c = find_hook (MUTT_CLOSEHOOK, ctx->path);
264 const char *a = find_hook (MUTT_APPENDHOOK, ctx->path);
265
266 COMPRESS_INFO *ci = safe_calloc (1, sizeof (COMPRESS_INFO));
267 ctx->compress_info = ci;
268
269 ci->open = safe_strdup (o);
270 ci->close = safe_strdup (c);
271 ci->append = safe_strdup (a);
272
273 return ci;
274}
275
276/**
277 * mutt_free_compress_info - Frees the compress info members and structure.
278 * @ctx: Mailbox to free compress_info for.
279 */
280void
281mutt_free_compress_info (CONTEXT *ctx)
282{
283 COMPRESS_INFO *ci;
284
285 if (!ctx || !ctx->compress_info)
286 return;
287
288 ci = ctx->compress_info;
289 FREE (&ci->open);
290 FREE (&ci->close);
291 FREE (&ci->append);
292
293 unlock_realpath (ctx);
294
295 FREE (&ctx->compress_info);
296}
297
298/**
299 * escape_path - Escapes single quotes in a path for a command string.
300 * @src - the path to escape.
301 *
302 * Returns: a pointer to the escaped string.
303 */
304static char *
305escape_path (char *src)
306{
307 static char dest[HUGE_STRING];
308 char *destp = dest;
309 int destsize = 0;
310
311 if (!src)
312 return NULL;
313
314 while (*src && (destsize < sizeof(dest) - 1))
315 {
316 if (*src != '\'')
317 {
318 *destp++ = *src++;
319 destsize++;
320 }
321 else
322 {
323 /* convert ' into '\'' */
324 if (destsize + 4 < sizeof(dest))
325 {
326 *destp++ = *src++;
327 *destp++ = '\\';
328 *destp++ = '\'';
329 *destp++ = '\'';
330 destsize += 4;
331 }
332 else
333 break;
334 }
335 }
336 *destp = '\0';
337
338 return dest;
339}
340
341/**
342 * cb_format_str - Expand the filenames in the command string
343 * @dest: Buffer in which to save string
344 * @destlen: Buffer length
345 * @col: Starting column, UNUSED
346 * @cols: Number of screen columns, UNUSED
347 * @op: printf-like operator, e.g. 't'
348 * @src: printf-like format string
349 * @fmt: Field formatting string, UNUSED
350 * @ifstring: If condition is met, display this string, UNUSED
351 * @elsestring: Otherwise, display this string, UNUSED
352 * @data: Pointer to the mailbox CONTEXT
353 * @flags: Format flags, UNUSED
354 *
355 * cb_format_str is a callback function for mutt_FormatString. It understands
356 * two operators. '%f' : 'from' filename, '%t' : 'to' filename.
357 *
358 * Returns: src (unchanged)
359 */
360static const char *
361cb_format_str (char *dest, size_t destlen, size_t col, int cols, char op, const char *src,
362 const char *fmt, const char *ifstring, const char *elsestring,
363 unsigned long data, format_flag flags)
364{
365 if (!dest || (data == 0))
366 return src;
367
368 CONTEXT *ctx = (CONTEXT *) data;
369
370 switch (op)
371 {
372 case 'f':
373 /* Compressed file */
374 snprintf (dest, destlen, "%s", NONULL (escape_path (ctx->realpath)));
375 break;
376 case 't':
377 /* Plaintext, temporary file */
378 snprintf (dest, destlen, "%s", NONULL (escape_path (ctx->path)));
379 break;
380 }
381 return src;
382}
383
384/**
385 * expand_command_str - Expand placeholders in command string
386 * @ctx: Mailbox for paths
387 * @buf: Buffer to store the command
388 * @buflen: Size of the buffer
389 *
390 * This function takes a hook command and expands the filename placeholders
391 * within it. The function calls mutt_FormatString() to do the replacement
392 * which calls our callback function cb_format_str(). e.g.
393 *
394 * Template command:
395 * gzip -cd '%f' > '%t'
396 *
397 * Result:
398 * gzip -dc '~/mail/abc.gz' > '/tmp/xyz'
399 */
400static void
401expand_command_str (const CONTEXT *ctx, const char *cmd, char *buf, int buflen)
402{
403 if (!ctx || !cmd || !buf)
404 return;
405
406 mutt_FormatString (buf, buflen, 0, buflen, cmd, cb_format_str, (unsigned long) ctx, 0);
407}
408
409/**
410 * execute_command - Run a system command
411 * @ctx: Mailbox to work with
412 * @command: Command string to execute
413 * @progress: Message to show the user
414 *
415 * Run the supplied command, taking care of all the Mutt requirements,
416 * such as locking files and blocking signals.
417 *
418 * Returns:
419 * 1: Success
420 * 0: Failure
421 */
422static int
423execute_command (CONTEXT *ctx, const char *command, const char *progress)
424{
425 int rc = 1;
426 char sys_cmd[HUGE_STRING];
427
428 if (!ctx || !command || !progress)
429 return 0;
430
431 if (!ctx->quiet)
432 mutt_message (progress, ctx->realpath);
433
434 mutt_block_signals();
435 endwin();
436 fflush (stdout);
437
438 expand_command_str (ctx, command, sys_cmd, sizeof (sys_cmd));
439
440 if (mutt_system (sys_cmd) != 0)
441 {
442 rc = 0;
443 mutt_any_key_to_continue (NULL);
444 mutt_error (_("Error running \"%s\"!"), sys_cmd);
445 }
446
447 mutt_unblock_signals();
448
449 return rc;
450}
451
452/**
453 * open_mailbox - Open a compressed mailbox
454 * @ctx: Mailbox to open
455 *
456 * Set up a compressed mailbox to be read.
457 * Decompress the mailbox and set up the paths and hooks needed.
458 * Then determine the type of the mailbox so we can delegate the handling of
459 * messages.
460 */
461static int
462open_mailbox (CONTEXT *ctx)
463{
464 if (!ctx || (ctx->magic != MUTT_COMPRESSED))
465 return -1;
466
467 COMPRESS_INFO *ci = set_compress_info (ctx);
468 if (!ci)
469 return -1;
470
471 /* If there's no close-hook, or the file isn't writable */
472 if (!ci->close || (access (ctx->path, W_OK) != 0))
473 ctx->readonly = 1;
474
475 if (setup_paths (ctx) != 0)
476 goto or_fail;
477 store_size (ctx);
478
479 if (!lock_realpath (ctx, 0))
480 {
481 mutt_error (_("Unable to lock mailbox!"));
482 goto or_fail;
483 }
484
485 int rc = execute_command (ctx, ci->open, _("Decompressing %s"));
486 if (rc == 0)
487 goto or_fail;
488
489 unlock_realpath (ctx);
490
491 ctx->magic = mx_get_magic (ctx->path);
492 if (ctx->magic == 0)
493 {
494 mutt_error (_("Can't identify the contents of the compressed file"));
495 goto or_fail;
496 }
497
498 ci->child_ops = mx_get_ops (ctx->magic);
499 if (!ci->child_ops)
500 {
501 mutt_error (_("Can't find mailbox ops for mailbox type %d"), ctx->magic);
502 goto or_fail;
503 }
504
505 return ci->child_ops->open (ctx);
506
507or_fail:
508 /* remove the partial uncompressed file */
509 remove (ctx->path);
510 mutt_free_compress_info (ctx);
511 return -1;
512}
513
514/**
515 * open_append_mailbox - Open a compressed mailbox for appending
516 * @ctx: Mailbox to open
517 * @flags: e.g. Does the file already exist?
518 *
519 * To append to a compressed mailbox we need an append-hook (or both open- and
520 * close-hooks).
521 *
522 * Returns:
523 * 0: Success
524 * -1: Failure
525 */
526static int
527open_append_mailbox (CONTEXT *ctx, int flags)
528{
529 if (!ctx)
530 return -1;
531
532 /* If this succeeds, we know there's an open-hook */
533 COMPRESS_INFO *ci = set_compress_info (ctx);
534 if (!ci)
535 return -1;
536
537 /* To append we need an append-hook or a close-hook */
538 if (!ci->append && !ci->close)
539 {
540 mutt_error (_("Cannot append without an append-hook or close-hook : %s"), ctx->path);
541 goto oa_fail1;
542 }
543
544 if (setup_paths (ctx) != 0)
545 goto oa_fail2;
546
547 /* Lock the realpath for the duration of the append.
548 * It will be unlocked in the close */
549 if (!lock_realpath (ctx, 1))
550 {
551 mutt_error (_("Unable to lock mailbox!"));
552 goto oa_fail2;
553 }
554
555 /* Open the existing mailbox, unless we are appending */
556 if (!ci->append && (get_size (ctx->realpath) > 0))
557 {
558 int rc = execute_command (ctx, ci->open, _("Decompressing %s"));
559 if (rc == 0)
560 {
561 mutt_error (_("Compress command failed: %s"), ci->open);
562 goto oa_fail2;
563 }
564 ctx->magic = mx_get_magic (ctx->path);
565 }
566 else
567 ctx->magic = DefaultMagic;
568
569 /* We can only deal with mbox and mmdf mailboxes */
570 if ((ctx->magic != MUTT_MBOX) && (ctx->magic != MUTT_MMDF))
571 {
572 mutt_error (_("Unsupported mailbox type for appending."));
573 goto oa_fail2;
574 }
575
576 ci->child_ops = mx_get_ops (ctx->magic);
577 if (!ci->child_ops)
578 {
579 mutt_error (_("Can't find mailbox ops for mailbox type %d"), ctx->magic);
580 goto oa_fail2;
581 }
582
583 if (ci->child_ops->open_append (ctx, flags) != 0)
584 goto oa_fail2;
585
586 return 0;
587
588oa_fail2:
589 /* remove the partial uncompressed file */
590 remove (ctx->path);
591oa_fail1:
592 /* Free the compress_info to prevent close from trying to recompress */
593 mutt_free_compress_info (ctx);
594
595 return -1;
596}
597
598/**
599 * close_mailbox - Close a compressed mailbox
600 * @ctx: Mailbox to close
601 *
602 * If the mailbox has been changed then re-compress the tmp file.
603 * Then delete the tmp file.
604 *
605 * Returns:
606 * 0: Success
607 * -1: Failure
608 */
609static int
610close_mailbox (CONTEXT *ctx)
611{
612 if (!ctx)
613 return -1;
614
615 COMPRESS_INFO *ci = ctx->compress_info;
616 if (!ci)
617 return -1;
618
619 struct mx_ops *ops = ci->child_ops;
620 if (!ops)
621 return -1;
622
623 ops->close (ctx);
624
625 /* sync has already been called, so we only need to delete some files */
626 if (!ctx->append)
627 {
628 /* If the file was removed, remove the compressed folder too */
629 if ((access (ctx->path, F_OK) != 0) && !option (OPTSAVEEMPTY))
630 {
631 remove (ctx->realpath);
632 }
633 else
634 {
635 remove (ctx->path);
636 }
637
638 return 0;
639 }
640
641 const char *append;
642 const char *msg;
643
644 /* The file exists and we can append */
645 if ((access (ctx->realpath, F_OK) == 0) && ci->append)
646 {
647 append = ci->append;
648 msg = _("Compressed-appending to %s...");
649 }
650 else
651 {
652 append = ci->close;
653 msg = _("Compressing %s...");
654 }
655
656 int rc = execute_command (ctx, append, msg);
657 if (rc == 0)
658 {
659 mutt_any_key_to_continue (NULL);
660 mutt_error (_("Error. Preserving temporary file: %s"), ctx->path);
661 }
662 else
663 remove (ctx->path);
664
665 unlock_realpath (ctx);
666
667 return 0;
668}
669
670/**
671 * check_mailbox - Check for changes in the compressed file
672 * @ctx: Mailbox
673 *
674 * If the compressed file changes in size but the mailbox hasn't been changed
675 * in Mutt, then we can close and reopen the mailbox.
676 *
677 * If the mailbox has been changed in Mutt, warn the user.
678 *
679 * The return codes are picked to match mx_check_mailbox().
680 *
681 * Returns:
682 * 0: Mailbox OK
683 * MUTT_REOPENED: The mailbox was closed and reopened
684 * -1: Mailbox bad
685 */
686static int
687check_mailbox (CONTEXT *ctx, int *index_hint)
688{
689 if (!ctx)
690 return -1;
691
692 COMPRESS_INFO *ci = ctx->compress_info;
693 if (!ci)
694 return -1;
695
696 struct mx_ops *ops = ci->child_ops;
697 if (!ops)
698 return -1;
699
700 int size = get_size (ctx->realpath);
701 if (size == ci->size)
702 return 0;
703
704 if (!lock_realpath (ctx, 0))
705 {
706 mutt_error (_("Unable to lock mailbox!"));
707 return -1;
708 }
709
710 int rc = execute_command (ctx, ci->open, _("Decompressing %s"));
711 store_size (ctx);
712 unlock_realpath (ctx);
713 if (rc == 0)
714 return -1;
715
716 return ops->check (ctx, index_hint);
717}
718
719
720/**
721 * open_message - Delegated to mbox handler
722 */
723static int
724open_message (CONTEXT *ctx, MESSAGE *msg, int msgno)
725{
726 if (!ctx)
727 return -1;
728
729 COMPRESS_INFO *ci = ctx->compress_info;
730 if (!ci)
731 return -1;
732
733 struct mx_ops *ops = ci->child_ops;
734 if (!ops)
735 return -1;
736
737 /* Delegate */
738 return ops->open_msg (ctx, msg, msgno);
739}
740
741/**
742 * close_message - Delegated to mbox handler
743 */
744static int
745close_message (CONTEXT *ctx, MESSAGE *msg)
746{
747 if (!ctx)
748 return -1;
749
750 COMPRESS_INFO *ci = ctx->compress_info;
751 if (!ci)
752 return -1;
753
754 struct mx_ops *ops = ci->child_ops;
755 if (!ops)
756 return -1;
757
758 /* Delegate */
759 return ops->close_msg (ctx, msg);
760}
761
762/**
763 * commit_message - Delegated to mbox handler
764 */
765static int
766commit_message (CONTEXT *ctx, MESSAGE *msg)
767{
768 if (!ctx)
769 return -1;
770
771 COMPRESS_INFO *ci = ctx->compress_info;
772 if (!ci)
773 return -1;
774
775 struct mx_ops *ops = ci->child_ops;
776 if (!ops)
777 return -1;
778
779 /* Delegate */
780 return ops->commit_msg (ctx, msg);
781}
782
783/**
784 * open_new_message - Delegated to mbox handler
785 */
786static int
787open_new_message (MESSAGE *msg, CONTEXT *ctx, HEADER *hdr)
788{
789 if (!ctx)
790 return -1;
791
792 COMPRESS_INFO *ci = ctx->compress_info;
793 if (!ci)
794 return -1;
795
796 struct mx_ops *ops = ci->child_ops;
797 if (!ops)
798 return -1;
799
800 /* Delegate */
801 return ops->open_new_msg (msg, ctx, hdr);
802}
803
804
805/**
806 * mutt_comp_can_append - Can we append to this path?
807 * @path: pathname of file to be tested
808 *
809 * To append to a file we can either use an 'append-hook' or a combination of
810 * 'open-hook' and 'close-hook'.
811 *
812 * A match means it's our responsibility to append to the file.
813 *
814 * Returns:
815 * 1: Yes, we can append to the file
816 * 0: No, appending isn't possible
817 */
818int
819mutt_comp_can_append (CONTEXT *ctx)
820{
821 if (!ctx)
822 return 0;
823
824 /* If this succeeds, we know there's an open-hook */
825 COMPRESS_INFO *ci = set_compress_info (ctx);
826 if (!ci)
827 return 0;
828
829 /* We have an open-hook, so to append we need an append-hook,
830 * or a close-hook. */
831 if (ci->append || ci->close)
832 return 1;
833
834 mutt_error (_("Cannot append without an append-hook or close-hook : %s"), ctx->path);
835 return 0;
836}
837
838/**
839 * mutt_comp_can_read - Can we read from this file?
840 * @path: Pathname of file to be tested
841 *
842 * Search for an 'open-hook' with a regex that matches the path.
843 *
844 * A match means it's our responsibility to open the file.
845 *
846 * Returns:
847 * 1: Yes, we can read the file
848 * 0: No, we cannot read the file
849 */
850int
851mutt_comp_can_read (const char *path)
852{
853 if (!path)
854 return 0;
855
856 if (find_hook (MUTT_OPENHOOK, path))
857 return 1;
858 else
859 return 0;
860}
861
862/**
863 * sync_mailbox - Save changes to the compressed mailbox file
864 * @ctx: Mailbox to sync
865 *
866 * Changes in Mutt only affect the tmp file. Calling sync_mailbox()
867 * will commit them to the compressed file.
868 *
869 * Returns:
870 * 0: Success
871 * -1: Failure
872 */
873static int
874sync_mailbox (CONTEXT *ctx, int *index_hint)
875{
876 if (!ctx)
877 return -1;
878
879 COMPRESS_INFO *ci = ctx->compress_info;
880 if (!ci)
881 return -1;
882
883 if (!ci->close)
884 {
885 mutt_error (_("Can't sync a compressed file without a close-hook"));
886 return -1;
887 }
888
889 struct mx_ops *ops = ci->child_ops;
890 if (!ops)
891 return -1;
892
893 if (!lock_realpath (ctx, 1))
894 {
895 mutt_error (_("Unable to lock mailbox!"));
896 return -1;
897 }
898
899 int rc = check_mailbox (ctx, index_hint);
900 if (rc != 0)
901 goto sync_cleanup;
902
903 rc = ops->sync (ctx, index_hint);
904 if (rc != 0)
905 goto sync_cleanup;
906
907 rc = execute_command (ctx, ci->close, _("Compressing %s"));
908 if (rc == 0)
909 {
910 rc = -1;
911 goto sync_cleanup;
912 }
913
914 rc = 0;
915
916sync_cleanup:
917 store_size (ctx);
918 unlock_realpath (ctx);
919 return rc;
920}
921
922/**
923 * mutt_comp_valid_command - Is this command string allowed?
924 * @cmd: Command string
925 *
926 * A valid command string must have both "%f" (from file) and "%t" (to file).
927 * We don't check if we can actually run the command.
928 *
929 * Returns:
930 * 1: Valid command
931 * 0: "%f" and/or "%t" is missing
932 */
933int
934mutt_comp_valid_command (const char *cmd)
935{
936 if (!cmd)
937 return 0;
938
939 return (strstr (cmd, "%f") && strstr (cmd, "%t"));
940}
941
942
943/**
944 * mx_comp_ops - Mailbox callback functions
945 *
946 * Compress only uses open, close and check.
947 * The message functions are delegated to mbox.
948 */
949struct mx_ops mx_comp_ops =
950{
951 .open = open_mailbox,
952 .open_append = open_append_mailbox,
953 .close = close_mailbox,
954 .check = check_mailbox,
955 .sync = sync_mailbox,
956 .open_msg = open_message,
957 .close_msg = close_message,
958 .commit_msg = commit_message,
959 .open_new_msg = open_new_message
960};
961