privsep.c: and just verify the box is also in CWD (wapiflapi)
[s-mailx.git] / attachment.c
blob95ae4bc7d2c8e8c98eef72373ddb9897c518a6c5
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 char *ecp;
67 int rv;
68 NYD2_ENTER;
70 rv = -1;
72 if(file[0] == '#'){
73 rv = (int)strtol(&file[1], &ecp, 10);
75 /* xxx 0 may later be a valid message number?!? */
76 if(rv <= 0 || rv > msgCount || *ecp != '\0')
77 rv = -1;
79 NYD2_LEAVE;
80 return rv;
83 static struct attachment *
84 a_attachment_setup_base(struct attachment *ap, char const *file){
85 NYD2_ENTER;
86 ap->a_input_charset = ap->a_charset = NULL;
87 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = file;
88 if((file = strrchr(file, '/')) != NULL)
89 ap->a_path_bname = ap->a_name = ++file;
90 else
91 file = ap->a_name;
92 ap->a_content_type = mime_type_classify_filename(file);
93 ap->a_content_disposition = "attachment";
94 ap->a_content_description = NULL;
95 ap->a_content_id = NULL;
96 NYD2_LEAVE;
97 return ap;
100 static struct attachment *
101 a_attachment_setup_msg(struct attachment *ap, char const *msgcp, int msgno){
102 NYD2_ENTER;
103 ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = msgcp;
104 ap->a_msgno = msgno;
105 ap->a_content_type =
106 ap->a_content_description =
107 ap->a_content_disposition = NULL;
108 ap->a_content_id = NULL;
109 NYD2_LEAVE;
110 return ap;
113 #ifdef HAVE_ICONV
114 static bool_t
115 a_attachment_iconv(struct attachment *ap, FILE *ifp){
116 struct str oul = {NULL, 0}, inl = {NULL, 0};
117 size_t cnt, lbsize;
118 iconv_t icp;
119 FILE *ofp;
120 NYD_ENTER;
122 hold_sigs(); /* TODO until we have signal manager (see TODO) */
124 ofp = NULL;
126 icp = n_iconv_open(ap->a_charset, ap->a_input_charset);
127 if(icp == (iconv_t)-1){
128 if(errno == EINVAL)
129 goto jeconv;
130 else
131 n_perr(_("iconv_open"), 0);
132 goto jerr;
135 cnt = (size_t)fsize(ifp);
137 if((ofp = Ftmp(NULL, "atticonv", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL){
138 n_perr(_("Temporary attachment data file"), 0);
139 goto jerr;
142 for(lbsize = 0;;){
143 if(fgetline(&inl.s, &lbsize, &cnt, &inl.l, ifp, 0) == NULL){
144 if(!cnt)
145 break;
146 n_perr(_("I/O read error occurred"), 0);
147 goto jerr;
150 if(n_iconv_str(icp, n_ICONV_IGN_NOREVERSE, &oul, &inl, NULL) != 0)
151 goto jeconv;
152 if((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, ofp)) != oul.l){
153 n_perr(_("I/O write error occurred"), 0);
154 goto jerr;
157 fflush_rewind(ofp);
159 ap->a_tmpf = ofp;
160 jleave:
161 if(inl.s != NULL)
162 free(inl.s);
163 if(oul.s != NULL)
164 free(oul.s);
165 if(icp != (iconv_t)-1)
166 n_iconv_close(icp);
167 Fclose(ifp);
169 rele_sigs(); /* TODO until we have signal manager (see TODO) */
170 NYD_LEAVE;
171 return (ofp != NULL);
173 jeconv:
174 n_err(_("Cannot convert from %s to %s\n"),
175 ap->a_input_charset, ap->a_charset);
176 jerr:
177 if(ofp != NULL)
178 Fclose(ofp);
179 ofp = NULL;
180 goto jleave;
182 #endif /* HAVE_ICONV */
184 static void
185 a_attachment_yay(struct attachment const *ap){
186 NYD2_ENTER;
187 if(ap->a_msgno > 0)
188 printf(_("Added message/rfc822 attachment for message #%u\n"),
189 ap->a_msgno);
190 else
191 printf(_("Added attachment %s (%s)\n"),
192 n_shexp_quote_cp(ap->a_name, FAL0),
193 n_shexp_quote_cp(ap->a_path_user, FAL0));
194 NYD2_LEAVE;
197 FL struct attachment *
198 n_attachment_append(struct attachment *aplist, char const *file,
199 enum n_attach_error *aerr_or_null, struct attachment **newap_or_null){
200 #ifdef HAVE_ICONV
201 FILE *cnvfp;
202 #endif
203 int msgno;
204 char const *file_user, *incs, *oucs;
205 struct attachment *nap, *ap;
206 enum n_attach_error aerr;
207 NYD_ENTER;
209 #ifdef HAVE_ICONV
210 cnvfp = NULL;
211 #endif
212 aerr = n_ATTACH_ERR_NONE;
213 nap = NULL;
214 file_user = savestr(file); /* TODO recreate after fexpand()!?! */
215 incs = oucs = NULL;
217 if((msgno = a_attachment_is_msg(file)) < 0){
218 int e;
219 char const *cp, *ncp;
221 jrefexp:
222 if((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL){
223 e = errno;
224 aerr = n_ATTACH_ERR_OTHER;
225 goto jleave;
228 #ifndef HAVE_ICONV
229 if(oucs != NULL && oucs != (char*)-1){
230 n_err(_("No iconv support, cannot do %s\n"),
231 n_shexp_quote_cp(file_user, FAL0));
232 aerr = n_ATTACH_ERR_ICONV_NAVAIL;
233 goto jleave;
235 #endif
237 if((
238 #ifdef HAVE_ICONV
239 (oucs != NULL && oucs != (char*)-1)
240 ? (cnvfp = Fopen(file, "r")) == NULL :
241 #endif
242 access(file, R_OK) != 0)){
243 e = errno;
245 /* It may not have worked because of a character-set specification,
246 * so try to extract that and retry once */
247 if(incs == NULL && (cp = strrchr(file, '=')) != NULL){
248 size_t i;
249 char *nfp, c;
251 nfp = savestrbuf(file, PTR2SIZE(cp - file));
252 file = nfp;
254 for(ncp = ++cp; (c = *cp) != '\0'; ++cp)
255 if(!alnumchar(c) && !punctchar(c))
256 break;
257 else if(c == '#'){
258 if(incs == NULL){
259 i = PTR2SIZE(cp - ncp);
260 incs = (i == 0 || (i == 1 && ncp[0] == '-'))
261 ? (char*)-1 : savestrbuf(ncp, i);
262 ncp = &cp[1];
263 }else
264 break;
266 if(c == '\0'){
267 char *xp;
269 i = PTR2SIZE(cp - ncp);
270 if(i == 0 || (i == 1 && ncp[0] == '-'))
271 xp = (char*)-1;
272 else
273 xp = savestrbuf(ncp, i);
274 if(incs == NULL)
275 incs = xp;
276 else
277 oucs = xp;
278 file = nfp;
279 goto jrefexp;
283 n_err(_("Failed to access attachment %s: %s\n"),
284 n_shexp_quote_cp(file, FAL0), strerror(e));
285 aerr = n_ATTACH_ERR_FILE_OPEN;
286 goto jleave;
290 nap = a_attachment_setup_base(csalloc(1, sizeof *nap), file);
291 nap->a_path_user = file_user;
292 if(msgno >= 0)
293 nap = a_attachment_setup_msg(nap, file, msgno);
294 else{
295 nap->a_input_charset = (incs == NULL || incs == (char*)-1)
296 ? savestr(ok_vlook(ttycharset)) : incs;
297 #ifdef HAVE_ICONV
298 if(cnvfp != NULL){
299 nap->a_charset = oucs;
300 if(!a_attachment_iconv(nap, cnvfp)){
301 nap = NULL;
302 aerr = n_ATTACH_ERR_ICONV_FAILED;
303 goto jleave;
305 nap->a_conv = AC_TMPFILE;
306 }else
307 #endif
308 if(incs != NULL && oucs == NULL)
309 nap->a_conv = AC_FIX_INCS;
310 else
311 nap->a_conv = AC_DEFAULT;
314 if(aplist != NULL){
315 for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink)
317 ap->a_flink = nap;
318 nap->a_blink = ap;
319 }else
320 aplist = nap;
322 jleave:
323 if(aerr_or_null != NULL)
324 *aerr_or_null = aerr;
325 if(newap_or_null != NULL)
326 *newap_or_null = nap;
327 NYD_LEAVE;
328 return aplist;
331 FL struct attachment *
332 n_attachment_append_list(struct attachment *aplist, char const *names){
333 struct str shin;
334 struct n_string shou, *shoup;
335 NYD_ENTER;
337 shoup = n_string_creat_auto(&shou);
339 for(shin.s = n_UNCONST(names), shin.l = UIZ_MAX;;){
340 struct attachment *nap;
341 enum n_shexp_state shs;
343 shs = n_shexp_parse_token(shoup, &shin, n_SHEXP_PARSE_TRUNC |
344 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
345 n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY);
346 if(shs & n_SHEXP_STATE_ERR_MASK)
347 break;
349 if(shs & n_SHEXP_STATE_OUTPUT){
350 aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap);
351 if(nap != NULL){
352 if(options & OPT_INTERACTIVE)
353 a_attachment_yay(nap);
357 if(shs & n_SHEXP_STATE_STOP)
358 break;
360 n_string_gut(shoup);
361 NYD_LEAVE;
362 return aplist;
365 FL struct attachment *
366 n_attachment_remove(struct attachment *aplist, struct attachment *ap){
367 struct attachment *bap, *fap;
368 NYD_ENTER;
370 #ifdef HAVE_DEVEL
371 for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink)
373 assert(aplist != NULL);
374 aplist = bap;
375 #endif
377 if(ap == aplist){
378 if((aplist = ap->a_flink) != NULL)
379 aplist->a_blink = NULL;
380 }else{
381 bap = ap->a_blink;
382 fap = ap->a_flink;
383 if(bap != NULL)
384 bap->a_flink = fap;
385 if(fap != NULL)
386 fap->a_blink = bap;
389 if(ap->a_conv == AC_TMPFILE)
390 Fclose(ap->a_tmpf);
391 NYD_LEAVE;
392 return aplist;
395 FL struct attachment *
396 n_attachment_find(struct attachment *aplist, char const *name,
397 bool_t *stat_or_null){
398 int msgno;
399 char const *bname;
400 bool_t status, sym;
401 struct attachment *saved;
402 NYD_ENTER;
404 saved = NULL;
405 status = FAL0;
407 if((bname = strrchr(name, '/')) != NULL){
408 for(++bname; aplist != NULL; aplist = aplist->a_flink)
409 if(!strcmp(name, aplist->a_path)){
410 status = TRU1;
411 /* Exact match with path components: done */
412 goto jleave;
413 }else if(!strcmp(bname, aplist->a_path_bname)){
414 if(!status){
415 saved = aplist;
416 status = TRU1;
417 }else
418 status = TRUM1;
420 }else if((msgno = a_attachment_is_msg(name)) < 0){
421 for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){
422 if(!strcmp(name, aplist->a_name)){
423 if(!status || !sym){
424 saved = aplist;
425 sym = TRU1;
427 }else if(!strcmp(name, aplist->a_path_bname)){
428 if(!status)
429 saved = aplist;
430 }else
431 continue;
432 status = status ? TRUM1 : TRU1;
434 }else{
435 for(; aplist != NULL; aplist = aplist->a_flink){
436 if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){
437 status = TRU1;
438 goto jleave;
442 if(saved != NULL)
443 aplist = saved;
445 jleave:
446 if(stat_or_null != NULL)
447 *stat_or_null = status;
448 NYD_LEAVE;
449 return aplist;
452 FL struct attachment *
453 n_attachment_list_edit(struct attachment *aplist, enum n_lexinput_flags lif){
454 char prefix[32];
455 struct attachment *naplist, *ap;
456 ui32_t attno;
457 NYD_ENTER;
459 if(options & OPT_INTERACTIVE){
460 static bool_t note_given; /* v15-compat: DROP */
462 if(!note_given){
463 note_given = TRU1;
464 printf(_("# Only supports sh(1)ell-style quoting for file names\n"));
468 /* Modify already present ones? Append some more? */
469 attno = 1;
471 for(naplist = NULL;;){
472 char const *filename;
474 snprintf(prefix, sizeof prefix, _("#%" PRIu32 " filename: "), attno);
476 if(aplist != NULL){
477 /* TODO If we would create .a_path_user in append() after any
478 * TODO expansion then we could avoid closing+rebuilding the temporary
479 * TODO file if the new user input matches the original value! */
480 if(aplist->a_conv == AC_TMPFILE)
481 Fclose(aplist->a_tmpf);
482 filename = n_shexp_quote_cp(aplist->a_path_user, FAL0);
483 }else
484 filename = n_empty;
486 ap = NULL;
487 if((filename = n_lex_input_cp(lif, prefix, filename)) != NULL){
488 naplist = n_attachment_append(naplist, filename, NULL, &ap);
489 if(ap != NULL){
490 if(options & OPT_INTERACTIVE)
491 a_attachment_yay(ap);
492 ++attno;
496 if(aplist != NULL)
497 aplist = aplist->a_flink;
498 else if(ap == NULL)
499 break;
501 NYD_LEAVE;
502 return naplist;
505 FL ssize_t
506 n_attachment_list_print(struct attachment const *aplist, FILE *fp){
507 struct attachment const *ap;
508 ui32_t attno;
509 ssize_t rv;
510 NYD_ENTER;
512 rv = 0;
514 for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){
515 if(ap->a_msgno > 0)
516 fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno);
517 else{
518 fprintf(fp, "#%" PRIu32 ": %s [%s -- %s",
519 attno, n_shexp_quote_cp(ap->a_name, FAL0),
520 n_shexp_quote_cp(ap->a_path, FAL0),
521 (ap->a_content_type != NULL
522 ? ap->a_content_type : _("unclassified content")));
524 if(ap->a_conv == AC_TMPFILE)
525 /* I18N: input and output character set as given */
526 fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"),
527 ap->a_input_charset, ap->a_charset);
528 else if(ap->a_conv == AC_FIX_INCS)
529 /* I18N: input character set as given, no conversion to apply */
530 fprintf(fp, _(", incs=%s (no conversion)"), ap->a_input_charset);
531 else if(ap->a_conv == AC_DEFAULT){
532 if(ap->a_input_charset != NULL)
533 /* I18N: input character set as given, output iterates */
534 fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"),
535 ap->a_input_charset);
536 else if(ap->a_content_type == NULL ||
537 !ascncasecmp(ap->a_content_type, "text/", 5))
538 fprintf(fp, _(", default character set handling"));
540 fprintf(fp, "]\n");
543 NYD_LEAVE;
544 return rv;
547 /* s-it-mode */