collect(): ~[FfMmUu]: should default to the "dot" (Andrew Gee)
[s-mailx.git] / attachment.c
blob0bfef5cf89291fae91f569e47c7f7c2a5d0b9f40
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 */
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 attachment
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* We use calloc() for struct attachment */
43 n_CTAV(AC_DEFAULT == 0);
45 /* Return >=0 if file denotes a valid message number */
46 static int a_attachment_is_msg(char const *file);
48 /* Fill in some basic attachment fields */
49 static struct attachment *a_attachment_setup_base(struct attachment *ap,
50 char const *file);
52 /* Setup ap to point to a message */
53 static struct attachment *a_attachment_setup_msg(struct attachment *ap,
54 char const *msgcp, int msgno);
56 /* Try to create temporary charset converted version */
57 #ifdef HAVE_ICONV
58 static bool_t a_attachment_iconv(struct attachment *ap, FILE *ifp);
59 #endif
61 /* */
62 static void a_attachment_yay(struct attachment const *ap);
64 static int
65 a_attachment_is_msg(char const *file){
66 int rv;
67 NYD2_ENTER;
69 rv = -1;
71 if(file[0] == '#'){
72 uiz_t ib;
74 /* TODO Message numbers should be size_t, and 0 may be a valid one */
75 if((n_idec_uiz_cp(&ib, &file[1], 10, NULL
76 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
77 ) != n_IDEC_STATE_CONSUMED || ib == 0 || UICMP(z, ib, >, msgCount))
78 rv = -1;
79 else
80 rv = (int)ib;
82 NYD2_LEAVE;
83 return rv;
86 static struct attachment *
87 a_attachment_setup_base(struct attachment *ap, char const *file){
88 NYD2_ENTER;
89 ap->a_input_charset = ap->a_charset = NULL;
90 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = file;
91 if((file = strrchr(file, '/')) != NULL)
92 ap->a_path_bname = ap->a_name = ++file;
93 else
94 file = ap->a_name;
95 ap->a_content_type = n_mimetype_classify_filename(file);
96 ap->a_content_disposition = "attachment";
97 ap->a_content_description = NULL;
98 ap->a_content_id = NULL;
99 NYD2_LEAVE;
100 return ap;
103 static struct attachment *
104 a_attachment_setup_msg(struct attachment *ap, char const *msgcp, int msgno){
105 NYD2_ENTER;
106 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = msgcp;
107 ap->a_msgno = msgno;
108 ap->a_content_type =
109 ap->a_content_description =
110 ap->a_content_disposition = NULL;
111 ap->a_content_id = NULL;
112 NYD2_LEAVE;
113 return ap;
116 #ifdef HAVE_ICONV
117 static bool_t
118 a_attachment_iconv(struct attachment *ap, FILE *ifp){
119 struct str oul = {NULL, 0}, inl = {NULL, 0};
120 size_t cnt, lbsize;
121 iconv_t icp;
122 FILE *ofp;
123 NYD_ENTER;
125 hold_sigs(); /* TODO until we have signal manager (see TODO) */
127 ofp = NULL;
129 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
130 if(icp == (iconv_t)-1){
131 if(n_err_no == n_ERR_INVAL)
132 goto jeconv;
133 else
134 n_perr(_("iconv_open"), 0);
135 goto jerr;
138 cnt = (size_t)fsize(ifp);
140 if((ofp = Ftmp(NULL, "atticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL){
141 n_perr(_("Temporary attachment data file"), 0);
142 goto jerr;
145 for(lbsize = 0;;){
146 if(fgetline(&inl.s, &lbsize, &cnt, &inl.l, ifp, 0) == NULL){
147 if(!cnt)
148 break;
149 n_perr(_("I/O read error occurred"), 0);
150 goto jerr;
153 if(n_iconv_str(icp, n_ICONV_IGN_NOREVERSE, &oul, &inl, NULL) != 0)
154 goto jeconv;
155 if((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, ofp)) != oul.l){
156 n_perr(_("I/O write error occurred"), 0);
157 goto jerr;
160 fflush_rewind(ofp);
162 ap->a_tmpf = ofp;
163 jleave:
164 if(inl.s != NULL)
165 free(inl.s);
166 if(oul.s != NULL)
167 free(oul.s);
168 if(icp != (iconv_t)-1)
169 n_iconv_close(icp);
170 Fclose(ifp);
172 rele_sigs(); /* TODO until we have signal manager (see TODO) */
173 NYD_LEAVE;
174 return (ofp != NULL);
176 jeconv:
177 n_err(_("Cannot convert from %s to %s\n"),
178 ap->a_input_charset, ap->a_charset);
179 jerr:
180 if(ofp != NULL)
181 Fclose(ofp);
182 ofp = NULL;
183 goto jleave;
185 #endif /* HAVE_ICONV */
187 static void
188 a_attachment_yay(struct attachment const *ap){
189 NYD2_ENTER;
190 if(ap->a_msgno > 0)
191 fprintf(n_stdout, _("Added message/rfc822 attachment for message #%u\n"),
192 ap->a_msgno);
193 else
194 fprintf(n_stdout, _("Added attachment %s (%s)\n"),
195 n_shexp_quote_cp(ap->a_name, FAL0),
196 n_shexp_quote_cp(ap->a_path_user, FAL0));
197 NYD2_LEAVE;
200 FL struct attachment *
201 n_attachment_append(struct attachment *aplist, char const *file,
202 enum n_attach_error *aerr_or_null, struct attachment **newap_or_null){
203 #ifdef HAVE_ICONV
204 FILE *cnvfp;
205 #endif
206 int msgno;
207 char const *file_user, *incs, *oucs;
208 struct attachment *nap, *ap;
209 enum n_attach_error aerr;
210 NYD_ENTER;
212 #ifdef HAVE_ICONV
213 cnvfp = NULL;
214 #endif
215 aerr = n_ATTACH_ERR_NONE;
216 nap = NULL;
217 incs = oucs = NULL;
219 if(*file == '\0'){
220 aerr = n_ATTACH_ERR_OTHER;
221 goto jleave;
223 file_user = savestr(file); /* TODO recreate after fexpand()!?! */
225 if((msgno = a_attachment_is_msg(file)) < 0){
226 int e;
227 char const *cp, *ncp;
229 jrefexp:
230 if((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL){
231 aerr = n_ATTACH_ERR_OTHER;
232 goto jleave;
235 #ifndef HAVE_ICONV
236 if(oucs != NULL && oucs != (char*)-1){
237 n_err(_("No iconv support, cannot do %s\n"),
238 n_shexp_quote_cp(file_user, FAL0));
239 aerr = n_ATTACH_ERR_ICONV_NAVAIL;
240 goto jleave;
242 #endif
244 if((
245 #ifdef HAVE_ICONV
246 (oucs != NULL && oucs != (char*)-1)
247 ? (cnvfp = Fopen(file, "r")) == NULL :
248 #endif
249 access(file, R_OK) != 0)){
250 e = n_err_no;
252 /* It may not have worked because of a character-set specification,
253 * so try to extract that and retry once */
254 if(incs == NULL && (cp = strrchr(file, '=')) != NULL){
255 size_t i;
256 char *nfp, c;
258 nfp = savestrbuf(file, PTR2SIZE(cp - file));
260 for(ncp = ++cp; (c = *cp) != '\0'; ++cp)
261 if(!alnumchar(c) && !punctchar(c))
262 break;
263 else if(c == '#'){
264 if(incs == NULL){
265 i = PTR2SIZE(cp - ncp);
266 if(i == 0 || (i == 1 && ncp[0] == '-'))
267 incs = (char*)-1;
268 else if((incs = n_iconv_normalize_name(savestrbuf(ncp, i))
269 ) == NULL){
270 e = n_ERR_INVAL;
271 goto jerr_fopen;
273 ncp = &cp[1];
274 }else
275 break;
277 if(c == '\0'){
278 char *xp;
280 i = PTR2SIZE(cp - ncp);
281 if(i == 0 || (i == 1 && ncp[0] == '-'))
282 xp = (char*)-1;
283 else if((xp = n_iconv_normalize_name(savestrbuf(ncp, i))
284 ) == NULL){
285 e = n_ERR_INVAL;
286 goto jerr_fopen;
288 if(incs == NULL)
289 incs = xp;
290 else
291 oucs = xp;
292 file = nfp;
293 goto jrefexp;
297 jerr_fopen:
298 n_err(_("Failed to access attachment %s: %s\n"),
299 n_shexp_quote_cp(file, FAL0), n_err_to_doc(e));
300 aerr = n_ATTACH_ERR_FILE_OPEN;
301 goto jleave;
305 nap = a_attachment_setup_base(csalloc(1, sizeof *nap), file);
306 nap->a_path_user = file_user;
307 if(msgno >= 0)
308 nap = a_attachment_setup_msg(nap, file, msgno);
309 else{
310 nap->a_input_charset = (incs == NULL || incs == (char*)-1)
311 ? savestr(ok_vlook(ttycharset)) : incs;
312 #ifdef HAVE_ICONV
313 if(cnvfp != NULL){
314 nap->a_charset = oucs;
315 if(!a_attachment_iconv(nap, cnvfp)){
316 nap = NULL;
317 aerr = n_ATTACH_ERR_ICONV_FAILED;
318 goto jleave;
320 nap->a_conv = AC_TMPFILE;
321 }else
322 #endif
323 if(incs != NULL && oucs == NULL)
324 nap->a_conv = AC_FIX_INCS;
325 else
326 nap->a_conv = AC_DEFAULT;
329 if(aplist != NULL){
330 for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink)
332 ap->a_flink = nap;
333 nap->a_blink = ap;
334 }else
335 aplist = nap;
337 jleave:
338 if(aerr_or_null != NULL)
339 *aerr_or_null = aerr;
340 if(newap_or_null != NULL)
341 *newap_or_null = nap;
342 NYD_LEAVE;
343 return aplist;
346 FL struct attachment *
347 n_attachment_append_list(struct attachment *aplist, char const *names){
348 struct str shin;
349 struct n_string shou, *shoup;
350 NYD_ENTER;
352 shoup = n_string_creat_auto(&shou);
354 for(shin.s = n_UNCONST(names), shin.l = UIZ_MAX;;){
355 struct attachment *nap;
356 enum n_shexp_state shs;
358 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
359 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
360 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY),
361 shoup, &shin, NULL);
362 if(shs & n_SHEXP_STATE_ERR_MASK)
363 break;
365 if(shs & n_SHEXP_STATE_OUTPUT){
366 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
367 if(nap != NULL){
368 if(n_psonce & n_PSO_INTERACTIVE)
369 a_attachment_yay(nap);
373 if(shs & n_SHEXP_STATE_STOP)
374 break;
376 n_string_gut(shoup);
377 NYD_LEAVE;
378 return aplist;
381 FL struct attachment *
382 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
383 struct attachment *bap, *fap;
384 NYD_ENTER;
386 #ifdef HAVE_DEVEL
387 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
389 assert(aplist != NULL);
390 aplist = bap;
391 #endif
393 if(ap == aplist){
394 if((aplist = ap->a_flink) != NULL)
395 aplist->a_blink = NULL;
396 }else{
397 bap = ap->a_blink;
398 fap = ap->a_flink;
399 if(bap != NULL)
400 bap->a_flink = fap;
401 if(fap != NULL)
402 fap->a_blink = bap;
405 if(ap->a_conv == AC_TMPFILE)
406 Fclose(ap->a_tmpf);
407 NYD_LEAVE;
408 return aplist;
411 FL struct attachment *
412 n_attachment_find(struct attachment *aplist, char const *name,
413 bool_t *stat_or_null){
414 int msgno;
415 char const *bname;
416 bool_t status, sym;
417 struct attachment *saved;
418 NYD_ENTER;
420 saved = NULL;
421 status = FAL0;
423 if((bname = strrchr(name, '/')) != NULL){
424 for(++bname; aplist != NULL; aplist = aplist->a_flink)
425 if(!strcmp(name, aplist->a_path)){
426 status = TRU1;
427 /* Exact match with path components: done */
428 goto jleave;
429 }else if(!strcmp(bname, aplist->a_path_bname)){
430 if(!status){
431 saved = aplist;
432 status = TRU1;
433 }else
434 status = TRUM1;
436 }else if((msgno = a_attachment_is_msg(name)) < 0){
437 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
438 if(!strcmp(name, aplist->a_name)){
439 if(!status || !sym){
440 saved = aplist;
441 sym = TRU1;
443 }else if(!strcmp(name, aplist->a_path_bname)){
444 if(!status)
445 saved = aplist;
446 }else
447 continue;
448 status = status ? TRUM1 : TRU1;
450 }else{
451 for(; aplist != NULL; aplist = aplist->a_flink){
452 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
453 status = TRU1;
454 goto jleave;
458 if(saved != NULL)
459 aplist = saved;
461 jleave:
462 if(stat_or_null != NULL)
463 *stat_or_null = status;
464 NYD_LEAVE;
465 return aplist;
468 FL struct attachment *
469 n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif){
470 char prefix[32];
471 struct str shin;
472 struct n_string shou, *shoup;
473 struct attachment *naplist, *ap;
474 ui32_t attno;
475 NYD_ENTER;
477 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED)
478 ) == n_PSO_INTERACTIVE){
479 n_psonce |= n_PSO_ATTACH_QUOTE_NOTED;
480 fprintf(n_stdout,
481 _("# Only supports sh(1)ell-style quoting for file names\n"));
484 shoup = n_string_creat_auto(&shou);
486 /* Modify already present ones? Append some more? */
487 attno = 1;
489 for(naplist = NULL;;){
490 snprintf(prefix, sizeof prefix, _("#%" PRIu32 " filename: "), attno);
492 if(aplist != NULL){
493 /* TODO If we would create .a_path_user in append() after any
494 * TODO expansion then we could avoid closing+rebuilding the temporary
495 * TODO file if the new user input matches the original value! */
496 if(aplist->a_conv == AC_TMPFILE)
497 Fclose(aplist->a_tmpf);
498 shin.s = n_shexp_quote_cp(aplist->a_path_user, FAL0);
499 }else
500 shin.s = n_UNCONST(n_empty);
502 ap = NULL;
503 if((shin.s = n_go_input_cp(gif, prefix, shin.s)) != NULL){
504 enum n_shexp_state shs;
505 char const *s_save;
507 s_save = shin.s;
508 shin.l = UIZ_MAX;
509 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
510 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
511 n_SHEXP_PARSE_IGNORE_EMPTY),
512 shoup, &shin, NULL);
513 if(!(shs & n_SHEXP_STATE_STOP))
514 n_err(_("# May be given one argument a time only: %s\n"),
515 n_shexp_quote_cp(s_save, FAL0));
516 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP |
517 n_SHEXP_STATE_ERR_MASK)
518 ) != (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP))
519 break;
521 naplist = n_attachment_append(naplist, n_string_cp(shoup), NULL, &ap);
522 if(ap != NULL){
523 if(n_psonce & n_PSO_INTERACTIVE)
524 a_attachment_yay(ap);
525 ++attno;
529 if(aplist != NULL){
530 aplist = aplist->a_flink;
531 /* In non-interactive or batch mode an empty line ends processing */
532 if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
533 continue;
535 if(ap == NULL)
536 break;
538 NYD_LEAVE;
539 return naplist;
542 FL ssize_t
543 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
544 struct attachment const *ap;
545 ui32_t attno;
546 ssize_t rv;
547 NYD_ENTER;
549 rv = 0;
551 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
552 if(ap->a_msgno > 0)
553 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
554 else{
555 char const *incs, *oucs;
557 if(!(n_psonce & n_PSO_REPRODUCIBLE)){
558 incs = ap->a_input_charset;
559 oucs = ap->a_charset;
560 }else
561 incs = oucs = n_reproducible_name;
563 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
564 attno, n_shexp_quote_cp(ap->a_name, FAL0),
565 n_shexp_quote_cp(ap->a_path, FAL0),
566 (ap->a_content_type != NULL
567 ? ap->a_content_type : _("unclassified content")));
569 if(ap->a_conv == AC_TMPFILE)
570 /* I18N: input and output character set as given */
571 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
572 incs, oucs);
573 else if(ap->a_conv == AC_FIX_INCS)
574 /* I18N: input character set as given, no conversion to apply */
575 fprintf(fp, _(", incs=%s (no conversion)"), incs);
576 else if(ap->a_conv == AC_DEFAULT){
577 if(incs != NULL)
578 /* I18N: input character set as given, output iterates */
579 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"), incs);
580 else if(ap->a_content_type == NULL ||
581 !ascncasecmp(ap->a_content_type, "text/", 5))
582 fprintf(fp, _(", default character set handling"));
584 fprintf(fp, "]\n");
587 NYD_LEAVE;
588 return rv;
591 /* s-it-mode */