1 /**
2  * @file
3  * Maildir local mailbox type
4  *
5  * @authors
6  * Copyright (C) 1996-2002,2007,2009 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2005 Thomas Roessler <roessler@does-not-exist.org>
8  * Copyright (C) 2010,2013 Michael R. Elkins <me@mutt.org>
9  * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
10  *
11  * @copyright
12  * This program is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free Software
14  * Foundation, either version 2 of the License, or (at your option) any later
15  * version.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 /**
27  * @page maildir_maildir Maildir local mailbox type
28  *
29  * Maildir local mailbox type
30  *
31  * Implementation: #MxMaildirOps
32  */
33 
34 #include "config.h"
35 #include <dirent.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <inttypes.h>
39 #include <limits.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/stat.h>
46 #include <unistd.h>
47 #include <utime.h>
48 #include "private.h"
49 #include "mutt/lib.h"
50 #include "config/lib.h"
51 #include "email/lib.h"
52 #include "core/lib.h"
53 #include "lib.h"
54 #include "progress/lib.h"
55 #include "copy.h"
56 #include "edata.h"
57 #include "mdata.h"
58 #include "mdemail.h"
59 #include "monitor.h"
60 #include "mutt_globals.h"
61 #include "mx.h"
62 #ifdef USE_HCACHE
63 #include "hcache/lib.h"
64 #endif
65 #ifdef USE_NOTMUCH
66 #include "notmuch/lib.h"
67 #endif
68 
69 struct Progress;
70 
71 // Flags for maildir_mbox_check()
72 #define MMC_NO_DIRS 0        ///< No directories changed
73 #define MMC_NEW_DIR (1 << 0) ///< 'new' directory changed
74 #define MMC_CUR_DIR (1 << 1) ///< 'cur' directory changed
75 
76 /**
77  * maildir_check_dir - Check for new mail / mail counts
78  * @param m           Mailbox to check
79  * @param dir_name    Path to Mailbox
80  * @param check_new   if true, check for new mail
81  * @param check_stats if true, count total, new, and flagged messages
82  *
83  * Checks the specified maildir subdir (cur or new) for new mail or mail counts.
84  */
maildir_check_dir(struct Mailbox * m,const char * dir_name,bool check_new,bool check_stats)85 static void maildir_check_dir(struct Mailbox *m, const char *dir_name,
86                               bool check_new, bool check_stats)
87 {
88   DIR *dirp = NULL;
89   struct dirent *de = NULL;
90   char *p = NULL;
91   struct stat st = { 0 };
92 
93   struct Buffer *path = mutt_buffer_pool_get();
94   struct Buffer *msgpath = mutt_buffer_pool_get();
95   mutt_buffer_printf(path, "%s/%s", mailbox_path(m), dir_name);
96 
97   /* when $mail_check_recent is set, if the new/ directory hasn't been modified since
98    * the user last exited the m, then we know there is no recent mail.  */
99   const bool c_mail_check_recent =
100       cs_subset_bool(NeoMutt->sub, "mail_check_recent");
101   if (check_new && c_mail_check_recent)
102   {
103     if ((stat(mutt_buffer_string(path), &st) == 0) &&
104         (mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->last_visited) < 0))
105     {
106       check_new = false;
107     }
108   }
109 
110   if (!(check_new || check_stats))
111     goto cleanup;
112 
113   dirp = opendir(mutt_buffer_string(path));
114   if (!dirp)
115   {
116     m->type = MUTT_UNKNOWN;
117     goto cleanup;
118   }
119 
120   while ((de = readdir(dirp)))
121   {
122     if (*de->d_name == '.')
123       continue;
124 
125     p = strstr(de->d_name, ":2,");
126     if (p && strchr(p + 3, 'T'))
127       continue;
128 
129     if (check_stats)
130     {
131       m->msg_count++;
132       if (p && strchr(p + 3, 'F'))
133         m->msg_flagged++;
134     }
135     if (!p || !strchr(p + 3, 'S'))
136     {
137       if (check_stats)
138         m->msg_unread++;
139       if (check_new)
140       {
141         if (c_mail_check_recent)
142         {
143           mutt_buffer_printf(msgpath, "%s/%s", mutt_buffer_string(path), de->d_name);
144           /* ensure this message was received since leaving this m */
145           if ((stat(mutt_buffer_string(msgpath), &st) == 0) &&
146               (mutt_file_stat_timespec_compare(&st, MUTT_STAT_CTIME, &m->last_visited) <= 0))
147           {
148             continue;
149           }
150         }
151         m->has_new = true;
152         check_new = false;
153         m->msg_new++;
154         if (!check_stats)
155           break;
156       }
157     }
158   }
159 
160   closedir(dirp);
161 
162 cleanup:
163   mutt_buffer_pool_release(&path);
164   mutt_buffer_pool_release(&msgpath);
165 }
166 
167 /**
168  * ch_compare - qsort() callback to sort characters
169  * @param a First  character to compare
170  * @param b Second character to compare
171  * @retval -1 a precedes b
172  * @retval  0 a and b are identical
173  * @retval  1 b precedes a
174  */
ch_compare(const void * a,const void * b)175 static int ch_compare(const void *a, const void *b)
176 {
177   return (int) (*((const char *) a) - *((const char *) b));
178 }
179 
180 /**
181  * maildir_gen_flags - Generate the Maildir flags for an email
182  * @param dest    Buffer for the result
183  * @param destlen Length of buffer
184  * @param e     Email
185  */
maildir_gen_flags(char * dest,size_t destlen,struct Email * e)186 void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
187 {
188   *dest = '\0';
189 
190   const char *flags = NULL;
191 
192   struct MaildirEmailData *edata = maildir_edata_get(e);
193   if (edata)
194     flags = edata->maildir_flags;
195 
196   /* The maildir specification requires that all files in the cur
197    * subdirectory have the :unique string appended, regardless of whether
198    * or not there are any flags.  If .old is set, we know that this message
199    * will end up in the cur directory, so we include it in the following
200    * test even though there is no associated flag.  */
201 
202   if (e->flagged || e->replied || e->read || e->deleted || e->old || flags)
203   {
204     char tmp[1024];
205     snprintf(tmp, sizeof(tmp), "%s%s%s%s%s", e->flagged ? "F" : "", e->replied ? "R" : "",
206              e->read ? "S" : "", e->deleted ? "T" : "", NONULL(flags));
207     if (flags)
208       qsort(tmp, strlen(tmp), 1, ch_compare);
209     snprintf(dest, destlen, ":2,%s", tmp);
210   }
211 }
212 
213 /**
214  * maildir_commit_message - Commit a message to a maildir folder
215  * @param m   Mailbox
216  * @param msg Message to commit
217  * @param e   Email
218  * @retval  0 Success
219  * @retval -1 Failure
220  *
221  * msg->path contains the file name of a file in tmp/. We take the
222  * flags from this file's name.
223  *
224  * m is the mail folder we commit to.
225  *
226  * e is a header structure to which we write the message's new
227  * file name.  This is used in the mh and maildir folder sync
228  * routines.  When this routine is invoked from mx_msg_commit(),
229  * e is NULL.
230  *
231  * msg->path looks like this:
232  *
233  *    tmp/{cur,new}.neomutt-HOSTNAME-PID-COUNTER:flags
234  *
235  * See also maildir_msg_open_new().
236  */
maildir_commit_message(struct Mailbox * m,struct Message * msg,struct Email * e)237 int maildir_commit_message(struct Mailbox *m, struct Message *msg, struct Email *e)
238 {
239   char subdir[4];
240   char suffix[16];
241   int rc = 0;
242 
243   if (mutt_file_fsync_close(&msg->fp))
244   {
245     mutt_perror(_("Could not flush message to disk"));
246     return -1;
247   }
248 
249   /* extract the subdir */
250   char *s = strrchr(msg->path, '/') + 1;
251   mutt_str_copy(subdir, s, 4);
252 
253   /* extract the flags */
254   s = strchr(s, ':');
255   if (s)
256     mutt_str_copy(suffix, s, sizeof(suffix));
257   else
258     suffix[0] = '\0';
259 
260   /* construct a new file name. */
261   struct Buffer *path = mutt_buffer_pool_get();
262   struct Buffer *full = mutt_buffer_pool_get();
263   while (true)
264   {
265     mutt_buffer_printf(path, "%s/%lld.R%" PRIu64 ".%s%s", subdir,
266                        (long long) mutt_date_epoch(), mutt_rand64(),
267                        NONULL(ShortHostname), suffix);
268     mutt_buffer_printf(full, "%s/%s", mailbox_path(m), mutt_buffer_string(path));
269 
270     mutt_debug(LL_DEBUG2, "renaming %s to %s\n", msg->path, mutt_buffer_string(full));
271 
272     if (mutt_file_safe_rename(msg->path, mutt_buffer_string(full)) == 0)
273     {
274       /* Adjust the mtime on the file to match the time at which this
275        * message was received.  Currently this is only set when copying
276        * messages between mailboxes, so we test to ensure that it is
277        * actually set.  */
278       if (msg->received)
279       {
280         struct utimbuf ut;
281         int rc_utime;
282 
283         ut.actime = msg->received;
284         ut.modtime = msg->received;
285         do
286         {
287           rc_utime = utime(mutt_buffer_string(full), &ut);
288         } while ((rc_utime == -1) && (errno == EINTR));
289         if (rc_utime == -1)
290         {
291           mutt_perror(
292               _("maildir_commit_message(): unable to set time on file"));
293           rc = -1;
294           goto cleanup;
295         }
296       }
297 
298 #ifdef USE_NOTMUCH
299       if (m->type == MUTT_NOTMUCH)
300         nm_update_filename(m, e->path, mutt_buffer_string(full), e);
301 #endif
302       if (e)
303         mutt_str_replace(&e->path, mutt_buffer_string(path));
304       mutt_str_replace(&msg->committed_path, mutt_buffer_string(full));
305       FREE(&msg->path);
306 
307       goto cleanup;
308     }
309     else if (errno != EEXIST)
310     {
311       mutt_perror(mailbox_path(m));
312       rc = -1;
313       goto cleanup;
314     }
315   }
316 
317 cleanup:
318   mutt_buffer_pool_release(&path);
319   mutt_buffer_pool_release(&full);
320 
321   return rc;
322 }
323 
324 /**
325  * maildir_rewrite_message - Sync a message in an MH folder
326  * @param m     Mailbox
327  * @param msgno Index number
328  * @retval  0 Success
329  * @retval -1 Error
330  */
maildir_rewrite_message(struct Mailbox * m,int msgno)331 int maildir_rewrite_message(struct Mailbox *m, int msgno)
332 {
333   if (!m || !m->emails || (msgno >= m->msg_count))
334     return -1;
335 
336   struct Email *e = m->emails[msgno];
337   if (!e)
338     return -1;
339 
340   bool restore = true;
341 
342   long old_body_offset = e->body->offset;
343   long old_body_length = e->body->length;
344   long old_hdr_lines = e->lines;
345 
346   struct Message *src = mx_msg_open(m, msgno);
347   struct Message *dest = mx_msg_open_new(m, e, MUTT_MSG_NO_FLAGS);
348   if (!src || !dest)
349     return -1;
350 
351   int rc = mutt_copy_message(dest->fp, e, src, MUTT_CM_UPDATE, CH_UPDATE | CH_UPDATE_LEN, 0);
352   if (rc == 0)
353   {
354     char oldpath[PATH_MAX];
355     char partpath[PATH_MAX];
356     snprintf(oldpath, sizeof(oldpath), "%s/%s", mailbox_path(m), e->path);
357     mutt_str_copy(partpath, e->path, sizeof(partpath));
358 
359     rc = maildir_commit_message(m, dest, e);
360 
361     if (rc == 0)
362     {
363       unlink(oldpath);
364       restore = false;
365     }
366   }
367   mx_msg_close(m, &src);
368   mx_msg_close(m, &dest);
369 
370   if ((rc == -1) && restore)
371   {
372     e->body->offset = old_body_offset;
373     e->body->length = old_body_length;
374     e->lines = old_hdr_lines;
375   }
376 
377   mutt_body_free(&e->body->parts);
378   return rc;
379 }
380 
381 /**
382  * maildir_sync_message - Sync an email to a Maildir folder
383  * @param m     Mailbox
384  * @param msgno Index number
385  * @retval  0 Success
386  * @retval -1 Error
387  */
maildir_sync_message(struct Mailbox * m,int msgno)388 int maildir_sync_message(struct Mailbox *m, int msgno)
389 {
390   if (!m || !m->emails || (msgno >= m->msg_count))
391     return -1;
392 
393   struct Email *e = m->emails[msgno];
394   if (!e)
395     return -1;
396 
397   struct Buffer *newpath = NULL;
398   struct Buffer *partpath = NULL;
399   struct Buffer *fullpath = NULL;
400   struct Buffer *oldpath = NULL;
401   char suffix[16];
402   int rc = 0;
403 
404   /* TODO: why the e->env check? */
405   if (e->attach_del || (e->env && e->env->changed))
406   {
407     /* when doing attachment deletion/rethreading, fall back to the MH case. */
408     if (maildir_rewrite_message(m, msgno) != 0)
409       return -1;
410     /* TODO: why the env check? */
411     if (e->env)
412       e->env->changed = 0;
413   }
414   else
415   {
416     /* we just have to rename the file. */
417 
418     char *p = strrchr(e->path, '/');
419     if (!p)
420     {
421       mutt_debug(LL_DEBUG1, "%s: unable to find subdir!\n", e->path);
422       return -1;
423     }
424     p++;
425     newpath = mutt_buffer_pool_get();
426     partpath = mutt_buffer_pool_get();
427     fullpath = mutt_buffer_pool_get();
428     oldpath = mutt_buffer_pool_get();
429 
430     mutt_buffer_strcpy(newpath, p);
431 
432     /* kill the previous flags */
433     p = strchr(newpath->data, ':');
434     if (p)
435     {
436       *p = '\0';
437       newpath->dptr = p; /* fix buffer up, just to be safe */
438     }
439 
440     maildir_gen_flags(suffix, sizeof(suffix), e);
441 
442     mutt_buffer_printf(partpath, "%s/%s%s", (e->read || e->old) ? "cur" : "new",
443                        mutt_buffer_string(newpath), suffix);
444     mutt_buffer_printf(fullpath, "%s/%s", mailbox_path(m), mutt_buffer_string(partpath));
445     mutt_buffer_printf(oldpath, "%s/%s", mailbox_path(m), e->path);
446 
447     if (mutt_str_equal(mutt_buffer_string(fullpath), mutt_buffer_string(oldpath)))
448     {
449       /* message hasn't really changed */
450       goto cleanup;
451     }
452 
453     /* record that the message is possibly marked as trashed on disk */
454     e->trash = e->deleted;
455 
456     if (rename(mutt_buffer_string(oldpath), mutt_buffer_string(fullpath)) != 0)
457     {
458       mutt_perror("rename");
459       rc = -1;
460       goto cleanup;
461     }
462     mutt_str_replace(&e->path, mutt_buffer_string(partpath));
463   }
464 
465 cleanup:
466   mutt_buffer_pool_release(&newpath);
467   mutt_buffer_pool_release(&partpath);
468   mutt_buffer_pool_release(&fullpath);
469   mutt_buffer_pool_release(&oldpath);
470 
471   return rc;
472 }
473 
474 /**
475  * maildir_update_mtime - Update our record of the Maildir modification time
476  * @param m Mailbox
477  */
maildir_update_mtime(struct Mailbox * m)478 void maildir_update_mtime(struct Mailbox *m)
479 {
480   char buf[PATH_MAX];
481   struct stat st = { 0 };
482   struct MaildirMboxData *mdata = maildir_mdata_get(m);
483 
484   snprintf(buf, sizeof(buf), "%s/%s", mailbox_path(m), "cur");
485   if (stat(buf, &st) == 0)
486     mutt_file_get_stat_timespec(&mdata->mtime_cur, &st, MUTT_STAT_MTIME);
487   snprintf(buf, sizeof(buf), "%s/%s", mailbox_path(m), "new");
488 
489   if (stat(buf, &st) == 0)
490     mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME);
491 }
492 
493 /**
494  * maildir_cmp_inode - Compare two Maildirs by inode number - Implements ::sort_t - @ingroup sort_api
495  */
maildir_cmp_inode(const void * a,const void * b)496 static int maildir_cmp_inode(const void *a, const void *b)
497 {
498   const struct MdEmail *ma = *(struct MdEmail **) a;
499   const struct MdEmail *mb = *(struct MdEmail **) b;
500 
501   return ma->inode - mb->inode;
502 }
503 
504 /**
505  * maildir_parse_dir - Read a Maildir mailbox
506  * @param[in]  m        Mailbox
507  * @param[out] mda      Array for results
508  * @param[in]  subdir   Subdirectory, e.g. 'new'
509  * @param[in]  progress Progress bar
510  * @retval  0 Success
511  * @retval -1 Error
512  * @retval -2 Aborted
513  */
maildir_parse_dir(struct Mailbox * m,struct MdEmailArray * mda,const char * subdir,struct Progress * progress)514 int maildir_parse_dir(struct Mailbox *m, struct MdEmailArray *mda,
515                       const char *subdir, struct Progress *progress)
516 {
517   struct dirent *de = NULL;
518   int rc = 0;
519   bool is_old = false;
520   struct MdEmail *entry = NULL;
521   struct Email *e = NULL;
522 
523   struct Buffer *buf = mutt_buffer_pool_get();
524 
525   mutt_buffer_printf(buf, "%s/%s", mailbox_path(m), subdir);
526   const bool c_mark_old = cs_subset_bool(NeoMutt->sub, "mark_old");
527   is_old = c_mark_old ? mutt_str_equal("cur", subdir) : false;
528 
529   DIR *dirp = opendir(mutt_buffer_string(buf));
530   if (!dirp)
531   {
532     rc = -1;
533     goto cleanup;
534   }
535 
536   while (((de = readdir(dirp))) && !SigInt)
537   {
538     if (*de->d_name == '.')
539       continue;
540 
541     /* FOO - really ignore the return value? */
542     mutt_debug(LL_DEBUG2, "queueing %s\n", de->d_name);
543 
544     e = email_new();
545     e->edata = maildir_edata_new();
546     e->edata_free = maildir_edata_free;
547 
548     e->old = is_old;
549     maildir_parse_flags(e, de->d_name);
550 
551     if (m->verbose && progress)
552       progress_update(progress, ARRAY_SIZE(mda) + 1, -1);
553 
554     mutt_buffer_printf(buf, "%s/%s", subdir, de->d_name);
555     e->path = mutt_buffer_strdup(buf);
556 
557     entry = maildir_entry_new();
558     entry->email = e;
559     entry->inode = de->d_ino;
560     ARRAY_ADD(mda, entry);
561   }
562 
563   closedir(dirp);
564 
565   if (SigInt)
566   {
567     SigInt = false;
568     return -2; /* action aborted */
569   }
570 
571   ARRAY_SORT(mda, maildir_cmp_inode);
572 
573 cleanup:
574   mutt_buffer_pool_release(&buf);
575 
576   return rc;
577 }
578 
579 /**
580  * maildir_hcache_keylen - Calculate the length of the Maildir path
581  * @param fn File name
582  * @retval num Length in bytes
583  *
584  * @note This length excludes the flags, which will vary
585  */
maildir_hcache_keylen(const char * fn)586 size_t maildir_hcache_keylen(const char *fn)
587 {
588   const char *p = strrchr(fn, ':');
589   return p ? (size_t) (p - fn) : mutt_str_len(fn);
590 }
591 
592 /**
593  * maildir_delayed_parsing - This function does the second parsing pass
594  * @param[in]  m   Mailbox
595  * @param[out] mda Maildir array to parse
596  * @param[in]  progress Progress bar
597  */
maildir_delayed_parsing(struct Mailbox * m,struct MdEmailArray * mda,struct Progress * progress)598 void maildir_delayed_parsing(struct Mailbox *m, struct MdEmailArray *mda,
599                              struct Progress *progress)
600 {
601   char fn[PATH_MAX];
602 
603 #ifdef USE_HCACHE
604   const char *const c_header_cache =
605       cs_subset_path(NeoMutt->sub, "header_cache");
606   struct HeaderCache *hc = mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
607 #endif
608 
609   struct MdEmail *md = NULL;
610   struct MdEmail **mdp = NULL;
611   ARRAY_FOREACH(mdp, mda)
612   {
613     md = *mdp;
614     if (!md || !md->email || md->header_parsed)
615       continue;
616 
617     if (m->verbose && progress)
618       progress_update(progress, ARRAY_FOREACH_IDX, -1);
619 
620     snprintf(fn, sizeof(fn), "%s/%s", mailbox_path(m), md->email->path);
621 
622 #ifdef USE_HCACHE
623     struct stat st_lastchanged = { 0 };
624     int rc = 0;
625     const bool c_maildir_header_cache_verify =
626         cs_subset_bool(NeoMutt->sub, "maildir_header_cache_verify");
627     if (c_maildir_header_cache_verify)
628     {
629       rc = stat(fn, &st_lastchanged);
630     }
631 
632     const char *key = md->email->path + 3;
633     size_t keylen = maildir_hcache_keylen(key);
634     struct HCacheEntry hce = mutt_hcache_fetch(hc, key, keylen, 0);
635 
636     if (hce.email && (rc == 0) && (st_lastchanged.st_mtime <= hce.uidvalidity))
637     {
638       hce.email->edata = maildir_edata_new();
639       hce.email->edata_free = maildir_edata_free;
640       hce.email->old = md->email->old;
641       hce.email->path = mutt_str_dup(md->email->path);
642       email_free(&md->email);
643       md->email = hce.email;
644       maildir_parse_flags(md->email, fn);
645     }
646     else
647 #endif
648     {
649       if (maildir_parse_message(m->type, fn, md->email->old, md->email))
650       {
651         md->header_parsed = true;
652 #ifdef USE_HCACHE
653         key = md->email->path + 3;
654         keylen = maildir_hcache_keylen(key);
655         mutt_hcache_store(hc, key, keylen, md->email, 0);
656 #endif
657       }
658       else
659         email_free(&md->email);
660     }
661   }
662 #ifdef USE_HCACHE
663   mutt_hcache_close(hc);
664 #endif
665 }
666 
667 /**
668  * maildir_read_dir - Read a Maildir style mailbox
669  * @param m      Mailbox
670  * @param subdir Subdir of the maildir mailbox to read from
671  * @retval  0 Success
672  * @retval -1 Failure
673  */
maildir_read_dir(struct Mailbox * m,const char * subdir)674 int maildir_read_dir(struct Mailbox *m, const char *subdir)
675 {
676   if (!m)
677     return -1;
678 
679   struct Progress *progress = NULL;
680 
681   if (m->verbose)
682   {
683     char msg[PATH_MAX];
684     snprintf(msg, sizeof(msg), _("Scanning %s..."), mailbox_path(m));
685     progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
686   }
687 
688   struct MaildirMboxData *mdata = maildir_mdata_get(m);
689   if (!mdata)
690   {
691     mdata = maildir_mdata_new();
692     m->mdata = mdata;
693     m->mdata_free = maildir_mdata_free;
694   }
695 
696   struct MdEmailArray mda = ARRAY_HEAD_INITIALIZER;
697   int rc = maildir_parse_dir(m, &mda, subdir, progress);
698   progress_free(&progress);
699   if (rc < 0)
700     return -1;
701 
702   if (m->verbose)
703   {
704     char msg[PATH_MAX];
705     snprintf(msg, sizeof(msg), _("Reading %s..."), mailbox_path(m));
706     progress = progress_new(msg, MUTT_PROGRESS_READ, ARRAY_SIZE(&mda));
707   }
708   maildir_delayed_parsing(m, &mda, progress);
709   progress_free(&progress);
710 
711   maildir_move_to_mailbox(m, &mda);
712 
713   if (!mdata->mh_umask)
714     mdata->mh_umask = mh_umask(m);
715 
716   return 0;
717 }
718 
719 /**
720  * maildir_canon_filename - Generate the canonical filename for a Maildir folder
721  * @param dest   Buffer for the result
722  * @param src    Buffer containing source filename
723  *
724  * @note         maildir filename is defined as: \<base filename\>:2,\<flags\>
725  *               but \<base filename\> may contain additional comma separated
726  *               fields.
727  */
maildir_canon_filename(struct Buffer * dest,const char * src)728 void maildir_canon_filename(struct Buffer *dest, const char *src)
729 {
730   if (!dest || !src)
731     return;
732 
733   char *t = strrchr(src, '/');
734   if (t)
735     src = t + 1;
736 
737   mutt_buffer_strcpy(dest, src);
738   char *u = strpbrk(dest->data, ",:");
739   if (u)
740   {
741     *u = '\0';
742     dest->dptr = u;
743   }
744 }
745 
746 /**
747  * maildir_open_find_message_dir - Find a message in a maildir folder
748  * @param[in]  folder    Base folder
749  * @param[in]  unique    Unique part of filename
750  * @param[in]  subfolder Subfolder to search, e.g. 'cur'
751  * @param[out] newname   File's new name
752  * @retval ptr File handle
753  *
754  * These functions try to find a message in a maildir folder when it
755  * has moved under our feet.  Note that this code is rather expensive, but
756  * then again, it's called rarely.
757  */
maildir_open_find_message_dir(const char * folder,const char * unique,const char * subfolder,char ** newname)758 static FILE *maildir_open_find_message_dir(const char *folder, const char *unique,
759                                            const char *subfolder, char **newname)
760 {
761   struct Buffer *dir = mutt_buffer_pool_get();
762   struct Buffer *tunique = mutt_buffer_pool_get();
763   struct Buffer *fname = mutt_buffer_pool_get();
764 
765   struct dirent *de = NULL;
766 
767   FILE *fp = NULL;
768   int oe = ENOENT;
769 
770   mutt_buffer_printf(dir, "%s/%s", folder, subfolder);
771 
772   DIR *dp = opendir(mutt_buffer_string(dir));
773   if (!dp)
774   {
775     errno = ENOENT;
776     goto cleanup;
777   }
778 
779   while ((de = readdir(dp)))
780   {
781     maildir_canon_filename(tunique, de->d_name);
782 
783     if (mutt_str_equal(mutt_buffer_string(tunique), unique))
784     {
785       mutt_buffer_printf(fname, "%s/%s/%s", folder, subfolder, de->d_name);
786       fp = fopen(mutt_buffer_string(fname), "r");
787       oe = errno;
788       break;
789     }
790   }
791 
792   closedir(dp);
793 
794   if (newname && fp)
795     *newname = mutt_buffer_strdup(fname);
796 
797   errno = oe;
798 
799 cleanup:
800   mutt_buffer_pool_release(&dir);
801   mutt_buffer_pool_release(&tunique);
802   mutt_buffer_pool_release(&fname);
803 
804   return fp;
805 }
806 
807 /**
808  * maildir_parse_flags - Parse Maildir file flags
809  * @param e    Email
810  * @param path Path to email file
811  */
maildir_parse_flags(struct Email * e,const char * path)812 void maildir_parse_flags(struct Email *e, const char *path)
813 {
814   char *q = NULL;
815 
816   e->flagged = false;
817   e->read = false;
818   e->replied = false;
819 
820   struct MaildirEmailData *edata = maildir_edata_get(e);
821 
822   char *p = strrchr(path, ':');
823   if (p && mutt_str_startswith(p + 1, "2,"))
824   {
825     p += 3;
826 
827     mutt_str_replace(&edata->maildir_flags, p);
828     q = edata->maildir_flags;
829 
830     while (*p)
831     {
832       switch (*p)
833       {
834         case 'F':
835           e->flagged = true;
836           break;
837 
838         case 'R': /* replied */
839           e->replied = true;
840           break;
841 
842         case 'S': /* seen */
843           e->read = true;
844           break;
845 
846         case 'T': /* trashed */
847         {
848           const bool c_flag_safe = cs_subset_bool(NeoMutt->sub, "flag_safe");
849           if (!e->flagged || !c_flag_safe)
850           {
851             e->trash = true;
852             e->deleted = true;
853           }
854           break;
855         }
856 
857         default:
858           *q++ = *p;
859           break;
860       }
861       p++;
862     }
863   }
864 
865   if (q == edata->maildir_flags)
866     FREE(&edata->maildir_flags);
867   else if (q)
868     *q = '\0';
869 }
870 
871 /**
872  * maildir_parse_stream - Parse a Maildir message
873  * @param type   Mailbox type, e.g. #MUTT_MAILDIR
874  * @param fp     Message file handle
875  * @param fname  Message filename
876  * @param is_old true, if the email is old (read)
877  * @param e      Email
878  * @retval ptr Populated Email
879  * @retval NULL on error
880  *
881  * Actually parse a maildir message.  This may also be used to fill
882  * out a fake header structure generated by lazy maildir parsing.
883  */
maildir_parse_stream(enum MailboxType type,FILE * fp,const char * fname,bool is_old,struct Email * e)884 struct Email *maildir_parse_stream(enum MailboxType type, FILE *fp,
885                                    const char *fname, bool is_old, struct Email *e)
886 {
887   const long size = mutt_file_get_size_fp(fp);
888   if (size == 0)
889   {
890     return NULL;
891   }
892 
893   if (!e)
894   {
895     e = email_new();
896     e->edata = maildir_edata_new();
897     e->edata_free = maildir_edata_free;
898   }
899   e->env = mutt_rfc822_read_header(fp, e, false, false);
900 
901   if (!e->received)
902     e->received = e->date_sent;
903 
904   /* always update the length since we have fresh information available. */
905   e->body->length = size - e->body->offset;
906 
907   e->index = -1;
908 
909   if (type == MUTT_MAILDIR)
910   {
911     /* maildir stores its flags in the filename, so ignore the
912      * flags in the header of the message */
913 
914     e->old = is_old;
915     maildir_parse_flags(e, fname);
916   }
917   return e;
918 }
919 
920 /**
921  * maildir_parse_message - Actually parse a maildir message
922  * @param type   Mailbox type, e.g. #MUTT_MAILDIR
923  * @param fname  Message filename
924  * @param is_old true, if the email is old (read)
925  * @param e      Email to populate (OPTIONAL)
926  * @retval ptr Populated Email
927  *
928  * This may also be used to fill out a fake header structure generated by lazy
929  * maildir parsing.
930  */
maildir_parse_message(enum MailboxType type,const char * fname,bool is_old,struct Email * e)931 struct Email *maildir_parse_message(enum MailboxType type, const char *fname,
932                                     bool is_old, struct Email *e)
933 {
934   FILE *fp = fopen(fname, "r");
935   if (!fp)
936     return NULL;
937 
938   struct Email *e_res = maildir_parse_stream(type, fp, fname, is_old, e);
939   mutt_file_fclose(&fp);
940   return e_res;
941 }
942 
943 /**
944  * maildir_sync_mailbox_message - Save changes to the mailbox
945  * @param m     Mailbox
946  * @param msgno Index number
947  * @param hc    Header cache handle
948  * @retval true Success
949  * @retval false Error
950  */
maildir_sync_mailbox_message(struct Mailbox * m,int msgno,struct HeaderCache * hc)951 bool maildir_sync_mailbox_message(struct Mailbox *m, int msgno, struct HeaderCache *hc)
952 {
953   struct Email *e = m->emails[msgno];
954   if (!e)
955     return false;
956 
957   const bool c_maildir_trash = cs_subset_bool(NeoMutt->sub, "maildir_trash");
958   if (e->deleted && !c_maildir_trash)
959   {
960     char path[PATH_MAX];
961     snprintf(path, sizeof(path), "%s/%s", mailbox_path(m), e->path);
962 #ifdef USE_HCACHE
963     if (hc)
964     {
965       const char *key = e->path + 3;
966       size_t keylen = maildir_hcache_keylen(key);
967       mutt_hcache_delete_record(hc, key, keylen);
968     }
969 #endif
970     unlink(path);
971   }
972   else if (e->changed || e->attach_del ||
973            ((c_maildir_trash || e->trash) && (e->deleted != e->trash)))
974   {
975     if (maildir_sync_message(m, msgno) == -1)
976       return false;
977   }
978 
979 #ifdef USE_HCACHE
980   if (hc && e->changed)
981   {
982     const char *key = e->path + 3;
983     size_t keylen = maildir_hcache_keylen(key);
984     mutt_hcache_store(hc, key, keylen, e, 0);
985   }
986 #endif
987 
988   return true;
989 }
990 
991 /**
992  * maildir_open_find_message - Find a new
993  * @param[in]  folder  Maildir path
994  * @param[in]  msg     Email path
995  * @param[out] newname New name, if it has moved
996  * @retval ptr File handle
997  */
maildir_open_find_message(const char * folder,const char * msg,char ** newname)998 FILE *maildir_open_find_message(const char *folder, const char *msg, char **newname)
999 {
1000   static unsigned int new_hits = 0, cur_hits = 0; /* simple dynamic optimization */
1001 
1002   struct Buffer *unique = mutt_buffer_pool_get();
1003   maildir_canon_filename(unique, msg);
1004 
1005   FILE *fp = maildir_open_find_message_dir(folder, mutt_buffer_string(unique),
1006                                            (new_hits > cur_hits) ? "new" : "cur", newname);
1007   if (fp || (errno != ENOENT))
1008   {
1009     if ((new_hits < UINT_MAX) && (cur_hits < UINT_MAX))
1010     {
1011       new_hits += ((new_hits > cur_hits) ? 1 : 0);
1012       cur_hits += ((new_hits > cur_hits) ? 0 : 1);
1013     }
1014 
1015     goto cleanup;
1016   }
1017   fp = maildir_open_find_message_dir(folder, mutt_buffer_string(unique),
1018                                      (new_hits > cur_hits) ? "cur" : "new", newname);
1019   if (fp || (errno != ENOENT))
1020   {
1021     if ((new_hits < UINT_MAX) && (cur_hits < UINT_MAX))
1022     {
1023       new_hits += ((new_hits > cur_hits) ? 0 : 1);
1024       cur_hits += ((new_hits > cur_hits) ? 1 : 0);
1025     }
1026 
1027     goto cleanup;
1028   }
1029 
1030   fp = NULL;
1031 
1032 cleanup:
1033   mutt_buffer_pool_release(&unique);
1034 
1035   return fp;
1036 }
1037 
1038 /**
1039  * maildir_check_empty - Is the mailbox empty
1040  * @param path Mailbox to check
1041  * @retval 1 Mailbox is empty
1042  * @retval 0 Mailbox contains mail
1043  * @retval -1 Error
1044  */
maildir_check_empty(const char * path)1045 int maildir_check_empty(const char *path)
1046 {
1047   DIR *dp = NULL;
1048   struct dirent *de = NULL;
1049   int rc = 1; /* assume empty until we find a message */
1050   char realpath[PATH_MAX];
1051   int iter = 0;
1052 
1053   /* Strategy here is to look for any file not beginning with a period */
1054 
1055   do
1056   {
1057     /* we do "cur" on the first iteration since it's more likely that we'll
1058      * find old messages without having to scan both subdirs */
1059     snprintf(realpath, sizeof(realpath), "%s/%s", path, (iter == 0) ? "cur" : "new");
1060     dp = opendir(realpath);
1061     if (!dp)
1062       return -1;
1063     while ((de = readdir(dp)))
1064     {
1065       if (*de->d_name != '.')
1066       {
1067         rc = 0;
1068         break;
1069       }
1070     }
1071     closedir(dp);
1072     iter++;
1073   } while (rc && iter < 2);
1074 
1075   return rc;
1076 }
1077 
1078 /**
1079  * maildir_ac_owns_path - Check whether an Account own a Mailbox path - Implements MxOps::ac_owns_path() - @ingroup mx_ac_owns_path
1080  */
maildir_ac_owns_path(struct Account * a,const char * path)1081 bool maildir_ac_owns_path(struct Account *a, const char *path)
1082 {
1083   return true;
1084 }
1085 
1086 /**
1087  * maildir_ac_add - Add a Mailbox to an Account - Implements MxOps::ac_add() - @ingroup mx_ac_add
1088  */
maildir_ac_add(struct Account * a,struct Mailbox * m)1089 bool maildir_ac_add(struct Account *a, struct Mailbox *m)
1090 {
1091   return true;
1092 }
1093 
1094 /**
1095  * maildir_mbox_open - Open a Mailbox - Implements MxOps::mbox_open() - @ingroup mx_mbox_open
1096  */
maildir_mbox_open(struct Mailbox * m)1097 static enum MxOpenReturns maildir_mbox_open(struct Mailbox *m)
1098 {
1099   /* maildir looks sort of like MH, except that there are two subdirectories
1100    * of the main folder path from which to read messages */
1101   if ((maildir_read_dir(m, "new") == -1) || (maildir_read_dir(m, "cur") == -1))
1102     return MX_OPEN_ERROR;
1103 
1104   return MX_OPEN_OK;
1105 }
1106 
1107 /**
1108  * maildir_mbox_open_append - Open a Mailbox for appending - Implements MxOps::mbox_open_append() - @ingroup mx_mbox_open_append
1109  */
maildir_mbox_open_append(struct Mailbox * m,OpenMailboxFlags flags)1110 static bool maildir_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
1111 {
1112   if (!(flags & (MUTT_APPEND | MUTT_APPENDNEW | MUTT_NEWFOLDER)))
1113   {
1114     return true;
1115   }
1116 
1117   errno = 0;
1118   if ((mutt_file_mkdir(mailbox_path(m), S_IRWXU) != 0) && (errno != EEXIST))
1119   {
1120     mutt_perror(mailbox_path(m));
1121     return false;
1122   }
1123 
1124   char tmp[PATH_MAX];
1125   snprintf(tmp, sizeof(tmp), "%s/cur", mailbox_path(m));
1126   errno = 0;
1127   if ((mkdir(tmp, S_IRWXU) != 0) && (errno != EEXIST))
1128   {
1129     mutt_perror(tmp);
1130     rmdir(mailbox_path(m));
1131     return false;
1132   }
1133 
1134   snprintf(tmp, sizeof(tmp), "%s/new", mailbox_path(m));
1135   errno = 0;
1136   if ((mkdir(tmp, S_IRWXU) != 0) && (errno != EEXIST))
1137   {
1138     mutt_perror(tmp);
1139     snprintf(tmp, sizeof(tmp), "%s/cur", mailbox_path(m));
1140     rmdir(tmp);
1141     rmdir(mailbox_path(m));
1142     return false;
1143   }
1144 
1145   snprintf(tmp, sizeof(tmp), "%s/tmp", mailbox_path(m));
1146   errno = 0;
1147   if ((mkdir(tmp, S_IRWXU) != 0) && (errno != EEXIST))
1148   {
1149     mutt_perror(tmp);
1150     snprintf(tmp, sizeof(tmp), "%s/cur", mailbox_path(m));
1151     rmdir(tmp);
1152     snprintf(tmp, sizeof(tmp), "%s/new", mailbox_path(m));
1153     rmdir(tmp);
1154     rmdir(mailbox_path(m));
1155     return false;
1156   }
1157 
1158   return true;
1159 }
1160 
1161 /**
1162  * maildir_mbox_check - Check for new mail - Implements MxOps::mbox_check() - @ingroup mx_mbox_check
1163  *
1164  * This function handles arrival of new mail and reopening of maildir folders.
1165  * The basic idea here is we check to see if either the new or cur
1166  * subdirectories have changed, and if so, we scan them for the list of files.
1167  * We check for newly added messages, and then merge the flags messages we
1168  * already knew about.  We don't treat either subdirectory differently, as mail
1169  * could be copied directly into the cur directory from another agent.
1170  */
maildir_mbox_check(struct Mailbox * m)1171 enum MxStatus maildir_mbox_check(struct Mailbox *m)
1172 {
1173   struct stat st_new = { 0 }; /* status of the "new" subdirectory */
1174   struct stat st_cur = { 0 }; /* status of the "cur" subdirectory */
1175   int changed = MMC_NO_DIRS;  /* which subdirectories have changed */
1176   bool occult = false;        /* messages were removed from the mailbox */
1177   int num_new = 0;            /* number of new messages added to the mailbox */
1178   bool flags_changed = false; /* message flags were changed in the mailbox */
1179   struct HashTable *fnames = NULL; /* hash table for quickly looking up the base filename
1180                                  for a maildir message */
1181   struct MaildirMboxData *mdata = maildir_mdata_get(m);
1182 
1183   /* XXX seems like this check belongs in mx_mbox_check() rather than here.  */
1184   const bool c_check_new = cs_subset_bool(NeoMutt->sub, "check_new");
1185   if (!c_check_new)
1186     return MX_STATUS_OK;
1187 
1188   struct Buffer *buf = mutt_buffer_pool_get();
1189   mutt_buffer_printf(buf, "%s/new", mailbox_path(m));
1190   if (stat(mutt_buffer_string(buf), &st_new) == -1)
1191   {
1192     mutt_buffer_pool_release(&buf);
1193     return MX_STATUS_ERROR;
1194   }
1195 
1196   mutt_buffer_printf(buf, "%s/cur", mailbox_path(m));
1197   if (stat(mutt_buffer_string(buf), &st_cur) == -1)
1198   {
1199     mutt_buffer_pool_release(&buf);
1200     return MX_STATUS_ERROR;
1201   }
1202 
1203   /* determine which subdirectories need to be scanned */
1204   if (mutt_file_stat_timespec_compare(&st_new, MUTT_STAT_MTIME, &m->mtime) > 0)
1205     changed = MMC_NEW_DIR;
1206   if (mutt_file_stat_timespec_compare(&st_cur, MUTT_STAT_MTIME, &mdata->mtime_cur) > 0)
1207     changed |= MMC_CUR_DIR;
1208 
1209   if (changed == MMC_NO_DIRS)
1210   {
1211     mutt_buffer_pool_release(&buf);
1212     return MX_STATUS_OK; /* nothing to do */
1213   }
1214 
1215   /* Update the modification times on the mailbox.
1216    *
1217    * The monitor code notices changes in the open mailbox too quickly.
1218    * In practice, this sometimes leads to all the new messages not being
1219    * noticed during the SAME group of mtime stat updates.  To work around
1220    * the problem, don't update the stat times for a monitor caused check. */
1221 #ifdef USE_INOTIFY
1222   if (MonitorContextChanged)
1223     MonitorContextChanged = false;
1224   else
1225 #endif
1226   {
1227     mutt_file_get_stat_timespec(&mdata->mtime_cur, &st_cur, MUTT_STAT_MTIME);
1228     mutt_file_get_stat_timespec(&m->mtime, &st_new, MUTT_STAT_MTIME);
1229   }
1230 
1231   /* do a fast scan of just the filenames in
1232    * the subdirectories that have changed.  */
1233   struct MdEmailArray mda = ARRAY_HEAD_INITIALIZER;
1234   if (changed & MMC_NEW_DIR)
1235     maildir_parse_dir(m, &mda, "new", NULL);
1236   if (changed & MMC_CUR_DIR)
1237     maildir_parse_dir(m, &mda, "cur", NULL);
1238 
1239   /* we create a hash table keyed off the canonical (sans flags) filename
1240    * of each message we scanned.  This is used in the loop over the
1241    * existing messages below to do some correlation.  */
1242   fnames = mutt_hash_new(ARRAY_SIZE(&mda), MUTT_HASH_NO_FLAGS);
1243 
1244   struct MdEmail *md = NULL;
1245   struct MdEmail **mdp = NULL;
1246   ARRAY_FOREACH(mdp, &mda)
1247   {
1248     md = *mdp;
1249     maildir_canon_filename(buf, md->email->path);
1250     md->canon_fname = mutt_buffer_strdup(buf);
1251     mutt_hash_insert(fnames, md->canon_fname, md);
1252   }
1253 
1254   /* check for modifications and adjust flags */
1255   for (int i = 0; i < m->msg_count; i++)
1256   {
1257     struct Email *e = m->emails[i];
1258     if (!e)
1259       break;
1260 
1261     e->active = false;
1262     maildir_canon_filename(buf, e->path);
1263     md = mutt_hash_find(fnames, mutt_buffer_string(buf));
1264     if (md && md->email)
1265     {
1266       /* message already exists, merge flags */
1267       e->active = true;
1268 
1269       /* check to see if the message has moved to a different
1270        * subdirectory.  If so, update the associated filename.  */
1271       if (!mutt_str_equal(e->path, md->email->path))
1272         mutt_str_replace(&e->path, md->email->path);
1273 
1274       /* if the user hasn't modified the flags on this message, update
1275        * the flags we just detected.  */
1276       if (!e->changed)
1277         if (maildir_update_flags(m, e, md->email))
1278           flags_changed = true;
1279 
1280       if (e->deleted == e->trash)
1281       {
1282         if (e->deleted != md->email->deleted)
1283         {
1284           e->deleted = md->email->deleted;
1285           flags_changed = true;
1286         }
1287       }
1288       e->trash = md->email->trash;
1289 
1290       /* this is a duplicate of an existing email, so remove it */
1291       email_free(&md->email);
1292     }
1293     /* This message was not in the list of messages we just scanned.
1294      * Check to see if we have enough information to know if the
1295      * message has disappeared out from underneath us.  */
1296     else if (((changed & MMC_NEW_DIR) && mutt_strn_equal(e->path, "new/", 4)) ||
1297              ((changed & MMC_CUR_DIR) && mutt_strn_equal(e->path, "cur/", 4)))
1298     {
1299       /* This message disappeared, so we need to simulate a "reopen"
1300        * event.  We know it disappeared because we just scanned the
1301        * subdirectory it used to reside in.  */
1302       occult = true;
1303       e->deleted = true;
1304       e->purge = true;
1305     }
1306     else
1307     {
1308       /* This message resides in a subdirectory which was not
1309        * modified, so we assume that it is still present and
1310        * unchanged.  */
1311       e->active = true;
1312     }
1313   }
1314 
1315   /* destroy the file name hash */
1316   mutt_hash_free(&fnames);
1317 
1318   /* If we didn't just get new mail, update the tables. */
1319   if (occult)
1320     mailbox_changed(m, NT_MAILBOX_RESORT);
1321 
1322   /* do any delayed parsing we need to do. */
1323   maildir_delayed_parsing(m, &mda, NULL);
1324 
1325   /* Incorporate new messages */
1326   num_new = maildir_move_to_mailbox(m, &mda);
1327   if (num_new > 0)
1328   {
1329     mailbox_changed(m, NT_MAILBOX_INVALID);
1330     m->changed = true;
1331   }
1332 
1333   mutt_buffer_pool_release(&buf);
1334 
1335   ARRAY_FREE(&mda);
1336   if (occult)
1337     return MX_STATUS_REOPENED;
1338   if (num_new > 0)
1339     return MX_STATUS_NEW_MAIL;
1340   if (flags_changed)
1341     return MX_STATUS_FLAGS;
1342   return MX_STATUS_OK;
1343 }
1344 
1345 /**
1346  * maildir_mbox_check_stats - Check the Mailbox statistics - Implements MxOps::mbox_check_stats() - @ingroup mx_mbox_check_stats
1347  */
maildir_mbox_check_stats(struct Mailbox * m,uint8_t flags)1348 static enum MxStatus maildir_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1349 {
1350   bool check_stats = flags;
1351   bool check_new = true;
1352 
1353   if (check_stats)
1354   {
1355     m->msg_count = 0;
1356     m->msg_unread = 0;
1357     m->msg_flagged = 0;
1358     m->msg_new = 0;
1359   }
1360 
1361   maildir_check_dir(m, "new", check_new, check_stats);
1362 
1363   const bool c_maildir_check_cur =
1364       cs_subset_bool(NeoMutt->sub, "maildir_check_cur");
1365   check_new = !m->has_new && c_maildir_check_cur;
1366   if (check_new || check_stats)
1367     maildir_check_dir(m, "cur", check_new, check_stats);
1368 
1369   return m->msg_new ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1370 }
1371 
1372 /**
1373  * maildir_mbox_sync - Save changes to the Mailbox - Implements MxOps::mbox_sync() - @ingroup mx_mbox_sync
1374  * @retval enum #MxStatus
1375  *
1376  * @note The flag retvals come from a call to a backend sync function
1377  */
maildir_mbox_sync(struct Mailbox * m)1378 enum MxStatus maildir_mbox_sync(struct Mailbox *m)
1379 {
1380   enum MxStatus check = maildir_mbox_check(m);
1381   if (check == MX_STATUS_ERROR)
1382     return check;
1383 
1384   struct HeaderCache *hc = NULL;
1385 #ifdef USE_HCACHE
1386   const char *const c_header_cache =
1387       cs_subset_path(NeoMutt->sub, "header_cache");
1388   if (m->type == MUTT_MAILDIR)
1389     hc = mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
1390 #endif
1391 
1392   struct Progress *progress = NULL;
1393   if (m->verbose)
1394   {
1395     char msg[PATH_MAX];
1396     snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
1397     progress = progress_new(msg, MUTT_PROGRESS_WRITE, m->msg_count);
1398   }
1399 
1400   for (int i = 0; i < m->msg_count; i++)
1401   {
1402     if (m->verbose)
1403       progress_update(progress, i, -1);
1404 
1405     if (!maildir_sync_mailbox_message(m, i, hc))
1406     {
1407       progress_free(&progress);
1408       goto err;
1409     }
1410   }
1411   progress_free(&progress);
1412 
1413 #ifdef USE_HCACHE
1414   if (m->type == MUTT_MAILDIR)
1415     mutt_hcache_close(hc);
1416 #endif
1417 
1418   /* XXX race condition? */
1419 
1420   maildir_update_mtime(m);
1421 
1422   /* adjust indices */
1423 
1424   if (m->msg_deleted)
1425   {
1426     for (int i = 0, j = 0; i < m->msg_count; i++)
1427     {
1428       struct Email *e = m->emails[i];
1429       if (!e)
1430         break;
1431 
1432       const bool c_maildir_trash =
1433           cs_subset_bool(NeoMutt->sub, "maildir_trash");
1434       if (!e->deleted || c_maildir_trash)
1435         e->index = j++;
1436     }
1437   }
1438 
1439   return check;
1440 
1441 err:
1442 #ifdef USE_HCACHE
1443   if (m->type == MUTT_MAILDIR)
1444     mutt_hcache_close(hc);
1445 #endif
1446   return MX_STATUS_ERROR;
1447 }
1448 
1449 /**
1450  * maildir_mbox_close - Close a Mailbox - Implements MxOps::mbox_close() - @ingroup mx_mbox_close
1451  * @retval MX_STATUS_OK Always
1452  */
maildir_mbox_close(struct Mailbox * m)1453 enum MxStatus maildir_mbox_close(struct Mailbox *m)
1454 {
1455   return MX_STATUS_OK;
1456 }
1457 
1458 /**
1459  * maildir_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
1460  */
maildir_msg_open(struct Mailbox * m,struct Message * msg,int msgno)1461 static bool maildir_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
1462 {
1463   struct Email *e = m->emails[msgno];
1464   if (!e)
1465     return false;
1466 
1467   char path[PATH_MAX];
1468 
1469   snprintf(path, sizeof(path), "%s/%s", mailbox_path(m), e->path);
1470 
1471   msg->fp = fopen(path, "r");
1472   if (!msg->fp && (errno == ENOENT))
1473     msg->fp = maildir_open_find_message(mailbox_path(m), e->path, NULL);
1474 
1475   if (!msg->fp)
1476   {
1477     mutt_perror(path);
1478     mutt_debug(LL_DEBUG1, "fopen: %s: %s (errno %d)\n", path, strerror(errno), errno);
1479     return false;
1480   }
1481 
1482   return true;
1483 }
1484 
1485 /**
1486  * maildir_msg_open_new - Open a new message in a Mailbox - Implements MxOps::msg_open_new() - @ingroup mx_msg_open_new
1487  *
1488  * Open a new (temporary) message in a maildir folder.
1489  *
1490  * @note This uses _almost_ the maildir file name format,
1491  * but with a {cur,new} prefix.
1492  */
maildir_msg_open_new(struct Mailbox * m,struct Message * msg,const struct Email * e)1493 bool maildir_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
1494 {
1495   int fd;
1496   char path[PATH_MAX];
1497   char suffix[16];
1498   char subdir[16];
1499 
1500   if (e)
1501   {
1502     struct Email tmp = *e;
1503     tmp.deleted = false;
1504     tmp.edata = NULL;
1505     maildir_gen_flags(suffix, sizeof(suffix), &tmp);
1506   }
1507   else
1508     *suffix = '\0';
1509 
1510   if (e && (e->read || e->old))
1511     mutt_str_copy(subdir, "cur", sizeof(subdir));
1512   else
1513     mutt_str_copy(subdir, "new", sizeof(subdir));
1514 
1515   mode_t omask = umask(mh_umask(m));
1516   while (true)
1517   {
1518     snprintf(path, sizeof(path), "%s/tmp/%s.%lld.R%" PRIu64 ".%s%s",
1519              mailbox_path(m), subdir, (long long) mutt_date_epoch(),
1520              mutt_rand64(), NONULL(ShortHostname), suffix);
1521 
1522     mutt_debug(LL_DEBUG2, "Trying %s\n", path);
1523 
1524     fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0666);
1525     if (fd == -1)
1526     {
1527       if (errno != EEXIST)
1528       {
1529         umask(omask);
1530         mutt_perror(path);
1531         return false;
1532       }
1533     }
1534     else
1535     {
1536       mutt_debug(LL_DEBUG2, "Success\n");
1537       msg->path = mutt_str_dup(path);
1538       break;
1539     }
1540   }
1541   umask(omask);
1542 
1543   msg->fp = fdopen(fd, "w");
1544   if (!msg->fp)
1545   {
1546     FREE(&msg->path);
1547     close(fd);
1548     unlink(path);
1549     return false;
1550   }
1551 
1552   return true;
1553 }
1554 
1555 /**
1556  * maildir_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
1557  */
maildir_msg_commit(struct Mailbox * m,struct Message * msg)1558 static int maildir_msg_commit(struct Mailbox *m, struct Message *msg)
1559 {
1560   return maildir_commit_message(m, msg, NULL);
1561 }
1562 
1563 /**
1564  * maildir_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
1565  *
1566  * @note May also return EOF Failure, see errno
1567  */
maildir_msg_close(struct Mailbox * m,struct Message * msg)1568 int maildir_msg_close(struct Mailbox *m, struct Message *msg)
1569 {
1570   return mutt_file_fclose(&msg->fp);
1571 }
1572 
1573 /**
1574  * maildir_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache
1575  */
maildir_msg_save_hcache(struct Mailbox * m,struct Email * e)1576 static int maildir_msg_save_hcache(struct Mailbox *m, struct Email *e)
1577 {
1578   int rc = 0;
1579 #ifdef USE_HCACHE
1580   const char *const c_header_cache =
1581       cs_subset_path(NeoMutt->sub, "header_cache");
1582   struct HeaderCache *hc = mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
1583   char *key = e->path + 3;
1584   int keylen = maildir_hcache_keylen(key);
1585   rc = mutt_hcache_store(hc, key, keylen, e, 0);
1586   mutt_hcache_close(hc);
1587 #endif
1588   return rc;
1589 }
1590 
1591 /**
1592  * maildir_path_canon - Canonicalise a Mailbox path - Implements MxOps::path_canon() - @ingroup mx_path_canon
1593  */
maildir_path_canon(char * buf,size_t buflen)1594 int maildir_path_canon(char *buf, size_t buflen)
1595 {
1596   mutt_path_canon(buf, buflen, HomeDir, true);
1597   return 0;
1598 }
1599 
1600 /**
1601  * maildir_path_parent - Find the parent of a Mailbox path - Implements MxOps::path_parent() - @ingroup mx_path_parent
1602  */
maildir_path_parent(char * buf,size_t buflen)1603 int maildir_path_parent(char *buf, size_t buflen)
1604 {
1605   if (mutt_path_parent(buf))
1606     return 0;
1607 
1608   if (buf[0] == '~')
1609     mutt_path_canon(buf, buflen, HomeDir, true);
1610 
1611   if (mutt_path_parent(buf))
1612     return 0;
1613 
1614   return -1;
1615 }
1616 
1617 /**
1618  * maildir_path_pretty - Abbreviate a Mailbox path - Implements MxOps::path_pretty() - @ingroup mx_path_pretty
1619  */
maildir_path_pretty(char * buf,size_t buflen,const char * folder)1620 int maildir_path_pretty(char *buf, size_t buflen, const char *folder)
1621 {
1622   if (mutt_path_abbr_folder(buf, folder))
1623     return 0;
1624 
1625   if (mutt_path_pretty(buf, buflen, HomeDir, false))
1626     return 0;
1627 
1628   return -1;
1629 }
1630 
1631 /**
1632  * maildir_path_probe - Is this a Maildir Mailbox? - Implements MxOps::path_probe() - @ingroup mx_path_probe
1633  */
maildir_path_probe(const char * path,const struct stat * st)1634 static enum MailboxType maildir_path_probe(const char *path, const struct stat *st)
1635 {
1636   if (!st || !S_ISDIR(st->st_mode))
1637     return MUTT_UNKNOWN;
1638 
1639   char cur[PATH_MAX];
1640   snprintf(cur, sizeof(cur), "%s/cur", path);
1641 
1642   struct stat st_cur = { 0 };
1643   if ((stat(cur, &st_cur) == 0) && S_ISDIR(st_cur.st_mode))
1644     return MUTT_MAILDIR;
1645 
1646   return MUTT_UNKNOWN;
1647 }
1648 
1649 /**
1650  * MxMaildirOps - Maildir Mailbox - Implements ::MxOps - @ingroup mx_api
1651  */
1652 struct MxOps MxMaildirOps = {
1653   // clang-format off
1654   .type            = MUTT_MAILDIR,
1655   .name             = "maildir",
1656   .is_local         = true,
1657   .ac_owns_path     = maildir_ac_owns_path,
1658   .ac_add           = maildir_ac_add,
1659   .mbox_open        = maildir_mbox_open,
1660   .mbox_open_append = maildir_mbox_open_append,
1661   .mbox_check       = maildir_mbox_check,
1662   .mbox_check_stats = maildir_mbox_check_stats,
1663   .mbox_sync        = maildir_mbox_sync,
1664   .mbox_close       = maildir_mbox_close,
1665   .msg_open         = maildir_msg_open,
1666   .msg_open_new     = maildir_msg_open_new,
1667   .msg_commit       = maildir_msg_commit,
1668   .msg_close        = maildir_msg_close,
1669   .msg_padding_size = NULL,
1670   .msg_save_hcache  = maildir_msg_save_hcache,
1671   .tags_edit        = NULL,
1672   .tags_commit      = NULL,
1673   .path_probe       = maildir_path_probe,
1674   .path_canon       = maildir_path_canon,
1675   .path_pretty      = maildir_path_pretty,
1676   .path_parent      = maildir_path_parent,
1677   .path_is_empty    = maildir_check_empty,
1678   // clang-format on
1679 };
1680