mk-conf.sh, mk-mk.in: diversify..
[s-mailx.git] / attachments.c
blob65b107c485e61f8b47802d4afb03b8fdfef9672a
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 CTA(AC_DEFAULT == 0);
45 /* Fill in some attachment fields; don't be interactive if number==0 */
46 static struct attachment * _fill_in(struct attachment *ap,
47 char const *file, ui32_t number);
49 /* Ask the user to edit file names and other data for the given attachment */
50 static struct attachment * _read_attachment_data(struct attachment *ap,
51 ui32_t number);
53 /* Try to create temporary charset converted version */
54 #ifdef HAVE_ICONV
55 static int _attach_iconv(struct attachment *ap);
56 #endif
58 static struct attachment *
59 _fill_in(struct attachment *ap, char const *file, ui32_t number)
61 /* XXX The "attachment-ask-content-*" variables are left undocumented
62 * since "they are for RFC connoisseurs only" ;) */
63 char prefix[80 * 2];
64 NYD_ENTER;
66 ap->a_input_charset = ap->a_charset = NULL;
68 ap->a_name = file;
69 if ((file = strrchr(file, '/')) != NULL)
70 ++file;
71 else
72 file = ap->a_name;
74 ap->a_content_type = mime_type_classify_filename(file);
75 if (number > 0 && ok_blook(attachment_ask_content_type)) {
76 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Type: ", number);
77 ap->a_content_type = n_lex_input_cp_addhist(prefix, ap->a_content_type,
78 TRU1);
81 if (number > 0 && ok_blook(attachment_ask_content_disposition)) {
82 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Disposition: ",
83 number);
84 if ((ap->a_content_disposition = n_lex_input_cp_addhist(prefix,
85 ap->a_content_disposition, TRU1)) == NULL)
86 goto jcdis;
87 } else
88 jcdis:
89 ap->a_content_disposition = "attachment";
91 if (number > 0 && ok_blook(attachment_ask_content_id)) {
92 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-ID: ", number);
93 ap->a_content_id = n_lex_input_cp_addhist(prefix, ap->a_content_id, TRU1);
94 } else
95 ap->a_content_id = NULL;
97 if (number > 0 && ok_blook(attachment_ask_content_description)) {
98 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Description: ",
99 number);
100 ap->a_content_description = n_lex_input_cp_addhist(prefix,
101 ap->a_content_description, TRU1);
103 NYD_LEAVE;
104 return ap;
107 static sigjmp_buf __atticonv_jmp; /* TODO oneday, we won't need it no more */
108 static int volatile __atticonv_sig; /* TODO oneday, we won't need it no more */
109 static void
110 __atticonv_onsig(int sig) /* TODO someday, we won't need it no more */
112 NYD_X; /* Signal handler */
113 __atticonv_sig = sig;
114 siglongjmp(__atticonv_jmp, 1);
117 static struct attachment *
118 _read_attachment_data(struct attachment * volatile ap, ui32_t number)
120 sighandler_type volatile ohdl;
121 char prefix[80 * 2];
122 struct str shin;
123 struct n_string shou, *shoup;
124 char const *cslc, *cp, *defcs;
125 NYD_ENTER;
127 UNINIT(cslc, NULL);
128 shoup = n_string_creat(&shou);
130 hold_sigs(); /* TODO until we have signal manager (see TODO) */
131 ohdl = safe_signal(SIGINT, SIG_IGN);
132 __atticonv_sig = 0;
133 if (sigsetjmp(__atticonv_jmp, 1)) {
134 ap = NULL;
135 goto jleave;
137 safe_signal(SIGINT, &__atticonv_onsig);
139 if (ap == NULL)
140 ap = csalloc(1, sizeof *ap);
141 else if (ap->a_msgno) {
142 char *ecp = salloc(24);
143 snprintf(ecp, 24, "#%" PRIu32, (ui32_t)ap->a_msgno);
144 ap->a_msgno = 0;
145 ap->a_content_description = NULL;
146 ap->a_name = ecp;
147 } else if (ap->a_conv == AC_TMPFILE) {
148 Fclose(ap->a_tmpf);
149 DBG( ap->a_tmpf = NULL; )
150 ap->a_conv = AC_DEFAULT;
153 rele_sigs(); /* TODO until we have signal manager (see TODO) */
154 snprintf(prefix, sizeof prefix, _("#%-5" PRIu32 " filename: "), number);
155 for (;;) {
156 if ((cp = ap->a_name) != NULL)
157 cp = n_shell_quote_cp(cp, FAL0);
158 if ((cp = n_lex_input_cp_addhist(prefix, cp, TRU1)) == NULL) {
159 ap->a_name = NULL;
160 ap = NULL;
161 goto jleave;
164 shin.s = UNCONST(cp);
165 shin.l = UIZ_MAX;
166 if((n_shell_parse_token(shoup, &shin, n_SHEXP_PARSE_TRUNC |
167 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
168 n_SHEXP_PARSE_IGNORE_EMPTY) &
169 (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) !=
170 n_SHEXP_STATE_OUTPUT)
171 continue;
172 if(shin.l != 0)
173 continue;
174 cp = n_string_cp(shoup);
176 /* May be a message number (XXX add "AC_MSG", use that not .a_msgno) */
177 if (cp[0] == '#') {
178 char *ecp;
179 int msgno = (int)strtol(cp + 1, &ecp, 10);
181 if (msgno > 0 && msgno <= msgCount && *ecp == '\0') {
182 ap->a_name = cp;
183 ap->a_msgno = msgno;
184 ap->a_content_type = ap->a_content_disposition =
185 ap->a_content_id = NULL;
186 ap->a_content_description = _("Attached message content");
187 if (options & OPT_INTERACTIVE)
188 printf(_("~@: added message #%" PRIu32 "\n"), (ui32_t)msgno);
189 goto jleave;
193 if ((cp = fexpand(cp, FEXP_LOCAL | FEXP_NVAR)) != NULL &&
194 !access(cp, R_OK)) {
195 ap->a_name = cp;
196 break;
198 n_perr(cp, 0);
201 ap = _fill_in(ap, cp, number);
204 * Character set of attachments: enum attach_conv
206 cslc = charset_get_lc();
207 #ifdef HAVE_ICONV
208 if (!(options & OPT_INTERACTIVE))
209 goto jcs;
210 if ((cp = ap->a_content_type) != NULL && ascncasecmp(cp, "text/", 5) != 0 &&
211 !getapproval(_("File doesn't indicate text content, "
212 "edit character sets"), TRU1)) {
213 ap->a_conv = AC_DEFAULT;
214 goto jleave;
217 jcs_restart:
218 charset_iter_reset(NULL);
219 jcs:
220 #endif
221 snprintf(prefix, sizeof prefix, _("#%-5" PRIu32 " input charset: "),
222 number);
223 if ((defcs = ap->a_input_charset) == NULL)
224 defcs = cslc;
225 cp = ap->a_input_charset = n_lex_input_cp_addhist(prefix, defcs, TRU1);
226 #ifdef HAVE_ICONV
227 if (!(options & OPT_INTERACTIVE)) {
228 #endif
229 ap->a_conv = (cp != NULL) ? AC_FIX_INCS : AC_DEFAULT;
230 #ifdef HAVE_ICONV
231 goto jleave;
234 snprintf(prefix, sizeof prefix,
235 _("#%-5" PRIu32 " output (send) charset: "), number);
236 if ((defcs = ap->a_charset) == NULL)
237 defcs = charset_iter();
238 defcs = ap->a_charset = n_lex_input_cp_addhist(prefix, defcs, TRU1);
240 /* Input, no output -> input=as given, output=no conversion at all */
241 if (cp != NULL && defcs == NULL) {
242 ap->a_conv = AC_FIX_INCS;
243 goto jdone;
246 /* No input, no output -> input=*ttycharset*, output=iterator */
247 if (cp == NULL && defcs == NULL) {
248 ap->a_conv = AC_DEFAULT;
249 ap->a_input_charset = cslc;
250 ap->a_charset = charset_iter();
251 assert(charset_iter_is_valid());
252 charset_iter_next();
254 /* No input, output -> input=*ttycharset*, output=as given */
255 else if (cp == NULL && defcs != NULL) {
256 ap->a_conv = AC_FIX_OUTCS;
257 ap->a_input_charset = cslc;
259 /* Input, output -> try conversion from input=as given to output=as given */
261 printf(_("Trying conversion from %s to %s\n"), ap->a_input_charset,
262 ap->a_charset);
263 if (_attach_iconv(ap))
264 ap->a_conv = AC_TMPFILE;
265 else {
266 ap->a_conv = AC_DEFAULT;
267 ap->a_input_charset = cp;
268 ap->a_charset = defcs;
269 if (!charset_iter_is_valid()) {
270 printf(_("*sendcharsets* and *charset-8bit* iteration "
271 "exhausted, restarting\n"));
272 goto jcs_restart;
274 goto jcs;
276 jdone:
277 #endif
278 if (options & OPT_INTERACTIVE)
279 printf(_("Added attachment %s\n"), n_shell_quote_cp(ap->a_name, FAL0));
280 jleave:
281 n_string_gut(shoup);
283 safe_signal(SIGINT, ohdl);/* TODO until we have signal manager (see TODO) */
284 if (__atticonv_sig != 0) {
285 sigset_t nset;
286 sigemptyset(&nset);
287 sigaddset(&nset, SIGINT);
288 sigprocmask(SIG_UNBLOCK, &nset, NULL);
289 /* Caller kills */
291 NYD_LEAVE;
292 return ap;
295 #ifdef HAVE_ICONV
296 static int
297 _attach_iconv(struct attachment *ap)
299 struct str oul = {NULL, 0}, inl = {NULL, 0};
300 FILE *fo = NULL, *fi = NULL;
301 size_t cnt, lbsize;
302 iconv_t icp;
303 NYD_ENTER;
305 hold_sigs(); /* TODO until we have signal manager (see TODO) */
307 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
308 if (icp == (iconv_t)-1) {
309 if (errno == EINVAL)
310 goto jeconv;
311 else
312 n_perr(_("iconv_open"), 0);
313 goto jerr;
316 if ((fi = Fopen(ap->a_name, "r")) == NULL) {
317 n_perr(ap->a_name, 0);
318 goto jerr;
320 cnt = fsize(fi);
322 if ((fo = Ftmp(NULL, "aticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
323 NULL) {
324 n_perr(_("temporary mail file"), 0);
325 goto jerr;
328 for (lbsize = 0;;) {
329 if (fgetline(&inl.s, &lbsize, &cnt, &inl.l, fi, 0) == NULL) {
330 if (!cnt)
331 break;
332 n_perr(_("I/O read error occurred"), 0);
333 goto jerr;
336 if (n_iconv_str(icp, &oul, &inl, NULL, FAL0) != 0)
337 goto jeconv;
338 if ((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, fo)) != oul.l) {
339 n_perr(_("I/O write error occurred"), 0);
340 goto jerr;
343 fflush_rewind(fo);
345 ap->a_tmpf = fo;
346 jleave:
347 if (inl.s != NULL)
348 free(inl.s);
349 if (oul.s != NULL)
350 free(oul.s);
351 if (fi != NULL)
352 Fclose(fi);
353 if (icp != (iconv_t)-1)
354 n_iconv_close(icp);
356 rele_sigs(); /* TODO until we have signal manager (see TODO) */
357 NYD_LEAVE;
358 return (fo != NULL);
360 jeconv:
361 n_err(_("Cannot convert from %s to %s\n"),
362 ap->a_input_charset, ap->a_charset);
363 jerr:
364 if (fo != NULL)
365 Fclose(fo);
366 fo = NULL;
367 goto jleave;
369 #endif /* HAVE_ICONV */
371 /* TODO add_attachment(): also work with **aphead, not *aphead ... */
372 FL struct attachment *
373 add_attachment(struct attachment *aphead, char *file, struct attachment **newap)
375 struct attachment *nap = NULL, *ap;
376 NYD_ENTER;
378 if ((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL)
379 goto jleave;
380 if (access(file, R_OK) != 0)
381 goto jleave;
383 nap = _fill_in(csalloc(1, sizeof *nap), file, 0);
384 if (newap != NULL)
385 *newap = nap;
386 if (aphead != NULL) {
387 for (ap = aphead; ap->a_flink != NULL; ap = ap->a_flink)
389 ap->a_flink = nap;
390 nap->a_blink = ap;
391 } else {
392 nap->a_blink = NULL;
393 aphead = nap;
395 nap = aphead;
396 jleave:
397 NYD_LEAVE;
398 return nap;
401 FL void
402 append_attachments(struct attachment **aphead, char *names){
403 struct str shin;
404 struct n_string shou, *shoup;
405 NYD_ENTER;
407 shoup = n_string_creat_auto(&shou);
409 for(shin.s = names, shin.l = UIZ_MAX;;){
410 struct attachment *xaph, *nap;
411 enum n_shexp_state shs;
413 shs = n_shell_parse_token(shoup, &shin, n_SHEXP_PARSE_TRUNC |
414 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
415 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY);
416 if(shs & n_SHEXP_STATE_ERR_MASK)
417 break;
419 if(shs & n_SHEXP_STATE_OUTPUT){
420 if((xaph = add_attachment(*aphead, n_string_cp(shoup), &nap)) != NULL){
421 *aphead = xaph;
422 if(options & OPT_INTERACTIVE)
423 printf(_("Added attachment %s\n"),
424 n_shell_quote_cp(nap->a_name, FAL0));
425 }else
426 n_perr(n_string_cp(shoup), 0);
429 if(shs & n_SHEXP_STATE_STOP)
430 break;
432 n_string_gut(shoup);
433 NYD_LEAVE;
436 FL void
437 edit_attachments(struct attachment **aphead)
439 struct attachment *ap, *fap, *bap;
440 ui32_t attno = 1;
441 NYD_ENTER;
443 /* C99 */{
444 static bool_t note_given; /* v15-compat: DROP */
446 if(!note_given){
447 note_given = TRU1;
448 printf(_("# Only supports sh(1)ell-style quoting for file names\n"));
452 /* Modify already present ones? */
453 for (ap = *aphead; ap != NULL; ap = fap) {
454 if (_read_attachment_data(ap, attno) != NULL) {
455 fap = ap->a_flink;
456 ++attno;
457 continue;
459 fap = ap->a_flink;
460 if ((bap = ap->a_blink) != NULL)
461 bap->a_flink = fap;
462 else
463 *aphead = fap;
464 if (fap != NULL)
465 fap->a_blink = bap;
466 /*else*//* TODO until we have signal manager (see TODO) */
467 if (__atticonv_sig != 0)
468 n_raise(SIGINT);
469 if (fap == NULL)
470 goto jleave;
473 /* Add some more? */
474 if ((bap = *aphead) != NULL)
475 while (bap->a_flink != NULL)
476 bap = bap->a_flink;
477 while ((fap = _read_attachment_data(NULL, attno)) != NULL) {
478 if (bap != NULL)
479 bap->a_flink = fap;
480 else
481 *aphead = fap;
482 fap->a_blink = bap;
483 fap->a_flink = NULL;
484 bap = fap;
485 ++attno;
487 if (__atticonv_sig != 0) /* TODO until we have signal manager (see TODO) */
488 n_raise(SIGINT);
489 jleave:
490 NYD_LEAVE;
493 /* s-it-mode */