/src/neomutt/attach/mutt_attach.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * Handling of email attachments |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1996-2000,2002,2013 Michael R. Elkins <me@mutt.org> |
7 | | * Copyright (C) 1999-2004,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_mutt_attach Shared attachments functions |
26 | | * |
27 | | * Handling of email attachments |
28 | | */ |
29 | | |
30 | | #include "config.h" |
31 | | #include <errno.h> |
32 | | #include <fcntl.h> |
33 | | #include <stdbool.h> |
34 | | #include <stdio.h> |
35 | | #include <string.h> |
36 | | #include <sys/stat.h> |
37 | | #include <sys/wait.h> |
38 | | #include <unistd.h> |
39 | | #include "mutt/lib.h" |
40 | | #include "config/lib.h" |
41 | | #include "email/lib.h" |
42 | | #include "core/lib.h" |
43 | | #include "gui/lib.h" |
44 | | #include "mutt_attach.h" |
45 | | #include "lib.h" |
46 | | #include "ncrypt/lib.h" |
47 | | #include "pager/lib.h" |
48 | | #include "question/lib.h" |
49 | | #include "send/lib.h" |
50 | | #include "cid.h" |
51 | | #include "copy.h" |
52 | | #include "globals.h" // IWYU pragma: keep |
53 | | #include "handler.h" |
54 | | #include "mailcap.h" |
55 | | #include "muttlib.h" |
56 | | #include "mx.h" |
57 | | #include "protos.h" |
58 | | #include "rfc3676.h" |
59 | | #ifdef USE_IMAP |
60 | | #include "imap/lib.h" |
61 | | #endif |
62 | | |
63 | | /** |
64 | | * mutt_get_tmp_attachment - Get a temporary copy of an attachment |
65 | | * @param a Attachment to copy |
66 | | * @retval 0 Success |
67 | | * @retval -1 Error |
68 | | */ |
69 | | int mutt_get_tmp_attachment(struct Body *a) |
70 | 0 | { |
71 | 0 | char type[256] = { 0 }; |
72 | |
|
73 | 0 | if (a->unlink) |
74 | 0 | return 0; |
75 | | |
76 | 0 | struct Buffer *tmpfile = buf_pool_get(); |
77 | 0 | struct MailcapEntry *entry = mailcap_entry_new(); |
78 | 0 | snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); |
79 | 0 | mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_NO_FLAGS); |
80 | 0 | mailcap_expand_filename(entry->nametemplate, a->filename, tmpfile); |
81 | |
|
82 | 0 | mailcap_entry_free(&entry); |
83 | |
|
84 | 0 | FILE *fp_in = NULL, *fp_out = NULL; |
85 | 0 | if ((fp_in = fopen(a->filename, "r")) && |
86 | 0 | (fp_out = mutt_file_fopen(buf_string(tmpfile), "w"))) |
87 | 0 | { |
88 | 0 | mutt_file_copy_stream(fp_in, fp_out); |
89 | 0 | mutt_str_replace(&a->filename, buf_string(tmpfile)); |
90 | 0 | a->unlink = true; |
91 | |
|
92 | 0 | struct stat st = { 0 }; |
93 | 0 | if ((fstat(fileno(fp_in), &st) == 0) && (a->stamp >= st.st_mtime)) |
94 | 0 | { |
95 | 0 | mutt_stamp_attachment(a); |
96 | 0 | } |
97 | 0 | } |
98 | 0 | else |
99 | 0 | { |
100 | 0 | mutt_perror(fp_in ? buf_string(tmpfile) : a->filename); |
101 | 0 | } |
102 | |
|
103 | 0 | mutt_file_fclose(&fp_in); |
104 | 0 | mutt_file_fclose(&fp_out); |
105 | |
|
106 | 0 | buf_pool_release(&tmpfile); |
107 | |
|
108 | 0 | return a->unlink ? 0 : -1; |
109 | 0 | } |
110 | | |
111 | | /** |
112 | | * mutt_compose_attachment - Create an attachment |
113 | | * @param a Body of email |
114 | | * @retval 1 Require full screen redraw |
115 | | * @retval 0 Otherwise |
116 | | */ |
117 | | int mutt_compose_attachment(struct Body *a) |
118 | 0 | { |
119 | 0 | char type[256] = { 0 }; |
120 | 0 | struct MailcapEntry *entry = mailcap_entry_new(); |
121 | 0 | bool unlink_newfile = false; |
122 | 0 | int rc = 0; |
123 | 0 | struct Buffer *cmd = buf_pool_get(); |
124 | 0 | struct Buffer *newfile = buf_pool_get(); |
125 | 0 | struct Buffer *tmpfile = buf_pool_get(); |
126 | |
|
127 | 0 | snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); |
128 | 0 | if (mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_COMPOSE)) |
129 | 0 | { |
130 | 0 | if (entry->composecommand || entry->composetypecommand) |
131 | 0 | { |
132 | 0 | if (entry->composetypecommand) |
133 | 0 | buf_strcpy(cmd, entry->composetypecommand); |
134 | 0 | else |
135 | 0 | buf_strcpy(cmd, entry->composecommand); |
136 | |
|
137 | 0 | mailcap_expand_filename(entry->nametemplate, a->filename, newfile); |
138 | 0 | mutt_debug(LL_DEBUG1, "oldfile: %s newfile: %s\n", a->filename, |
139 | 0 | buf_string(newfile)); |
140 | 0 | if (mutt_file_symlink(a->filename, buf_string(newfile)) == -1) |
141 | 0 | { |
142 | 0 | if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES) |
143 | 0 | goto bailout; |
144 | 0 | buf_strcpy(newfile, a->filename); |
145 | 0 | } |
146 | 0 | else |
147 | 0 | { |
148 | 0 | unlink_newfile = true; |
149 | 0 | } |
150 | | |
151 | 0 | if (mailcap_expand_command(a, buf_string(newfile), type, cmd)) |
152 | 0 | { |
153 | | /* For now, editing requires a file, no piping */ |
154 | 0 | mutt_error(_("Mailcap compose entry requires %%s")); |
155 | 0 | } |
156 | 0 | else |
157 | 0 | { |
158 | 0 | int r; |
159 | |
|
160 | 0 | mutt_endwin(); |
161 | 0 | r = mutt_system(buf_string(cmd)); |
162 | 0 | if (r == -1) |
163 | 0 | mutt_error(_("Error running \"%s\""), buf_string(cmd)); |
164 | |
|
165 | 0 | if ((r != -1) && entry->composetypecommand) |
166 | 0 | { |
167 | 0 | struct Body *b = NULL; |
168 | |
|
169 | 0 | FILE *fp = mutt_file_fopen(a->filename, "r"); |
170 | 0 | if (!fp) |
171 | 0 | { |
172 | 0 | mutt_perror(_("Failure to open file to parse headers")); |
173 | 0 | goto bailout; |
174 | 0 | } |
175 | | |
176 | 0 | b = mutt_read_mime_header(fp, 0); |
177 | 0 | if (b) |
178 | 0 | { |
179 | 0 | if (!TAILQ_EMPTY(&b->parameter)) |
180 | 0 | { |
181 | 0 | mutt_param_free(&a->parameter); |
182 | 0 | a->parameter = b->parameter; |
183 | 0 | TAILQ_INIT(&b->parameter); |
184 | 0 | } |
185 | 0 | if (b->description) |
186 | 0 | { |
187 | 0 | FREE(&a->description); |
188 | 0 | a->description = b->description; |
189 | 0 | b->description = NULL; |
190 | 0 | } |
191 | 0 | if (b->form_name) |
192 | 0 | { |
193 | 0 | FREE(&a->form_name); |
194 | 0 | a->form_name = b->form_name; |
195 | 0 | b->form_name = NULL; |
196 | 0 | } |
197 | | |
198 | | /* Remove headers by copying out data to another file, then |
199 | | * copying the file back */ |
200 | 0 | const LOFF_T offset = b->offset; |
201 | 0 | mutt_body_free(&b); |
202 | 0 | if (!mutt_file_seek(fp, offset, SEEK_SET)) |
203 | 0 | { |
204 | 0 | goto bailout; |
205 | 0 | } |
206 | | |
207 | 0 | buf_mktemp(tmpfile); |
208 | 0 | FILE *fp_tmp = mutt_file_fopen(buf_string(tmpfile), "w"); |
209 | 0 | if (!fp_tmp) |
210 | 0 | { |
211 | 0 | mutt_perror(_("Failure to open file to strip headers")); |
212 | 0 | mutt_file_fclose(&fp); |
213 | 0 | goto bailout; |
214 | 0 | } |
215 | 0 | mutt_file_copy_stream(fp, fp_tmp); |
216 | 0 | mutt_file_fclose(&fp); |
217 | 0 | mutt_file_fclose(&fp_tmp); |
218 | 0 | mutt_file_unlink(a->filename); |
219 | 0 | if (mutt_file_rename(buf_string(tmpfile), a->filename) != 0) |
220 | 0 | { |
221 | 0 | mutt_perror(_("Failure to rename file")); |
222 | 0 | goto bailout; |
223 | 0 | } |
224 | 0 | } |
225 | 0 | } |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | 0 | else |
230 | 0 | { |
231 | 0 | mutt_message(_("No mailcap compose entry for %s, creating empty file"), type); |
232 | 0 | rc = 1; |
233 | 0 | goto bailout; |
234 | 0 | } |
235 | | |
236 | 0 | rc = 1; |
237 | |
|
238 | 0 | bailout: |
239 | |
|
240 | 0 | if (unlink_newfile) |
241 | 0 | unlink(buf_string(newfile)); |
242 | |
|
243 | 0 | buf_pool_release(&cmd); |
244 | 0 | buf_pool_release(&newfile); |
245 | 0 | buf_pool_release(&tmpfile); |
246 | |
|
247 | 0 | mailcap_entry_free(&entry); |
248 | 0 | return rc; |
249 | 0 | } |
250 | | |
251 | | /** |
252 | | * mutt_edit_attachment - Edit an attachment |
253 | | * @param a Email containing attachment |
254 | | * @retval true Editor found |
255 | | * @retval false Editor not found |
256 | | * |
257 | | * Currently, this only works for send mode, as it assumes that the |
258 | | * Body->filename actually contains the information. I'm not sure |
259 | | * we want to deal with editing attachments we've already received, |
260 | | * so this should be ok. |
261 | | * |
262 | | * Returning 0 is useful to tell the calling menu to redraw |
263 | | */ |
264 | | bool mutt_edit_attachment(struct Body *a) |
265 | 0 | { |
266 | 0 | char type[256] = { 0 }; |
267 | 0 | struct MailcapEntry *entry = mailcap_entry_new(); |
268 | 0 | bool unlink_newfile = false; |
269 | 0 | bool rc = false; |
270 | 0 | struct Buffer *cmd = buf_pool_get(); |
271 | 0 | struct Buffer *newfile = buf_pool_get(); |
272 | |
|
273 | 0 | snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); |
274 | 0 | if (mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_EDIT)) |
275 | 0 | { |
276 | 0 | if (entry->editcommand) |
277 | 0 | { |
278 | 0 | buf_strcpy(cmd, entry->editcommand); |
279 | 0 | mailcap_expand_filename(entry->nametemplate, a->filename, newfile); |
280 | 0 | mutt_debug(LL_DEBUG1, "oldfile: %s newfile: %s\n", a->filename, |
281 | 0 | buf_string(newfile)); |
282 | 0 | if (mutt_file_symlink(a->filename, buf_string(newfile)) == -1) |
283 | 0 | { |
284 | 0 | if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES) |
285 | 0 | goto bailout; |
286 | 0 | buf_strcpy(newfile, a->filename); |
287 | 0 | } |
288 | 0 | else |
289 | 0 | { |
290 | 0 | unlink_newfile = true; |
291 | 0 | } |
292 | | |
293 | 0 | if (mailcap_expand_command(a, buf_string(newfile), type, cmd)) |
294 | 0 | { |
295 | | /* For now, editing requires a file, no piping */ |
296 | 0 | mutt_error(_("Mailcap Edit entry requires %%s")); |
297 | 0 | goto bailout; |
298 | 0 | } |
299 | 0 | else |
300 | 0 | { |
301 | 0 | mutt_endwin(); |
302 | 0 | if (mutt_system(buf_string(cmd)) == -1) |
303 | 0 | { |
304 | 0 | mutt_error(_("Error running \"%s\""), buf_string(cmd)); |
305 | 0 | goto bailout; |
306 | 0 | } |
307 | 0 | } |
308 | 0 | } |
309 | 0 | } |
310 | 0 | else if (a->type == TYPE_TEXT) |
311 | 0 | { |
312 | | /* On text, default to editor */ |
313 | 0 | const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor"); |
314 | 0 | mutt_edit_file(NONULL(c_editor), a->filename); |
315 | 0 | } |
316 | 0 | else |
317 | 0 | { |
318 | 0 | mutt_error(_("No mailcap edit entry for %s"), type); |
319 | 0 | goto bailout; |
320 | 0 | } |
321 | | |
322 | 0 | rc = true; |
323 | |
|
324 | 0 | bailout: |
325 | |
|
326 | 0 | if (unlink_newfile) |
327 | 0 | unlink(buf_string(newfile)); |
328 | |
|
329 | 0 | buf_pool_release(&cmd); |
330 | 0 | buf_pool_release(&newfile); |
331 | |
|
332 | 0 | mailcap_entry_free(&entry); |
333 | 0 | return rc; |
334 | 0 | } |
335 | | |
336 | | /** |
337 | | * mutt_check_lookup_list - Update the mime type |
338 | | * @param b Message attachment body |
339 | | * @param type Buffer with mime type of attachment in "type/subtype" format |
340 | | * @param len Buffer length |
341 | | */ |
342 | | void mutt_check_lookup_list(struct Body *b, char *type, size_t len) |
343 | 0 | { |
344 | 0 | struct ListNode *np = NULL; |
345 | 0 | STAILQ_FOREACH(np, &MimeLookupList, entries) |
346 | 0 | { |
347 | 0 | const int i = mutt_str_len(np->data) - 1; |
348 | 0 | if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') && |
349 | 0 | mutt_istrn_equal(type, np->data, i)) || |
350 | 0 | mutt_istr_equal(type, np->data)) |
351 | 0 | { |
352 | 0 | struct Body tmp = { 0 }; |
353 | 0 | enum ContentType n; |
354 | 0 | if ((n = mutt_lookup_mime_type(&tmp, b->filename)) != TYPE_OTHER || |
355 | 0 | (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER) |
356 | 0 | { |
357 | 0 | snprintf(type, len, "%s/%s", |
358 | 0 | (n == TYPE_AUDIO) ? "audio" : |
359 | 0 | (n == TYPE_APPLICATION) ? "application" : |
360 | 0 | (n == TYPE_IMAGE) ? "image" : |
361 | 0 | (n == TYPE_MESSAGE) ? "message" : |
362 | 0 | (n == TYPE_MODEL) ? "model" : |
363 | 0 | (n == TYPE_MULTIPART) ? "multipart" : |
364 | 0 | (n == TYPE_TEXT) ? "text" : |
365 | 0 | (n == TYPE_VIDEO) ? "video" : |
366 | 0 | "other", |
367 | 0 | tmp.subtype); |
368 | 0 | mutt_debug(LL_DEBUG1, "\"%s\" -> %s\n", b->filename, type); |
369 | 0 | } |
370 | 0 | FREE(&tmp.subtype); |
371 | 0 | FREE(&tmp.xtype); |
372 | 0 | } |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | /** |
377 | | * wait_interactive_filter - Wait after an interactive filter |
378 | | * @param pid Process id of the process to wait for |
379 | | * @retval num Exit status of the process identified by pid |
380 | | * @retval -1 Error |
381 | | * |
382 | | * This is used for filters that are actually interactive commands |
383 | | * with input piped in: e.g. in mutt_view_attachment(), a mailcap |
384 | | * entry without copiousoutput _and_ without a %s. |
385 | | * |
386 | | * For those cases, we treat it like a blocking system command, and |
387 | | * poll IMAP to keep connections open. |
388 | | */ |
389 | | static int wait_interactive_filter(pid_t pid) |
390 | 0 | { |
391 | 0 | int rc; |
392 | |
|
393 | 0 | #ifdef USE_IMAP |
394 | 0 | rc = imap_wait_keep_alive(pid); |
395 | | #else |
396 | | waitpid(pid, &rc, 0); |
397 | | #endif |
398 | 0 | mutt_sig_unblock_system(true); |
399 | 0 | rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : -1; |
400 | |
|
401 | 0 | return rc; |
402 | 0 | } |
403 | | |
404 | | /** |
405 | | * mutt_view_attachment - View an attachment |
406 | | * @param fp Source file stream. Can be NULL |
407 | | * @param a The message body containing the attachment |
408 | | * @param mode How the attachment should be viewed, see #ViewAttachMode |
409 | | * @param e Current Email. Can be NULL |
410 | | * @param actx Attachment context |
411 | | * @param win Window |
412 | | * @retval 0 The viewer is run and exited successfully |
413 | | * @retval -1 Error |
414 | | * @retval num Return value of mutt_do_pager() when it is used |
415 | | * |
416 | | * Display a message attachment using the viewer program configured in mailcap. |
417 | | * If there is no mailcap entry for a file type, view the image as text. |
418 | | * Viewer processes are opened and waited on synchronously so viewing an |
419 | | * attachment this way will block the main neomutt process until the viewer process |
420 | | * exits. |
421 | | */ |
422 | | int mutt_view_attachment(FILE *fp, struct Body *a, enum ViewAttachMode mode, |
423 | | struct Email *e, struct AttachCtx *actx, struct MuttWindow *win) |
424 | 0 | { |
425 | 0 | bool use_mailcap = false; |
426 | 0 | bool use_pipe = false; |
427 | 0 | bool use_pager = true; |
428 | 0 | char type[256] = { 0 }; |
429 | 0 | char desc[256] = { 0 }; |
430 | 0 | char *fname = NULL; |
431 | 0 | struct MailcapEntry *entry = NULL; |
432 | 0 | int rc = -1; |
433 | 0 | bool has_tempfile = false; |
434 | 0 | bool unlink_pagerfile = false; |
435 | |
|
436 | 0 | bool is_message = mutt_is_message_type(a->type, a->subtype); |
437 | 0 | if ((WithCrypto != 0) && is_message && a->email && |
438 | 0 | (a->email->security & SEC_ENCRYPT) && !crypt_valid_passphrase(a->email->security)) |
439 | 0 | { |
440 | 0 | return rc; |
441 | 0 | } |
442 | | |
443 | 0 | struct Buffer *tmpfile = buf_pool_get(); |
444 | 0 | struct Buffer *pagerfile = buf_pool_get(); |
445 | 0 | struct Buffer *cmd = buf_pool_get(); |
446 | |
|
447 | 0 | use_mailcap = ((mode == MUTT_VA_MAILCAP) || |
448 | 0 | ((mode == MUTT_VA_REGULAR) && mutt_needs_mailcap(a)) || |
449 | 0 | (mode == MUTT_VA_PAGER)); |
450 | 0 | snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); |
451 | |
|
452 | 0 | char columns[16] = { 0 }; |
453 | 0 | snprintf(columns, sizeof(columns), "%d", win->state.cols); |
454 | 0 | envlist_set(&EnvList, "COLUMNS", columns, true); |
455 | |
|
456 | 0 | if (use_mailcap) |
457 | 0 | { |
458 | 0 | entry = mailcap_entry_new(); |
459 | 0 | enum MailcapLookup mailcap_opt = (mode == MUTT_VA_PAGER) ? MUTT_MC_AUTOVIEW : MUTT_MC_NO_FLAGS; |
460 | 0 | if (!mailcap_lookup(a, type, sizeof(type), entry, mailcap_opt)) |
461 | 0 | { |
462 | 0 | if ((mode == MUTT_VA_REGULAR) || (mode == MUTT_VA_PAGER)) |
463 | 0 | { |
464 | | /* fallback to view as text */ |
465 | 0 | mailcap_entry_free(&entry); |
466 | 0 | mutt_error(_("No matching mailcap entry found. Viewing as text.")); |
467 | 0 | mode = MUTT_VA_AS_TEXT; |
468 | 0 | use_mailcap = false; |
469 | 0 | } |
470 | 0 | else |
471 | 0 | { |
472 | 0 | goto return_error; |
473 | 0 | } |
474 | 0 | } |
475 | 0 | } |
476 | | |
477 | 0 | if (use_mailcap) |
478 | 0 | { |
479 | 0 | if (!entry->command) |
480 | 0 | { |
481 | 0 | mutt_error(_("MIME type not defined. Can't view attachment.")); |
482 | 0 | goto return_error; |
483 | 0 | } |
484 | 0 | buf_strcpy(cmd, entry->command); |
485 | |
|
486 | 0 | fname = mutt_str_dup(a->filename); |
487 | | /* In send mode(!fp), we allow slashes because those are part of |
488 | | * the tmpfile. The path will be removed in expand_filename */ |
489 | 0 | mutt_file_sanitize_filename(fname, fp ? true : false); |
490 | 0 | mailcap_expand_filename(entry->nametemplate, fname, tmpfile); |
491 | 0 | FREE(&fname); |
492 | |
|
493 | 0 | if (mutt_save_attachment(fp, a, buf_string(tmpfile), 0, NULL) == -1) |
494 | 0 | goto return_error; |
495 | 0 | has_tempfile = true; |
496 | |
|
497 | 0 | mutt_rfc3676_space_unstuff_attachment(a, buf_string(tmpfile)); |
498 | | |
499 | | /* check for multipart/related and save attachments with a Content-ID */ |
500 | 0 | if (mutt_str_equal(type, "text/html")) |
501 | 0 | { |
502 | 0 | struct Body *related_ancestor = NULL; |
503 | 0 | if (actx->body_idx && (WithCrypto != 0) && (e->security & SEC_ENCRYPT)) |
504 | 0 | related_ancestor = attach_body_ancestor(actx->body_idx[0], a, "related"); |
505 | 0 | else |
506 | 0 | related_ancestor = attach_body_ancestor(e->body, a, "related"); |
507 | 0 | if (related_ancestor) |
508 | 0 | { |
509 | 0 | struct CidMapList cid_map_list = STAILQ_HEAD_INITIALIZER(cid_map_list); |
510 | 0 | mutt_debug(LL_DEBUG2, "viewing text/html attachment in multipart/related group\n"); |
511 | | /* save attachments and build cid_map_list Content-ID to filename mapping list */ |
512 | 0 | cid_save_attachments(related_ancestor->parts, &cid_map_list); |
513 | | /* replace Content-IDs with filenames */ |
514 | 0 | cid_to_filename(tmpfile, &cid_map_list); |
515 | | /* empty Content-ID to filename mapping list */ |
516 | 0 | cid_map_list_clear(&cid_map_list); |
517 | 0 | } |
518 | 0 | } |
519 | |
|
520 | 0 | use_pipe = mailcap_expand_command(a, buf_string(tmpfile), type, cmd); |
521 | 0 | use_pager = entry->copiousoutput; |
522 | 0 | } |
523 | | |
524 | 0 | if (use_pager) |
525 | 0 | { |
526 | 0 | if (fp && !use_mailcap && a->filename) |
527 | 0 | { |
528 | | /* recv case */ |
529 | 0 | buf_strcpy(pagerfile, a->filename); |
530 | 0 | mutt_adv_mktemp(pagerfile); |
531 | 0 | } |
532 | 0 | else |
533 | 0 | { |
534 | 0 | buf_mktemp(pagerfile); |
535 | 0 | } |
536 | 0 | } |
537 | |
|
538 | 0 | if (use_mailcap) |
539 | 0 | { |
540 | 0 | pid_t pid = 0; |
541 | 0 | int fd_temp = -1, fd_pager = -1; |
542 | |
|
543 | 0 | if (!use_pager) |
544 | 0 | mutt_endwin(); |
545 | |
|
546 | 0 | const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key"); |
547 | 0 | if (use_pager || use_pipe) |
548 | 0 | { |
549 | 0 | if (use_pager && ((fd_pager = mutt_file_open(buf_string(pagerfile), |
550 | 0 | O_CREAT | O_EXCL | O_WRONLY)) == -1)) |
551 | 0 | { |
552 | 0 | mutt_perror("open"); |
553 | 0 | goto return_error; |
554 | 0 | } |
555 | 0 | unlink_pagerfile = true; |
556 | |
|
557 | 0 | if (use_pipe && ((fd_temp = open(buf_string(tmpfile), 0)) == -1)) |
558 | 0 | { |
559 | 0 | if (fd_pager != -1) |
560 | 0 | close(fd_pager); |
561 | 0 | mutt_perror("open"); |
562 | 0 | goto return_error; |
563 | 0 | } |
564 | 0 | unlink_pagerfile = true; |
565 | |
|
566 | 0 | pid = filter_create_fd(buf_string(cmd), NULL, NULL, NULL, |
567 | 0 | use_pipe ? fd_temp : -1, use_pager ? fd_pager : -1, -1); |
568 | |
|
569 | 0 | if (pid == -1) |
570 | 0 | { |
571 | 0 | if (fd_pager != -1) |
572 | 0 | close(fd_pager); |
573 | |
|
574 | 0 | if (fd_temp != -1) |
575 | 0 | close(fd_temp); |
576 | |
|
577 | 0 | mutt_error(_("Can't create filter")); |
578 | 0 | goto return_error; |
579 | 0 | } |
580 | | |
581 | 0 | if (use_pager) |
582 | 0 | { |
583 | 0 | if (a->description) |
584 | 0 | { |
585 | 0 | snprintf(desc, sizeof(desc), _("---Command: %-20.20s Description: %s"), |
586 | 0 | buf_string(cmd), a->description); |
587 | 0 | } |
588 | 0 | else |
589 | 0 | { |
590 | 0 | snprintf(desc, sizeof(desc), _("---Command: %-30.30s Attachment: %s"), |
591 | 0 | buf_string(cmd), type); |
592 | 0 | } |
593 | 0 | filter_wait(pid); |
594 | 0 | } |
595 | 0 | else |
596 | 0 | { |
597 | 0 | if (wait_interactive_filter(pid) || (entry->needsterminal && c_wait_key)) |
598 | 0 | mutt_any_key_to_continue(NULL); |
599 | 0 | } |
600 | |
|
601 | 0 | if (fd_temp != -1) |
602 | 0 | close(fd_temp); |
603 | 0 | if (fd_pager != -1) |
604 | 0 | close(fd_pager); |
605 | 0 | } |
606 | 0 | else |
607 | 0 | { |
608 | | /* interactive cmd */ |
609 | 0 | int rv = mutt_system(buf_string(cmd)); |
610 | 0 | if (rv == -1) |
611 | 0 | mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data); |
612 | |
|
613 | 0 | if ((rv != 0) || (entry->needsterminal && c_wait_key)) |
614 | 0 | mutt_any_key_to_continue(NULL); |
615 | 0 | } |
616 | 0 | } |
617 | 0 | else |
618 | 0 | { |
619 | | /* Don't use mailcap; the attachment is viewed in the pager */ |
620 | |
|
621 | 0 | if (mode == MUTT_VA_AS_TEXT) |
622 | 0 | { |
623 | | /* just let me see the raw data */ |
624 | 0 | if (fp) |
625 | 0 | { |
626 | | /* Viewing from a received message. |
627 | | * |
628 | | * Don't use mutt_save_attachment() because we want to perform charset |
629 | | * conversion since this will be displayed by the internal pager. */ |
630 | 0 | struct State state = { 0 }; |
631 | |
|
632 | 0 | state.fp_out = mutt_file_fopen(buf_string(pagerfile), "w"); |
633 | 0 | if (!state.fp_out) |
634 | 0 | { |
635 | 0 | mutt_debug(LL_DEBUG1, "mutt_file_fopen(%s) errno=%d %s\n", |
636 | 0 | buf_string(pagerfile), errno, strerror(errno)); |
637 | 0 | mutt_perror(buf_string(pagerfile)); |
638 | 0 | goto return_error; |
639 | 0 | } |
640 | 0 | state.fp_in = fp; |
641 | 0 | state.flags = STATE_CHARCONV; |
642 | 0 | mutt_decode_attachment(a, &state); |
643 | 0 | if (mutt_file_fclose(&state.fp_out) == EOF) |
644 | 0 | { |
645 | 0 | mutt_debug(LL_DEBUG1, "fclose(%s) errno=%d %s\n", |
646 | 0 | buf_string(pagerfile), errno, strerror(errno)); |
647 | 0 | } |
648 | 0 | } |
649 | 0 | else |
650 | 0 | { |
651 | | /* in compose mode, just copy the file. we can't use |
652 | | * mutt_decode_attachment() since it assumes the content-encoding has |
653 | | * already been applied */ |
654 | 0 | if (mutt_save_attachment(fp, a, buf_string(pagerfile), MUTT_SAVE_NO_FLAGS, NULL)) |
655 | 0 | goto return_error; |
656 | 0 | unlink_pagerfile = true; |
657 | 0 | } |
658 | 0 | mutt_rfc3676_space_unstuff_attachment(a, buf_string(pagerfile)); |
659 | 0 | } |
660 | 0 | else |
661 | 0 | { |
662 | 0 | StateFlags flags = STATE_DISPLAY | STATE_DISPLAY_ATTACH; |
663 | 0 | const char *const c_pager = pager_get_pager(NeoMutt->sub); |
664 | 0 | if (!c_pager) |
665 | 0 | flags |= STATE_PAGER; |
666 | | |
667 | | /* Use built-in handler */ |
668 | 0 | if (mutt_decode_save_attachment(fp, a, buf_string(pagerfile), flags, MUTT_SAVE_NO_FLAGS)) |
669 | 0 | { |
670 | 0 | goto return_error; |
671 | 0 | } |
672 | 0 | unlink_pagerfile = true; |
673 | 0 | } |
674 | | |
675 | 0 | if (a->description) |
676 | 0 | mutt_str_copy(desc, a->description, sizeof(desc)); |
677 | 0 | else if (a->filename) |
678 | 0 | snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), a->filename, type); |
679 | 0 | else |
680 | 0 | snprintf(desc, sizeof(desc), _("---Attachment: %s"), type); |
681 | 0 | } |
682 | | |
683 | | /* We only reach this point if there have been no errors */ |
684 | | |
685 | 0 | if (use_pager) |
686 | 0 | { |
687 | 0 | struct PagerData pdata = { 0 }; |
688 | 0 | struct PagerView pview = { &pdata }; |
689 | |
|
690 | 0 | pdata.actx = actx; |
691 | 0 | pdata.body = a; |
692 | 0 | pdata.fname = buf_string(pagerfile); |
693 | 0 | pdata.fp = fp; |
694 | |
|
695 | 0 | pview.banner = desc; |
696 | 0 | pview.flags = MUTT_PAGER_ATTACHMENT | |
697 | 0 | (is_message ? MUTT_PAGER_MESSAGE : MUTT_PAGER_NO_FLAGS) | |
698 | 0 | ((use_mailcap && entry->xneomuttnowrap) ? MUTT_PAGER_NOWRAP : |
699 | 0 | MUTT_PAGER_NO_FLAGS); |
700 | 0 | pview.mode = PAGER_MODE_ATTACH; |
701 | |
|
702 | 0 | rc = mutt_do_pager(&pview, e); |
703 | |
|
704 | 0 | buf_reset(pagerfile); |
705 | 0 | unlink_pagerfile = false; |
706 | 0 | } |
707 | 0 | else |
708 | 0 | { |
709 | 0 | rc = 0; |
710 | 0 | } |
711 | |
|
712 | 0 | return_error: |
713 | |
|
714 | 0 | if (!entry || !entry->xneomuttkeep) |
715 | 0 | { |
716 | 0 | if ((fp && !buf_is_empty(tmpfile)) || has_tempfile) |
717 | 0 | { |
718 | | /* add temporary file to TempAttachmentsList to be deleted on timeout hook */ |
719 | 0 | mutt_add_temp_attachment(buf_string(tmpfile)); |
720 | 0 | } |
721 | 0 | } |
722 | |
|
723 | 0 | mailcap_entry_free(&entry); |
724 | |
|
725 | 0 | if (unlink_pagerfile) |
726 | 0 | mutt_file_unlink(buf_string(pagerfile)); |
727 | |
|
728 | 0 | buf_pool_release(&tmpfile); |
729 | 0 | buf_pool_release(&pagerfile); |
730 | 0 | buf_pool_release(&cmd); |
731 | 0 | envlist_unset(&EnvList, "COLUMNS"); |
732 | |
|
733 | 0 | return rc; |
734 | 0 | } |
735 | | |
736 | | /** |
737 | | * mutt_pipe_attachment - Pipe an attachment to a command |
738 | | * @param fp File to pipe into the command |
739 | | * @param b Attachment |
740 | | * @param path Path to command |
741 | | * @param outfile File to save output to |
742 | | * @retval 1 Success |
743 | | * @retval 0 Error |
744 | | */ |
745 | | int mutt_pipe_attachment(FILE *fp, struct Body *b, const char *path, char *outfile) |
746 | 0 | { |
747 | 0 | pid_t pid = 0; |
748 | 0 | int out = -1, rc = 0; |
749 | 0 | bool is_flowed = false; |
750 | 0 | bool unlink_unstuff = false; |
751 | 0 | FILE *fp_filter = NULL, *fp_unstuff = NULL, *fp_in = NULL; |
752 | 0 | struct Buffer *unstuff_tempfile = NULL; |
753 | |
|
754 | 0 | if (outfile && *outfile) |
755 | 0 | { |
756 | 0 | out = mutt_file_open(outfile, O_CREAT | O_EXCL | O_WRONLY); |
757 | 0 | if (out < 0) |
758 | 0 | { |
759 | 0 | mutt_perror("open"); |
760 | 0 | return 0; |
761 | 0 | } |
762 | 0 | } |
763 | | |
764 | 0 | if (mutt_rfc3676_is_format_flowed(b)) |
765 | 0 | { |
766 | 0 | is_flowed = true; |
767 | 0 | unstuff_tempfile = buf_pool_get(); |
768 | 0 | buf_mktemp(unstuff_tempfile); |
769 | 0 | } |
770 | |
|
771 | 0 | mutt_endwin(); |
772 | |
|
773 | 0 | if (outfile && *outfile) |
774 | 0 | pid = filter_create_fd(path, &fp_filter, NULL, NULL, -1, out, -1); |
775 | 0 | else |
776 | 0 | pid = filter_create(path, &fp_filter, NULL, NULL); |
777 | 0 | if (pid < 0) |
778 | 0 | { |
779 | 0 | mutt_perror(_("Can't create filter")); |
780 | 0 | goto bail; |
781 | 0 | } |
782 | | |
783 | | /* recv case */ |
784 | 0 | if (fp) |
785 | 0 | { |
786 | 0 | struct State state = { 0 }; |
787 | | |
788 | | /* perform charset conversion on text attachments when piping */ |
789 | 0 | state.flags = STATE_CHARCONV; |
790 | |
|
791 | 0 | if (is_flowed) |
792 | 0 | { |
793 | 0 | fp_unstuff = mutt_file_fopen(buf_string(unstuff_tempfile), "w"); |
794 | 0 | if (fp_unstuff == NULL) |
795 | 0 | { |
796 | 0 | mutt_perror("mutt_file_fopen"); |
797 | 0 | goto bail; |
798 | 0 | } |
799 | 0 | unlink_unstuff = true; |
800 | |
|
801 | 0 | state.fp_in = fp; |
802 | 0 | state.fp_out = fp_unstuff; |
803 | 0 | mutt_decode_attachment(b, &state); |
804 | 0 | mutt_file_fclose(&fp_unstuff); |
805 | |
|
806 | 0 | mutt_rfc3676_space_unstuff_attachment(b, buf_string(unstuff_tempfile)); |
807 | |
|
808 | 0 | fp_unstuff = mutt_file_fopen(buf_string(unstuff_tempfile), "r"); |
809 | 0 | if (fp_unstuff == NULL) |
810 | 0 | { |
811 | 0 | mutt_perror("mutt_file_fopen"); |
812 | 0 | goto bail; |
813 | 0 | } |
814 | 0 | mutt_file_copy_stream(fp_unstuff, fp_filter); |
815 | 0 | mutt_file_fclose(&fp_unstuff); |
816 | 0 | } |
817 | 0 | else |
818 | 0 | { |
819 | 0 | state.fp_in = fp; |
820 | 0 | state.fp_out = fp_filter; |
821 | 0 | mutt_decode_attachment(b, &state); |
822 | 0 | } |
823 | 0 | } |
824 | 0 | else |
825 | 0 | { |
826 | | /* send case */ |
827 | 0 | const char *infile = NULL; |
828 | |
|
829 | 0 | if (is_flowed) |
830 | 0 | { |
831 | 0 | if (mutt_save_attachment(fp, b, buf_string(unstuff_tempfile), |
832 | 0 | MUTT_SAVE_NO_FLAGS, NULL) == -1) |
833 | 0 | { |
834 | 0 | goto bail; |
835 | 0 | } |
836 | 0 | unlink_unstuff = true; |
837 | 0 | mutt_rfc3676_space_unstuff_attachment(b, buf_string(unstuff_tempfile)); |
838 | 0 | infile = buf_string(unstuff_tempfile); |
839 | 0 | } |
840 | 0 | else |
841 | 0 | { |
842 | 0 | infile = b->filename; |
843 | 0 | } |
844 | | |
845 | 0 | fp_in = fopen(infile, "r"); |
846 | 0 | if (!fp_in) |
847 | 0 | { |
848 | 0 | mutt_perror("fopen"); |
849 | 0 | goto bail; |
850 | 0 | } |
851 | | |
852 | 0 | mutt_file_copy_stream(fp_in, fp_filter); |
853 | 0 | mutt_file_fclose(&fp_in); |
854 | 0 | } |
855 | | |
856 | 0 | mutt_file_fclose(&fp_filter); |
857 | 0 | rc = 1; |
858 | |
|
859 | 0 | bail: |
860 | 0 | if (outfile && *outfile) |
861 | 0 | { |
862 | 0 | close(out); |
863 | 0 | if (rc == 0) |
864 | 0 | unlink(outfile); |
865 | 0 | else if (is_flowed) |
866 | 0 | mutt_rfc3676_space_stuff_attachment(NULL, outfile); |
867 | 0 | } |
868 | |
|
869 | 0 | mutt_file_fclose(&fp_unstuff); |
870 | 0 | mutt_file_fclose(&fp_filter); |
871 | 0 | mutt_file_fclose(&fp_in); |
872 | |
|
873 | 0 | if (unlink_unstuff) |
874 | 0 | mutt_file_unlink(buf_string(unstuff_tempfile)); |
875 | 0 | buf_pool_release(&unstuff_tempfile); |
876 | | |
877 | | /* check for error exit from child process */ |
878 | 0 | if ((pid > 0) && (filter_wait(pid) != 0)) |
879 | 0 | rc = 0; |
880 | |
|
881 | 0 | const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key"); |
882 | 0 | if ((rc == 0) || c_wait_key) |
883 | 0 | mutt_any_key_to_continue(NULL); |
884 | 0 | return rc; |
885 | 0 | } |
886 | | |
887 | | /** |
888 | | * save_attachment_open - Open a file to write an attachment to |
889 | | * @param path Path to file to open |
890 | | * @param opt Save option, see #SaveAttach |
891 | | * @retval ptr File handle to attachment file |
892 | | */ |
893 | | static FILE *save_attachment_open(const char *path, enum SaveAttach opt) |
894 | 0 | { |
895 | 0 | if (opt == MUTT_SAVE_APPEND) |
896 | 0 | return fopen(path, "a"); |
897 | 0 | if (opt == MUTT_SAVE_OVERWRITE) |
898 | 0 | return fopen(path, "w"); |
899 | | |
900 | 0 | return mutt_file_fopen(path, "w"); |
901 | 0 | } |
902 | | |
903 | | /** |
904 | | * mutt_save_attachment - Save an attachment |
905 | | * @param fp Source file stream. Can be NULL |
906 | | * @param m Email Body |
907 | | * @param path Where to save the attachment |
908 | | * @param opt Save option, see #SaveAttach |
909 | | * @param e Current Email. Can be NULL |
910 | | * @retval 0 Success |
911 | | * @retval -1 Error |
912 | | */ |
913 | | int mutt_save_attachment(FILE *fp, struct Body *m, const char *path, |
914 | | enum SaveAttach opt, struct Email *e) |
915 | 0 | { |
916 | 0 | if (!m) |
917 | 0 | return -1; |
918 | | |
919 | 0 | if (fp) |
920 | 0 | { |
921 | | /* recv mode */ |
922 | |
|
923 | 0 | if (e && m->email && (m->encoding != ENC_BASE64) && |
924 | 0 | (m->encoding != ENC_QUOTED_PRINTABLE) && mutt_is_message_type(m->type, m->subtype)) |
925 | 0 | { |
926 | | /* message type attachments are written to mail folders. */ |
927 | |
|
928 | 0 | char buf[8192] = { 0 }; |
929 | 0 | struct Message *msg = NULL; |
930 | 0 | CopyHeaderFlags chflags = CH_NO_FLAGS; |
931 | 0 | int rc = -1; |
932 | |
|
933 | 0 | struct Email *e_new = m->email; |
934 | 0 | e_new->msgno = e->msgno; /* required for MH/maildir */ |
935 | 0 | e_new->read = true; |
936 | |
|
937 | 0 | if (!mutt_file_seek(fp, m->offset, SEEK_SET)) |
938 | 0 | return -1; |
939 | 0 | if (!fgets(buf, sizeof(buf), fp)) |
940 | 0 | return -1; |
941 | 0 | struct Mailbox *m_att = mx_path_resolve(path); |
942 | 0 | if (!mx_mbox_open(m_att, MUTT_APPEND | MUTT_QUIET)) |
943 | 0 | { |
944 | 0 | mailbox_free(&m_att); |
945 | 0 | return -1; |
946 | 0 | } |
947 | 0 | msg = mx_msg_open_new(m_att, e_new, |
948 | 0 | is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM); |
949 | 0 | if (!msg) |
950 | 0 | { |
951 | 0 | mx_mbox_close(m_att); |
952 | 0 | return -1; |
953 | 0 | } |
954 | 0 | if ((m_att->type == MUTT_MBOX) || (m_att->type == MUTT_MMDF)) |
955 | 0 | chflags = CH_FROM | CH_UPDATE_LEN; |
956 | 0 | chflags |= ((m_att->type == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE); |
957 | 0 | if ((mutt_copy_message_fp(msg->fp, fp, e_new, MUTT_CM_NO_FLAGS, chflags, 0) == 0) && |
958 | 0 | (mx_msg_commit(m_att, msg) == 0)) |
959 | 0 | { |
960 | 0 | rc = 0; |
961 | 0 | } |
962 | 0 | else |
963 | 0 | { |
964 | 0 | rc = -1; |
965 | 0 | } |
966 | |
|
967 | 0 | mx_msg_close(m_att, &msg); |
968 | 0 | mx_mbox_close(m_att); |
969 | 0 | return rc; |
970 | 0 | } |
971 | 0 | else |
972 | 0 | { |
973 | | /* In recv mode, extract from folder and decode */ |
974 | |
|
975 | 0 | struct State state = { 0 }; |
976 | |
|
977 | 0 | state.fp_out = save_attachment_open(path, opt); |
978 | 0 | if (!state.fp_out) |
979 | 0 | { |
980 | 0 | mutt_perror("fopen"); |
981 | 0 | return -1; |
982 | 0 | } |
983 | 0 | if (!mutt_file_seek((state.fp_in = fp), m->offset, SEEK_SET)) |
984 | 0 | { |
985 | 0 | mutt_file_fclose(&state.fp_out); |
986 | 0 | return -1; |
987 | 0 | } |
988 | 0 | mutt_decode_attachment(m, &state); |
989 | |
|
990 | 0 | if (mutt_file_fsync_close(&state.fp_out) != 0) |
991 | 0 | { |
992 | 0 | mutt_perror("fclose"); |
993 | 0 | return -1; |
994 | 0 | } |
995 | 0 | } |
996 | 0 | } |
997 | 0 | else |
998 | 0 | { |
999 | 0 | if (!m->filename) |
1000 | 0 | return -1; |
1001 | | |
1002 | | /* In send mode, just copy file */ |
1003 | | |
1004 | 0 | FILE *fp_old = fopen(m->filename, "r"); |
1005 | 0 | if (!fp_old) |
1006 | 0 | { |
1007 | 0 | mutt_perror("fopen"); |
1008 | 0 | return -1; |
1009 | 0 | } |
1010 | | |
1011 | 0 | FILE *fp_new = save_attachment_open(path, opt); |
1012 | 0 | if (!fp_new) |
1013 | 0 | { |
1014 | 0 | mutt_perror("fopen"); |
1015 | 0 | mutt_file_fclose(&fp_old); |
1016 | 0 | return -1; |
1017 | 0 | } |
1018 | | |
1019 | 0 | if (mutt_file_copy_stream(fp_old, fp_new) == -1) |
1020 | 0 | { |
1021 | 0 | mutt_error(_("Write fault")); |
1022 | 0 | mutt_file_fclose(&fp_old); |
1023 | 0 | mutt_file_fclose(&fp_new); |
1024 | 0 | return -1; |
1025 | 0 | } |
1026 | 0 | mutt_file_fclose(&fp_old); |
1027 | 0 | if (mutt_file_fsync_close(&fp_new) != 0) |
1028 | 0 | { |
1029 | 0 | mutt_error(_("Write fault")); |
1030 | 0 | return -1; |
1031 | 0 | } |
1032 | 0 | } |
1033 | | |
1034 | 0 | return 0; |
1035 | 0 | } |
1036 | | |
1037 | | /** |
1038 | | * mutt_decode_save_attachment - Decode, then save an attachment |
1039 | | * @param fp File to read from (OPTIONAL) |
1040 | | * @param m Attachment |
1041 | | * @param path Path to save the Attachment to |
1042 | | * @param flags Flags, e.g. #STATE_DISPLAY |
1043 | | * @param opt Save option, see #SaveAttach |
1044 | | * @retval 0 Success |
1045 | | * @retval -1 Error |
1046 | | */ |
1047 | | int mutt_decode_save_attachment(FILE *fp, struct Body *m, const char *path, |
1048 | | StateFlags flags, enum SaveAttach opt) |
1049 | 0 | { |
1050 | 0 | struct State state = { 0 }; |
1051 | 0 | unsigned int saved_encoding = 0; |
1052 | 0 | struct Body *saved_parts = NULL; |
1053 | 0 | struct Email *e_saved = NULL; |
1054 | 0 | int rc = 0; |
1055 | |
|
1056 | 0 | state.flags = flags; |
1057 | |
|
1058 | 0 | if (opt == MUTT_SAVE_APPEND) |
1059 | 0 | state.fp_out = fopen(path, "a"); |
1060 | 0 | else if (opt == MUTT_SAVE_OVERWRITE) |
1061 | 0 | state.fp_out = fopen(path, "w"); |
1062 | 0 | else |
1063 | 0 | state.fp_out = mutt_file_fopen(path, "w"); |
1064 | |
|
1065 | 0 | if (!state.fp_out) |
1066 | 0 | { |
1067 | 0 | mutt_perror("fopen"); |
1068 | 0 | return -1; |
1069 | 0 | } |
1070 | | |
1071 | 0 | if (fp) |
1072 | 0 | { |
1073 | 0 | state.fp_in = fp; |
1074 | 0 | state.flags |= STATE_CHARCONV; |
1075 | 0 | } |
1076 | 0 | else |
1077 | 0 | { |
1078 | | /* When called from the compose menu, the attachment isn't parsed, |
1079 | | * so we need to do it here. */ |
1080 | 0 | state.fp_in = fopen(m->filename, "r"); |
1081 | 0 | if (!state.fp_in) |
1082 | 0 | { |
1083 | 0 | mutt_perror("fopen"); |
1084 | 0 | mutt_file_fclose(&state.fp_out); |
1085 | 0 | return -1; |
1086 | 0 | } |
1087 | | |
1088 | 0 | struct stat st = { 0 }; |
1089 | 0 | if (fstat(fileno(state.fp_in), &st) == -1) |
1090 | 0 | { |
1091 | 0 | mutt_perror("stat"); |
1092 | 0 | mutt_file_fclose(&state.fp_in); |
1093 | 0 | mutt_file_fclose(&state.fp_out); |
1094 | 0 | return -1; |
1095 | 0 | } |
1096 | | |
1097 | 0 | saved_encoding = m->encoding; |
1098 | 0 | if (!is_multipart(m)) |
1099 | 0 | m->encoding = ENC_8BIT; |
1100 | |
|
1101 | 0 | m->length = st.st_size; |
1102 | 0 | m->offset = 0; |
1103 | 0 | saved_parts = m->parts; |
1104 | 0 | e_saved = m->email; |
1105 | 0 | mutt_parse_part(state.fp_in, m); |
1106 | |
|
1107 | 0 | if (m->noconv || is_multipart(m)) |
1108 | 0 | state.flags |= STATE_CHARCONV; |
1109 | 0 | } |
1110 | | |
1111 | 0 | mutt_body_handler(m, &state); |
1112 | |
|
1113 | 0 | if (mutt_file_fsync_close(&state.fp_out) != 0) |
1114 | 0 | { |
1115 | 0 | mutt_perror("fclose"); |
1116 | 0 | rc = -1; |
1117 | 0 | } |
1118 | 0 | if (!fp) |
1119 | 0 | { |
1120 | 0 | m->length = 0; |
1121 | 0 | m->encoding = saved_encoding; |
1122 | 0 | if (saved_parts) |
1123 | 0 | { |
1124 | 0 | email_free(&m->email); |
1125 | 0 | m->parts = saved_parts; |
1126 | 0 | m->email = e_saved; |
1127 | 0 | } |
1128 | 0 | mutt_file_fclose(&state.fp_in); |
1129 | 0 | } |
1130 | |
|
1131 | 0 | return rc; |
1132 | 0 | } |
1133 | | |
1134 | | /** |
1135 | | * mutt_print_attachment - Print out an attachment |
1136 | | * @param fp File to write to |
1137 | | * @param a Attachment |
1138 | | * @retval 1 Success |
1139 | | * @retval 0 Error |
1140 | | * |
1141 | | * Ok, the difference between send and receive: |
1142 | | * recv: Body->filename is a suggested name, and Mailbox|Email points |
1143 | | * to the attachment in mailbox which is encoded |
1144 | | * send: Body->filename points to the un-encoded file which contains the |
1145 | | * attachment |
1146 | | */ |
1147 | | int mutt_print_attachment(FILE *fp, struct Body *a) |
1148 | 0 | { |
1149 | 0 | char type[256] = { 0 }; |
1150 | 0 | pid_t pid; |
1151 | 0 | FILE *fp_in = NULL, *fp_out = NULL; |
1152 | 0 | bool unlink_newfile = false; |
1153 | 0 | struct Buffer *newfile = buf_pool_get(); |
1154 | 0 | struct Buffer *cmd = buf_pool_get(); |
1155 | |
|
1156 | 0 | int rc = 0; |
1157 | |
|
1158 | 0 | snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); |
1159 | |
|
1160 | 0 | if (mailcap_lookup(a, type, sizeof(type), NULL, MUTT_MC_PRINT)) |
1161 | 0 | { |
1162 | 0 | mutt_debug(LL_DEBUG2, "Using mailcap\n"); |
1163 | |
|
1164 | 0 | struct MailcapEntry *entry = mailcap_entry_new(); |
1165 | 0 | mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_PRINT); |
1166 | |
|
1167 | 0 | char *sanitized_fname = mutt_str_dup(a->filename); |
1168 | | /* In send mode (!fp), we allow slashes because those are part of |
1169 | | * the tempfile. The path will be removed in expand_filename */ |
1170 | 0 | mutt_file_sanitize_filename(sanitized_fname, fp ? true : false); |
1171 | 0 | mailcap_expand_filename(entry->nametemplate, sanitized_fname, newfile); |
1172 | 0 | FREE(&sanitized_fname); |
1173 | |
|
1174 | 0 | if (mutt_save_attachment(fp, a, buf_string(newfile), MUTT_SAVE_NO_FLAGS, NULL) == -1) |
1175 | 0 | { |
1176 | 0 | goto mailcap_cleanup; |
1177 | 0 | } |
1178 | 0 | unlink_newfile = 1; |
1179 | |
|
1180 | 0 | mutt_rfc3676_space_unstuff_attachment(a, buf_string(newfile)); |
1181 | |
|
1182 | 0 | buf_strcpy(cmd, entry->printcommand); |
1183 | |
|
1184 | 0 | bool piped = mailcap_expand_command(a, buf_string(newfile), type, cmd); |
1185 | |
|
1186 | 0 | mutt_endwin(); |
1187 | |
|
1188 | 0 | const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key"); |
1189 | | /* interactive program */ |
1190 | 0 | if (piped) |
1191 | 0 | { |
1192 | 0 | fp_in = fopen(buf_string(newfile), "r"); |
1193 | 0 | if (!fp_in) |
1194 | 0 | { |
1195 | 0 | mutt_perror("fopen"); |
1196 | 0 | mailcap_entry_free(&entry); |
1197 | 0 | goto mailcap_cleanup; |
1198 | 0 | } |
1199 | | |
1200 | 0 | pid = filter_create(buf_string(cmd), &fp_out, NULL, NULL); |
1201 | 0 | if (pid < 0) |
1202 | 0 | { |
1203 | 0 | mutt_perror(_("Can't create filter")); |
1204 | 0 | mailcap_entry_free(&entry); |
1205 | 0 | mutt_file_fclose(&fp_in); |
1206 | 0 | goto mailcap_cleanup; |
1207 | 0 | } |
1208 | 0 | mutt_file_copy_stream(fp_in, fp_out); |
1209 | 0 | mutt_file_fclose(&fp_out); |
1210 | 0 | mutt_file_fclose(&fp_in); |
1211 | 0 | if (filter_wait(pid) || c_wait_key) |
1212 | 0 | mutt_any_key_to_continue(NULL); |
1213 | 0 | } |
1214 | 0 | else |
1215 | 0 | { |
1216 | 0 | int rc2 = mutt_system(buf_string(cmd)); |
1217 | 0 | if (rc2 == -1) |
1218 | 0 | mutt_debug(LL_DEBUG1, "Error running \"%s\"\n", cmd->data); |
1219 | |
|
1220 | 0 | if ((rc2 != 0) || c_wait_key) |
1221 | 0 | mutt_any_key_to_continue(NULL); |
1222 | 0 | } |
1223 | | |
1224 | 0 | rc = 1; |
1225 | |
|
1226 | 0 | mailcap_cleanup: |
1227 | 0 | if (unlink_newfile) |
1228 | 0 | mutt_file_unlink(buf_string(newfile)); |
1229 | |
|
1230 | 0 | mailcap_entry_free(&entry); |
1231 | 0 | goto out; |
1232 | 0 | } |
1233 | | |
1234 | 0 | const char *const c_print_command = cs_subset_string(NeoMutt->sub, "print_command"); |
1235 | 0 | if (mutt_istr_equal("text/plain", type) || mutt_istr_equal("application/postscript", type)) |
1236 | 0 | { |
1237 | 0 | rc = (mutt_pipe_attachment(fp, a, NONULL(c_print_command), NULL)); |
1238 | 0 | goto out; |
1239 | 0 | } |
1240 | 0 | else if (mutt_can_decode(a)) |
1241 | 0 | { |
1242 | | /* decode and print */ |
1243 | |
|
1244 | 0 | fp_in = NULL; |
1245 | 0 | fp_out = NULL; |
1246 | |
|
1247 | 0 | buf_mktemp(newfile); |
1248 | 0 | if (mutt_decode_save_attachment(fp, a, buf_string(newfile), STATE_PRINTING, |
1249 | 0 | MUTT_SAVE_NO_FLAGS) == 0) |
1250 | 0 | { |
1251 | 0 | unlink_newfile = true; |
1252 | 0 | mutt_debug(LL_DEBUG2, "successfully decoded %s type attachment to %s\n", |
1253 | 0 | type, buf_string(newfile)); |
1254 | |
|
1255 | 0 | fp_in = fopen(buf_string(newfile), "r"); |
1256 | 0 | if (!fp_in) |
1257 | 0 | { |
1258 | 0 | mutt_perror("fopen"); |
1259 | 0 | goto decode_cleanup; |
1260 | 0 | } |
1261 | | |
1262 | 0 | mutt_debug(LL_DEBUG2, "successfully opened %s read-only\n", buf_string(newfile)); |
1263 | |
|
1264 | 0 | mutt_endwin(); |
1265 | 0 | pid = filter_create(NONULL(c_print_command), &fp_out, NULL, NULL); |
1266 | 0 | if (pid < 0) |
1267 | 0 | { |
1268 | 0 | mutt_perror(_("Can't create filter")); |
1269 | 0 | goto decode_cleanup; |
1270 | 0 | } |
1271 | | |
1272 | 0 | mutt_debug(LL_DEBUG2, "Filter created\n"); |
1273 | |
|
1274 | 0 | mutt_file_copy_stream(fp_in, fp_out); |
1275 | |
|
1276 | 0 | mutt_file_fclose(&fp_out); |
1277 | 0 | mutt_file_fclose(&fp_in); |
1278 | |
|
1279 | 0 | const bool c_wait_key = cs_subset_bool(NeoMutt->sub, "wait_key"); |
1280 | 0 | if ((filter_wait(pid) != 0) || c_wait_key) |
1281 | 0 | mutt_any_key_to_continue(NULL); |
1282 | 0 | rc = 1; |
1283 | 0 | } |
1284 | 0 | decode_cleanup: |
1285 | 0 | mutt_file_fclose(&fp_in); |
1286 | 0 | mutt_file_fclose(&fp_out); |
1287 | 0 | if (unlink_newfile) |
1288 | 0 | mutt_file_unlink(buf_string(newfile)); |
1289 | 0 | } |
1290 | 0 | else |
1291 | 0 | { |
1292 | 0 | mutt_error(_("I don't know how to print that")); |
1293 | 0 | rc = 0; |
1294 | 0 | } |
1295 | | |
1296 | 0 | out: |
1297 | 0 | buf_pool_release(&newfile); |
1298 | 0 | buf_pool_release(&cmd); |
1299 | |
|
1300 | 0 | return rc; |
1301 | 0 | } |
1302 | | |
1303 | | /** |
1304 | | * mutt_add_temp_attachment - Add file to list of temporary attachments |
1305 | | * @param filename filename with full path |
1306 | | */ |
1307 | | void mutt_add_temp_attachment(const char *filename) |
1308 | 0 | { |
1309 | 0 | mutt_list_insert_tail(&TempAttachmentsList, mutt_str_dup(filename)); |
1310 | 0 | } |
1311 | | |
1312 | | /** |
1313 | | * mutt_unlink_temp_attachments - Delete all temporary attachments |
1314 | | */ |
1315 | | void mutt_unlink_temp_attachments(void) |
1316 | 0 | { |
1317 | 0 | struct ListNode *np = NULL; |
1318 | |
|
1319 | 0 | STAILQ_FOREACH(np, &TempAttachmentsList, entries) |
1320 | 0 | { |
1321 | 0 | (void) mutt_file_chmod_add(np->data, S_IWUSR); |
1322 | 0 | mutt_file_unlink(np->data); |
1323 | 0 | } |
1324 | |
|
1325 | 0 | mutt_list_free(&TempAttachmentsList); |
1326 | 0 | } |