*quote-fold*: add optional third argument to improve break algo
[s-mailx.git] / attachment.c
blobc9175ea93732bac0c9250a6977ae1896a754d7c3
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(file[2] == '\0' && file[1] == '.'){
76 if(dot != NULL)
77 rv = (int)PTR2SIZE(dot - message + 1);
78 }else if((n_idec_uiz_cp(&ib, &file[1], 10, NULL
79 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
80 ) != n_IDEC_STATE_CONSUMED || ib == 0 || UICMP(z, ib, >, msgCount))
81 rv = -1;
82 else
83 rv = (int)ib;
85 NYD2_LEAVE;
86 return rv;
89 static struct attachment *
90 a_attachment_setup_base(struct attachment *ap, char const *file){
91 NYD2_ENTER;
92 ap->a_input_charset = ap->a_charset = NULL;
93 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = file;
94 if((file = strrchr(file, '/')) != NULL)
95 ap->a_path_bname = ap->a_name = ++file;
96 else
97 file = ap->a_name;
98 ap->a_content_type = n_mimetype_classify_filename(file);
99 ap->a_content_disposition = "attachment";
100 ap->a_content_description = NULL;
101 ap->a_content_id = NULL;
102 NYD2_LEAVE;
103 return ap;
106 static struct attachment *
107 a_attachment_setup_msg(struct attachment *ap, char const *msgcp, int msgno){
108 NYD2_ENTER;
109 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = msgcp;
110 ap->a_msgno = msgno;
111 ap->a_content_type =
112 ap->a_content_description =
113 ap->a_content_disposition = NULL;
114 ap->a_content_id = NULL;
115 NYD2_LEAVE;
116 return ap;
119 #ifdef HAVE_ICONV
120 static bool_t
121 a_attachment_iconv(struct attachment *ap, FILE *ifp){
122 struct str oul = {NULL, 0}, inl = {NULL, 0};
123 size_t cnt, lbsize;
124 iconv_t icp;
125 FILE *ofp;
126 NYD_ENTER;
128 hold_sigs(); /* TODO until we have signal manager (see TODO) */
130 ofp = NULL;
132 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
133 if(icp == (iconv_t)-1){
134 if(n_err_no == n_ERR_INVAL)
135 goto jeconv;
136 else
137 n_perr(_("iconv_open"), 0);
138 goto jerr;
141 cnt = (size_t)fsize(ifp);
143 if((ofp = Ftmp(NULL, "atticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL){
144 n_perr(_("Temporary attachment data file"), 0);
145 goto jerr;
148 for(lbsize = 0;;){
149 if(fgetline(&inl.s, &lbsize, &cnt, &inl.l, ifp, 0) == NULL){
150 if(!cnt)
151 break;
152 n_perr(_("I/O read error occurred"), 0);
153 goto jerr;
156 if(n_iconv_str(icp, n_ICONV_IGN_NOREVERSE, &oul, &inl, NULL) != 0)
157 goto jeconv;
158 if((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, ofp)) != oul.l){
159 n_perr(_("I/O write error occurred"), 0);
160 goto jerr;
163 fflush_rewind(ofp);
165 ap->a_tmpf = ofp;
166 jleave:
167 if(inl.s != NULL)
168 n_free(inl.s);
169 if(oul.s != NULL)
170 n_free(oul.s);
171 if(icp != (iconv_t)-1)
172 n_iconv_close(icp);
173 Fclose(ifp);
175 rele_sigs(); /* TODO until we have signal manager (see TODO) */
176 NYD_LEAVE;
177 return (ofp != NULL);
179 jeconv:
180 n_err(_("Cannot convert from %s to %s\n"),
181 ap->a_input_charset, ap->a_charset);
182 jerr:
183 if(ofp != NULL)
184 Fclose(ofp);
185 ofp = NULL;
186 goto jleave;
188 #endif /* HAVE_ICONV */
190 static void
191 a_attachment_yay(struct attachment const *ap){
192 NYD2_ENTER;
193 if(ap->a_msgno > 0)
194 fprintf(n_stdout, _("Added message/rfc822 attachment for message #%u\n"),
195 ap->a_msgno);
196 else
197 fprintf(n_stdout, _("Added attachment %s (%s)\n"),
198 n_shexp_quote_cp(ap->a_name, FAL0),
199 n_shexp_quote_cp(ap->a_path_user, FAL0));
200 NYD2_LEAVE;
203 FL struct attachment *
204 n_attachment_append(struct attachment *aplist, char const *file,
205 enum n_attach_error *aerr_or_null, struct attachment **newap_or_null){
206 #ifdef HAVE_ICONV
207 FILE *cnvfp;
208 #endif
209 int msgno;
210 char const *file_user, *incs, *oucs;
211 struct attachment *nap, *ap;
212 enum n_attach_error aerr;
213 NYD_ENTER;
215 #ifdef HAVE_ICONV
216 cnvfp = NULL;
217 #endif
218 aerr = n_ATTACH_ERR_NONE;
219 nap = NULL;
220 incs = oucs = NULL;
222 if(*file == '\0'){
223 aerr = n_ATTACH_ERR_OTHER;
224 goto jleave;
226 file_user = savestr(file); /* TODO recreate after fexpand()!?! */
228 if((msgno = a_attachment_is_msg(file)) < 0){
229 int e;
230 char const *cp, *ncp;
232 jrefexp:
233 if((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL){
234 aerr = n_ATTACH_ERR_OTHER;
235 goto jleave;
238 #ifndef HAVE_ICONV
239 if(oucs != NULL && oucs != (char*)-1){
240 n_err(_("No iconv support, cannot do %s\n"),
241 n_shexp_quote_cp(file_user, FAL0));
242 aerr = n_ATTACH_ERR_ICONV_NAVAIL;
243 goto jleave;
245 #endif
247 if((
248 #ifdef HAVE_ICONV
249 (oucs != NULL && oucs != (char*)-1)
250 ? (cnvfp = Fopen(file, "r")) == NULL :
251 #endif
252 access(file, R_OK) != 0)){
253 e = n_err_no;
255 /* It may not have worked because of a character-set specification,
256 * so try to extract that and retry once */
257 if(incs == NULL && (cp = strrchr(file, '=')) != NULL){
258 size_t i;
259 char *nfp, c;
261 nfp = savestrbuf(file, PTR2SIZE(cp - file));
263 for(ncp = ++cp; (c = *cp) != '\0'; ++cp)
264 if(!alnumchar(c) && !punctchar(c))
265 break;
266 else if(c == '#'){
267 if(incs == NULL){
268 i = PTR2SIZE(cp - ncp);
269 if(i == 0 || (i == 1 && ncp[0] == '-'))
270 incs = (char*)-1;
271 else if((incs = n_iconv_normalize_name(savestrbuf(ncp, i))
272 ) == NULL){
273 e = n_ERR_INVAL;
274 goto jerr_fopen;
276 ncp = &cp[1];
277 }else
278 break;
280 if(c == '\0'){
281 char *xp;
283 i = PTR2SIZE(cp - ncp);
284 if(i == 0 || (i == 1 && ncp[0] == '-'))
285 xp = (char*)-1;
286 else if((xp = n_iconv_normalize_name(savestrbuf(ncp, i))
287 ) == NULL){
288 e = n_ERR_INVAL;
289 goto jerr_fopen;
291 if(incs == NULL)
292 incs = xp;
293 else
294 oucs = xp;
295 file = nfp;
296 goto jrefexp;
300 jerr_fopen:
301 n_err(_("Failed to access attachment %s: %s\n"),
302 n_shexp_quote_cp(file, FAL0), n_err_to_doc(e));
303 aerr = n_ATTACH_ERR_FILE_OPEN;
304 goto jleave;
308 nap = a_attachment_setup_base(n_autorec_calloc(1, sizeof *nap), file);
309 nap->a_path_user = file_user;
310 if(msgno >= 0)
311 nap = a_attachment_setup_msg(nap, file, msgno);
312 else{
313 nap->a_input_charset = (incs == NULL || incs == (char*)-1)
314 ? savestr(ok_vlook(ttycharset)) : incs;
315 #ifdef HAVE_ICONV
316 if(cnvfp != NULL){
317 nap->a_charset = oucs;
318 if(!a_attachment_iconv(nap, cnvfp)){
319 nap = NULL;
320 aerr = n_ATTACH_ERR_ICONV_FAILED;
321 goto jleave;
323 nap->a_conv = AC_TMPFILE;
324 }else
325 #endif
326 if(incs != NULL && oucs == NULL)
327 nap->a_conv = AC_FIX_INCS;
328 else
329 nap->a_conv = AC_DEFAULT;
332 if(aplist != NULL){
333 for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink)
335 ap->a_flink = nap;
336 nap->a_blink = ap;
337 }else
338 aplist = nap;
340 jleave:
341 if(aerr_or_null != NULL)
342 *aerr_or_null = aerr;
343 if(newap_or_null != NULL)
344 *newap_or_null = nap;
345 NYD_LEAVE;
346 return aplist;
349 FL struct attachment *
350 n_attachment_append_list(struct attachment *aplist, char const *names){
351 struct str shin;
352 struct n_string shou, *shoup;
353 NYD_ENTER;
355 shoup = n_string_creat_auto(&shou);
357 for(shin.s = n_UNCONST(names), shin.l = UIZ_MAX;;){
358 struct attachment *nap;
359 enum n_shexp_state shs;
361 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
362 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
363 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY),
364 shoup, &shin, NULL);
365 if(shs & n_SHEXP_STATE_ERR_MASK)
366 break;
368 if(shs & n_SHEXP_STATE_OUTPUT){
369 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
370 if(nap != NULL){
371 if(n_psonce & n_PSO_INTERACTIVE)
372 a_attachment_yay(nap);
376 if(shs & n_SHEXP_STATE_STOP)
377 break;
379 n_string_gut(shoup);
380 NYD_LEAVE;
381 return aplist;
384 FL struct attachment *
385 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
386 struct attachment *bap, *fap;
387 NYD_ENTER;
389 #ifdef HAVE_DEVEL
390 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
392 assert(aplist != NULL);
393 aplist = bap;
394 #endif
396 if(ap == aplist){
397 if((aplist = ap->a_flink) != NULL)
398 aplist->a_blink = NULL;
399 }else{
400 bap = ap->a_blink;
401 fap = ap->a_flink;
402 if(bap != NULL)
403 bap->a_flink = fap;
404 if(fap != NULL)
405 fap->a_blink = bap;
408 if(ap->a_conv == AC_TMPFILE)
409 Fclose(ap->a_tmpf);
410 NYD_LEAVE;
411 return aplist;
414 FL struct attachment *
415 n_attachment_find(struct attachment *aplist, char const *name,
416 bool_t *stat_or_null){
417 int msgno;
418 char const *bname;
419 bool_t status, sym;
420 struct attachment *saved;
421 NYD_ENTER;
423 saved = NULL;
424 status = FAL0;
426 if((bname = strrchr(name, '/')) != NULL){
427 for(++bname; aplist != NULL; aplist = aplist->a_flink)
428 if(!strcmp(name, aplist->a_path)){
429 status = TRU1;
430 /* Exact match with path components: done */
431 goto jleave;
432 }else if(!strcmp(bname, aplist->a_path_bname)){
433 if(!status){
434 saved = aplist;
435 status = TRU1;
436 }else
437 status = TRUM1;
439 }else if((msgno = a_attachment_is_msg(name)) < 0){
440 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
441 if(!strcmp(name, aplist->a_name)){
442 if(!status || !sym){
443 saved = aplist;
444 sym = TRU1;
446 }else if(!strcmp(name, aplist->a_path_bname)){
447 if(!status)
448 saved = aplist;
449 }else
450 continue;
451 status = status ? TRUM1 : TRU1;
453 }else{
454 for(; aplist != NULL; aplist = aplist->a_flink){
455 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
456 status = TRU1;
457 goto jleave;
461 if(saved != NULL)
462 aplist = saved;
464 jleave:
465 if(stat_or_null != NULL)
466 *stat_or_null = status;
467 NYD_LEAVE;
468 return aplist;
471 FL struct attachment *
472 n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif){
473 char prefix[32];
474 struct str shin;
475 struct n_string shou, *shoup;
476 struct attachment *naplist, *ap;
477 ui32_t attno;
478 NYD_ENTER;
480 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED)
481 ) == n_PSO_INTERACTIVE){
482 n_psonce |= n_PSO_ATTACH_QUOTE_NOTED;
483 fprintf(n_stdout,
484 _("# Only supports sh(1)ell-style quoting for file names\n"));
487 shoup = n_string_creat_auto(&shou);
489 /* Modify already present ones? Append some more? */
490 attno = 1;
492 for(naplist = NULL;;){
493 snprintf(prefix, sizeof prefix, A_("#%" PRIu32 " filename: "), attno);
495 if(aplist != NULL){
496 /* TODO If we would create .a_path_user in append() after any
497 * TODO expansion then we could avoid closing+rebuilding the temporary
498 * TODO file if the new user input matches the original value! */
499 if(aplist->a_conv == AC_TMPFILE)
500 Fclose(aplist->a_tmpf);
501 shin.s = n_shexp_quote_cp(aplist->a_path_user, FAL0);
502 }else
503 shin.s = n_UNCONST(n_empty);
505 ap = NULL;
506 if((shin.s = n_go_input_cp(gif, prefix, shin.s)) != NULL){
507 enum n_shexp_state shs;
508 char const *s_save;
510 s_save = shin.s;
511 shin.l = UIZ_MAX;
512 shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
513 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG |
514 n_SHEXP_PARSE_IGNORE_EMPTY),
515 shoup, &shin, NULL);
516 UIS(
517 if(!(shs & n_SHEXP_STATE_STOP))
518 n_err(_("# May be given one argument a time only: %s\n"),
519 n_shexp_quote_cp(s_save, FAL0));
521 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP |
522 n_SHEXP_STATE_ERR_MASK)
523 ) != (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP))
524 break;
526 naplist = n_attachment_append(naplist, n_string_cp(shoup), NULL, &ap);
527 if(ap != NULL){
528 if(n_psonce & n_PSO_INTERACTIVE)
529 a_attachment_yay(ap);
530 ++attno;
534 if(aplist != NULL){
535 aplist = aplist->a_flink;
536 /* In non-interactive or batch mode an empty line ends processing */
537 if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
538 continue;
540 if(ap == NULL)
541 break;
543 NYD_LEAVE;
544 return naplist;
547 FL ssize_t
548 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
549 struct attachment const *ap;
550 ui32_t attno;
551 ssize_t rv;
552 NYD_ENTER;
554 rv = 0;
556 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
557 if(ap->a_msgno > 0)
558 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
559 else{
560 char const *incs, *oucs;
562 if(!(n_psonce & n_PSO_REPRODUCIBLE)){
563 incs = ap->a_input_charset;
564 oucs = ap->a_charset;
565 }else
566 incs = oucs = n_reproducible_name;
568 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
569 attno, n_shexp_quote_cp(ap->a_name, FAL0),
570 n_shexp_quote_cp(ap->a_path, FAL0),
571 (ap->a_content_type != NULL
572 ? ap->a_content_type : _("unclassified content")));
574 if(ap->a_conv == AC_TMPFILE)
575 /* I18N: input and output character set as given */
576 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
577 incs, oucs);
578 else if(ap->a_conv == AC_FIX_INCS)
579 /* I18N: input character set as given, no conversion to apply */
580 fprintf(fp, _(", incs=%s (no conversion)"), incs);
581 else if(ap->a_conv == AC_DEFAULT){
582 if(incs != NULL)
583 /* I18N: input character set as given, output iterates */
584 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"), incs);
585 else if(ap->a_content_type == NULL ||
586 !ascncasecmp(ap->a_content_type, "text/", 5))
587 fprintf(fp, _(", default character set handling"));
589 fprintf(fp, "]\n");
592 NYD_LEAVE;
593 return rv;
596 /* s-it-mode */