Fix [69225c69]: compare against the correct FILE*
[s-mailx.git] / attachment.c
blobec6c4171ca96223b168fe0e23d0c5ad36c8577fe
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));
259 file = nfp;
261 for(ncp = ++cp; (c = *cp) != '\0'; ++cp)
262 if(!alnumchar(c) && !punctchar(c))
263 break;
264 else if(c == '#'){
265 if(incs == NULL){
266 i = PTR2SIZE(cp - ncp);
267 incs = (i == 0 || (i == 1 && ncp[0] == '-'))
268 ? (char*)-1 : savestrbuf(ncp, i);
269 ncp = &cp[1];
270 }else
271 break;
273 if(c == '\0'){
274 char *xp;
276 i = PTR2SIZE(cp - ncp);
277 if(i == 0 || (i == 1 && ncp[0] == '-'))
278 xp = (char*)-1;
279 else
280 xp = savestrbuf(ncp, i);
281 if(incs == NULL)
282 incs = xp;
283 else
284 oucs = xp;
285 file = nfp;
286 goto jrefexp;
290 n_err(_("Failed to access attachment %s: %s\n"),
291 n_shexp_quote_cp(file, FAL0), n_err_to_doc(e));
292 aerr = n_ATTACH_ERR_FILE_OPEN;
293 goto jleave;
297 nap = a_attachment_setup_base(csalloc(1, sizeof *nap), file);
298 nap->a_path_user = file_user;
299 if(msgno >= 0)
300 nap = a_attachment_setup_msg(nap, file, msgno);
301 else{
302 nap->a_input_charset = (incs == NULL || incs == (char*)-1)
303 ? savestr(ok_vlook(ttycharset)) : incs;
304 #ifdef HAVE_ICONV
305 if(cnvfp != NULL){
306 nap->a_charset = oucs;
307 if(!a_attachment_iconv(nap, cnvfp)){
308 nap = NULL;
309 aerr = n_ATTACH_ERR_ICONV_FAILED;
310 goto jleave;
312 nap->a_conv = AC_TMPFILE;
313 }else
314 #endif
315 if(incs != NULL && oucs == NULL)
316 nap->a_conv = AC_FIX_INCS;
317 else
318 nap->a_conv = AC_DEFAULT;
321 if(aplist != NULL){
322 for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink)
324 ap->a_flink = nap;
325 nap->a_blink = ap;
326 }else
327 aplist = nap;
329 jleave:
330 if(aerr_or_null != NULL)
331 *aerr_or_null = aerr;
332 if(newap_or_null != NULL)
333 *newap_or_null = nap;
334 NYD_LEAVE;
335 return aplist;
338 FL struct attachment *
339 n_attachment_append_list(struct attachment *aplist, char const *names){
340 struct str shin;
341 struct n_string shou, *shoup;
342 NYD_ENTER;
344 shoup = n_string_creat_auto(&shou);
346 for(shin.s = n_UNCONST(names), shin.l = UIZ_MAX;;){
347 struct attachment *nap;
348 enum n_shexp_state shs;
350 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
351 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
352 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY),
353 shoup, &shin, NULL);
354 if(shs & n_SHEXP_STATE_ERR_MASK)
355 break;
357 if(shs & n_SHEXP_STATE_OUTPUT){
358 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
359 if(nap != NULL){
360 if(n_psonce & n_PSO_INTERACTIVE)
361 a_attachment_yay(nap);
365 if(shs & n_SHEXP_STATE_STOP)
366 break;
368 n_string_gut(shoup);
369 NYD_LEAVE;
370 return aplist;
373 FL struct attachment *
374 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
375 struct attachment *bap, *fap;
376 NYD_ENTER;
378 #ifdef HAVE_DEVEL
379 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
381 assert(aplist != NULL);
382 aplist = bap;
383 #endif
385 if(ap == aplist){
386 if((aplist = ap->a_flink) != NULL)
387 aplist->a_blink = NULL;
388 }else{
389 bap = ap->a_blink;
390 fap = ap->a_flink;
391 if(bap != NULL)
392 bap->a_flink = fap;
393 if(fap != NULL)
394 fap->a_blink = bap;
397 if(ap->a_conv == AC_TMPFILE)
398 Fclose(ap->a_tmpf);
399 NYD_LEAVE;
400 return aplist;
403 FL struct attachment *
404 n_attachment_find(struct attachment *aplist, char const *name,
405 bool_t *stat_or_null){
406 int msgno;
407 char const *bname;
408 bool_t status, sym;
409 struct attachment *saved;
410 NYD_ENTER;
412 saved = NULL;
413 status = FAL0;
415 if((bname = strrchr(name, '/')) != NULL){
416 for(++bname; aplist != NULL; aplist = aplist->a_flink)
417 if(!strcmp(name, aplist->a_path)){
418 status = TRU1;
419 /* Exact match with path components: done */
420 goto jleave;
421 }else if(!strcmp(bname, aplist->a_path_bname)){
422 if(!status){
423 saved = aplist;
424 status = TRU1;
425 }else
426 status = TRUM1;
428 }else if((msgno = a_attachment_is_msg(name)) < 0){
429 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
430 if(!strcmp(name, aplist->a_name)){
431 if(!status || !sym){
432 saved = aplist;
433 sym = TRU1;
435 }else if(!strcmp(name, aplist->a_path_bname)){
436 if(!status)
437 saved = aplist;
438 }else
439 continue;
440 status = status ? TRUM1 : TRU1;
442 }else{
443 for(; aplist != NULL; aplist = aplist->a_flink){
444 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
445 status = TRU1;
446 goto jleave;
450 if(saved != NULL)
451 aplist = saved;
453 jleave:
454 if(stat_or_null != NULL)
455 *stat_or_null = status;
456 NYD_LEAVE;
457 return aplist;
460 FL struct attachment *
461 n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif){
462 char prefix[32];
463 struct str shin;
464 struct n_string shou, *shoup;
465 struct attachment *naplist, *ap;
466 ui32_t attno;
467 NYD_ENTER;
469 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED)
470 ) == n_PSO_INTERACTIVE){
471 n_psonce |= n_PSO_ATTACH_QUOTE_NOTED;
472 fprintf(n_stdout,
473 _("# Only supports sh(1)ell-style quoting for file names\n"));
476 shoup = n_string_creat_auto(&shou);
478 /* Modify already present ones? Append some more? */
479 attno = 1;
481 for(naplist = NULL;;){
482 snprintf(prefix, sizeof prefix, _("#%" PRIu32 " filename: "), attno);
484 if(aplist != NULL){
485 /* TODO If we would create .a_path_user in append() after any
486 * TODO expansion then we could avoid closing+rebuilding the temporary
487 * TODO file if the new user input matches the original value! */
488 if(aplist->a_conv == AC_TMPFILE)
489 Fclose(aplist->a_tmpf);
490 shin.s = n_shexp_quote_cp(aplist->a_path_user, FAL0);
491 }else
492 shin.s = n_UNCONST(n_empty);
494 ap = NULL;
495 if((shin.s = n_go_input_cp(gif, prefix, shin.s)) != NULL){
496 enum n_shexp_state shs;
497 char const *s_save;
499 s_save = shin.s;
500 shin.l = UIZ_MAX;
501 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
502 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
503 n_SHEXP_PARSE_IGNORE_EMPTY),
504 shoup, &shin, NULL);
505 if(!(shs & n_SHEXP_STATE_STOP))
506 n_err(_("# May be given one argument a time only: %s\n"),
507 n_shexp_quote_cp(s_save, FAL0));
508 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP |
509 n_SHEXP_STATE_ERR_MASK)
510 ) != (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP))
511 break;
513 naplist = n_attachment_append(naplist, n_string_cp(shoup), NULL, &ap);
514 if(ap != NULL){
515 if(n_psonce & n_PSO_INTERACTIVE)
516 a_attachment_yay(ap);
517 ++attno;
521 if(aplist != NULL){
522 aplist = aplist->a_flink;
523 /* In non-interactive or batch mode an empty line ends processing */
524 if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
525 continue;
527 if(ap == NULL)
528 break;
530 NYD_LEAVE;
531 return naplist;
534 FL ssize_t
535 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
536 struct attachment const *ap;
537 ui32_t attno;
538 ssize_t rv;
539 NYD_ENTER;
541 rv = 0;
543 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
544 if(ap->a_msgno > 0)
545 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
546 else{
547 char const *incs, *oucs;
549 if(!(n_psonce & n_PSO_REPRODUCIBLE)){
550 incs = ap->a_input_charset;
551 oucs = ap->a_charset;
552 }else
553 incs = oucs = n_reproducible_name;
555 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
556 attno, n_shexp_quote_cp(ap->a_name, FAL0),
557 n_shexp_quote_cp(ap->a_path, FAL0),
558 (ap->a_content_type != NULL
559 ? ap->a_content_type : _("unclassified content")));
561 if(ap->a_conv == AC_TMPFILE)
562 /* I18N: input and output character set as given */
563 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
564 incs, oucs);
565 else if(ap->a_conv == AC_FIX_INCS)
566 /* I18N: input character set as given, no conversion to apply */
567 fprintf(fp, _(", incs=%s (no conversion)"), incs);
568 else if(ap->a_conv == AC_DEFAULT){
569 if(incs != NULL)
570 /* I18N: input character set as given, output iterates */
571 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"), incs);
572 else if(ap->a_content_type == NULL ||
573 !ascncasecmp(ap->a_content_type, "text/", 5))
574 fprintf(fp, _(", default character set handling"));
576 fprintf(fp, "]\n");
579 NYD_LEAVE;
580 return rv;
583 /* s-it-mode */