mk-conf.sh: this BSD software actually REQUIRES SA_RESTART!
[s-mailx.git] / attachments.c
blob41b4d6dbce526476126cbee1f30f30c6f648cedc
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_input_cp_addhist(prefix, ap->a_content_type, TRU1);
80 if (number > 0 && ok_blook(attachment_ask_content_disposition)) {
81 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Disposition: ",
82 number);
83 if ((ap->a_content_disposition = n_input_cp_addhist(prefix,
84 ap->a_content_disposition, TRU1)) == NULL)
85 goto jcdis;
86 } else
87 jcdis:
88 ap->a_content_disposition = "attachment";
90 if (number > 0 && ok_blook(attachment_ask_content_id)) {
91 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-ID: ", number);
92 ap->a_content_id = n_input_cp_addhist(prefix, ap->a_content_id, TRU1);
93 } else
94 ap->a_content_id = NULL;
96 if (number > 0 && ok_blook(attachment_ask_content_description)) {
97 snprintf(prefix, sizeof prefix, "#%-5" PRIu32 " Content-Description: ",
98 number);
99 ap->a_content_description = n_input_cp_addhist(prefix,
100 ap->a_content_description, TRU1);
102 NYD_LEAVE;
103 return ap;
106 static sigjmp_buf __atticonv_jmp; /* TODO oneday, we won't need it no more */
107 static int volatile __atticonv_sig; /* TODO oneday, we won't need it no more */
108 static void
109 __atticonv_onsig(int sig) /* TODO someday, we won't need it no more */
111 NYD_X; /* Signal handler */
112 __atticonv_sig = sig;
113 siglongjmp(__atticonv_jmp, 1);
116 static struct attachment *
117 _read_attachment_data(struct attachment * volatile ap, ui32_t number)
119 sighandler_type volatile ohdl;
120 char prefix[80 * 2];
121 char const *cslc = NULL/*cc uninit?*/, *cp, *defcs;
122 NYD_ENTER;
124 hold_sigs(); /* TODO until we have signal manager (see TODO) */
125 ohdl = safe_signal(SIGINT, SIG_IGN);
126 __atticonv_sig = 0;
127 if (sigsetjmp(__atticonv_jmp, 1)) {
128 ap = NULL;
129 goto jleave;
131 safe_signal(SIGINT, &__atticonv_onsig);
133 if (ap == NULL)
134 ap = csalloc(1, sizeof *ap);
135 else if (ap->a_msgno) {
136 char *ecp = salloc(24);
137 snprintf(ecp, 24, "#%" PRIu32, (ui32_t)ap->a_msgno);
138 ap->a_msgno = 0;
139 ap->a_content_description = NULL;
140 ap->a_name = ecp;
141 } else if (ap->a_conv == AC_TMPFILE) {
142 Fclose(ap->a_tmpf);
143 DBG( ap->a_tmpf = NULL; )
144 ap->a_conv = AC_DEFAULT;
147 rele_sigs(); /* TODO until we have signal manager (see TODO) */
148 snprintf(prefix, sizeof prefix, _("#%-5" PRIu32 " filename: "), number);
149 for (;;) {
150 if ((cp = ap->a_name) != NULL)
151 cp = fexpand_nshell_quote(cp);
152 if ((cp = n_input_cp_addhist(prefix, cp, TRU1)) == NULL) {
153 ap->a_name = NULL;
154 ap = NULL;
155 goto jleave;
158 /* May be a message number (XXX add "AC_MSG", use that not .a_msgno) */
159 if (cp[0] == '#') {
160 char *ecp;
161 int msgno = (int)strtol(cp + 1, &ecp, 10);
163 if (msgno > 0 && msgno <= msgCount && *ecp == '\0') {
164 ap->a_name = cp;
165 ap->a_msgno = msgno;
166 ap->a_content_type = ap->a_content_disposition =
167 ap->a_content_id = NULL;
168 ap->a_content_description = _("Attached message content");
169 if (options & OPT_INTERACTIVE)
170 printf(_("~@: added message #%" PRIu32 "\n"), (ui32_t)msgno);
171 goto jleave;
175 if ((cp = fexpand(cp, FEXP_LOCAL | FEXP_NSHELL)) != NULL &&
176 !access(cp, R_OK)) {
177 ap->a_name = cp;
178 break;
180 n_perr(cp, 0);
183 ap = _fill_in(ap, cp, number);
186 * Character set of attachments: enum attach_conv
188 cslc = charset_get_lc();
189 #ifdef HAVE_ICONV
190 if (!(options & OPT_INTERACTIVE))
191 goto jcs;
192 if ((cp = ap->a_content_type) != NULL && ascncasecmp(cp, "text/", 5) != 0 &&
193 !getapproval(_("File doesn't indicate text content, "
194 "edit character sets"), TRU1)) {
195 ap->a_conv = AC_DEFAULT;
196 goto jleave;
199 jcs_restart:
200 charset_iter_reset(NULL);
201 jcs:
202 #endif
203 snprintf(prefix, sizeof prefix, _("#%-5" PRIu32 " input charset: "),
204 number);
205 if ((defcs = ap->a_input_charset) == NULL)
206 defcs = cslc;
207 cp = ap->a_input_charset = n_input_cp_addhist(prefix, defcs, TRU1);
208 #ifdef HAVE_ICONV
209 if (!(options & OPT_INTERACTIVE)) {
210 #endif
211 ap->a_conv = (cp != NULL) ? AC_FIX_INCS : AC_DEFAULT;
212 #ifdef HAVE_ICONV
213 goto jleave;
216 snprintf(prefix, sizeof prefix,
217 _("#%-5" PRIu32 " output (send) charset: "), number);
218 if ((defcs = ap->a_charset) == NULL)
219 defcs = charset_iter();
220 defcs = ap->a_charset = n_input_cp_addhist(prefix, defcs, TRU1);
222 /* Input, no output -> input=as given, output=no conversion at all */
223 if (cp != NULL && defcs == NULL) {
224 ap->a_conv = AC_FIX_INCS;
225 goto jdone;
228 /* No input, no output -> input=*ttycharset*, output=iterator */
229 if (cp == NULL && defcs == NULL) {
230 ap->a_conv = AC_DEFAULT;
231 ap->a_input_charset = cslc;
232 ap->a_charset = charset_iter();
233 assert(charset_iter_is_valid());
234 charset_iter_next();
236 /* No input, output -> input=*ttycharset*, output=as given */
237 else if (cp == NULL && defcs != NULL) {
238 ap->a_conv = AC_FIX_OUTCS;
239 ap->a_input_charset = cslc;
241 /* Input, output -> try conversion from input=as given to output=as given */
243 printf(_("Trying conversion from %s to %s\n"), ap->a_input_charset,
244 ap->a_charset);
245 if (_attach_iconv(ap))
246 ap->a_conv = AC_TMPFILE;
247 else {
248 ap->a_conv = AC_DEFAULT;
249 ap->a_input_charset = cp;
250 ap->a_charset = defcs;
251 if (!charset_iter_is_valid()) {
252 printf(_("*sendcharsets* and *charset-8bit* iteration "
253 "exhausted, restarting\n"));
254 goto jcs_restart;
256 goto jcs;
258 jdone:
259 #endif
260 if (options & OPT_INTERACTIVE)
261 printf(_("~@: added attachment \"%s\"\n"), ap->a_name);
262 jleave:
263 safe_signal(SIGINT, ohdl);/* TODO until we have signal manager (see TODO) */
264 if (__atticonv_sig != 0) {
265 sigset_t nset;
266 sigemptyset(&nset);
267 sigaddset(&nset, SIGINT);
268 sigprocmask(SIG_UNBLOCK, &nset, NULL);
269 /* Caller kills */
271 NYD_LEAVE;
272 return ap;
275 #ifdef HAVE_ICONV
276 static int
277 _attach_iconv(struct attachment *ap)
279 struct str oul = {NULL, 0}, inl = {NULL, 0};
280 FILE *fo = NULL, *fi = NULL;
281 size_t cnt, lbsize;
282 iconv_t icp;
283 NYD_ENTER;
285 hold_sigs(); /* TODO until we have signal manager (see TODO) */
287 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
288 if (icp == (iconv_t)-1) {
289 if (errno == EINVAL)
290 goto jeconv;
291 else
292 n_perr(_("iconv_open"), 0);
293 goto jerr;
296 if ((fi = Fopen(ap->a_name, "r")) == NULL) {
297 n_perr(ap->a_name, 0);
298 goto jerr;
300 cnt = fsize(fi);
302 if ((fo = Ftmp(NULL, "aticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
303 NULL) {
304 n_perr(_("temporary mail file"), 0);
305 goto jerr;
308 for (lbsize = 0;;) {
309 if (fgetline(&inl.s, &lbsize, &cnt, &inl.l, fi, 0) == NULL) {
310 if (!cnt)
311 break;
312 n_perr(_("I/O read error occurred"), 0);
313 goto jerr;
316 if (n_iconv_str(icp, &oul, &inl, NULL, FAL0) != 0)
317 goto jeconv;
318 if ((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, fo)) != oul.l) {
319 n_perr(_("I/O write error occurred"), 0);
320 goto jerr;
323 fflush_rewind(fo);
325 ap->a_tmpf = fo;
326 jleave:
327 if (inl.s != NULL)
328 free(inl.s);
329 if (oul.s != NULL)
330 free(oul.s);
331 if (fi != NULL)
332 Fclose(fi);
333 if (icp != (iconv_t)-1)
334 n_iconv_close(icp);
336 rele_sigs(); /* TODO until we have signal manager (see TODO) */
337 NYD_LEAVE;
338 return (fo != NULL);
340 jeconv:
341 n_err(_("Cannot convert from %s to %s\n"),
342 ap->a_input_charset, ap->a_charset);
343 jerr:
344 if (fo != NULL)
345 Fclose(fo);
346 fo = NULL;
347 goto jleave;
349 #endif /* HAVE_ICONV */
351 /* TODO add_attachment(): also work with **aphead, not *aphead ... */
352 FL struct attachment *
353 add_attachment(struct attachment *aphead, char *file, struct attachment **newap)
355 struct attachment *nap = NULL, *ap;
356 NYD_ENTER;
358 if ((file = fexpand(file, FEXP_LOCAL | FEXP_NSHELL)) == NULL)
359 goto jleave;
360 if (access(file, R_OK) != 0)
361 goto jleave;
363 nap = _fill_in(csalloc(1, sizeof *nap), file, 0);
364 if (newap != NULL)
365 *newap = nap;
366 if (aphead != NULL) {
367 for (ap = aphead; ap->a_flink != NULL; ap = ap->a_flink)
369 ap->a_flink = nap;
370 nap->a_blink = ap;
371 } else {
372 nap->a_blink = NULL;
373 aphead = nap;
375 nap = aphead;
376 jleave:
377 NYD_LEAVE;
378 return nap;
381 FL void
382 append_attachments(struct attachment **aphead, char *names)
384 char *cp;
385 struct attachment *xaph, *nap;
386 NYD_ENTER;
388 while ((cp = n_strescsep(&names, ',', TRU1)) != NULL) {
389 xaph = add_attachment(*aphead, fexpand_nshell_quote(cp), &nap);
390 if (xaph != NULL) {
391 *aphead = xaph;
392 if (options & OPT_INTERACTIVE)
393 printf(_("~@: added attachment \"%s\"\n"), nap->a_name);
394 } else
395 n_perr(cp, 0);
397 NYD_LEAVE;
400 FL void
401 edit_attachments(struct attachment **aphead)
403 struct attachment *ap, *fap, *bap;
404 ui32_t attno = 1;
405 NYD_ENTER;
407 if (!(pstate & PS_ATTACHMENTS_NOTED)) {
408 pstate |= PS_ATTACHMENTS_NOTED;
409 printf(_("# Please be aware that \"\\\" must be escaped, e.g., "
410 "\"\\\\\", \"\\$HOME\"\n"));
413 /* Modify already present ones? */
414 for (ap = *aphead; ap != NULL; ap = fap) {
415 if (_read_attachment_data(ap, attno) != NULL) {
416 fap = ap->a_flink;
417 ++attno;
418 continue;
420 fap = ap->a_flink;
421 if ((bap = ap->a_blink) != NULL)
422 bap->a_flink = fap;
423 else
424 *aphead = fap;
425 if (fap != NULL)
426 fap->a_blink = bap;
427 /*else*//* TODO until we have signal manager (see TODO) */
428 if (__atticonv_sig != 0)
429 n_raise(SIGINT);
430 if (fap == NULL)
431 goto jleave;
434 /* Add some more? */
435 if ((bap = *aphead) != NULL)
436 while (bap->a_flink != NULL)
437 bap = bap->a_flink;
438 while ((fap = _read_attachment_data(NULL, attno)) != NULL) {
439 if (bap != NULL)
440 bap->a_flink = fap;
441 else
442 *aphead = fap;
443 fap->a_blink = bap;
444 fap->a_flink = NULL;
445 bap = fap;
446 ++attno;
448 if (__atticonv_sig != 0) /* TODO until we have signal manager (see TODO) */
449 n_raise(SIGINT);
450 jleave:
451 NYD_LEAVE;
454 /* s-it-mode */