Coverage Report

Created: 2023-09-25 07:17

/src/neomutt/attach/dlg_attach.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Attachment Selection Dialog
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 1999-2006 Thomas Roessler <roessler@does-not-exist.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 attach_dlg_attach Attachment Selection Dialog
26
 *
27
 * The Attachment Selection Dialog lets the user select an email attachment.
28
 *
29
 * This is a @ref gui_simple
30
 *
31
 * ## Windows
32
 *
33
 * | Name                        | Type              | See Also         |
34
 * | :-------------------------- | :---------------- | :--------------- |
35
 * | Attachment Selection Dialog | WT_DLG_ATTACHMENT | dlg_attachment() |
36
 *
37
 * **Parent**
38
 * - @ref gui_dialog
39
 *
40
 * **Children**
41
 * - See: @ref gui_simple
42
 *
43
 * ## Data
44
 * - #Menu
45
 * - #Menu::mdata
46
 * - #AttachCtx
47
 *
48
 * The @ref gui_simple holds a Menu.  The Attachment Selection Dialog stores
49
 * its data (#AttachCtx) in Menu::mdata.
50
 *
51
 * ## Events
52
 *
53
 * Once constructed, it is controlled by the following events:
54
 *
55
 * | Event Type  | Handler                  |
56
 * | :---------- | :----------------------- |
57
 * | #NT_CONFIG  | attach_config_observer() |
58
 * | #NT_WINDOW  | attach_window_observer() |
59
 *
60
 * The Attachment Selection Dialog doesn't have any specific colours, so it
61
 * doesn't need to support #NT_COLOR.
62
 *
63
 * The Attachment Selection Dialog does not implement MuttWindow::recalc() or
64
 * MuttWindow::repaint().
65
 *
66
 * Some other events are handled by the @ref gui_simple.
67
 */
68
69
#include "config.h"
70
#include <stdbool.h>
71
#include <stdint.h>
72
#include <stdio.h>
73
#include "mutt/lib.h"
74
#include "config/lib.h"
75
#include "email/lib.h"
76
#include "core/lib.h"
77
#include "gui/lib.h"
78
#include "key/lib.h"
79
#include "menu/lib.h"
80
#include "attach.h"
81
#include "attachments.h"
82
#include "format_flags.h"
83
#include "functions.h"
84
#include "hdrline.h"
85
#include "hook.h"
86
#include "mutt_logging.h"
87
#include "muttlib.h"
88
#include "mview.h"
89
#include "private_data.h"
90
#include "recvattach.h"
91
92
/// Help Bar for the Attachment selection dialog
93
static const struct Mapping AttachmentHelp[] = {
94
  // clang-format off
95
  { N_("Exit"),  OP_EXIT },
96
  { N_("Save"),  OP_ATTACHMENT_SAVE },
97
  { N_("Pipe"),  OP_PIPE },
98
  { N_("Print"), OP_ATTACHMENT_PRINT },
99
  { N_("Help"),  OP_HELP },
100
  { NULL, 0 },
101
  // clang-format on
102
};
103
104
/**
105
 * attach_config_observer - Notification that a Config Variable has changed - Implements ::observer_t - @ingroup observer_api
106
 *
107
 * The Address Book Window is affected by changes to `$sort_attach`.
108
 */
109
static int attach_config_observer(struct NotifyCallback *nc)
110
0
{
111
0
  if (nc->event_type != NT_CONFIG)
112
0
    return 0;
113
0
  if (!nc->global_data || !nc->event_data)
114
0
    return -1;
115
116
0
  struct EventConfig *ev_c = nc->event_data;
117
118
0
  if (!mutt_str_equal(ev_c->name, "attach_format") && !mutt_str_equal(ev_c->name, "message_format"))
119
0
    return 0;
120
121
0
  struct Menu *menu = nc->global_data;
122
0
  menu_queue_redraw(menu, MENU_REDRAW_FULL);
123
0
  mutt_debug(LL_DEBUG5, "config done, request WA_RECALC, MENU_REDRAW_FULL\n");
124
125
0
  return 0;
126
0
}
127
128
/**
129
 * attach_format_str - Format a string for the attachment menu - Implements ::format_t - @ingroup expando_api
130
 *
131
 * | Expando | Description
132
 * | :------ | :-------------------------------------------------------
133
 * | \%C     | Character set
134
 * | \%c     | Character set: convert?
135
 * | \%D     | Deleted flag
136
 * | \%d     | Description
137
 * | \%e     | MIME content-transfer-encoding
138
 * | \%f     | Filename
139
 * | \%F     | Filename for content-disposition header
140
 * | \%I     | Content-disposition, either I (inline) or A (attachment)
141
 * | \%m     | Major MIME type
142
 * | \%M     | MIME subtype
143
 * | \%n     | Attachment number
144
 * | \%Q     | 'Q', if MIME part qualifies for attachment counting
145
 * | \%s     | Size
146
 * | \%t     | Tagged flag
147
 * | \%T     | Tree chars
148
 * | \%u     | Unlink
149
 * | \%X     | Number of qualifying MIME parts in this part and its children
150
 */
151
const char *attach_format_str(char *buf, size_t buflen, size_t col, int cols, char op,
152
                              const char *src, const char *prec, const char *if_str,
153
                              const char *else_str, intptr_t data, MuttFormatFlags flags)
154
0
{
155
0
  char fmt[128] = { 0 };
156
0
  char charset[128] = { 0 };
157
0
  struct AttachPtr *aptr = (struct AttachPtr *) data;
158
0
  bool optional = (flags & MUTT_FORMAT_OPTIONAL);
159
160
0
  switch (op)
161
0
  {
162
0
    case 'C':
163
0
      if (!optional)
164
0
      {
165
0
        if (mutt_is_text_part(aptr->body) &&
166
0
            mutt_body_get_charset(aptr->body, charset, sizeof(charset)))
167
0
        {
168
0
          mutt_format_s(buf, buflen, prec, charset);
169
0
        }
170
0
        else
171
0
        {
172
0
          mutt_format_s(buf, buflen, prec, "");
173
0
        }
174
0
      }
175
0
      else if (!mutt_is_text_part(aptr->body) ||
176
0
               !mutt_body_get_charset(aptr->body, charset, sizeof(charset)))
177
0
      {
178
0
        optional = false;
179
0
      }
180
0
      break;
181
0
    case 'c':
182
      /* XXX */
183
0
      if (!optional)
184
0
      {
185
0
        snprintf(fmt, sizeof(fmt), "%%%sc", prec);
186
0
        snprintf(buf, buflen, fmt,
187
0
                 ((aptr->body->type != TYPE_TEXT) || aptr->body->noconv) ? 'n' : 'c');
188
0
      }
189
0
      else if ((aptr->body->type != TYPE_TEXT) || aptr->body->noconv)
190
0
      {
191
0
        optional = false;
192
0
      }
193
0
      break;
194
0
    case 'd':
195
0
    {
196
0
      const char *const c_message_format = cs_subset_string(NeoMutt->sub, "message_format");
197
0
      if (!optional)
198
0
      {
199
0
        if (aptr->body->description)
200
0
        {
201
0
          mutt_format_s(buf, buflen, prec, aptr->body->description);
202
0
          break;
203
0
        }
204
0
        if (mutt_is_message_type(aptr->body->type, aptr->body->subtype) &&
205
0
            c_message_format && aptr->body->email)
206
0
        {
207
0
          char s[128] = { 0 };
208
0
          mutt_make_string(s, sizeof(s), cols, c_message_format, NULL, -1,
209
0
                           aptr->body->email,
210
0
                           MUTT_FORMAT_FORCESUBJ | MUTT_FORMAT_ARROWCURSOR, NULL);
211
0
          if (*s)
212
0
          {
213
0
            mutt_format_s(buf, buflen, prec, s);
214
0
            break;
215
0
          }
216
0
        }
217
0
        if (!aptr->body->d_filename && !aptr->body->filename)
218
0
        {
219
0
          mutt_format_s(buf, buflen, prec, "<no description>");
220
0
          break;
221
0
        }
222
0
      }
223
0
      else if (aptr->body->description ||
224
0
               (mutt_is_message_type(aptr->body->type, aptr->body->subtype) &&
225
0
                c_message_format && aptr->body->email))
226
0
      {
227
0
        break;
228
0
      }
229
0
    }
230
    /* fallthrough */
231
0
    case 'F':
232
0
      if (!optional)
233
0
      {
234
0
        if (aptr->body->d_filename)
235
0
        {
236
0
          mutt_format_s(buf, buflen, prec, aptr->body->d_filename);
237
0
          break;
238
0
        }
239
0
      }
240
0
      else if (!aptr->body->d_filename && !aptr->body->filename)
241
0
      {
242
0
        optional = false;
243
0
        break;
244
0
      }
245
    /* fallthrough */
246
0
    case 'f':
247
0
      if (!optional)
248
0
      {
249
0
        if (aptr->body->filename && (*aptr->body->filename == '/'))
250
0
        {
251
0
          struct Buffer *path = buf_pool_get();
252
253
0
          buf_strcpy(path, aptr->body->filename);
254
0
          buf_pretty_mailbox(path);
255
0
          mutt_format_s(buf, buflen, prec, buf_string(path));
256
0
          buf_pool_release(&path);
257
0
        }
258
0
        else
259
0
        {
260
0
          mutt_format_s(buf, buflen, prec, NONULL(aptr->body->filename));
261
0
        }
262
0
      }
263
0
      else if (!aptr->body->filename)
264
0
      {
265
0
        optional = false;
266
0
      }
267
0
      break;
268
0
    case 'D':
269
0
      if (!optional)
270
0
        snprintf(buf, buflen, "%c", aptr->body->deleted ? 'D' : ' ');
271
0
      else if (!aptr->body->deleted)
272
0
        optional = false;
273
0
      break;
274
0
    case 'e':
275
0
      if (!optional)
276
0
        mutt_format_s(buf, buflen, prec, ENCODING(aptr->body->encoding));
277
0
      break;
278
0
    case 'I':
279
0
      if (optional)
280
0
        break;
281
282
0
      const char dispchar[] = { 'I', 'A', 'F', '-' };
283
0
      char ch;
284
285
0
      if (aptr->body->disposition < sizeof(dispchar))
286
0
      {
287
0
        ch = dispchar[aptr->body->disposition];
288
0
      }
289
0
      else
290
0
      {
291
0
        mutt_debug(LL_DEBUG1, "ERROR: invalid content-disposition %d\n",
292
0
                   aptr->body->disposition);
293
0
        ch = '!';
294
0
      }
295
0
      snprintf(buf, buflen, "%c", ch);
296
0
      break;
297
0
    case 'm':
298
0
      if (!optional)
299
0
        mutt_format_s(buf, buflen, prec, TYPE(aptr->body));
300
0
      break;
301
0
    case 'M':
302
0
      if (!optional)
303
0
        mutt_format_s(buf, buflen, prec, aptr->body->subtype);
304
0
      else if (!aptr->body->subtype)
305
0
        optional = false;
306
0
      break;
307
0
    case 'n':
308
0
      if (optional)
309
0
        break;
310
311
0
      snprintf(fmt, sizeof(fmt), "%%%sd", prec);
312
0
      snprintf(buf, buflen, fmt, aptr->num + 1);
313
0
      break;
314
0
    case 'Q':
315
0
      if (optional)
316
0
      {
317
0
        optional = aptr->body->attach_qualifies;
318
0
      }
319
0
      else
320
0
      {
321
0
        snprintf(fmt, sizeof(fmt), "%%%sc", prec);
322
0
        mutt_format_s(buf, buflen, fmt, "Q");
323
0
      }
324
0
      break;
325
0
    case 's':
326
0
    {
327
0
      size_t l = 0;
328
0
      if (aptr->body->filename && (flags & MUTT_FORMAT_STAT_FILE))
329
0
      {
330
0
        l = mutt_file_get_size(aptr->body->filename);
331
0
      }
332
0
      else
333
0
      {
334
0
        l = aptr->body->length;
335
0
      }
336
337
0
      if (!optional)
338
0
      {
339
0
        char tmp[128] = { 0 };
340
0
        mutt_str_pretty_size(tmp, sizeof(tmp), l);
341
0
        mutt_format_s(buf, buflen, prec, tmp);
342
0
      }
343
0
      else if (l == 0)
344
0
      {
345
0
        optional = false;
346
0
      }
347
348
0
      break;
349
0
    }
350
0
    case 't':
351
0
      if (!optional)
352
0
        snprintf(buf, buflen, "%c", aptr->body->tagged ? '*' : ' ');
353
0
      else if (!aptr->body->tagged)
354
0
        optional = false;
355
0
      break;
356
0
    case 'T':
357
0
      if (!optional)
358
0
        mutt_format_s_tree(buf, buflen, prec, NONULL(aptr->tree));
359
0
      else if (!aptr->tree)
360
0
        optional = false;
361
0
      break;
362
0
    case 'u':
363
0
      if (!optional)
364
0
        snprintf(buf, buflen, "%c", aptr->body->unlink ? '-' : ' ');
365
0
      else if (!aptr->body->unlink)
366
0
        optional = false;
367
0
      break;
368
0
    case 'X':
369
0
      if (optional)
370
0
      {
371
0
        optional = ((aptr->body->attach_count + aptr->body->attach_qualifies) != 0);
372
0
      }
373
0
      else
374
0
      {
375
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
376
0
        snprintf(buf, buflen, fmt, aptr->body->attach_count + aptr->body->attach_qualifies);
377
0
      }
378
0
      break;
379
0
    default:
380
0
      *buf = '\0';
381
0
  }
382
383
0
  if (optional)
384
0
  {
385
0
    mutt_expando_format(buf, buflen, col, cols, if_str, attach_format_str, data,
386
0
                        MUTT_FORMAT_NO_FLAGS);
387
0
  }
388
0
  else if (flags & MUTT_FORMAT_OPTIONAL)
389
0
  {
390
0
    mutt_expando_format(buf, buflen, col, cols, else_str, attach_format_str,
391
0
                        data, MUTT_FORMAT_NO_FLAGS);
392
0
  }
393
394
  /* We return the format string, unchanged */
395
0
  return src;
396
0
}
397
398
/**
399
 * attach_make_entry - Format a menu item for the attachment list - Implements Menu::make_entry() - @ingroup menu_make_entry
400
 *
401
 * @sa $attach_format, attach_format_str()
402
 */
403
static void attach_make_entry(struct Menu *menu, char *buf, size_t buflen, int line)
404
0
{
405
0
  struct AttachPrivateData *priv = menu->mdata;
406
0
  struct AttachCtx *actx = priv->actx;
407
408
0
  const char *const c_attach_format = cs_subset_string(NeoMutt->sub, "attach_format");
409
0
  mutt_expando_format(buf, buflen, 0, menu->win->state.cols, NONULL(c_attach_format),
410
0
                      attach_format_str, (intptr_t) (actx->idx[actx->v2r[line]]),
411
0
                      MUTT_FORMAT_ARROWCURSOR);
412
0
}
413
414
/**
415
 * attach_tag - Tag an attachment - Implements Menu::tag() - @ingroup menu_tag
416
 */
417
static int attach_tag(struct Menu *menu, int sel, int act)
418
0
{
419
0
  struct AttachPrivateData *priv = menu->mdata;
420
0
  struct AttachCtx *actx = priv->actx;
421
422
0
  struct Body *cur = actx->idx[actx->v2r[sel]]->body;
423
0
  bool ot = cur->tagged;
424
425
0
  cur->tagged = ((act >= 0) ? act : !cur->tagged);
426
0
  return cur->tagged - ot;
427
0
}
428
429
/**
430
 * attach_window_observer - Notification that a Window has changed - Implements ::observer_t - @ingroup observer_api
431
 *
432
 * This function is triggered by changes to the windows.
433
 *
434
 * - Delete (this window): clean up the resources held by the Help Bar
435
 */
436
static int attach_window_observer(struct NotifyCallback *nc)
437
0
{
438
0
  if (nc->event_type != NT_WINDOW)
439
0
    return 0;
440
0
  if (!nc->global_data || !nc->event_data)
441
0
    return -1;
442
0
  if (nc->event_subtype != NT_WINDOW_DELETE)
443
0
    return 0;
444
445
0
  struct MuttWindow *win_menu = nc->global_data;
446
0
  struct EventWindow *ev_w = nc->event_data;
447
0
  if (ev_w->win != win_menu)
448
0
    return 0;
449
450
0
  struct Menu *menu = win_menu->wdata;
451
452
0
  notify_observer_remove(NeoMutt->sub->notify, attach_config_observer, menu);
453
0
  notify_observer_remove(win_menu->notify, attach_window_observer, win_menu);
454
455
0
  mutt_debug(LL_DEBUG5, "window delete done\n");
456
0
  return 0;
457
0
}
458
459
/**
460
 * dlg_attachment - Show the attachments in a Menu - @ingroup gui_dlg
461
 * @param sub        Config Subset
462
 * @param mv         Mailbox view
463
 * @param e          Email
464
 * @param fp         File with the content of the email, or NULL
465
 * @param attach_msg Are we in "attach message" mode?
466
 *
467
 * The Select Attachment dialog shows an Email's attachments.
468
 * They can be viewed using the Pager or Mailcap programs.
469
 * They can also be saved, printed, deleted, etc.
470
 */
471
void dlg_attachment(struct ConfigSubset *sub, struct MailboxView *mv,
472
                    struct Email *e, FILE *fp, bool attach_msg)
473
0
{
474
0
  if (!mv || !mv->mailbox || !e || !fp)
475
0
    return;
476
477
0
  struct Mailbox *m = mv->mailbox;
478
479
  /* make sure we have parsed this message */
480
0
  mutt_parse_mime_message(e, fp);
481
0
  mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
482
483
0
  struct MuttWindow *dlg = simple_dialog_new(MENU_ATTACHMENT, WT_DLG_ATTACHMENT, AttachmentHelp);
484
0
  struct Menu *menu = dlg->wdata;
485
0
  menu->make_entry = attach_make_entry;
486
0
  menu->tag = attach_tag;
487
488
0
  struct AttachCtx *actx = mutt_actx_new();
489
0
  actx->email = e;
490
0
  actx->fp_root = fp;
491
0
  mutt_update_recvattach_menu(actx, menu, true);
492
493
0
  struct AttachPrivateData *priv = attach_private_data_new();
494
0
  priv->menu = menu;
495
0
  priv->actx = actx;
496
0
  priv->sub = sub;
497
0
  priv->mailbox = m;
498
0
  priv->attach_msg = attach_msg;
499
0
  menu->mdata = priv;
500
0
  menu->mdata_free = attach_private_data_free;
501
502
  // NT_COLOR is handled by the SimpleDialog
503
0
  notify_observer_add(NeoMutt->sub->notify, NT_CONFIG, attach_config_observer, menu);
504
0
  notify_observer_add(menu->win->notify, NT_WINDOW, attach_window_observer, menu->win);
505
506
0
  struct MuttWindow *sbar = window_find_child(dlg, WT_STATUS_BAR);
507
0
  sbar_set_title(sbar, _("Attachments"));
508
509
0
  struct MuttWindow *old_focus = window_set_focus(menu->win);
510
  // ---------------------------------------------------------------------------
511
  // Event Loop
512
0
  int rc = 0;
513
0
  int op = OP_NULL;
514
0
  do
515
0
  {
516
0
    menu_tagging_dispatcher(menu->win, op);
517
0
    window_redraw(NULL);
518
519
0
    op = km_dokey(MENU_ATTACHMENT, GETCH_NO_FLAGS);
520
0
    mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", opcodes_get_name(op), op);
521
0
    if (op < 0)
522
0
      continue;
523
0
    if (op == OP_NULL)
524
0
    {
525
0
      km_error_key(MENU_ATTACHMENT);
526
0
      continue;
527
0
    }
528
0
    mutt_clear_error();
529
530
0
    rc = attach_function_dispatcher(dlg, op);
531
0
    if (rc == FR_UNKNOWN)
532
0
      rc = menu_function_dispatcher(menu->win, op);
533
0
    if (rc == FR_UNKNOWN)
534
0
      rc = global_function_dispatcher(NULL, op);
535
536
0
    if (rc == FR_CONTINUE)
537
0
    {
538
0
      op = priv->op;
539
0
    }
540
541
0
  } while (rc != FR_DONE);
542
  // ---------------------------------------------------------------------------
543
544
0
  window_set_focus(old_focus);
545
0
  simple_dialog_free(&dlg);
546
0
}