/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 | } |