make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / attachment.c
blob5dee0b8310c2b826f2be7779db18dc4f0be5e94c
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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE attachment
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 /* We use calloc() for struct attachment */
44 n_CTAV(AC_DEFAULT == 0);
46 /* Return >=0 if file denotes a valid message number */
47 static int a_attachment_is_msg(char const *file);
49 /* Fill in some basic attachment fields */
50 static struct attachment *a_attachment_setup_base(struct attachment *ap,
51 char const *file);
53 /* Setup ap to point to a message */
54 static struct attachment *a_attachment_setup_msg(struct attachment *ap,
55 char const *msgcp, int msgno);
57 /* Try to create temporary charset converted version */
58 #ifdef HAVE_ICONV
59 static bool_t a_attachment_iconv(struct attachment *ap, FILE *ifp);
60 #endif
62 /* */
63 static void a_attachment_yay(struct attachment const *ap);
65 static int
66 a_attachment_is_msg(char const *file){
67 int rv;
68 NYD2_ENTER;
70 rv = -1;
72 if(file[0] == '#'){
73 uiz_t ib;
75 /* TODO Message numbers should be size_t, and 0 may be a valid one */
76 if(file[2] == '\0' && file[1] == '.'){
77 if(dot != NULL)
78 rv = (int)PTR2SIZE(dot - message + 1);
79 }else if((n_idec_uiz_cp(&ib, &file[1], 10, NULL
80 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
81 ) != n_IDEC_STATE_CONSUMED || ib == 0 || UICMP(z, ib, >, msgCount))
82 rv = -1;
83 else
84 rv = (int)ib;
86 NYD2_LEAVE;
87 return rv;
90 static struct attachment *
91 a_attachment_setup_base(struct attachment *ap, char const *file){
92 NYD2_ENTER;
93 ap->a_input_charset = ap->a_charset = NULL;
94 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = file;
95 if((file = strrchr(file, '/')) != NULL)
96 ap->a_path_bname = ap->a_name = ++file;
97 else
98 file = ap->a_name;
99 ap->a_content_type = n_mimetype_classify_filename(file);
100 ap->a_content_disposition = "attachment";
101 ap->a_content_description = NULL;
102 ap->a_content_id = NULL;
103 NYD2_LEAVE;
104 return ap;
107 static struct attachment *
108 a_attachment_setup_msg(struct attachment *ap, char const *msgcp, int msgno){
109 NYD2_ENTER;
110 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = msgcp;
111 ap->a_msgno = msgno;
112 ap->a_content_type =
113 ap->a_content_description =
114 ap->a_content_disposition = NULL;
115 ap->a_content_id = NULL;
116 NYD2_LEAVE;
117 return ap;
120 #ifdef HAVE_ICONV
121 static bool_t
122 a_attachment_iconv(struct attachment *ap, FILE *ifp){
123 struct str oul = {NULL, 0}, inl = {NULL, 0};
124 size_t cnt, lbsize;
125 iconv_t icp;
126 FILE *ofp;
127 NYD_ENTER;
129 hold_sigs(); /* TODO until we have signal manager (see TODO) */
131 ofp = NULL;
133 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
134 if(icp == (iconv_t)-1){
135 if(n_err_no == n_ERR_INVAL)
136 goto jeconv;
137 else
138 n_perr(_("iconv_open"), 0);
139 goto jerr;
142 cnt = (size_t)fsize(ifp);
144 if((ofp = Ftmp(NULL, "atticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL){
145 n_perr(_("Temporary attachment data file"), 0);
146 goto jerr;
149 for(lbsize = 0;;){
150 if(fgetline(&inl.s, &lbsize, &cnt, &inl.l, ifp, 0) == NULL){
151 if(!cnt)
152 break;
153 n_perr(_("I/O read error occurred"), 0);
154 goto jerr;
157 if(n_iconv_str(icp, n_ICONV_IGN_NOREVERSE, &oul, &inl, NULL) != 0)
158 goto jeconv;
159 if((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, ofp)) != oul.l){
160 n_perr(_("I/O write error occurred"), 0);
161 goto jerr;
164 fflush_rewind(ofp);
166 ap->a_tmpf = ofp;
167 jleave:
168 if(inl.s != NULL)
169 n_free(inl.s);
170 if(oul.s != NULL)
171 n_free(oul.s);
172 if(icp != (iconv_t)-1)
173 n_iconv_close(icp);
174 Fclose(ifp);
176 rele_sigs(); /* TODO until we have signal manager (see TODO) */
177 NYD_LEAVE;
178 return (ofp != NULL);
180 jeconv:
181 n_err(_("Cannot convert from %s to %s\n"),
182 ap->a_input_charset, ap->a_charset);
183 jerr:
184 if(ofp != NULL)
185 Fclose(ofp);
186 ofp = NULL;
187 goto jleave;
189 #endif /* HAVE_ICONV */
191 static void
192 a_attachment_yay(struct attachment const *ap){
193 NYD2_ENTER;
194 if(ap->a_msgno > 0)
195 fprintf(n_stdout, _("Added message/rfc822 attachment for message #%u\n"),
196 ap->a_msgno);
197 else
198 fprintf(n_stdout, _("Added attachment %s (%s)\n"),
199 n_shexp_quote_cp(ap->a_name, FAL0),
200 n_shexp_quote_cp(ap->a_path_user, FAL0));
201 NYD2_LEAVE;
204 FL struct attachment *
205 n_attachment_append(struct attachment *aplist, char const *file,
206 enum n_attach_error *aerr_or_null, struct attachment **newap_or_null){
207 #ifdef HAVE_ICONV
208 FILE *cnvfp;
209 #endif
210 int msgno;
211 char const *file_user, *incs, *oucs;
212 struct attachment *nap, *ap;
213 enum n_attach_error aerr;
214 NYD_ENTER;
216 #ifdef HAVE_ICONV
217 cnvfp = NULL;
218 #endif
219 aerr = n_ATTACH_ERR_NONE;
220 nap = NULL;
221 incs = oucs = NULL;
223 if(*file == '\0'){
224 aerr = n_ATTACH_ERR_OTHER;
225 goto jleave;
227 file_user = savestr(file); /* TODO recreate after fexpand()!?! */
229 if((msgno = a_attachment_is_msg(file)) < 0){
230 int e;
231 char const *cp, *ncp;
233 jrefexp:
234 if((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL){
235 aerr = n_ATTACH_ERR_OTHER;
236 goto jleave;
239 #ifndef HAVE_ICONV
240 if(oucs != NULL && oucs != (char*)-1){
241 n_err(_("No iconv support, cannot do %s\n"),
242 n_shexp_quote_cp(file_user, FAL0));
243 aerr = n_ATTACH_ERR_ICONV_NAVAIL;
244 goto jleave;
246 #endif
248 if((
249 #ifdef HAVE_ICONV
250 (oucs != NULL && oucs != (char*)-1)
251 ? (cnvfp = Fopen(file, "r")) == NULL :
252 #endif
253 access(file, R_OK) != 0)){
254 e = n_err_no;
256 /* It may not have worked because of a character-set specification,
257 * so try to extract that and retry once */
258 if(incs == NULL && (cp = strrchr(file, '=')) != NULL){
259 size_t i;
260 char *nfp, c;
262 nfp = savestrbuf(file, PTR2SIZE(cp - file));
264 for(ncp = ++cp; (c = *cp) != '\0'; ++cp)
265 if(!alnumchar(c) && !punctchar(c))
266 break;
267 else if(c == '#'){
268 if(incs == NULL){
269 i = PTR2SIZE(cp - ncp);
270 if(i == 0 || (i == 1 && ncp[0] == '-'))
271 incs = (char*)-1;
272 else if((incs = n_iconv_normalize_name(savestrbuf(ncp, i))
273 ) == NULL){
274 e = n_ERR_INVAL;
275 goto jerr_fopen;
277 ncp = &cp[1];
278 }else
279 break;
281 if(c == '\0'){
282 char *xp;
284 i = PTR2SIZE(cp - ncp);
285 if(i == 0 || (i == 1 && ncp[0] == '-'))
286 xp = (char*)-1;
287 else if((xp = n_iconv_normalize_name(savestrbuf(ncp, i))
288 ) == NULL){
289 e = n_ERR_INVAL;
290 goto jerr_fopen;
292 if(incs == NULL)
293 incs = xp;
294 else
295 oucs = xp;
296 file = nfp;
297 goto jrefexp;
301 jerr_fopen:
302 n_err(_("Failed to access attachment %s: %s\n"),
303 n_shexp_quote_cp(file, FAL0), n_err_to_doc(e));
304 aerr = n_ATTACH_ERR_FILE_OPEN;
305 goto jleave;
309 nap = a_attachment_setup_base(n_autorec_calloc(1, sizeof *nap), file);
310 nap->a_path_user = file_user;
311 if(msgno >= 0)
312 nap = a_attachment_setup_msg(nap, file, msgno);
313 else{
314 nap->a_input_charset = (incs == NULL || incs == (char*)-1)
315 ? savestr(ok_vlook(ttycharset)) : incs;
316 #ifdef HAVE_ICONV
317 if(cnvfp != NULL){
318 nap->a_charset = oucs;
319 if(!a_attachment_iconv(nap, cnvfp)){
320 nap = NULL;
321 aerr = n_ATTACH_ERR_ICONV_FAILED;
322 goto jleave;
324 nap->a_conv = AC_TMPFILE;
325 }else
326 #endif
327 if(incs != NULL && oucs == NULL)
328 nap->a_conv = AC_FIX_INCS;
329 else
330 nap->a_conv = AC_DEFAULT;
333 if(aplist != NULL){
334 for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink)
336 ap->a_flink = nap;
337 nap->a_blink = ap;
338 }else
339 aplist = nap;
341 jleave:
342 if(aerr_or_null != NULL)
343 *aerr_or_null = aerr;
344 if(newap_or_null != NULL)
345 *newap_or_null = nap;
346 NYD_LEAVE;
347 return aplist;
350 FL struct attachment *
351 n_attachment_append_list(struct attachment *aplist, char const *names){
352 struct str shin;
353 struct n_string shou, *shoup;
354 NYD_ENTER;
356 shoup = n_string_creat_auto(&shou);
358 for(shin.s = n_UNCONST(names), shin.l = UIZ_MAX;;){
359 struct attachment *nap;
360 enum n_shexp_state shs;
362 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
363 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
364 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY),
365 shoup, &shin, NULL);
366 if(shs & n_SHEXP_STATE_ERR_MASK)
367 break;
369 if(shs & n_SHEXP_STATE_OUTPUT){
370 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
371 if(nap != NULL){
372 if(n_psonce & n_PSO_INTERACTIVE)
373 a_attachment_yay(nap);
377 if(shs & n_SHEXP_STATE_STOP)
378 break;
380 n_string_gut(shoup);
381 NYD_LEAVE;
382 return aplist;
385 FL struct attachment *
386 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
387 struct attachment *bap, *fap;
388 NYD_ENTER;
390 #ifdef HAVE_DEVEL
391 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
393 assert(aplist != NULL);
394 aplist = bap;
395 #endif
397 if(ap == aplist){
398 if((aplist = ap->a_flink) != NULL)
399 aplist->a_blink = NULL;
400 }else{
401 bap = ap->a_blink;
402 fap = ap->a_flink;
403 if(bap != NULL)
404 bap->a_flink = fap;
405 if(fap != NULL)
406 fap->a_blink = bap;
409 if(ap->a_conv == AC_TMPFILE)
410 Fclose(ap->a_tmpf);
411 NYD_LEAVE;
412 return aplist;
415 FL struct attachment *
416 n_attachment_find(struct attachment *aplist, char const *name,
417 bool_t *stat_or_null){
418 int msgno;
419 char const *bname;
420 bool_t status, sym;
421 struct attachment *saved;
422 NYD_ENTER;
424 saved = NULL;
425 status = FAL0;
427 if((bname = strrchr(name, '/')) != NULL){
428 for(++bname; aplist != NULL; aplist = aplist->a_flink)
429 if(!strcmp(name, aplist->a_path)){
430 status = TRU1;
431 /* Exact match with path components: done */
432 goto jleave;
433 }else if(!strcmp(bname, aplist->a_path_bname)){
434 if(!status){
435 saved = aplist;
436 status = TRU1;
437 }else
438 status = TRUM1;
440 }else if((msgno = a_attachment_is_msg(name)) < 0){
441 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
442 if(!strcmp(name, aplist->a_name)){
443 if(!status || !sym){
444 saved = aplist;
445 sym = TRU1;
447 }else if(!strcmp(name, aplist->a_path_bname)){
448 if(!status)
449 saved = aplist;
450 }else
451 continue;
452 status = status ? TRUM1 : TRU1;
454 }else{
455 for(; aplist != NULL; aplist = aplist->a_flink){
456 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
457 status = TRU1;
458 goto jleave;
462 if(saved != NULL)
463 aplist = saved;
465 jleave:
466 if(stat_or_null != NULL)
467 *stat_or_null = status;
468 NYD_LEAVE;
469 return aplist;
472 FL struct attachment *
473 n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif){
474 char prefix[32];
475 struct str shin;
476 struct n_string shou, *shoup;
477 struct attachment *naplist, *ap;
478 ui32_t attno;
479 NYD_ENTER;
481 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED)
482 ) == n_PSO_INTERACTIVE){
483 n_psonce |= n_PSO_ATTACH_QUOTE_NOTED;
484 fprintf(n_stdout,
485 _("# Only supports sh(1)ell-style quoting for file names\n"));
488 shoup = n_string_creat_auto(&shou);
490 /* Modify already present ones? Append some more? */
491 attno = 1;
493 for(naplist = NULL;;){
494 snprintf(prefix, sizeof prefix, A_("#%" PRIu32 " filename: "), attno);
496 if(aplist != NULL){
497 /* TODO If we would create .a_path_user in append() after any
498 * TODO expansion then we could avoid closing+rebuilding the temporary
499 * TODO file if the new user input matches the original value! */
500 if(aplist->a_conv == AC_TMPFILE)
501 Fclose(aplist->a_tmpf);
502 shin.s = n_shexp_quote_cp(aplist->a_path_user, FAL0);
503 }else
504 shin.s = n_UNCONST(n_empty);
506 ap = NULL;
507 if((shin.s = n_go_input_cp(gif, prefix, shin.s)) != NULL){
508 enum n_shexp_state shs;
509 char const *s_save;
511 s_save = shin.s;
512 shin.l = UIZ_MAX;
513 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
514 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
515 n_SHEXP_PARSE_IGNORE_EMPTY),
516 shoup, &shin, NULL);
517 UIS(
518 if(!(shs & n_SHEXP_STATE_STOP))
519 n_err(_("# May be given one argument a time only: %s\n"),
520 n_shexp_quote_cp(s_save, FAL0));
522 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP |
523 n_SHEXP_STATE_ERR_MASK)
524 ) != (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP))
525 break;
527 naplist = n_attachment_append(naplist, n_string_cp(shoup), NULL, &ap);
528 if(ap != NULL){
529 if(n_psonce & n_PSO_INTERACTIVE)
530 a_attachment_yay(ap);
531 ++attno;
535 if(aplist != NULL){
536 aplist = aplist->a_flink;
537 /* In non-interactive or batch mode an empty line ends processing */
538 if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
539 continue;
541 if(ap == NULL)
542 break;
544 NYD_LEAVE;
545 return naplist;
548 FL ssize_t
549 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
550 struct attachment const *ap;
551 ui32_t attno;
552 ssize_t rv;
553 NYD_ENTER;
555 rv = 0;
557 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
558 if(ap->a_msgno > 0)
559 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
560 else{
561 char const *incs, *oucs;
563 if(!(n_psonce & n_PSO_REPRODUCIBLE)){
564 incs = ap->a_input_charset;
565 oucs = ap->a_charset;
566 }else
567 incs = oucs = n_reproducible_name;
569 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
570 attno, n_shexp_quote_cp(ap->a_name, FAL0),
571 n_shexp_quote_cp(ap->a_path, FAL0),
572 (ap->a_content_type != NULL
573 ? ap->a_content_type : _("unclassified content")));
575 if(ap->a_conv == AC_TMPFILE)
576 /* I18N: input and output character set as given */
577 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
578 incs, oucs);
579 else if(ap->a_conv == AC_FIX_INCS)
580 /* I18N: input character set as given, no conversion to apply */
581 fprintf(fp, _(", incs=%s (no conversion)"), incs);
582 else if(ap->a_conv == AC_DEFAULT){
583 if(incs != NULL)
584 /* I18N: input character set as given, output iterates */
585 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"), incs);
586 else if(ap->a_content_type == NULL ||
587 !ascncasecmp(ap->a_content_type, "text/", 5))
588 fprintf(fp, _(", default character set handling"));
590 fprintf(fp, "]\n");
593 NYD_LEAVE;
594 return rv;
597 /* s-it-mode */