cc-test.sh: add some more MIME (header) tests
[s-mailx.git] / attachments.c
blob13ded31d7b691b9ba49f330ee45ee22edd9fe8df
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 - 2014 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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 /* We use calloc() for struct attachment */
45 CTA(AC_DEFAULT == 0);
47 /* Fill in some attachment fields; don't be interactive if number==0n */
48 static struct attachment * _fill_in(struct attachment *ap,
49 char const *file, ui32_t number);
51 /* Ask the user to edit file names and other data for the given attachment */
52 static struct attachment * _read_attachment_data(struct attachment *ap,
53 ui32_t number);
55 /* Try to create temporary charset converted version */
56 #ifdef HAVE_ICONV
57 static int _attach_iconv(struct attachment *ap);
58 #endif
60 static struct attachment *
61 _fill_in(struct attachment *ap, char const *file, 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_classify_content_type_by_fileext(file);
77 if (number > 0 && ok_blook(attachment_ask_content_type)) {
78 snprintf(prefix, sizeof prefix, "#%u\tContent-Type: ", number);
79 ap->a_content_type = readstr_input(prefix, ap->a_content_type);
82 if (number > 0 && ok_blook(attachment_ask_content_disposition)) {
83 snprintf(prefix, sizeof prefix, "#%u\tContent-Disposition: ", number);
84 if ((ap->a_content_disposition = readstr_input(prefix,
85 ap->a_content_disposition)) == 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, "#%u\tContent-ID: ", number);
93 ap->a_content_id = readstr_input(prefix, ap->a_content_id);
94 } else
95 ap->a_content_id = NULL;
97 if (number > 0 && ok_blook(attachment_ask_content_description)) {
98 snprintf(prefix, sizeof prefix, "#%u\tContent-Description: ", number);
99 ap->a_content_description = readstr_input(prefix,
100 ap->a_content_description);
102 NYD_LEAVE;
103 return ap;
106 static sigjmp_buf __atticonv_jmp; /* TODO someday, we won't need it no more */
107 static int __atticonv_sig; /* TODO someday, 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, _("#%" PRIu32 "\tfilename: "), number);
149 for (;;) {
150 if ((ap->a_name = readstr_input(prefix, ap->a_name)) == NULL) {
151 ap = NULL;
152 goto jleave;
155 /* May be a message number (XXX add "AC_MSG", use that not .a_msgno) */
156 if (ap->a_name[0] == '#') {
157 char *ecp;
158 int msgno = (int)strtol(ap->a_name + 1, &ecp, 10);
159 if (msgno > 0 && msgno <= msgCount && *ecp == '\0') {
160 ap->a_msgno = msgno;
161 ap->a_content_type = ap->a_content_disposition =
162 ap->a_content_id = NULL;
163 ap->a_content_description = _("Attached message content");
164 if (options & OPT_INTERACTIVE)
165 printf(_("~@: added message #%" PRIu32 "\n"), (ui32_t)msgno);
166 goto jleave;
170 if ((cp = file_expand(ap->a_name)) != NULL && !access(cp, R_OK)) {
171 ap->a_name = cp;
172 break;
174 perror(cp);
177 ap = _fill_in(ap, cp, number);
180 * Character set of attachments: enum attach_conv
182 cslc = charset_get_lc();
183 #ifdef HAVE_ICONV
184 if (!(options & OPT_INTERACTIVE))
185 goto jcs;
186 if ((cp = ap->a_content_type) != NULL && ascncasecmp(cp, "text/", 5) != 0 &&
187 !getapproval(_("Filename doesn't indicate text content - "
188 "edit charsets nonetheless? "), TRU1)) {
189 ap->a_conv = AC_DEFAULT;
190 goto jleave;
193 jcs_restart:
194 charset_iter_reset(NULL);
195 jcs:
196 #endif
197 snprintf(prefix, sizeof prefix, _("#%" PRIu32 "\tinput charset: "),
198 number);
199 if ((defcs = ap->a_input_charset) == NULL)
200 defcs = cslc;
201 cp = ap->a_input_charset = readstr_input(prefix, defcs);
202 #ifdef HAVE_ICONV
203 if (!(options & OPT_INTERACTIVE)) {
204 #endif
205 ap->a_conv = (cp != NULL) ? AC_FIX_INCS : AC_DEFAULT;
206 #ifdef HAVE_ICONV
207 goto jleave;
210 snprintf(prefix, sizeof prefix,
211 _("#%" PRIu32 "\toutput (send) charset: "), number);
212 if ((defcs = ap->a_charset) == NULL)
213 defcs = charset_iter();
214 defcs = ap->a_charset = readstr_input(prefix, defcs);
216 /* Input, no output -> input=as given, output=no conversion at all */
217 if (cp != NULL && defcs == NULL) {
218 ap->a_conv = AC_FIX_INCS;
219 goto jdone;
222 /* No input, no output -> input=*ttycharset*, output=iterator */
223 if (cp == NULL && defcs == NULL) {
224 ap->a_conv = AC_DEFAULT;
225 ap->a_input_charset = cslc;
226 ap->a_charset = charset_iter();
227 assert(charset_iter_is_valid());
228 charset_iter_next();
230 /* No input, output -> input=*ttycharset*, output=as given */
231 else if (cp == NULL && defcs != NULL) {
232 ap->a_conv = AC_FIX_OUTCS;
233 ap->a_input_charset = cslc;
235 /* Input, output -> try conversion from input=as given to output=as given */
237 printf(_("Trying conversion from %s to %s\n"), ap->a_input_charset,
238 ap->a_charset);
239 if (_attach_iconv(ap))
240 ap->a_conv = AC_TMPFILE;
241 else {
242 ap->a_conv = AC_DEFAULT;
243 ap->a_input_charset = cp;
244 ap->a_charset = defcs;
245 if (!charset_iter_is_valid()) {
246 printf(_("*sendcharsets* and *charset-8bit* iteration "
247 "exhausted, restarting\n"));
248 goto jcs_restart;
250 goto jcs;
252 jdone:
253 #endif
254 if (options & OPT_INTERACTIVE)
255 printf(_("~@: added attachment \"%s\"\n"), ap->a_name);
256 jleave:
257 safe_signal(SIGINT, ohdl);/* TODO until we have signal manager (see TODO) */
258 if (__atticonv_sig != 0) {
259 sigset_t nset;
260 sigemptyset(&nset);
261 sigaddset(&nset, SIGINT);
262 sigprocmask(SIG_UNBLOCK, &nset, NULL);
263 /* Caller kills */
265 NYD_LEAVE;
266 return ap;
269 #ifdef HAVE_ICONV
270 static int
271 _attach_iconv(struct attachment *ap)
273 struct str oul = {NULL, 0}, inl = {NULL, 0};
274 FILE *fo = NULL, *fi = NULL;
275 size_t cnt, lbsize;
276 iconv_t icp;
277 NYD_ENTER;
279 hold_sigs(); /* TODO until we have signal manager (see TODO) */
281 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
282 if (icp == (iconv_t)-1) {
283 if (errno == EINVAL)
284 goto jeconv;
285 else
286 perror("iconv_open");
287 goto jerr;
290 if ((fi = Fopen(ap->a_name, "r")) == NULL) {
291 perror(ap->a_name);
292 goto jerr;
294 cnt = fsize(fi);
296 if ((fo = Ftmp(NULL, "atic", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
297 NULL) {
298 perror(_("temporary mail file"));
299 goto jerr;
302 for (lbsize = 0;;) {
303 if (fgetline(&inl.s, &lbsize, &cnt, &inl.l, fi, 0) == NULL) {
304 if (!cnt)
305 break;
306 perror(_("I/O read error occurred"));
307 goto jerr;
310 if (n_iconv_str(icp, &oul, &inl, NULL, FAL0) != 0)
311 goto jeconv;
312 if ((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, fo)) != oul.l) {
313 perror(_("I/O write error occurred"));
314 goto jerr;
317 fflush_rewind(fo);
319 ap->a_tmpf = fo;
320 jleave:
321 if (inl.s != NULL)
322 free(inl.s);
323 if (oul.s != NULL)
324 free(oul.s);
325 if (fi != NULL)
326 Fclose(fi);
327 if (icp != (iconv_t)-1)
328 n_iconv_close(icp);
330 rele_sigs(); /* TODO until we have signal manager (see TODO) */
331 NYD_LEAVE;
332 return (fo != NULL);
334 jeconv:
335 fprintf(stderr, _("Cannot convert from %s to %s\n"),
336 ap->a_input_charset, ap->a_charset);
337 jerr:
338 if (fo != NULL)
339 Fclose(fo);
340 fo = NULL;
341 goto jleave;
343 #endif /* HAVE_ICONV */
345 /* TODO add_attachment(): also work with **aphead, not *aphead ... */
346 FL struct attachment *
347 add_attachment(struct attachment *aphead, char *file, struct attachment **newap)
349 struct attachment *nap = NULL, *ap;
350 NYD_ENTER;
352 if ((file = file_expand(file)) == NULL)
353 goto jleave;
354 if (access(file, R_OK) != 0)
355 goto jleave;
357 nap = _fill_in(csalloc(1, sizeof *nap), file, 0);
358 if (newap != NULL)
359 *newap = nap;
360 if (aphead != NULL) {
361 for (ap = aphead; ap->a_flink != NULL; ap = ap->a_flink)
363 ap->a_flink = nap;
364 nap->a_blink = ap;
365 } else {
366 nap->a_blink = NULL;
367 aphead = nap;
369 nap = aphead;
370 jleave:
371 NYD_LEAVE;
372 return nap;
375 FL void
376 append_attachments(struct attachment **aphead, char *names)
378 char *cp;
379 struct attachment *xaph, *nap;
380 NYD_ENTER;
382 while ((cp = n_strsep(&names, ',', 1)) != NULL) {
383 if ((xaph = add_attachment(*aphead, cp, &nap)) != NULL) {
384 *aphead = xaph;
385 if (options & OPT_INTERACTIVE)
386 printf(_("~@: added attachment \"%s\"\n"), nap->a_name);
387 } else
388 perror(cp);
390 NYD_LEAVE;
393 FL void
394 edit_attachments(struct attachment **aphead)
396 struct attachment *ap, *fap, *bap;
397 ui32_t attno = 1;
398 NYD_ENTER;
400 /* Modify already present ones? */
401 for (ap = *aphead; ap != NULL; ap = fap) {
402 if (_read_attachment_data(ap, attno) != NULL) {
403 fap = ap->a_flink;
404 ++attno;
405 continue;
407 fap = ap->a_flink;
408 if ((bap = ap->a_blink) != NULL)
409 bap->a_flink = fap;
410 else
411 *aphead = fap;
412 if (fap != NULL)
413 fap->a_blink = bap;
414 /*else*//* TODO until we have signal manager (see TODO) */
415 if (__atticonv_sig != 0)
416 kill(0, SIGINT);
417 if (fap == NULL)
418 goto jleave;
421 /* Add some more? */
422 if ((bap = *aphead) != NULL)
423 while (bap->a_flink != NULL)
424 bap = bap->a_flink;
425 while ((fap = _read_attachment_data(NULL, attno)) != NULL) {
426 if (bap != NULL)
427 bap->a_flink = fap;
428 else
429 *aphead = fap;
430 fap->a_blink = bap;
431 fap->a_flink = NULL;
432 bap = fap;
433 ++attno;
435 if (__atticonv_sig != 0) /* TODO until we have signal manager (see TODO) */
436 kill(0, SIGINT);
437 jleave:
438 NYD_LEAVE;
441 /* s-it-mode */