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