Show the Content-Description:, as applicable
[s-mailx.git] / attachments.c
blob8b1dec47a517a8ad8b3f26df5c9e618fb1a9f7e6
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Handling of attachments.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE attachments
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* We use calloc() for struct attachment */
43 n_CTAV(AC_DEFAULT == 0);
45 /* Fill in some attachment fields; don't be interactive if number==0 */
46 static struct attachment * _fill_in(enum n_lexinput_flags lif,
47 struct attachment *ap, char const *file,
48 ui32_t number);
50 /* Ask the user to edit file names and other data for the given attachment */
51 static struct attachment * _read_attachment_data(enum n_lexinput_flags lif,
52 struct attachment *ap, ui32_t number);
54 /* Try to create temporary charset converted version */
55 #ifdef HAVE_ICONV
56 static int _attach_iconv(struct attachment *ap);
57 #endif
59 static struct attachment *
60 _fill_in(enum n_lexinput_flags lif, struct attachment *ap, char const *file,
61 ui32_t number)
63 /* XXX The "attachment-ask-content-*" variables are left undocumented
64 * since "they are for RFC connoisseurs only" ;) */
65 char prefix[80 * 2];
66 NYD_ENTER;
68 ap->a_input_charset = ap->a_charset = NULL;
70 ap->a_name = file;
71 if ((file = strrchr(file, '/')) != NULL)
72 ++file;
73 else
74 file = ap->a_name;
76 ap->a_content_type = mime_type_classify_filename(file);
77 if (number > 0 && ok_blook(attachment_ask_content_type)) {
78 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Type: ", number);
79 ap->a_content_type = n_lex_input_cp(lif, prefix, ap->a_content_type);
82 if (number > 0 && ok_blook(attachment_ask_content_disposition)) {
83 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Disposition: ",
84 number);
85 if ((ap->a_content_disposition = n_lex_input_cp(lif, prefix,
86 ap->a_content_disposition)) == NULL)
87 goto jcdis;
88 } else
89 jcdis:
90 ap->a_content_disposition = "attachment";
92 if (number > 0 && ok_blook(attachment_ask_content_id)) {
93 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-ID: ", number);
94 ap->a_content_id = n_lex_input_cp(lif, prefix, ap->a_content_id);
95 } else
96 ap->a_content_id = NULL;
98 if (number > 0 && ok_blook(attachment_ask_content_description)) {
99 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Description: ",
100 number);
101 ap->a_content_description = n_lex_input_cp(lif, prefix,
102 ap->a_content_description);
104 NYD_LEAVE;
105 return ap;
108 static sigjmp_buf __atticonv_jmp; /* TODO oneday, we won't need it no more */
109 static int volatile __atticonv_sig; /* TODO oneday, we won't need it no more */
110 static void
111 __atticonv_onsig(int sig) /* TODO someday, we won't need it no more */
113 NYD_X; /* Signal handler */
114 __atticonv_sig = sig;
115 siglongjmp(__atticonv_jmp, 1);
118 static struct attachment *
119 _read_attachment_data(enum n_lexinput_flags lif,
120 struct attachment * volatile ap, ui32_t number)
122 sighandler_type volatile ohdl;
123 char prefix[80 * 2];
124 struct str shin;
125 struct n_string shou, *shoup;
126 char const *cslc, *cp, *defcs;
127 NYD_ENTER;
129 n_UNINIT(cslc, NULL);
130 shoup = n_string_creat(&shou);
132 hold_sigs(); /* TODO until we have signal manager (see TODO) */
133 ohdl = safe_signal(SIGINT, SIG_IGN);
134 __atticonv_sig = 0;
135 if (sigsetjmp(__atticonv_jmp, 1)) {
136 ap = NULL;
137 goto jleave;
139 safe_signal(SIGINT, &__atticonv_onsig);
141 if (ap == NULL)
142 ap = csalloc(1, sizeof *ap);
143 else if (ap->a_msgno) {
144 char *ecp = salloc(24);
145 snprintf(ecp, 24, "#%" PRIu32, (ui32_t)ap->a_msgno);
146 ap->a_msgno = 0;
147 ap->a_content_description = NULL;
148 ap->a_name = ecp;
149 } else if (ap->a_conv == AC_TMPFILE) {
150 Fclose(ap->a_tmpf);
151 DBG( ap->a_tmpf = NULL; )
152 ap->a_conv = AC_DEFAULT;
155 rele_sigs(); /* TODO until we have signal manager (see TODO) */
156 snprintf(prefix, sizeof prefix, _("#%-5" PRIu32 " filename: "), number);
157 for (;;) {
158 if ((cp = ap->a_name) != NULL)
159 cp = n_shexp_quote_cp(cp, FAL0);
160 if ((cp = n_lex_input_cp(lif, prefix, cp)) == NULL) {
161 ap->a_name = NULL;
162 ap = NULL;
163 goto jleave;
166 shin.s = n_UNCONST(cp);
167 shin.l = UIZ_MAX;
168 if((n_shexp_parse_token(shoup, &shin, n_SHEXP_PARSE_TRUNC |
169 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
170 n_SHEXP_PARSE_IGNORE_EMPTY) &
171 (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT |
172 n_SHEXP_STATE_ERR_MASK)
173 ) != (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT))
174 continue;
175 cp = n_string_cp(shoup);
177 /* May be a message number (XXX add "AC_MSG", use that not .a_msgno) */
178 if (cp[0] == '#') {
179 char *ecp;
180 int msgno = (int)strtol(cp + 1, &ecp, 10);
182 if (msgno > 0 && msgno <= msgCount && *ecp == '\0') {
183 ap->a_name = cp;
184 ap->a_msgno = msgno;
185 ap->a_content_type = ap->a_content_disposition =
186 ap->a_content_id = NULL;
187 ap->a_content_description = _("Attached message content");
188 if (options & OPT_INTERACTIVE)
189 printf(_("~@: added message #%" PRIu32 "\n"), (ui32_t)msgno);
190 goto jleave;
194 if ((cp = fexpand(cp, FEXP_LOCAL | FEXP_NVAR)) != NULL &&
195 !access(cp, R_OK)) {
196 ap->a_name = cp;
197 break;
199 n_perr(cp, 0);
202 ap = _fill_in(lif, ap, cp, number);
205 * Character set of attachments: enum attach_conv
207 cslc = charset_get_lc();
208 #ifdef HAVE_ICONV
209 if (!(options & OPT_INTERACTIVE))
210 goto jcs;
211 if ((cp = ap->a_content_type) != NULL && ascncasecmp(cp, "text/", 5) != 0 &&
212 !getapproval(_("File doesn't indicate text content, "
213 "edit character sets"), TRU1)) {
214 ap->a_conv = AC_DEFAULT;
215 goto jleave;
218 jcs_restart:
219 charset_iter_reset(NULL);
220 jcs:
221 #endif
222 snprintf(prefix, sizeof prefix, _("#%-5" PRIu32 " input charset: "),
223 number);
224 if ((defcs = ap->a_input_charset) == NULL)
225 defcs = cslc;
226 cp = ap->a_input_charset = n_lex_input_cp(lif, prefix, defcs);
227 #ifdef HAVE_ICONV
228 if (!(options & OPT_INTERACTIVE)) {
229 #endif
230 ap->a_conv = (cp != NULL) ? AC_FIX_INCS : AC_DEFAULT;
231 #ifdef HAVE_ICONV
232 goto jleave;
235 snprintf(prefix, sizeof prefix,
236 _("#%-5" PRIu32 " output (send) charset: "), number);
237 if ((defcs = ap->a_charset) == NULL)
238 defcs = charset_iter();
239 defcs = ap->a_charset = n_lex_input_cp(lif, prefix, defcs);
241 /* Input, no output -> input=as given, output=no conversion at all */
242 if (cp != NULL && defcs == NULL) {
243 ap->a_conv = AC_FIX_INCS;
244 goto jdone;
247 /* No input, no output -> input=*ttycharset*, output=iterator */
248 if (cp == NULL && defcs == NULL) {
249 ap->a_conv = AC_DEFAULT;
250 ap->a_input_charset = cslc;
251 ap->a_charset = charset_iter();
252 assert(charset_iter_is_valid());
253 charset_iter_next();
255 /* No input, output -> input=*ttycharset*, output=as given */
256 else if (cp == NULL && defcs != NULL) {
257 ap->a_conv = AC_FIX_OUTCS;
258 ap->a_input_charset = cslc;
260 /* Input, output -> try conversion from input=as given to output=as given */
262 printf(_("Trying conversion from %s to %s\n"), ap->a_input_charset,
263 ap->a_charset);
264 if (_attach_iconv(ap))
265 ap->a_conv = AC_TMPFILE;
266 else {
267 ap->a_conv = AC_DEFAULT;
268 ap->a_input_charset = cp;
269 ap->a_charset = defcs;
270 if (!charset_iter_is_valid()) {
271 printf(_("*sendcharsets* and *charset-8bit* iteration "
272 "exhausted, restarting\n"));
273 goto jcs_restart;
275 goto jcs;
277 jdone:
278 #endif
279 if (options & OPT_INTERACTIVE)
280 printf(_("Added attachment %s\n"), n_shexp_quote_cp(ap->a_name, FAL0));
281 jleave:
282 n_string_gut(shoup);
284 safe_signal(SIGINT, ohdl);/* TODO until we have signal manager (see TODO) */
285 if (__atticonv_sig != 0) {
286 sigset_t nset;
287 sigemptyset(&nset);
288 sigaddset(&nset, SIGINT);
289 sigprocmask(SIG_UNBLOCK, &nset, NULL);
290 /* Caller kills */
292 NYD_LEAVE;
293 return ap;
296 #ifdef HAVE_ICONV
297 static int
298 _attach_iconv(struct attachment *ap)
300 struct str oul = {NULL, 0}, inl = {NULL, 0};
301 FILE *fo = NULL, *fi = NULL;
302 size_t cnt, lbsize;
303 iconv_t icp;
304 NYD_ENTER;
306 hold_sigs(); /* TODO until we have signal manager (see TODO) */
308 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
309 if (icp == (iconv_t)-1) {
310 if (errno == EINVAL)
311 goto jeconv;
312 else
313 n_perr(_("iconv_open"), 0);
314 goto jerr;
317 if ((fi = Fopen(ap->a_name, "r")) == NULL) {
318 n_perr(ap->a_name, 0);
319 goto jerr;
321 cnt = (size_t)fsize(fi);
323 if ((fo = Ftmp(NULL, "aticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
324 NULL) {
325 n_perr(_("temporary mail file"), 0);
326 goto jerr;
329 for (lbsize = 0;;) {
330 if (fgetline(&inl.s, &lbsize, &cnt, &inl.l, fi, 0) == NULL) {
331 if (!cnt)
332 break;
333 n_perr(_("I/O read error occurred"), 0);
334 goto jerr;
337 if (n_iconv_str(icp, n_ICONV_IGN_NOREVERSE, &oul, &inl, NULL) != 0)
338 goto jeconv;
339 if ((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, fo)) != oul.l) {
340 n_perr(_("I/O write error occurred"), 0);
341 goto jerr;
344 fflush_rewind(fo);
346 ap->a_tmpf = fo;
347 jleave:
348 if (inl.s != NULL)
349 free(inl.s);
350 if (oul.s != NULL)
351 free(oul.s);
352 if (fi != NULL)
353 Fclose(fi);
354 if (icp != (iconv_t)-1)
355 n_iconv_close(icp);
357 rele_sigs(); /* TODO until we have signal manager (see TODO) */
358 NYD_LEAVE;
359 return (fo != NULL);
361 jeconv:
362 n_err(_("Cannot convert from %s to %s\n"),
363 ap->a_input_charset, ap->a_charset);
364 jerr:
365 if (fo != NULL)
366 Fclose(fo);
367 fo = NULL;
368 goto jleave;
370 #endif /* HAVE_ICONV */
372 /* TODO add_attachment(): also work with **aphead, not *aphead ... */
373 FL struct attachment *
374 add_attachment(enum n_lexinput_flags lif, struct attachment *aphead,
375 char *file, struct attachment **newap)
377 struct attachment *nap = NULL, *ap;
378 NYD_ENTER;
380 if ((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL)
381 goto jleave;
382 if (access(file, R_OK) != 0)
383 goto jleave;
385 nap = _fill_in(lif, csalloc(1, sizeof *nap), file, 0);
386 if (newap != NULL)
387 *newap = nap;
388 if (aphead != NULL) {
389 for (ap = aphead; ap->a_flink != NULL; ap = ap->a_flink)
391 ap->a_flink = nap;
392 nap->a_blink = ap;
393 } else {
394 nap->a_blink = NULL;
395 aphead = nap;
397 nap = aphead;
398 jleave:
399 NYD_LEAVE;
400 return nap;
403 FL void
404 append_attachments(enum n_lexinput_flags lif, struct attachment **aphead,
405 char *names){
406 struct str shin;
407 struct n_string shou, *shoup;
408 NYD_ENTER;
410 shoup = n_string_creat_auto(&shou);
412 for(shin.s = names, shin.l = UIZ_MAX;;){
413 struct attachment *xaph, *nap;
414 enum n_shexp_state shs;
416 shs = n_shexp_parse_token(shoup, &shin, n_SHEXP_PARSE_TRUNC |
417 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
418 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY);
419 if(shs & n_SHEXP_STATE_ERR_MASK)
420 break;
422 if(shs & n_SHEXP_STATE_OUTPUT){
423 if((xaph = add_attachment(lif, *aphead, n_string_cp(shoup), &nap)
424 ) != NULL){
425 *aphead = xaph;
426 if(options & OPT_INTERACTIVE)
427 printf(_("Added attachment %s\n"),
428 n_shexp_quote_cp(nap->a_name, FAL0));
429 }else
430 n_perr(n_string_cp(shoup), 0);
433 if(shs & n_SHEXP_STATE_STOP)
434 break;
436 n_string_gut(shoup);
437 NYD_LEAVE;
440 FL void
441 edit_attachments(enum n_lexinput_flags lif, struct attachment **aphead)
443 struct attachment *ap, *fap, *bap;
444 ui32_t attno = 1;
445 NYD_ENTER;
447 /* C99 */{
448 static bool_t note_given; /* v15-compat: DROP */
450 if(!note_given){
451 note_given = TRU1;
452 printf(_("# Only supports sh(1)ell-style quoting for file names\n"));
456 /* Modify already present ones? */
457 for (ap = *aphead; ap != NULL; ap = fap) {
458 if (_read_attachment_data(lif, ap, attno) != NULL) {
459 fap = ap->a_flink;
460 ++attno;
461 continue;
463 fap = ap->a_flink;
464 if ((bap = ap->a_blink) != NULL)
465 bap->a_flink = fap;
466 else
467 *aphead = fap;
468 if (fap != NULL)
469 fap->a_blink = bap;
470 /*else*//* TODO until we have signal manager (see TODO) */
471 if (__atticonv_sig != 0)
472 n_raise(SIGINT);
473 if (fap == NULL)
474 goto jleave;
477 /* Add some more? */
478 if ((bap = *aphead) != NULL)
479 while (bap->a_flink != NULL)
480 bap = bap->a_flink;
481 while ((fap = _read_attachment_data(lif, NULL, attno)) != NULL) {
482 if (bap != NULL)
483 bap->a_flink = fap;
484 else
485 *aphead = fap;
486 fap->a_blink = bap;
487 fap->a_flink = NULL;
488 bap = fap;
489 ++attno;
491 if (__atticonv_sig != 0) /* TODO until we have signal manager (see TODO) */
492 n_raise(SIGINT);
493 jleave:
494 NYD_LEAVE;
497 /* s-it-mode */