Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/maildir/sequence.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * MH Mailbox Sequences
4
 *
5
 * @authors
6
 * Copyright (C) 2020 Richard Russon <rich@flatcap.org>
7
 *
8
 * @copyright
9
 * This program is free software: you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License as published by the Free Software
11
 * Foundation, either version 2 of the License, or (at your option) any later
12
 * version.
13
 *
14
 * This program is distributed in the hope that it will be useful, but WITHOUT
15
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17
 * details.
18
 *
19
 * You should have received a copy of the GNU General Public License along with
20
 * this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
/**
24
 * @page maildir_sequence MH Mailbox Sequences
25
 *
26
 * MH Mailbox Sequences
27
 */
28
29
#include "config.h"
30
#include <limits.h>
31
#include <stdio.h>
32
#include <string.h>
33
#include <sys/stat.h>
34
#include <unistd.h>
35
#include "private.h"
36
#include "mutt/lib.h"
37
#include "config/lib.h"
38
#include "email/lib.h"
39
#include "core/lib.h"
40
#include "sequence.h"
41
42
/**
43
 * mh_seq_alloc - Allocate more memory for sequences
44
 * @param mhs Existing sequences
45
 * @param i   Number required
46
 *
47
 * @note Memory is allocated in blocks of 128.
48
 */
49
static void mh_seq_alloc(struct MhSequences *mhs, int i)
50
0
{
51
0
  if ((i <= mhs->max) && mhs->flags)
52
0
    return;
53
54
0
  const int newmax = i + 128;
55
0
  int j = mhs->flags ? mhs->max + 1 : 0;
56
0
  mutt_mem_realloc(&mhs->flags, sizeof(mhs->flags[0]) * (newmax + 1));
57
0
  while (j <= newmax)
58
0
    mhs->flags[j++] = 0;
59
60
0
  mhs->max = newmax;
61
0
}
62
63
/**
64
 * mh_seq_free - Free some sequences
65
 * @param mhs Sequences to free
66
 */
67
void mh_seq_free(struct MhSequences *mhs)
68
0
{
69
0
  FREE(&mhs->flags);
70
0
}
71
72
/**
73
 * mh_seq_check - Get the flags for a given sequence
74
 * @param mhs Sequences
75
 * @param i   Index number required
76
 * @retval num Flags, see #MhSeqFlags
77
 */
78
MhSeqFlags mh_seq_check(struct MhSequences *mhs, int i)
79
0
{
80
0
  if (!mhs->flags || (i > mhs->max))
81
0
    return 0;
82
0
  return mhs->flags[i];
83
0
}
84
85
/**
86
 * mh_seq_set - Set a flag for a given sequence
87
 * @param mhs Sequences
88
 * @param i   Index number
89
 * @param f   Flags, see #MhSeqFlags
90
 * @retval num Resulting flags, see #MhSeqFlags
91
 */
92
static MhSeqFlags mh_seq_set(struct MhSequences *mhs, int i, MhSeqFlags f)
93
0
{
94
0
  mh_seq_alloc(mhs, i);
95
0
  mhs->flags[i] |= f;
96
0
  return mhs->flags[i];
97
0
}
98
99
/**
100
 * mh_seq_add_one - Update the flags for one sequence
101
 * @param m       Mailbox
102
 * @param n       Sequence number to update
103
 * @param unseen  Update the unseen sequence
104
 * @param flagged Update the flagged sequence
105
 * @param replied Update the replied sequence
106
 */
107
void mh_seq_add_one(struct Mailbox *m, int n, bool unseen, bool flagged, bool replied)
108
0
{
109
0
  bool unseen_done = false;
110
0
  bool flagged_done = false;
111
0
  bool replied_done = false;
112
113
0
  char *tmpfname = NULL;
114
0
  char sequences[PATH_MAX] = { 0 };
115
116
0
  char seq_unseen[256] = { 0 };
117
0
  char seq_replied[256] = { 0 };
118
0
  char seq_flagged[256] = { 0 };
119
120
0
  char *buf = NULL;
121
0
  size_t sz;
122
123
0
  FILE *fp_new = NULL;
124
0
  if (!mh_mkstemp(m, &fp_new, &tmpfname))
125
0
    return;
126
127
0
  const char *const c_mh_seq_unseen = cs_subset_string(NeoMutt->sub, "mh_seq_unseen");
128
0
  const char *const c_mh_seq_replied = cs_subset_string(NeoMutt->sub, "mh_seq_replied");
129
0
  const char *const c_mh_seq_flagged = cs_subset_string(NeoMutt->sub, "mh_seq_flagged");
130
0
  snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(c_mh_seq_unseen));
131
0
  snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(c_mh_seq_replied));
132
0
  snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(c_mh_seq_flagged));
133
134
0
  snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", mailbox_path(m));
135
0
  FILE *fp_old = fopen(sequences, "r");
136
0
  if (fp_old)
137
0
  {
138
0
    while ((buf = mutt_file_read_line(buf, &sz, fp_old, NULL, MUTT_RL_NO_FLAGS)))
139
0
    {
140
0
      if (unseen && mutt_strn_equal(buf, seq_unseen, mutt_str_len(seq_unseen)))
141
0
      {
142
0
        fprintf(fp_new, "%s %d\n", buf, n);
143
0
        unseen_done = true;
144
0
      }
145
0
      else if (flagged && mutt_strn_equal(buf, seq_flagged, mutt_str_len(seq_flagged)))
146
0
      {
147
0
        fprintf(fp_new, "%s %d\n", buf, n);
148
0
        flagged_done = true;
149
0
      }
150
0
      else if (replied && mutt_strn_equal(buf, seq_replied, mutt_str_len(seq_replied)))
151
0
      {
152
0
        fprintf(fp_new, "%s %d\n", buf, n);
153
0
        replied_done = true;
154
0
      }
155
0
      else
156
0
      {
157
0
        fprintf(fp_new, "%s\n", buf);
158
0
      }
159
0
    }
160
0
  }
161
0
  mutt_file_fclose(&fp_old);
162
0
  FREE(&buf);
163
164
0
  if (!unseen_done && unseen)
165
0
    fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_unseen), n);
166
0
  if (!flagged_done && flagged)
167
0
    fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_flagged), n);
168
0
  if (!replied_done && replied)
169
0
    fprintf(fp_new, "%s: %d\n", NONULL(c_mh_seq_replied), n);
170
171
0
  mutt_file_fclose(&fp_new);
172
173
0
  unlink(sequences);
174
0
  if (mutt_file_safe_rename(tmpfname, sequences) != 0)
175
0
    unlink(tmpfname);
176
177
0
  FREE(&tmpfname);
178
0
}
179
180
/**
181
 * mh_seq_write_one - Write a flag sequence to a file
182
 * @param fp  File to write to
183
 * @param mhs Sequence list
184
 * @param f   Flag, see #MhSeqFlags
185
 * @param tag string tag, e.g. "unseen"
186
 */
187
static void mh_seq_write_one(FILE *fp, struct MhSequences *mhs, MhSeqFlags f, const char *tag)
188
0
{
189
0
  fprintf(fp, "%s:", tag);
190
191
0
  int first = -1;
192
0
  int last = -1;
193
194
0
  for (int i = 0; i <= mhs->max; i++)
195
0
  {
196
0
    if ((mh_seq_check(mhs, i) & f))
197
0
    {
198
0
      if (first < 0)
199
0
        first = i;
200
0
      else
201
0
        last = i;
202
0
    }
203
0
    else if (first >= 0)
204
0
    {
205
0
      if (last < 0)
206
0
        fprintf(fp, " %d", first);
207
0
      else
208
0
        fprintf(fp, " %d-%d", first, last);
209
210
0
      first = -1;
211
0
      last = -1;
212
0
    }
213
0
  }
214
215
0
  if (first >= 0)
216
0
  {
217
0
    if (last < 0)
218
0
      fprintf(fp, " %d", first);
219
0
    else
220
0
      fprintf(fp, " %d-%d", first, last);
221
0
  }
222
223
0
  fputc('\n', fp);
224
0
}
225
226
/**
227
 * mh_seq_update - Update sequence numbers
228
 * @param m Mailbox
229
 *
230
 * XXX we don't currently remove deleted messages from sequences we don't know.
231
 * Should we?
232
 */
233
void mh_seq_update(struct Mailbox *m)
234
0
{
235
0
  char sequences[PATH_MAX] = { 0 };
236
0
  char *tmpfname = NULL;
237
0
  char *buf = NULL;
238
0
  char *p = NULL;
239
0
  size_t s;
240
0
  int seq_num = 0;
241
242
0
  int unseen = 0;
243
0
  int flagged = 0;
244
0
  int replied = 0;
245
246
0
  char seq_unseen[256] = { 0 };
247
0
  char seq_replied[256] = { 0 };
248
0
  char seq_flagged[256] = { 0 };
249
250
0
  struct MhSequences mhs = { 0 };
251
252
0
  const char *const c_mh_seq_unseen = cs_subset_string(NeoMutt->sub, "mh_seq_unseen");
253
0
  const char *const c_mh_seq_replied = cs_subset_string(NeoMutt->sub, "mh_seq_replied");
254
0
  const char *const c_mh_seq_flagged = cs_subset_string(NeoMutt->sub, "mh_seq_flagged");
255
0
  snprintf(seq_unseen, sizeof(seq_unseen), "%s:", NONULL(c_mh_seq_unseen));
256
0
  snprintf(seq_replied, sizeof(seq_replied), "%s:", NONULL(c_mh_seq_replied));
257
0
  snprintf(seq_flagged, sizeof(seq_flagged), "%s:", NONULL(c_mh_seq_flagged));
258
259
0
  FILE *fp_new = NULL;
260
0
  if (!mh_mkstemp(m, &fp_new, &tmpfname))
261
0
  {
262
    /* error message? */
263
0
    return;
264
0
  }
265
266
0
  snprintf(sequences, sizeof(sequences), "%s/.mh_sequences", mailbox_path(m));
267
268
  /* first, copy unknown sequences */
269
0
  FILE *fp_old = fopen(sequences, "r");
270
0
  if (fp_old)
271
0
  {
272
0
    while ((buf = mutt_file_read_line(buf, &s, fp_old, NULL, MUTT_RL_NO_FLAGS)))
273
0
    {
274
0
      if (mutt_str_startswith(buf, seq_unseen) || mutt_str_startswith(buf, seq_flagged) ||
275
0
          mutt_str_startswith(buf, seq_replied))
276
0
      {
277
0
        continue;
278
0
      }
279
280
0
      fprintf(fp_new, "%s\n", buf);
281
0
    }
282
0
  }
283
0
  mutt_file_fclose(&fp_old);
284
285
  /* now, update our unseen, flagged, and replied sequences */
286
0
  for (int i = 0; i < m->msg_count; i++)
287
0
  {
288
0
    struct Email *e = m->emails[i];
289
0
    if (!e)
290
0
      break;
291
292
0
    if (e->deleted)
293
0
      continue;
294
295
0
    p = strrchr(e->path, '/');
296
0
    if (p)
297
0
      p++;
298
0
    else
299
0
      p = e->path;
300
301
0
    if (!mutt_str_atoi_full(p, &seq_num))
302
0
      continue;
303
304
0
    if (!e->read)
305
0
    {
306
0
      mh_seq_set(&mhs, seq_num, MH_SEQ_UNSEEN);
307
0
      unseen++;
308
0
    }
309
0
    if (e->flagged)
310
0
    {
311
0
      mh_seq_set(&mhs, seq_num, MH_SEQ_FLAGGED);
312
0
      flagged++;
313
0
    }
314
0
    if (e->replied)
315
0
    {
316
0
      mh_seq_set(&mhs, seq_num, MH_SEQ_REPLIED);
317
0
      replied++;
318
0
    }
319
0
  }
320
321
  /* write out the new sequences */
322
0
  if (unseen)
323
0
    mh_seq_write_one(fp_new, &mhs, MH_SEQ_UNSEEN, NONULL(c_mh_seq_unseen));
324
0
  if (flagged)
325
0
    mh_seq_write_one(fp_new, &mhs, MH_SEQ_FLAGGED, NONULL(c_mh_seq_flagged));
326
0
  if (replied)
327
0
    mh_seq_write_one(fp_new, &mhs, MH_SEQ_REPLIED, NONULL(c_mh_seq_replied));
328
329
0
  mh_seq_free(&mhs);
330
331
  /* try to commit the changes - no guarantee here */
332
0
  mutt_file_fclose(&fp_new);
333
334
0
  unlink(sequences);
335
0
  if (mutt_file_safe_rename(tmpfname, sequences) != 0)
336
0
  {
337
    /* report an error? */
338
0
    unlink(tmpfname);
339
0
  }
340
341
0
  FREE(&tmpfname);
342
0
}
343
344
/**
345
 * mh_seq_read_token - Parse a number, or number range
346
 * @param t     String to parse
347
 * @param first First number
348
 * @param last  Last number (if a range, first number if not)
349
 * @retval  0 Success
350
 * @retval -1 Error
351
 */
352
static int mh_seq_read_token(char *t, int *first, int *last)
353
0
{
354
0
  char *p = strchr(t, '-');
355
0
  if (p)
356
0
  {
357
0
    *p++ = '\0';
358
0
    if (!mutt_str_atoi_full(t, first) || !mutt_str_atoi_full(p, last))
359
0
      return -1;
360
0
  }
361
0
  else
362
0
  {
363
0
    if (!mutt_str_atoi_full(t, first))
364
0
      return -1;
365
0
    *last = *first;
366
0
  }
367
0
  return 0;
368
0
}
369
370
/**
371
 * mh_seq_read - Read a set of MH sequences
372
 * @param mhs  Existing sequences
373
 * @param path File to read from
374
 * @retval  0 Success
375
 * @retval -1 Error
376
 */
377
int mh_seq_read(struct MhSequences *mhs, const char *path)
378
0
{
379
0
  char *buf = NULL;
380
0
  size_t sz = 0;
381
382
0
  MhSeqFlags flags;
383
0
  int first, last, rc = 0;
384
385
0
  char pathname[PATH_MAX] = { 0 };
386
0
  snprintf(pathname, sizeof(pathname), "%s/.mh_sequences", path);
387
388
0
  FILE *fp = fopen(pathname, "r");
389
0
  if (!fp)
390
0
    return 0; /* yes, ask callers to silently ignore the error */
391
392
0
  const char *const c_mh_seq_unseen = cs_subset_string(NeoMutt->sub, "mh_seq_unseen");
393
0
  const char *const c_mh_seq_flagged = cs_subset_string(NeoMutt->sub, "mh_seq_flagged");
394
0
  const char *const c_mh_seq_replied = cs_subset_string(NeoMutt->sub, "mh_seq_replied");
395
0
  while ((buf = mutt_file_read_line(buf, &sz, fp, NULL, MUTT_RL_NO_FLAGS)))
396
0
  {
397
0
    char *t = strtok(buf, " \t:");
398
0
    if (!t)
399
0
      continue;
400
401
0
    if (mutt_str_equal(t, c_mh_seq_unseen))
402
0
      flags = MH_SEQ_UNSEEN;
403
0
    else if (mutt_str_equal(t, c_mh_seq_flagged))
404
0
      flags = MH_SEQ_FLAGGED;
405
0
    else if (mutt_str_equal(t, c_mh_seq_replied))
406
0
      flags = MH_SEQ_REPLIED;
407
0
    else /* unknown sequence */
408
0
      continue;
409
410
0
    while ((t = strtok(NULL, " \t:")))
411
0
    {
412
0
      if (mh_seq_read_token(t, &first, &last) < 0)
413
0
      {
414
0
        mh_seq_free(mhs);
415
0
        rc = -1;
416
0
        goto out;
417
0
      }
418
0
      for (; first <= last; first++)
419
0
        mh_seq_set(mhs, first, flags);
420
0
    }
421
0
  }
422
423
0
  rc = 0;
424
425
0
out:
426
0
  FREE(&buf);
427
0
  mutt_file_fclose(&fp);
428
0
  return rc;
429
0
}
430
431
/**
432
 * mh_seq_changed - Has the mailbox changed
433
 * @param m Mailbox
434
 * @retval 1 mh_sequences last modification time is more recent than the last visit to this mailbox
435
 * @retval 0 modification time is older
436
 * @retval -1 Error
437
 */
438
int mh_seq_changed(struct Mailbox *m)
439
0
{
440
0
  char path[PATH_MAX] = { 0 };
441
0
  struct stat st = { 0 };
442
443
0
  if ((snprintf(path, sizeof(path), "%s/.mh_sequences", mailbox_path(m)) < sizeof(path)) &&
444
0
      (stat(path, &st) == 0))
445
0
  {
446
0
    return (mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->last_visited) > 0);
447
0
  }
448
0
  return -1;
449
0
}