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 * SPDX-License-Identifier: BSD-3-Clause
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 #define n_FILE attachment
39 #ifndef HAVE_AMALGAMATION
43 /* We use calloc() for struct attachment */
44 n_CTAV(AC_DEFAULT
== 0);
46 /* Return >=0 if file denotes a valid message number */
47 static int a_attachment_is_msg(char const *file
);
49 /* Fill in some basic attachment fields */
50 static struct attachment
*a_attachment_setup_base(struct attachment
*ap
,
53 /* Setup ap to point to a message */
54 static struct attachment
*a_attachment_setup_msg(struct attachment
*ap
,
55 char const *msgcp
, int msgno
);
57 /* Try to create temporary charset converted version */
59 static bool_t
a_attachment_iconv(struct attachment
*ap
, FILE *ifp
);
63 static void a_attachment_yay(struct attachment
const *ap
);
66 a_attachment_is_msg(char const *file
){
75 /* TODO Message numbers should be size_t, and 0 may be a valid one */
76 if(file
[2] == '\0' && file
[1] == '.'){
78 rv
= (int)PTR2SIZE(dot
- message
+ 1);
79 }else if((n_idec_uiz_cp(&ib
, &file
[1], 10, NULL
80 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
81 ) != n_IDEC_STATE_CONSUMED
|| ib
== 0 || UICMP(z
, ib
, >, msgCount
))
90 static struct attachment
*
91 a_attachment_setup_base(struct attachment
*ap
, char const *file
){
93 ap
->a_input_charset
= ap
->a_charset
= NULL
;
94 ap
->a_path_user
= ap
->a_path
= ap
->a_path_bname
= ap
->a_name
= file
;
95 if((file
= strrchr(file
, '/')) != NULL
)
96 ap
->a_path_bname
= ap
->a_name
= ++file
;
99 ap
->a_content_type
= n_mimetype_classify_filename(file
);
100 ap
->a_content_disposition
= "attachment";
101 ap
->a_content_description
= NULL
;
102 ap
->a_content_id
= NULL
;
107 static struct attachment
*
108 a_attachment_setup_msg(struct attachment
*ap
, char const *msgcp
, int msgno
){
110 ap
->a_path_user
= ap
->a_path
= ap
->a_path_bname
= ap
->a_name
= msgcp
;
113 ap
->a_content_description
=
114 ap
->a_content_disposition
= NULL
;
115 ap
->a_content_id
= NULL
;
122 a_attachment_iconv(struct attachment
*ap
, FILE *ifp
){
123 struct str oul
= {NULL
, 0}, inl
= {NULL
, 0};
129 hold_sigs(); /* TODO until we have signal manager (see TODO) */
133 icp
= n_iconv_open(ap
->a_charset
, ap
->a_input_charset
);
134 if(icp
== (iconv_t
)-1){
135 if(n_err_no
== n_ERR_INVAL
)
138 n_perr(_("iconv_open"), 0);
142 cnt
= (size_t)fsize(ifp
);
144 if((ofp
= Ftmp(NULL
, "atticonv", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==NULL
){
145 n_perr(_("Temporary attachment data file"), 0);
150 if(fgetline(&inl
.s
, &lbsize
, &cnt
, &inl
.l
, ifp
, 0) == NULL
){
153 n_perr(_("I/O read error occurred"), 0);
157 if(n_iconv_str(icp
, n_ICONV_IGN_NOREVERSE
, &oul
, &inl
, NULL
) != 0)
159 if((inl
.l
= fwrite(oul
.s
, sizeof *oul
.s
, oul
.l
, ofp
)) != oul
.l
){
160 n_perr(_("I/O write error occurred"), 0);
172 if(icp
!= (iconv_t
)-1)
176 rele_sigs(); /* TODO until we have signal manager (see TODO) */
178 return (ofp
!= NULL
);
181 n_err(_("Cannot convert from %s to %s\n"),
182 ap
->a_input_charset
, ap
->a_charset
);
189 #endif /* HAVE_ICONV */
192 a_attachment_yay(struct attachment
const *ap
){
195 fprintf(n_stdout
, _("Added message/rfc822 attachment for message #%u\n"),
198 fprintf(n_stdout
, _("Added attachment %s (%s)\n"),
199 n_shexp_quote_cp(ap
->a_name
, FAL0
),
200 n_shexp_quote_cp(ap
->a_path_user
, FAL0
));
204 FL
struct attachment
*
205 n_attachment_append(struct attachment
*aplist
, char const *file
,
206 enum n_attach_error
*aerr_or_null
, struct attachment
**newap_or_null
){
211 char const *file_user
, *incs
, *oucs
;
212 struct attachment
*nap
, *ap
;
213 enum n_attach_error aerr
;
219 aerr
= n_ATTACH_ERR_NONE
;
224 aerr
= n_ATTACH_ERR_OTHER
;
227 file_user
= savestr(file
); /* TODO recreate after fexpand()!?! */
229 if((msgno
= a_attachment_is_msg(file
)) < 0){
231 char const *cp
, *ncp
;
234 if((file
= fexpand(file
, FEXP_LOCAL
| FEXP_NVAR
)) == NULL
){
235 aerr
= n_ATTACH_ERR_OTHER
;
240 if(oucs
!= NULL
&& oucs
!= (char*)-1){
241 n_err(_("No iconv support, cannot do %s\n"),
242 n_shexp_quote_cp(file_user
, FAL0
));
243 aerr
= n_ATTACH_ERR_ICONV_NAVAIL
;
250 (oucs
!= NULL
&& oucs
!= (char*)-1)
251 ? (cnvfp
= Fopen(file
, "r")) == NULL
:
253 access(file
, R_OK
) != 0)){
256 /* It may not have worked because of a character-set specification,
257 * so try to extract that and retry once */
258 if(incs
== NULL
&& (cp
= strrchr(file
, '=')) != NULL
){
262 nfp
= savestrbuf(file
, PTR2SIZE(cp
- file
));
264 for(ncp
= ++cp
; (c
= *cp
) != '\0'; ++cp
)
265 if(!alnumchar(c
) && !punctchar(c
))
269 i
= PTR2SIZE(cp
- ncp
);
270 if(i
== 0 || (i
== 1 && ncp
[0] == '-'))
272 else if((incs
= n_iconv_normalize_name(savestrbuf(ncp
, i
))
284 i
= PTR2SIZE(cp
- ncp
);
285 if(i
== 0 || (i
== 1 && ncp
[0] == '-'))
287 else if((xp
= n_iconv_normalize_name(savestrbuf(ncp
, i
))
302 n_err(_("Failed to access attachment %s: %s\n"),
303 n_shexp_quote_cp(file
, FAL0
), n_err_to_doc(e
));
304 aerr
= n_ATTACH_ERR_FILE_OPEN
;
309 nap
= a_attachment_setup_base(n_autorec_calloc(1, sizeof *nap
), file
);
310 nap
->a_path_user
= file_user
;
312 nap
= a_attachment_setup_msg(nap
, file
, msgno
);
314 nap
->a_input_charset
= (incs
== NULL
|| incs
== (char*)-1)
315 ? savestr(ok_vlook(ttycharset
)) : incs
;
318 nap
->a_charset
= oucs
;
319 if(!a_attachment_iconv(nap
, cnvfp
)){
321 aerr
= n_ATTACH_ERR_ICONV_FAILED
;
324 nap
->a_conv
= AC_TMPFILE
;
327 if(incs
!= NULL
&& oucs
== NULL
)
328 nap
->a_conv
= AC_FIX_INCS
;
330 nap
->a_conv
= AC_DEFAULT
;
334 for(ap
= aplist
; ap
->a_flink
!= NULL
; ap
= ap
->a_flink
)
342 if(aerr_or_null
!= NULL
)
343 *aerr_or_null
= aerr
;
344 if(newap_or_null
!= NULL
)
345 *newap_or_null
= nap
;
350 FL
struct attachment
*
351 n_attachment_append_list(struct attachment
*aplist
, char const *names
){
353 struct n_string shou
, *shoup
;
356 shoup
= n_string_creat_auto(&shou
);
358 for(shin
.s
= n_UNCONST(names
), shin
.l
= UIZ_MAX
;;){
359 struct attachment
*nap
;
360 enum n_shexp_state shs
;
362 shs
= n_shexp_parse_token((n_SHEXP_PARSE_TRUNC
|
363 n_SHEXP_PARSE_TRIM_SPACE
| n_SHEXP_PARSE_LOG
|
364 n_SHEXP_PARSE_IFS_ADD_COMMA
| n_SHEXP_PARSE_IGNORE_EMPTY
),
366 if(shs
& n_SHEXP_STATE_ERR_MASK
)
369 if(shs
& n_SHEXP_STATE_OUTPUT
){
370 aplist
= n_attachment_append(aplist
, n_string_cp(shoup
), NULL
, &nap
);
372 if(n_psonce
& n_PSO_INTERACTIVE
)
373 a_attachment_yay(nap
);
377 if(shs
& n_SHEXP_STATE_STOP
)
385 FL
struct attachment
*
386 n_attachment_remove(struct attachment
*aplist
, struct attachment
*ap
){
387 struct attachment
*bap
, *fap
;
391 for(bap
= aplist
; aplist
!= NULL
&& aplist
!= ap
; aplist
= aplist
->a_flink
)
393 assert(aplist
!= NULL
);
398 if((aplist
= ap
->a_flink
) != NULL
)
399 aplist
->a_blink
= NULL
;
409 if(ap
->a_conv
== AC_TMPFILE
)
415 FL
struct attachment
*
416 n_attachment_find(struct attachment
*aplist
, char const *name
,
417 bool_t
*stat_or_null
){
421 struct attachment
*saved
;
427 if((bname
= strrchr(name
, '/')) != NULL
){
428 for(++bname
; aplist
!= NULL
; aplist
= aplist
->a_flink
)
429 if(!strcmp(name
, aplist
->a_path
)){
431 /* Exact match with path components: done */
433 }else if(!strcmp(bname
, aplist
->a_path_bname
)){
440 }else if((msgno
= a_attachment_is_msg(name
)) < 0){
441 for(sym
= FAL0
; aplist
!= NULL
; aplist
= aplist
->a_flink
){
442 if(!strcmp(name
, aplist
->a_name
)){
447 }else if(!strcmp(name
, aplist
->a_path_bname
)){
452 status
= status
? TRUM1
: TRU1
;
455 for(; aplist
!= NULL
; aplist
= aplist
->a_flink
){
456 if(aplist
->a_msgno
> 0 && aplist
->a_msgno
== msgno
){
466 if(stat_or_null
!= NULL
)
467 *stat_or_null
= status
;
472 FL
struct attachment
*
473 n_attachment_list_edit(struct attachment
*aplist
, enum n_go_input_flags gif
){
476 struct n_string shou
, *shoup
;
477 struct attachment
*naplist
, *ap
;
481 if((n_psonce
& (n_PSO_INTERACTIVE
| n_PSO_ATTACH_QUOTE_NOTED
)
482 ) == n_PSO_INTERACTIVE
){
483 n_psonce
|= n_PSO_ATTACH_QUOTE_NOTED
;
485 _("# Only supports sh(1)ell-style quoting for file names\n"));
488 shoup
= n_string_creat_auto(&shou
);
490 /* Modify already present ones? Append some more? */
493 for(naplist
= NULL
;;){
494 snprintf(prefix
, sizeof prefix
, A_("#%" PRIu32
" filename: "), attno
);
497 /* TODO If we would create .a_path_user in append() after any
498 * TODO expansion then we could avoid closing+rebuilding the temporary
499 * TODO file if the new user input matches the original value! */
500 if(aplist
->a_conv
== AC_TMPFILE
)
501 Fclose(aplist
->a_tmpf
);
502 shin
.s
= n_shexp_quote_cp(aplist
->a_path_user
, FAL0
);
504 shin
.s
= n_UNCONST(n_empty
);
507 if((shin
.s
= n_go_input_cp(gif
, prefix
, shin
.s
)) != NULL
){
508 enum n_shexp_state shs
;
513 shs
= n_shexp_parse_token((n_SHEXP_PARSE_TRUNC
|
514 n_SHEXP_PARSE_TRIM_SPACE
| n_SHEXP_PARSE_LOG
|
515 n_SHEXP_PARSE_IGNORE_EMPTY
),
518 if(!(shs
& n_SHEXP_STATE_STOP
))
519 n_err(_("# May be given one argument a time only: %s\n"),
520 n_shexp_quote_cp(s_save
, FAL0
));
522 if((shs
& (n_SHEXP_STATE_OUTPUT
| n_SHEXP_STATE_STOP
|
523 n_SHEXP_STATE_ERR_MASK
)
524 ) != (n_SHEXP_STATE_OUTPUT
| n_SHEXP_STATE_STOP
))
527 naplist
= n_attachment_append(naplist
, n_string_cp(shoup
), NULL
, &ap
);
529 if(n_psonce
& n_PSO_INTERACTIVE
)
530 a_attachment_yay(ap
);
536 aplist
= aplist
->a_flink
;
537 /* In non-interactive or batch mode an empty line ends processing */
538 if((n_psonce
& n_PSO_INTERACTIVE
) && !(n_poption
& n_PO_BATCH_FLAG
))
549 n_attachment_list_print(struct attachment
const *aplist
, FILE *fp
){
550 struct attachment
const *ap
;
557 for(attno
= 1, ap
= aplist
; ap
!= NULL
; ++rv
, ++attno
, ap
= ap
->a_flink
){
559 fprintf(fp
, "#%" PRIu32
". message/rfc822: %u\n", attno
, ap
->a_msgno
);
561 char const *incs
, *oucs
;
563 if(!(n_psonce
& n_PSO_REPRODUCIBLE
)){
564 incs
= ap
->a_input_charset
;
565 oucs
= ap
->a_charset
;
567 incs
= oucs
= n_reproducible_name
;
569 fprintf(fp
, "#%" PRIu32
": %s [%s -- %s",
570 attno
, n_shexp_quote_cp(ap
->a_name
, FAL0
),
571 n_shexp_quote_cp(ap
->a_path
, FAL0
),
572 (ap
->a_content_type
!= NULL
573 ? ap
->a_content_type
: _("unclassified content")));
575 if(ap
->a_conv
== AC_TMPFILE
)
576 /* I18N: input and output character set as given */
577 fprintf(fp
, _(", incs=%s -> oucs=%s (readily converted)"),
579 else if(ap
->a_conv
== AC_FIX_INCS
)
580 /* I18N: input character set as given, no conversion to apply */
581 fprintf(fp
, _(", incs=%s (no conversion)"), incs
);
582 else if(ap
->a_conv
== AC_DEFAULT
){
584 /* I18N: input character set as given, output iterates */
585 fprintf(fp
, _(", incs=%s -> oucs=*sendcharsets*"), incs
);
586 else if(ap
->a_content_type
== NULL
||
587 !ascncasecmp(ap
->a_content_type
, "text/", 5))
588 fprintf(fp
, _(", default character set handling"));