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("ed);
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