Coverage Report

Created: 2025-04-22 06:17

/src/neomutt/attach/cid.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Attachment Content-ID header functions
4
 *
5
 * @authors
6
 * Copyright (C) 2022 David Purton <dcpurton@marshwiggle.net>
7
 * Copyright (C) 2023 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 attach_cid Attachment Content-ID header functions
26
 *
27
 * Attachment Content-ID header functions
28
 */
29
30
#include "config.h"
31
#include <stdbool.h>
32
#include <stddef.h>
33
#include <stdio.h>
34
#include <string.h>
35
#include "email/lib.h"
36
#include "core/lib.h"
37
#include "cid.h"
38
#include "attach.h"
39
#include "mailcap.h"
40
#include "mutt_attach.h"
41
42
/**
43
 * cid_map_free - Free a CidMap
44
 * @param[out] ptr CidMap to free
45
 */
46
void cid_map_free(struct CidMap **ptr)
47
0
{
48
0
  if (!ptr || !*ptr)
49
0
    return;
50
51
0
  struct CidMap *cid_map = *ptr;
52
53
0
  FREE(&cid_map->cid);
54
0
  FREE(&cid_map->fname);
55
56
0
  FREE(ptr);
57
0
}
58
59
/**
60
 * cid_map_new - Initialise a new CidMap
61
 * @param  cid      Content-ID to replace including "cid:" prefix
62
 * @param  filename Path to file to replace Content-ID with
63
 * @retval ptr      Newly allocated CidMap
64
 */
65
struct CidMap *cid_map_new(const char *cid, const char *filename)
66
0
{
67
0
  if (!cid || !filename)
68
0
    return NULL;
69
70
0
  struct CidMap *cid_map = MUTT_MEM_CALLOC(1, struct CidMap);
71
72
0
  cid_map->cid = mutt_str_dup(cid);
73
0
  cid_map->fname = mutt_str_dup(filename);
74
75
0
  return cid_map;
76
0
}
77
78
/**
79
 * cid_map_list_clear - Empty a CidMapList
80
 * @param cid_map_list List of Content-ID to filename mappings
81
 */
82
void cid_map_list_clear(struct CidMapList *cid_map_list)
83
0
{
84
0
  if (!cid_map_list)
85
0
    return;
86
87
0
  while (!STAILQ_EMPTY(cid_map_list))
88
0
  {
89
0
    struct CidMap *cid_map = STAILQ_FIRST(cid_map_list);
90
0
    STAILQ_REMOVE_HEAD(cid_map_list, entries);
91
0
    cid_map_free(&cid_map);
92
0
  }
93
0
}
94
95
/**
96
 * cid_save_attachment - Save attachment if it has a Content-ID
97
 * @param[in]  b            Body to check and save
98
 * @param[out] cid_map_list List of Content-ID to filename mappings
99
 *
100
 * If body has a Content-ID, it is saved to disk and a new Content-ID to filename
101
 * mapping is added to cid_map_list.
102
 */
103
static void cid_save_attachment(struct Body *b, struct CidMapList *cid_map_list)
104
0
{
105
0
  if (!b || !cid_map_list)
106
0
    return;
107
108
0
  char *id = b->content_id;
109
0
  if (!id)
110
0
    return;
111
112
0
  struct Buffer *tempfile = buf_pool_get();
113
0
  struct Buffer *cid = buf_pool_get();
114
0
  bool has_tempfile = false;
115
0
  FILE *fp = NULL;
116
117
0
  mutt_debug(LL_DEBUG2, "attachment found with \"Content-ID: %s\"\n", id);
118
  /* get filename */
119
0
  char *fname = mutt_str_dup(b->filename);
120
0
  if (b->aptr)
121
0
    fp = b->aptr->fp;
122
0
  mutt_file_sanitize_filename(fname, fp ? true : false);
123
0
  mailcap_expand_filename("%s", fname, tempfile);
124
0
  FREE(&fname);
125
126
  /* save attachment */
127
0
  if (mutt_save_attachment(fp, b, buf_string(tempfile), 0, NULL) == -1)
128
0
    goto bail;
129
0
  has_tempfile = true;
130
0
  mutt_debug(LL_DEBUG2, "attachment with \"Content-ID: %s\" saved to file \"%s\"\n",
131
0
             id, buf_string(tempfile));
132
133
  /* add Content-ID to filename mapping to list */
134
0
  buf_printf(cid, "cid:%s", id);
135
0
  struct CidMap *cid_map = cid_map_new(buf_string(cid), buf_string(tempfile));
136
0
  STAILQ_INSERT_TAIL(cid_map_list, cid_map, entries);
137
138
0
bail:
139
140
0
  if ((fp && !buf_is_empty(tempfile)) || has_tempfile)
141
0
    mutt_add_temp_attachment(buf_string(tempfile));
142
0
  buf_pool_release(&tempfile);
143
0
  buf_pool_release(&cid);
144
0
}
145
146
/**
147
 * cid_save_attachments - Save all attachments in a "multipart/related" group with a Content-ID
148
 * @param[in]  body         First body in "multipart/related" group
149
 * @param[out] cid_map_list List of Content-ID to filename mappings
150
 */
151
void cid_save_attachments(struct Body *body, struct CidMapList *cid_map_list)
152
0
{
153
0
  if (!body || !cid_map_list)
154
0
    return;
155
156
0
  for (struct Body *b = body; b; b = b->next)
157
0
  {
158
0
    if (b->parts)
159
0
      cid_save_attachments(b->parts, cid_map_list);
160
0
    else
161
0
      cid_save_attachment(b, cid_map_list);
162
0
  }
163
0
}
164
165
/**
166
 * cid_to_filename - Replace Content-IDs with filenames
167
 * @param filename     Path to file to replace Content-IDs with filenames
168
 * @param cid_map_list List of Content-ID to filename mappings
169
 */
170
void cid_to_filename(struct Buffer *filename, const struct CidMapList *cid_map_list)
171
0
{
172
0
  if (!filename || !cid_map_list)
173
0
    return;
174
175
0
  FILE *fp_out = NULL;
176
0
  char *pbuf = NULL;
177
0
  char *searchbuf = NULL;
178
0
  char *buf = NULL;
179
0
  char *cid = NULL;
180
0
  size_t blen = 0;
181
0
  struct CidMap *cid_map = NULL;
182
183
0
  struct Buffer *tempfile = buf_pool_get();
184
0
  struct Buffer *tmpbuf = buf_pool_get();
185
186
0
  FILE *fp_in = mutt_file_fopen(buf_string(filename), "r");
187
0
  if (!fp_in)
188
0
    goto bail;
189
190
  /* ensure tempfile has the same file extension as filename otherwise an
191
   * HTML file may be opened as plain text by the viewer */
192
0
  const char *suffix = buf_rfind(filename, ".");
193
0
  if (suffix && *(suffix++))
194
0
    buf_mktemp_pfx_sfx(tempfile, "neomutt", suffix);
195
0
  else
196
0
    buf_mktemp(tempfile);
197
0
  fp_out = mutt_file_fopen(buf_string(tempfile), "w+");
198
0
  if (!fp_out)
199
0
    goto bail;
200
201
  /* Read in lines from filename into buf */
202
0
  while ((buf = mutt_file_read_line(buf, &blen, fp_in, NULL, MUTT_RL_NO_FLAGS)) != NULL)
203
0
  {
204
0
    if (mutt_str_len(buf) == 0)
205
0
    {
206
0
      fputs(buf, fp_out);
207
0
      continue;
208
0
    }
209
210
    /* copy buf to searchbuf because we need to edit multiple times */
211
0
    searchbuf = mutt_str_dup(buf);
212
0
    buf_reset(tmpbuf);
213
214
    /* loop through Content-ID to filename mappings and do search and replace */
215
0
    STAILQ_FOREACH(cid_map, cid_map_list, entries)
216
0
    {
217
0
      pbuf = searchbuf;
218
0
      while ((cid = strstr(pbuf, cid_map->cid)) != NULL)
219
0
      {
220
0
        buf_addstr_n(tmpbuf, pbuf, cid - pbuf);
221
0
        buf_addstr(tmpbuf, cid_map->fname);
222
0
        pbuf = cid + mutt_str_len(cid_map->cid);
223
0
        mutt_debug(LL_DEBUG2, "replaced \"%s\" with \"%s\" in file \"%s\"\n",
224
0
                   cid_map->cid, cid_map->fname, buf_string(filename));
225
0
      }
226
0
      buf_addstr(tmpbuf, pbuf);
227
0
      FREE(&searchbuf);
228
0
      searchbuf = buf_strdup(tmpbuf);
229
0
      buf_reset(tmpbuf);
230
0
    }
231
232
    /* write edited line to output file */
233
0
    fputs(searchbuf, fp_out);
234
0
    fputs("\n", fp_out);
235
0
    FREE(&searchbuf);
236
0
  }
237
238
0
  mutt_file_set_mtime(buf_string(filename), buf_string(tempfile));
239
240
  /* add filename to TempAtachmentsList so it doesn't get left lying around */
241
0
  mutt_add_temp_attachment(buf_string(filename));
242
  /* update filename to point to new file */
243
0
  buf_copy(filename, tempfile);
244
245
0
bail:
246
0
  FREE(&buf);
247
0
  mutt_file_fclose(&fp_in);
248
0
  mutt_file_fclose(&fp_out);
249
0
  buf_pool_release(&tempfile);
250
0
  buf_pool_release(&tmpbuf);
251
0
}