..and.. FIX previous: do not stop list walk upon match, of course
[s-mailx.git] / attachment.c
blob71222f74f10f8beaa61fa529aa6979940e66728e
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 - 2017 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, 10, NULL
76 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
77 ) != n_IDEC_STATE_CONSUMED || rv == 0 || UICMP(z, rv, >, 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 file_user = savestr(file); /* TODO recreate after fexpand()!?! */
218 incs = oucs = NULL;
220 if((msgno = a_attachment_is_msg(file)) < 0){
221 int e;
222 char const *cp, *ncp;
224 jrefexp:
225 if((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL){
226 e = n_err_no;
227 aerr = n_ATTACH_ERR_OTHER;
228 goto jleave;
231 #ifndef HAVE_ICONV
232 if(oucs != NULL && oucs != (char*)-1){
233 n_err(_("No iconv support, cannot do %s\n"),
234 n_shexp_quote_cp(file_user, FAL0));
235 aerr = n_ATTACH_ERR_ICONV_NAVAIL;
236 goto jleave;
238 #endif
240 if((
241 #ifdef HAVE_ICONV
242 (oucs != NULL && oucs != (char*)-1)
243 ? (cnvfp = Fopen(file, "r")) == NULL :
244 #endif
245 access(file, R_OK) != 0)){
246 e = n_err_no;
248 /* It may not have worked because of a character-set specification,
249 * so try to extract that and retry once */
250 if(incs == NULL && (cp = strrchr(file, '=')) != NULL){
251 size_t i;
252 char *nfp, c;
254 nfp = savestrbuf(file, PTR2SIZE(cp - file));
255 file = nfp;
257 for(ncp = ++cp; (c = *cp) != '\0'; ++cp)
258 if(!alnumchar(c) && !punctchar(c))
259 break;
260 else if(c == '#'){
261 if(incs == NULL){
262 i = PTR2SIZE(cp - ncp);
263 incs = (i == 0 || (i == 1 && ncp[0] == '-'))
264 ? (char*)-1 : savestrbuf(ncp, i);
265 ncp = &cp[1];
266 }else
267 break;
269 if(c == '\0'){
270 char *xp;
272 i = PTR2SIZE(cp - ncp);
273 if(i == 0 || (i == 1 && ncp[0] == '-'))
274 xp = (char*)-1;
275 else
276 xp = savestrbuf(ncp, i);
277 if(incs == NULL)
278 incs = xp;
279 else
280 oucs = xp;
281 file = nfp;
282 goto jrefexp;
286 n_err(_("Failed to access attachment %s: %s\n"),
287 n_shexp_quote_cp(file, FAL0), n_err_to_doc(e));
288 aerr = n_ATTACH_ERR_FILE_OPEN;
289 goto jleave;
293 nap = a_attachment_setup_base(csalloc(1, sizeof *nap), file);
294 nap->a_path_user = file_user;
295 if(msgno >= 0)
296 nap = a_attachment_setup_msg(nap, file, msgno);
297 else{
298 nap->a_input_charset = (incs == NULL || incs == (char*)-1)
299 ? savestr(ok_vlook(ttycharset)) : incs;
300 #ifdef HAVE_ICONV
301 if(cnvfp != NULL){
302 nap->a_charset = oucs;
303 if(!a_attachment_iconv(nap, cnvfp)){
304 nap = NULL;
305 aerr = n_ATTACH_ERR_ICONV_FAILED;
306 goto jleave;
308 nap->a_conv = AC_TMPFILE;
309 }else
310 #endif
311 if(incs != NULL && oucs == NULL)
312 nap->a_conv = AC_FIX_INCS;
313 else
314 nap->a_conv = AC_DEFAULT;
317 if(aplist != NULL){
318 for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink)
320 ap->a_flink = nap;
321 nap->a_blink = ap;
322 }else
323 aplist = nap;
325 jleave:
326 if(aerr_or_null != NULL)
327 *aerr_or_null = aerr;
328 if(newap_or_null != NULL)
329 *newap_or_null = nap;
330 NYD_LEAVE;
331 return aplist;
334 FL struct attachment *
335 n_attachment_append_list(struct attachment *aplist, char const *names){
336 struct str shin;
337 struct n_string shou, *shoup;
338 NYD_ENTER;
340 shoup = n_string_creat_auto(&shou);
342 for(shin.s = n_UNCONST(names), shin.l = UIZ_MAX;;){
343 struct attachment *nap;
344 enum n_shexp_state shs;
346 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
347 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
348 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY),
349 shoup, &shin, NULL);
350 if(shs & n_SHEXP_STATE_ERR_MASK)
351 break;
353 if(shs & n_SHEXP_STATE_OUTPUT){
354 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
355 if(nap != NULL){
356 if(n_psonce & n_PSO_INTERACTIVE)
357 a_attachment_yay(nap);
361 if(shs & n_SHEXP_STATE_STOP)
362 break;
364 n_string_gut(shoup);
365 NYD_LEAVE;
366 return aplist;
369 FL struct attachment *
370 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
371 struct attachment *bap, *fap;
372 NYD_ENTER;
374 #ifdef HAVE_DEVEL
375 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
377 assert(aplist != NULL);
378 aplist = bap;
379 #endif
381 if(ap == aplist){
382 if((aplist = ap->a_flink) != NULL)
383 aplist->a_blink = NULL;
384 }else{
385 bap = ap->a_blink;
386 fap = ap->a_flink;
387 if(bap != NULL)
388 bap->a_flink = fap;
389 if(fap != NULL)
390 fap->a_blink = bap;
393 if(ap->a_conv == AC_TMPFILE)
394 Fclose(ap->a_tmpf);
395 NYD_LEAVE;
396 return aplist;
399 FL struct attachment *
400 n_attachment_find(struct attachment *aplist, char const *name,
401 bool_t *stat_or_null){
402 int msgno;
403 char const *bname;
404 bool_t status, sym;
405 struct attachment *saved;
406 NYD_ENTER;
408 saved = NULL;
409 status = FAL0;
411 if((bname = strrchr(name, '/')) != NULL){
412 for(++bname; aplist != NULL; aplist = aplist->a_flink)
413 if(!strcmp(name, aplist->a_path)){
414 status = TRU1;
415 /* Exact match with path components: done */
416 goto jleave;
417 }else if(!strcmp(bname, aplist->a_path_bname)){
418 if(!status){
419 saved = aplist;
420 status = TRU1;
421 }else
422 status = TRUM1;
424 }else if((msgno = a_attachment_is_msg(name)) < 0){
425 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
426 if(!strcmp(name, aplist->a_name)){
427 if(!status || !sym){
428 saved = aplist;
429 sym = TRU1;
431 }else if(!strcmp(name, aplist->a_path_bname)){
432 if(!status)
433 saved = aplist;
434 }else
435 continue;
436 status = status ? TRUM1 : TRU1;
438 }else{
439 for(; aplist != NULL; aplist = aplist->a_flink){
440 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
441 status = TRU1;
442 goto jleave;
446 if(saved != NULL)
447 aplist = saved;
449 jleave:
450 if(stat_or_null != NULL)
451 *stat_or_null = status;
452 NYD_LEAVE;
453 return aplist;
456 FL struct attachment *
457 n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif){
458 char prefix[32];
459 struct attachment *naplist, *ap;
460 ui32_t attno;
461 NYD_ENTER;
463 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED)
464 ) == n_PSO_INTERACTIVE){
465 n_psonce |= n_PSO_ATTACH_QUOTE_NOTED;
466 fprintf(n_stdout,
467 _("# Only supports sh(1)ell-style quoting for file names\n"));
470 /* Modify already present ones? Append some more? */
471 attno = 1;
473 for(naplist = NULL;;){
474 char const *filename;
476 snprintf(prefix, sizeof prefix, _("#%" PRIu32 " filename: "), attno);
478 if(aplist != NULL){
479 /* TODO If we would create .a_path_user in append() after any
480 * TODO expansion then we could avoid closing+rebuilding the temporary
481 * TODO file if the new user input matches the original value! */
482 if(aplist->a_conv == AC_TMPFILE)
483 Fclose(aplist->a_tmpf);
484 filename = n_shexp_quote_cp(aplist->a_path_user, FAL0);
485 }else
486 filename = n_empty;
488 ap = NULL;
489 if((filename = n_go_input_cp(gif, prefix, filename)) != NULL){
490 naplist = n_attachment_append(naplist, filename, NULL, &ap);
491 if(ap != NULL){
492 if(n_psonce & n_PSO_INTERACTIVE)
493 a_attachment_yay(ap);
494 ++attno;
498 if(aplist != NULL)
499 aplist = aplist->a_flink;
500 else if(ap == NULL)
501 break;
503 NYD_LEAVE;
504 return naplist;
507 FL ssize_t
508 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
509 struct attachment const *ap;
510 ui32_t attno;
511 ssize_t rv;
512 NYD_ENTER;
514 rv = 0;
516 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
517 if(ap->a_msgno > 0)
518 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
519 else{
520 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
521 attno, n_shexp_quote_cp(ap->a_name, FAL0),
522 n_shexp_quote_cp(ap->a_path, FAL0),
523 (ap->a_content_type != NULL
524 ? ap->a_content_type : _("unclassified content")));
526 if(ap->a_conv == AC_TMPFILE)
527 /* I18N: input and output character set as given */
528 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
529 ap->a_input_charset, ap->a_charset);
530 else if(ap->a_conv == AC_FIX_INCS)
531 /* I18N: input character set as given, no conversion to apply */
532 fprintf(fp, _(", incs=%s (no conversion)"), ap->a_input_charset);
533 else if(ap->a_conv == AC_DEFAULT){
534 if(ap->a_input_charset != NULL)
535 /* I18N: input character set as given, output iterates */
536 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"),
537 ap->a_input_charset);
538 else if(ap->a_content_type == NULL ||
539 !ascncasecmp(ap->a_content_type, "text/", 5))
540 fprintf(fp, _(", default character set handling"));
542 fprintf(fp, "]\n");
545 NYD_LEAVE;
546 return rv;
549 /* s-it-mode */