1 /**
2  * @file
3  * Compressed mbox local mailbox type
4  *
5  * @authors
6  * Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
7  * Copyright (C) 2016-2018 Richard Russon <rich@flatcap.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page compmbox_compress Compressed mailbox functions
26  *
27  * Compressed mbox local mailbox type
28  *
29  * @note
30  * Any references to compressed files also apply to encrypted files.
31  * - mailbox->path     == plaintext file
32  * - mailbox->realpath == compressed file
33  *
34  * Implementation: #MxCompOps
35  */
36 
37 #include "config.h"
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include "mutt/lib.h"
44 #include "config/lib.h"
45 #include "core/lib.h"
46 #include "gui/lib.h"
47 #include "lib.h"
48 #include "format_flags.h"
49 #include "hook.h"
50 #include "mutt_commands.h"
51 #include "mutt_globals.h"
52 #include "muttlib.h"
53 #include "mx.h"
54 #include "protos.h"
55 
56 struct Email;
57 
58 static const struct Command comp_commands[] = {
59   // clang-format off
60   { "append-hook", mutt_parse_hook, MUTT_APPEND_HOOK },
61   { "close-hook",  mutt_parse_hook, MUTT_CLOSE_HOOK },
62   { "open-hook",   mutt_parse_hook, MUTT_OPEN_HOOK },
63   // clang-format on
64 };
65 
66 /**
67  * mutt_comp_init - Setup feature commands
68  */
mutt_comp_init(void)69 void mutt_comp_init(void)
70 {
71   COMMANDS_REGISTER(comp_commands);
72 }
73 
74 /**
75  * lock_realpath - Try to lock the ctx->realpath
76  * @param m    Mailbox to lock
77  * @param excl Lock exclusively?
78  * @retval true  Success (locked or readonly)
79  * @retval false Error (can't lock the file)
80  *
81  * Try to (exclusively) lock the mailbox.  If we succeed, then we mark the
82  * mailbox as locked.  If we fail, but we didn't want exclusive rights, then
83  * the mailbox will be marked readonly.
84  */
lock_realpath(struct Mailbox * m,bool excl)85 static bool lock_realpath(struct Mailbox *m, bool excl)
86 {
87   if (!m || !m->compress_info)
88     return false;
89 
90   struct CompressInfo *ci = m->compress_info;
91 
92   if (ci->locked)
93     return true;
94 
95   if (excl)
96     ci->fp_lock = fopen(m->realpath, "a");
97   else
98     ci->fp_lock = fopen(m->realpath, "r");
99   if (!ci->fp_lock)
100   {
101     mutt_perror(m->realpath);
102     return false;
103   }
104 
105   int r = mutt_file_lock(fileno(ci->fp_lock), excl, true);
106   if (r == 0)
107     ci->locked = true;
108   else if (excl)
109   {
110     mutt_file_fclose(&ci->fp_lock);
111     m->readonly = true;
112     return true;
113   }
114 
115   return r == 0;
116 }
117 
118 /**
119  * unlock_realpath - Unlock the mailbox->realpath
120  * @param m Mailbox to unlock
121  *
122  * Unlock a mailbox previously locked by lock_mailbox().
123  */
unlock_realpath(struct Mailbox * m)124 static void unlock_realpath(struct Mailbox *m)
125 {
126   if (!m || !m->compress_info)
127     return;
128 
129   struct CompressInfo *ci = m->compress_info;
130 
131   if (!ci->locked)
132     return;
133 
134   mutt_file_unlock(fileno(ci->fp_lock));
135 
136   ci->locked = false;
137   mutt_file_fclose(&ci->fp_lock);
138 }
139 
140 /**
141  * setup_paths - Set the mailbox paths
142  * @param m Mailbox to modify
143  * @retval  0 Success
144  * @retval -1 Error
145  *
146  * Save the compressed filename in mailbox->realpath.
147  * Create a temporary filename and put its name in mailbox->path.
148  * The temporary file is created to prevent symlink attacks.
149  */
setup_paths(struct Mailbox * m)150 static int setup_paths(struct Mailbox *m)
151 {
152   if (!m)
153     return -1;
154 
155   /* Setup the right paths */
156   mutt_str_replace(&m->realpath, mailbox_path(m));
157 
158   /* We will uncompress to TMPDIR */
159   struct Buffer *buf = mutt_buffer_pool_get();
160   mutt_buffer_mktemp(buf);
161   mutt_buffer_copy(&m->pathbuf, buf);
162   mutt_buffer_pool_release(&buf);
163 
164   FILE *fp = mutt_file_fopen(mailbox_path(m), "w");
165   if (!fp)
166     return -1;
167 
168   mutt_file_fclose(&fp);
169   return 0;
170 }
171 
172 /**
173  * store_size - Save the size of the compressed file
174  * @param m Mailbox
175  *
176  * Save the compressed file size in the compress_info struct.
177  */
store_size(const struct Mailbox * m)178 static void store_size(const struct Mailbox *m)
179 {
180   if (!m || !m->compress_info)
181     return;
182 
183   struct CompressInfo *ci = m->compress_info;
184 
185   ci->size = mutt_file_get_size(m->realpath);
186 }
187 
188 /**
189  * set_compress_info - Find the compress hooks for a mailbox
190  * @param m Mailbox to examine
191  * @retval ptr  CompressInfo Hook info for the mailbox's path
192  * @retval NULL Error
193  *
194  * When a mailbox is opened, we check if there are any matching hooks.
195  */
set_compress_info(struct Mailbox * m)196 static struct CompressInfo *set_compress_info(struct Mailbox *m)
197 {
198   if (!m)
199     return NULL;
200 
201   if (m->compress_info)
202     return m->compress_info;
203 
204   /* Open is compulsory */
205   const char *o = mutt_find_hook(MUTT_OPEN_HOOK, mailbox_path(m));
206   if (!o)
207     return NULL;
208 
209   const char *c = mutt_find_hook(MUTT_CLOSE_HOOK, mailbox_path(m));
210   const char *a = mutt_find_hook(MUTT_APPEND_HOOK, mailbox_path(m));
211 
212   struct CompressInfo *ci = mutt_mem_calloc(1, sizeof(struct CompressInfo));
213   m->compress_info = ci;
214 
215   ci->cmd_open = mutt_str_dup(o);
216   ci->cmd_close = mutt_str_dup(c);
217   ci->cmd_append = mutt_str_dup(a);
218 
219   return ci;
220 }
221 
222 /**
223  * compress_info_free - Frees the compress info members and structure
224  * @param m Mailbox to free compress_info for
225  */
compress_info_free(struct Mailbox * m)226 static void compress_info_free(struct Mailbox *m)
227 {
228   if (!m || !m->compress_info)
229     return;
230 
231   struct CompressInfo *ci = m->compress_info;
232   FREE(&ci->cmd_open);
233   FREE(&ci->cmd_close);
234   FREE(&ci->cmd_append);
235 
236   unlock_realpath(m);
237 
238   FREE(&m->compress_info);
239 }
240 
241 /**
242  * compress_format_str - Expand the filenames in a command string - Implements ::format_t - @ingroup expando_api
243  *
244  * | Expando | Description
245  * |:--------|:--------------------------------------------------------
246  * | \%f     | Compressed file
247  * | \%t     | Plaintext, temporary file
248  */
compress_format_str(char * buf,size_t buflen,size_t col,int cols,char op,const char * src,const char * prec,const char * if_str,const char * else_str,intptr_t data,MuttFormatFlags flags)249 static const char *compress_format_str(char *buf, size_t buflen, size_t col, int cols,
250                                        char op, const char *src, const char *prec,
251                                        const char *if_str, const char *else_str,
252                                        intptr_t data, MuttFormatFlags flags)
253 {
254   if (!buf || (data == 0))
255     return src;
256 
257   struct Mailbox *m = (struct Mailbox *) data;
258 
259   /* NOTE the compressed file config vars expect %f and %t to be
260    * surrounded by '' (unlike other NeoMutt config vars, which add the
261    * outer quotes for the user).  This is why we use the
262    * mutt_buffer_quote_filename() form with add_outer of false. */
263   struct Buffer *quoted = mutt_buffer_pool_get();
264   switch (op)
265   {
266     case 'f':
267       /* Compressed file */
268       mutt_buffer_quote_filename(quoted, m->realpath, false);
269       snprintf(buf, buflen, "%s", mutt_buffer_string(quoted));
270       break;
271     case 't':
272       /* Plaintext, temporary file */
273       mutt_buffer_quote_filename(quoted, mailbox_path(m), false);
274       snprintf(buf, buflen, "%s", mutt_buffer_string(quoted));
275       break;
276   }
277 
278   mutt_buffer_pool_release(&quoted);
279   return src;
280 }
281 
282 /**
283  * expand_command_str - Expand placeholders in command string
284  * @param m      Mailbox for paths
285  * @param cmd    Template command to be expanded
286  * @param buf    Buffer to store the command
287  * @param buflen Size of the buffer
288  *
289  * This function takes a hook command and expands the filename placeholders
290  * within it.  The function calls mutt_expando_format() to do the replacement
291  * which calls our callback function compress_format_str(). e.g.
292  *
293  * Template command:
294  *      gzip -cd '%f' > '%t'
295  *
296  * Result:
297  *      gzip -dc '~/mail/abc.gz' > '/tmp/xyz'
298  */
expand_command_str(const struct Mailbox * m,const char * cmd,char * buf,int buflen)299 static void expand_command_str(const struct Mailbox *m, const char *cmd, char *buf, int buflen)
300 {
301   if (!m || !cmd || !buf)
302     return;
303 
304   mutt_expando_format(buf, buflen, 0, buflen, cmd, compress_format_str,
305                       (intptr_t) m, MUTT_FORMAT_NO_FLAGS);
306 }
307 
308 /**
309  * execute_command - Run a system command
310  * @param m        Mailbox to work with
311  * @param command  Command string to execute
312  * @param progress Message to show the user
313  * @retval 1 Success
314  * @retval 0 Failure
315  *
316  * Run the supplied command, taking care of all the NeoMutt requirements,
317  * such as locking files and blocking signals.
318  */
execute_command(struct Mailbox * m,const char * command,const char * progress)319 static int execute_command(struct Mailbox *m, const char *command, const char *progress)
320 {
321   if (!m || !command || !progress)
322     return 0;
323 
324   if (m->verbose)
325     mutt_message(progress, m->realpath);
326 
327   int rc = 1;
328   char sys_cmd[STR_COMMAND];
329 
330   mutt_sig_block();
331   endwin();
332   fflush(stdout);
333 
334   expand_command_str(m, command, sys_cmd, sizeof(sys_cmd));
335 
336   if (mutt_system(sys_cmd) != 0)
337   {
338     rc = 0;
339     mutt_any_key_to_continue(NULL);
340     mutt_error(_("Error running \"%s\""), sys_cmd);
341   }
342 
343   mutt_sig_unblock();
344 
345   return rc;
346 }
347 
348 /**
349  * mutt_comp_can_append - Can we append to this path?
350  * @param m Mailbox
351  * @retval true  Yes, we can append to the file
352  * @retval false No, appending isn't possible
353  *
354  * To append to a file we can either use an 'append-hook' or a combination of
355  * 'open-hook' and 'close-hook'.
356  *
357  * A match means it's our responsibility to append to the file.
358  */
mutt_comp_can_append(struct Mailbox * m)359 bool mutt_comp_can_append(struct Mailbox *m)
360 {
361   if (!m)
362     return false;
363 
364   /* If this succeeds, we know there's an open-hook */
365   struct CompressInfo *ci = set_compress_info(m);
366   if (!ci)
367     return false;
368 
369   /* We have an open-hook, so to append we need an append-hook,
370    * or a close-hook. */
371   if (ci->cmd_append || ci->cmd_close)
372     return true;
373 
374   mutt_error(_("Can't append without an append-hook or close-hook : %s"), mailbox_path(m));
375   return false;
376 }
377 
378 /**
379  * mutt_comp_can_read - Can we read from this file?
380  * @param path Pathname of file to be tested
381  * @retval true  Yes, we can read the file
382  * @retval false No, we can't read the file
383  *
384  * Search for an 'open-hook' with a regex that matches the path.
385  *
386  * A match means it's our responsibility to open the file.
387  */
mutt_comp_can_read(const char * path)388 bool mutt_comp_can_read(const char *path)
389 {
390   if (!path)
391     return false;
392 
393   if (mutt_find_hook(MUTT_OPEN_HOOK, path))
394     return true;
395 
396   return false;
397 }
398 
399 /**
400  * mutt_comp_valid_command - Is this command string allowed?
401  * @param cmd  Command string
402  * @retval 1 Valid command
403  * @retval 0 "%f" and/or "%t" is missing
404  *
405  * A valid command string must have both "%f" (from file) and "%t" (to file).
406  * We don't check if we can actually run the command.
407  */
mutt_comp_valid_command(const char * cmd)408 int mutt_comp_valid_command(const char *cmd)
409 {
410   if (!cmd)
411     return 0;
412 
413   return strstr(cmd, "%f") && strstr(cmd, "%t");
414 }
415 
416 /**
417  * comp_ac_owns_path - Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() - @ingroup mx_ac_owns_path
418  */
comp_ac_owns_path(struct Account * a,const char * path)419 static bool comp_ac_owns_path(struct Account *a, const char *path)
420 {
421   return false;
422 }
423 
424 /**
425  * comp_ac_add - Add a Mailbox to an Account - Implements MxOps::ac_add() - @ingroup mx_ac_add
426  */
comp_ac_add(struct Account * a,struct Mailbox * m)427 static bool comp_ac_add(struct Account *a, struct Mailbox *m)
428 {
429   return true;
430 }
431 
432 /**
433  * comp_mbox_open - Open a Mailbox - Implements MxOps::mbox_open() - @ingroup mx_mbox_open
434  *
435  * Set up a compressed mailbox to be read.
436  * Decompress the mailbox and set up the paths and hooks needed.
437  * Then determine the type of the mailbox so we can delegate the handling of
438  * messages.
439  */
comp_mbox_open(struct Mailbox * m)440 static enum MxOpenReturns comp_mbox_open(struct Mailbox *m)
441 {
442   struct CompressInfo *ci = set_compress_info(m);
443   if (!ci)
444     return MX_OPEN_ERROR;
445 
446   /* If there's no close-hook, or the file isn't writable */
447   if (!ci->cmd_close || (access(mailbox_path(m), W_OK) != 0))
448     m->readonly = true;
449 
450   if (setup_paths(m) != 0)
451     goto cmo_fail;
452   store_size(m);
453 
454   if (!lock_realpath(m, false))
455   {
456     mutt_error(_("Unable to lock mailbox"));
457     goto cmo_fail;
458   }
459 
460   int rc = execute_command(m, ci->cmd_open, _("Decompressing %s"));
461   if (rc == 0)
462     goto cmo_fail;
463 
464   unlock_realpath(m);
465 
466   m->type = mx_path_probe(mailbox_path(m));
467   if (m->type == MUTT_UNKNOWN)
468   {
469     mutt_error(_("Can't identify the contents of the compressed file"));
470     goto cmo_fail;
471   }
472 
473   ci->child_ops = mx_get_ops(m->type);
474   if (!ci->child_ops)
475   {
476     mutt_error(_("Can't find mailbox ops for mailbox type %d"), m->type);
477     goto cmo_fail;
478   }
479 
480   m->account->type = m->type;
481   return ci->child_ops->mbox_open(m);
482 
483 cmo_fail:
484   /* remove the partial uncompressed file */
485   (void) remove(mailbox_path(m));
486   compress_info_free(m);
487   return MX_OPEN_ERROR;
488 }
489 
490 /**
491  * comp_mbox_open_append - Open a Mailbox for appending - Implements MxOps::mbox_open_append() - @ingroup mx_mbox_open_append
492  *
493  * flags may also contain #MUTT_NEWFOLDER
494  *
495  * To append to a compressed mailbox we need an append-hook (or both open- and
496  * close-hooks).
497  */
comp_mbox_open_append(struct Mailbox * m,OpenMailboxFlags flags)498 static bool comp_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
499 {
500   /* If this succeeds, we know there's an open-hook */
501   struct CompressInfo *ci = set_compress_info(m);
502   if (!ci)
503     return false;
504 
505   /* To append we need an append-hook or a close-hook */
506   if (!ci->cmd_append && !ci->cmd_close)
507   {
508     mutt_error(_("Can't append without an append-hook or close-hook : %s"),
509                mailbox_path(m));
510     goto cmoa_fail1;
511   }
512 
513   if (setup_paths(m) != 0)
514     goto cmoa_fail2;
515 
516   /* Lock the realpath for the duration of the append.
517    * It will be unlocked in the close */
518   if (!lock_realpath(m, true))
519   {
520     mutt_error(_("Unable to lock mailbox"));
521     goto cmoa_fail2;
522   }
523 
524   /* Open the existing mailbox, unless we are appending */
525   if (!ci->cmd_append && (mutt_file_get_size(m->realpath) > 0))
526   {
527     int rc = execute_command(m, ci->cmd_open, _("Decompressing %s"));
528     if (rc == 0)
529     {
530       mutt_error(_("Compress command failed: %s"), ci->cmd_open);
531       goto cmoa_fail2;
532     }
533     m->type = mx_path_probe(mailbox_path(m));
534   }
535   else
536   {
537     m->type = cs_subset_enum(NeoMutt->sub, "mbox_type");
538   }
539 
540   /* We can only deal with mbox and mmdf mailboxes */
541   if ((m->type != MUTT_MBOX) && (m->type != MUTT_MMDF))
542   {
543     mutt_error(_("Unsupported mailbox type for appending"));
544     goto cmoa_fail2;
545   }
546 
547   ci->child_ops = mx_get_ops(m->type);
548   if (!ci->child_ops)
549   {
550     mutt_error(_("Can't find mailbox ops for mailbox type %d"), m->type);
551     goto cmoa_fail2;
552   }
553 
554   if (!ci->child_ops->mbox_open_append(m, flags))
555     goto cmoa_fail2;
556 
557   return true;
558 
559 cmoa_fail2:
560   /* remove the partial uncompressed file */
561   (void) remove(mailbox_path(m));
562 cmoa_fail1:
563   /* Free the compress_info to prevent close from trying to recompress */
564   compress_info_free(m);
565 
566   return false;
567 }
568 
569 /**
570  * comp_mbox_check - Check for new mail - Implements MxOps::mbox_check() - @ingroup mx_mbox_check
571  * @param m Mailbox
572  * @retval enum #MxStatus
573  *
574  * If the compressed file changes in size but the mailbox hasn't been changed
575  * in NeoMutt, then we can close and reopen the mailbox.
576  *
577  * If the mailbox has been changed in NeoMutt, warn the user.
578  */
comp_mbox_check(struct Mailbox * m)579 static enum MxStatus comp_mbox_check(struct Mailbox *m)
580 {
581   if (!m->compress_info)
582     return MX_STATUS_ERROR;
583 
584   struct CompressInfo *ci = m->compress_info;
585 
586   const struct MxOps *ops = ci->child_ops;
587   if (!ops)
588     return MX_STATUS_ERROR;
589 
590   int size = mutt_file_get_size(m->realpath);
591   if (size == ci->size)
592     return MX_STATUS_OK;
593 
594   if (!lock_realpath(m, false))
595   {
596     mutt_error(_("Unable to lock mailbox"));
597     return MX_STATUS_ERROR;
598   }
599 
600   int rc = execute_command(m, ci->cmd_open, _("Decompressing %s"));
601   store_size(m);
602   unlock_realpath(m);
603   if (rc == 0)
604     return MX_STATUS_ERROR;
605 
606   return ops->mbox_check(m);
607 }
608 
609 /**
610  * comp_mbox_sync - Save changes to the Mailbox - Implements MxOps::mbox_sync() - @ingroup mx_mbox_sync
611  *
612  * Changes in NeoMutt only affect the tmp file.
613  * Calling comp_mbox_sync() will commit them to the compressed file.
614  */
comp_mbox_sync(struct Mailbox * m)615 static enum MxStatus comp_mbox_sync(struct Mailbox *m)
616 {
617   if (!m->compress_info)
618     return MX_STATUS_ERROR;
619 
620   struct CompressInfo *ci = m->compress_info;
621 
622   if (!ci->cmd_close)
623   {
624     mutt_error(_("Can't sync a compressed file without a close-hook"));
625     return MX_STATUS_ERROR;
626   }
627 
628   const struct MxOps *ops = ci->child_ops;
629   if (!ops)
630     return MX_STATUS_ERROR;
631 
632   if (!lock_realpath(m, true))
633   {
634     mutt_error(_("Unable to lock mailbox"));
635     return MX_STATUS_ERROR;
636   }
637 
638   enum MxStatus check = comp_mbox_check(m);
639   if (check != MX_STATUS_OK)
640     goto sync_cleanup;
641 
642   check = ops->mbox_sync(m);
643   if (check != MX_STATUS_OK)
644     goto sync_cleanup;
645 
646   int rc = execute_command(m, ci->cmd_close, _("Compressing %s"));
647   if (rc == 0)
648   {
649     check = MX_STATUS_ERROR;
650     goto sync_cleanup;
651   }
652 
653   check = MX_STATUS_OK;
654 
655 sync_cleanup:
656   store_size(m);
657   unlock_realpath(m);
658   return check;
659 }
660 
661 /**
662  * comp_mbox_close - Close a Mailbox - Implements MxOps::mbox_close() - @ingroup mx_mbox_close
663  *
664  * If the mailbox has been changed then re-compress the tmp file.
665  * Then delete the tmp file.
666  */
comp_mbox_close(struct Mailbox * m)667 static enum MxStatus comp_mbox_close(struct Mailbox *m)
668 {
669   if (!m->compress_info)
670     return MX_STATUS_ERROR;
671 
672   struct CompressInfo *ci = m->compress_info;
673 
674   const struct MxOps *ops = ci->child_ops;
675   if (!ops)
676   {
677     compress_info_free(m);
678     return MX_STATUS_ERROR;
679   }
680 
681   ops->mbox_close(m);
682 
683   /* sync has already been called, so we only need to delete some files */
684   if (m->append)
685   {
686     const char *append = NULL;
687     const char *msg = NULL;
688 
689     /* The file exists and we can append */
690     if ((access(m->realpath, F_OK) == 0) && ci->cmd_append)
691     {
692       append = ci->cmd_append;
693       msg = _("Compressed-appending to %s...");
694     }
695     else
696     {
697       append = ci->cmd_close;
698       msg = _("Compressing %s");
699     }
700 
701     int rc = execute_command(m, append, msg);
702     if (rc == 0)
703     {
704       mutt_any_key_to_continue(NULL);
705       mutt_error(_("Error. Preserving temporary file: %s"), mailbox_path(m));
706     }
707     else
708       remove(mailbox_path(m));
709 
710     unlock_realpath(m);
711   }
712   else
713   {
714     /* If the file was removed, remove the compressed folder too */
715     const bool c_save_empty = cs_subset_bool(NeoMutt->sub, "save_empty");
716     if ((access(mailbox_path(m), F_OK) != 0) && !c_save_empty)
717     {
718       remove(m->realpath);
719     }
720     else
721     {
722       remove(mailbox_path(m));
723     }
724   }
725 
726   compress_info_free(m);
727 
728   return MX_STATUS_OK;
729 }
730 
731 /**
732  * comp_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
733  */
comp_msg_open(struct Mailbox * m,struct Message * msg,int msgno)734 static bool comp_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
735 {
736   if (!m->compress_info)
737     return false;
738 
739   struct CompressInfo *ci = m->compress_info;
740 
741   const struct MxOps *ops = ci->child_ops;
742   if (!ops)
743     return false;
744 
745   /* Delegate */
746   return ops->msg_open(m, msg, msgno);
747 }
748 
749 /**
750  * comp_msg_open_new - Open a new message in a Mailbox - Implements MxOps::msg_open_new() - @ingroup mx_msg_open_new
751  */
comp_msg_open_new(struct Mailbox * m,struct Message * msg,const struct Email * e)752 static bool comp_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
753 {
754   if (!m->compress_info)
755     return false;
756 
757   struct CompressInfo *ci = m->compress_info;
758 
759   const struct MxOps *ops = ci->child_ops;
760   if (!ops)
761     return false;
762 
763   /* Delegate */
764   return ops->msg_open_new(m, msg, e);
765 }
766 
767 /**
768  * comp_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
769  */
comp_msg_commit(struct Mailbox * m,struct Message * msg)770 static int comp_msg_commit(struct Mailbox *m, struct Message *msg)
771 {
772   if (!m->compress_info)
773     return -1;
774 
775   struct CompressInfo *ci = m->compress_info;
776 
777   const struct MxOps *ops = ci->child_ops;
778   if (!ops)
779     return -1;
780 
781   /* Delegate */
782   return ops->msg_commit(m, msg);
783 }
784 
785 /**
786  * comp_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
787  */
comp_msg_close(struct Mailbox * m,struct Message * msg)788 static int comp_msg_close(struct Mailbox *m, struct Message *msg)
789 {
790   if (!m->compress_info)
791     return -1;
792 
793   struct CompressInfo *ci = m->compress_info;
794 
795   const struct MxOps *ops = ci->child_ops;
796   if (!ops)
797     return -1;
798 
799   /* Delegate */
800   return ops->msg_close(m, msg);
801 }
802 
803 /**
804  * comp_msg_padding_size - Bytes of padding between messages - Implements MxOps::msg_padding_size() - @ingroup mx_msg_padding_size
805  */
comp_msg_padding_size(struct Mailbox * m)806 static int comp_msg_padding_size(struct Mailbox *m)
807 {
808   if (!m->compress_info)
809     return 0;
810 
811   struct CompressInfo *ci = m->compress_info;
812 
813   const struct MxOps *ops = ci->child_ops;
814   if (!ops || !ops->msg_padding_size)
815     return 0;
816 
817   return ops->msg_padding_size(m);
818 }
819 
820 /**
821  * comp_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache
822  */
comp_msg_save_hcache(struct Mailbox * m,struct Email * e)823 static int comp_msg_save_hcache(struct Mailbox *m, struct Email *e)
824 {
825   if (!m->compress_info)
826     return 0;
827 
828   struct CompressInfo *ci = m->compress_info;
829 
830   const struct MxOps *ops = ci->child_ops;
831   if (!ops || !ops->msg_save_hcache)
832     return 0;
833 
834   return ops->msg_save_hcache(m, e);
835 }
836 
837 /**
838  * comp_tags_edit - Prompt and validate new messages tags - Implements MxOps::tags_edit() - @ingroup mx_tags_edit
839  */
comp_tags_edit(struct Mailbox * m,const char * tags,char * buf,size_t buflen)840 static int comp_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
841 {
842   if (!m->compress_info)
843     return 0;
844 
845   struct CompressInfo *ci = m->compress_info;
846 
847   const struct MxOps *ops = ci->child_ops;
848   if (!ops || !ops->tags_edit)
849     return 0;
850 
851   return ops->tags_edit(m, tags, buf, buflen);
852 }
853 
854 /**
855  * comp_tags_commit - Save the tags to a message - Implements MxOps::tags_commit() - @ingroup mx_tags_commit
856  */
comp_tags_commit(struct Mailbox * m,struct Email * e,char * buf)857 static int comp_tags_commit(struct Mailbox *m, struct Email *e, char *buf)
858 {
859   if (!m->compress_info)
860     return 0;
861 
862   struct CompressInfo *ci = m->compress_info;
863 
864   const struct MxOps *ops = ci->child_ops;
865   if (!ops || !ops->tags_commit)
866     return 0;
867 
868   return ops->tags_commit(m, e, buf);
869 }
870 
871 /**
872  * comp_path_probe - Is this a compressed Mailbox? - Implements MxOps::path_probe() - @ingroup mx_path_probe
873  */
comp_path_probe(const char * path,const struct stat * st)874 static enum MailboxType comp_path_probe(const char *path, const struct stat *st)
875 {
876   if (!st || !S_ISREG(st->st_mode))
877     return MUTT_UNKNOWN;
878 
879   if (mutt_comp_can_read(path))
880     return MUTT_COMPRESSED;
881 
882   return MUTT_UNKNOWN;
883 }
884 
885 /**
886  * comp_path_canon - Canonicalise a Mailbox path - Implements MxOps::path_canon() - @ingroup mx_path_canon
887  */
comp_path_canon(char * buf,size_t buflen)888 static int comp_path_canon(char *buf, size_t buflen)
889 {
890   mutt_path_canon(buf, buflen, HomeDir, false);
891   return 0;
892 }
893 
894 /**
895  * comp_path_pretty - Abbreviate a Mailbox path - Implements MxOps::path_pretty() - @ingroup mx_path_pretty
896  */
comp_path_pretty(char * buf,size_t buflen,const char * folder)897 static int comp_path_pretty(char *buf, size_t buflen, const char *folder)
898 {
899   if (mutt_path_abbr_folder(buf, folder))
900     return 0;
901 
902   if (mutt_path_pretty(buf, buflen, HomeDir, false))
903     return 0;
904 
905   return -1;
906 }
907 
908 /**
909  * comp_path_parent - Find the parent of a Mailbox path - Implements MxOps::path_parent() - @ingroup mx_path_parent
910  */
comp_path_parent(char * buf,size_t buflen)911 static int comp_path_parent(char *buf, size_t buflen)
912 {
913   if (mutt_path_parent(buf))
914     return 0;
915 
916   if (buf[0] == '~')
917     mutt_path_canon(buf, buflen, HomeDir, false);
918 
919   if (mutt_path_parent(buf))
920     return 0;
921 
922   return -1;
923 }
924 
925 /**
926  * MxCompOps - Compressed Mailbox - Implements ::MxOps - @ingroup mx_api
927  *
928  * Compress only uses open, close and check.
929  * The message functions are delegated to mbox.
930  */
931 struct MxOps MxCompOps = {
932   // clang-format off
933   .type            = MUTT_COMPRESSED,
934   .name             = "compressed",
935   .is_local         = true,
936   .ac_owns_path     = comp_ac_owns_path,
937   .ac_add           = comp_ac_add,
938   .mbox_open        = comp_mbox_open,
939   .mbox_open_append = comp_mbox_open_append,
940   .mbox_check       = comp_mbox_check,
941   .mbox_check_stats = NULL,
942   .mbox_sync        = comp_mbox_sync,
943   .mbox_close       = comp_mbox_close,
944   .msg_open         = comp_msg_open,
945   .msg_open_new     = comp_msg_open_new,
946   .msg_commit       = comp_msg_commit,
947   .msg_close        = comp_msg_close,
948   .msg_padding_size = comp_msg_padding_size,
949   .msg_save_hcache  = comp_msg_save_hcache,
950   .tags_edit        = comp_tags_edit,
951   .tags_commit      = comp_tags_commit,
952   .path_probe       = comp_path_probe,
953   .path_canon       = comp_path_canon,
954   .path_pretty      = comp_path_pretty,
955   .path_parent      = comp_path_parent,
956   .path_is_empty    = NULL,
957   // clang-format on
958 };
959