Several: some backslash->reverse solidus missed in [db9a7082]
[s-mailx.git] / attachment.c
blobeb8bd561b3cea22ca1d53d0ffd163bf54731b46c
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 = mime_type_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(errno == EINVAL)
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 = errno;
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 = errno;
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), strerror(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 | n_SHEXP_PARSE_TRIMSPACE |
347 n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_IFS_ADD_COMMA |
348 n_SHEXP_PARSE_IGNORE_EMPTY), shoup, &shin, NULL);
349 if(shs & n_SHEXP_STATE_ERR_MASK)
350 break;
352 if(shs & n_SHEXP_STATE_OUTPUT){
353 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
354 if(nap != NULL){
355 if(n_psonce & n_PSO_INTERACTIVE)
356 a_attachment_yay(nap);
360 if(shs & n_SHEXP_STATE_STOP)
361 break;
363 n_string_gut(shoup);
364 NYD_LEAVE;
365 return aplist;
368 FL struct attachment *
369 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
370 struct attachment *bap, *fap;
371 NYD_ENTER;
373 #ifdef HAVE_DEVEL
374 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
376 assert(aplist != NULL);
377 aplist = bap;
378 #endif
380 if(ap == aplist){
381 if((aplist = ap->a_flink) != NULL)
382 aplist->a_blink = NULL;
383 }else{
384 bap = ap->a_blink;
385 fap = ap->a_flink;
386 if(bap != NULL)
387 bap->a_flink = fap;
388 if(fap != NULL)
389 fap->a_blink = bap;
392 if(ap->a_conv == AC_TMPFILE)
393 Fclose(ap->a_tmpf);
394 NYD_LEAVE;
395 return aplist;
398 FL struct attachment *
399 n_attachment_find(struct attachment *aplist, char const *name,
400 bool_t *stat_or_null){
401 int msgno;
402 char const *bname;
403 bool_t status, sym;
404 struct attachment *saved;
405 NYD_ENTER;
407 saved = NULL;
408 status = FAL0;
410 if((bname = strrchr(name, '/')) != NULL){
411 for(++bname; aplist != NULL; aplist = aplist->a_flink)
412 if(!strcmp(name, aplist->a_path)){
413 status = TRU1;
414 /* Exact match with path components: done */
415 goto jleave;
416 }else if(!strcmp(bname, aplist->a_path_bname)){
417 if(!status){
418 saved = aplist;
419 status = TRU1;
420 }else
421 status = TRUM1;
423 }else if((msgno = a_attachment_is_msg(name)) < 0){
424 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
425 if(!strcmp(name, aplist->a_name)){
426 if(!status || !sym){
427 saved = aplist;
428 sym = TRU1;
430 }else if(!strcmp(name, aplist->a_path_bname)){
431 if(!status)
432 saved = aplist;
433 }else
434 continue;
435 status = status ? TRUM1 : TRU1;
437 }else{
438 for(; aplist != NULL; aplist = aplist->a_flink){
439 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
440 status = TRU1;
441 goto jleave;
445 if(saved != NULL)
446 aplist = saved;
448 jleave:
449 if(stat_or_null != NULL)
450 *stat_or_null = status;
451 NYD_LEAVE;
452 return aplist;
455 FL struct attachment *
456 n_attachment_list_edit(struct attachment *aplist, enum n_lexinput_flags lif){
457 char prefix[32];
458 struct attachment *naplist, *ap;
459 ui32_t attno;
460 NYD_ENTER;
462 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED)
463 ) == n_PSO_INTERACTIVE){
464 n_psonce |= n_PSO_ATTACH_QUOTE_NOTED;
465 fprintf(n_stdout,
466 _("# Only supports sh(1)ell-style quoting for file names\n"));
469 /* Modify already present ones? Append some more? */
470 attno = 1;
472 for(naplist = NULL;;){
473 char const *filename;
475 snprintf(prefix, sizeof prefix, _("#%" PRIu32 " filename: "), attno);
477 if(aplist != NULL){
478 /* TODO If we would create .a_path_user in append() after any
479 * TODO expansion then we could avoid closing+rebuilding the temporary
480 * TODO file if the new user input matches the original value! */
481 if(aplist->a_conv == AC_TMPFILE)
482 Fclose(aplist->a_tmpf);
483 filename = n_shexp_quote_cp(aplist->a_path_user, FAL0);
484 }else
485 filename = n_empty;
487 ap = NULL;
488 if((filename = n_lex_input_cp(lif, prefix, filename)) != NULL){
489 naplist = n_attachment_append(naplist, filename, NULL, &ap);
490 if(ap != NULL){
491 if(n_psonce & n_PSO_INTERACTIVE)
492 a_attachment_yay(ap);
493 ++attno;
497 if(aplist != NULL)
498 aplist = aplist->a_flink;
499 else if(ap == NULL)
500 break;
502 NYD_LEAVE;
503 return naplist;
506 FL ssize_t
507 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
508 struct attachment const *ap;
509 ui32_t attno;
510 ssize_t rv;
511 NYD_ENTER;
513 rv = 0;
515 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
516 if(ap->a_msgno > 0)
517 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
518 else{
519 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
520 attno, n_shexp_quote_cp(ap->a_name, FAL0),
521 n_shexp_quote_cp(ap->a_path, FAL0),
522 (ap->a_content_type != NULL
523 ? ap->a_content_type : _("unclassified content")));
525 if(ap->a_conv == AC_TMPFILE)
526 /* I18N: input and output character set as given */
527 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
528 ap->a_input_charset, ap->a_charset);
529 else if(ap->a_conv == AC_FIX_INCS)
530 /* I18N: input character set as given, no conversion to apply */
531 fprintf(fp, _(", incs=%s (no conversion)"), ap->a_input_charset);
532 else if(ap->a_conv == AC_DEFAULT){
533 if(ap->a_input_charset != NULL)
534 /* I18N: input character set as given, output iterates */
535 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"),
536 ap->a_input_charset);
537 else if(ap->a_content_type == NULL ||
538 !ascncasecmp(ap->a_content_type, "text/", 5))
539 fprintf(fp, _(", default character set handling"));
541 fprintf(fp, "]\n");
544 NYD_LEAVE;
545 return rv;
548 /* s-it-mode */