1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Implementation of mailcap.h.
3 *@ TODO - We do not support the additional formats '%n' and '%F' (manual!).
4 *@ TODO - We do not support handlers for multipart MIME parts (manual!).
5 *@ TODO - We only support viewing/quoting (+ implications on fmt expansion).
6 *@ TODO - With an on_loop_tick_event, trigger cache update once per loop max.
7 *@ TODO (or, if we ever get there, use a path_monitor: for all such!)
9 * Copyright (c) 2019 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * SPDX-License-Identifier: ISC
12 * Permission to use, copy, modify, and/or distribute this software for any
13 * purpose with or without fee is hereby granted, provided that the above
14 * copyright notice and this permission notice appear in all copies.
16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 #define su_FILE mailcap
27 #define mx_SOURCE_MAILCAP
29 #ifndef mx_HAVE_AMALGAMATION
34 #ifdef mx_HAVE_MAILCAP
36 #include "su/cs-dict.h"
41 #include "mx/file-streams.h"
42 #include "mx/mime-type.h"
44 #include "mx/mailcap.h"
45 #include "su/code-in.h"
47 /* Whether we should try to place as much on a line as possible (Y).
48 * Otherwise (X) we place commands on lines of their own */
49 #define a_MAILCAP_DUMP_SEP_INJ(X,Y) X /* Y */
51 /* Dictionary stores a_mailcap_hdl* list, not owned */
52 #define a_MAILCAP_CSD_FLAGS (su_CS_DICT_CASE | su_CS_DICT_HEAD_RESORT |\
54 #define a_MAILCAP_CSD_TRESHOLD_SHIFT 3
56 /* Must be alphabetical */
57 enum a_mailcap_sfields
{
60 a_MAILCAP_SF_COMPOSETYPED
,
61 a_MAILCAP_SF_DESCRIPTION
,
63 a_MAILCAP_SF_NAMETEMPLATE
,
66 a_MAILCAP_SF_X11_BITMAP
68 CTAV(a_MAILCAP_SF_CMD
== 0);
69 enum {a_MAILCAP_SF_MAX
= a_MAILCAP_SF_X11_BITMAP
+ 1};
71 /* sfields we really handle, less test (no format expansion in there) */
72 #define a_MAILCAP_SFIELD_SUPPORTED(X) \
73 ((X) == a_MAILCAP_SF_CMD /*|| (X) == a_MAILCAP_SF_TEST*/)
75 enum a_mailcap_handler_flags
{
76 a_MAILCAP_F_TEXTUALNEWLINES
= mx_MIMETYPE_HDL_MAX
<< 1,
77 a_MAILCAP_F_TESTONCE
= mx_MIMETYPE_HDL_MAX
<< 2,
78 a_MAILCAP_F_TEST_ONCE_DONE
= mx_MIMETYPE_HDL_MAX
<< 3,
79 a_MAILCAP_F_TEST_ONCE_SUCCESS
= mx_MIMETYPE_HDL_MAX
<< 4,
80 a_MAILCAP_F_HAS_S_FORMAT
= mx_MIMETYPE_HDL_MAX
<< 5, /* Somewhere a %s */
81 a_MAILCAP_F_LAST_RESORT
= mx_MIMETYPE_HDL_MAX
<< 6,
82 a_MAILCAP_F_IGNORE
= mx_MIMETYPE_HDL_MAX
<< 7
84 enum {a_MAILCAP_F_MAX
= a_MAILCAP_F_IGNORE
};
85 CTA(a_MAILCAP_F_MAX
<= S32_MAX
,
86 "a_mailcap_hdl.mch_flags bit range excessed");
89 struct a_mailcap_hdl
*mch_next
;
90 BITENUM_IS(u32
,mx_mimetype_handler_flags
) mch_flags
;
92 /* All strings are placed in successive memory after "self".
93 * Since mch_cmd always exists 0 is the invalid offset for the rest.
94 * The sum of all strings fits in S32_MAX.
95 * sfield_has_format is a bitset */
96 u16 mch_sfield_has_format
;
97 u32 mch_sfields
[a_MAILCAP_SF_MAX
];
100 struct a_mailcap_load_stack
{
101 char const *mcls_name
;
102 char const *mcls_name_quoted
; /* Messages somewhat common, just do it. */
104 char const *mcls_type_subtype
;
106 struct str mcls_conti_dat
;
108 /* Preparated handler; during preparation string data is temporarily stored
109 * in .mcls_hdl_buf */
110 struct a_mailcap_hdl mcls_hdl
;
111 struct n_string mcls_hdl_buf
;
114 struct a_mailcap_flags
{
115 BITENUM_IS(u32
,mx_mimetype_handler_flags
) mcf_flags
;
119 static struct a_mailcap_flags
const a_mailcap_flags
[] = {
120 /* In manual order */
121 {mx_MIMETYPE_HDL_COPIOUSOUTPUT
, "copiousoutput"},
122 {mx_MIMETYPE_HDL_NEEDSTERM
, "needsterminal"},
123 {a_MAILCAP_F_TEXTUALNEWLINES
, "textualnewlines"},
124 {mx_MIMETYPE_HDL_ASYNC
, "x-mailx-async"},
125 {mx_MIMETYPE_HDL_NOQUOTE
, "x-mailx-noquote"},
126 {a_MAILCAP_F_TESTONCE
, "x-mailx-test-once"},
127 {mx_MIMETYPE_HDL_TMPF
, "x-mailx-tmpfile"},
128 {mx_MIMETYPE_HDL_TMPF_FILL
, "x-mailx-tmpfile-fill"},
129 {mx_MIMETYPE_HDL_TMPF_UNLINK
, "x-mailx-tmpfile-unlink\0"},
130 {a_MAILCAP_F_LAST_RESORT
, "x-mailx-last-resort"},
131 {a_MAILCAP_F_IGNORE
, "x-mailx-ignore"}
134 static struct su_cs_dict
*a_mailcap_dp
, a_mailcap__d
; /* XXX atexit _gut() */
136 /* We stop parsing and _gut(FAL0) on hard errors like NOMEM, OVERFLOW and IO.
137 * The __parse*() series return is-error, with TRUM1 being a fatal one */
138 static void a_mailcap_create(void);
140 static boole
a_mailcap__load_file(struct a_mailcap_load_stack
*mclsp
);
141 static boole
a_mailcap__parse_line(struct a_mailcap_load_stack
*mclsp
,
143 static boole
a_mailcap__parse_kv(struct a_mailcap_load_stack
*mclsp
,
145 static boole
a_mailcap__parse_value(u32 sfield
,
146 struct a_mailcap_load_stack
*mclsp
, struct str
*s
);
147 static boole
a_mailcap__parse_flag(struct a_mailcap_load_stack
*mclsp
,
149 static boole
a_mailcap__parse_create_hdl(struct a_mailcap_load_stack
*mclsp
,
150 struct a_mailcap_hdl
**ins_or_nil
);
152 static void a_mailcap_gut(boole gut_dp
);
155 static struct n_strlist
*a_mailcap_dump(char const *cmdname
, char const *key
,
158 static void a_mailcap__dump_kv(u32 sfield
, struct n_string
*s
, uz
*llp
,
159 char const *pre
, char const *vp
);
160 static struct n_string
*a_mailcap__dump_quote(struct n_string
*s
,
161 char const *cp
, boole quotequote
);
163 /* Expand a command string with embedded formats */
164 static char const *a_mailcap_expand_formats(char const *format
,
165 struct mimepart
const *mpp
, char const *ct
);
168 a_mailcap_create(void){
169 struct a_mailcap_load_stack mcls
;
173 a_mailcap_dp
= su_cs_dict_set_treshold_shift(
174 su_cs_dict_create(&a_mailcap__d
, a_MAILCAP_CSD_FLAGS
, NIL
),
175 a_MAILCAP_CSD_TRESHOLD_SHIFT
);
177 if(*(cp_base
= UNCONST(char*,ok_vlook(MAILCAPS
))) == '\0')
180 su_mem_set(&mcls
, 0, sizeof mcls
);
181 mx_fs_linepool_aquire(&mcls
.mcls_dat
.s
, &mcls
.mcls_dat
.l
);
182 n_string_book(n_string_creat(&mcls
.mcls_hdl_buf
), 248); /* ovflw not yet */
184 for(cp_base
= savestr(cp_base
);
185 (cp
= su_cs_sep_c(&cp_base
, ':', TRU1
)) != NIL
;){
186 if((cp
= fexpand(cp
, (FEXP_NOPROTO
| FEXP_LOCAL_FILE
| FEXP_NSHELL
))
190 mcls
.mcls_name_quoted
= n_shexp_quote_cp(cp
, FAL0
);
191 if((mcls
.mcls_fp
= mx_fs_open(mcls
.mcls_name
= cp
, "r")) == NIL
){
194 if((eno
= su_err_no()) != su_ERR_NOENT
)
195 n_err(_("$MAILCAPS: cannot open %s: %s\n"),
196 mcls
.mcls_name_quoted
, su_err_doc(eno
));
200 if(!a_mailcap__load_file(&mcls
))
203 mx_fs_close(mcls
.mcls_fp
);
211 if(mcls
.mcls_conti_dat
.s
!= NIL
)
212 mx_fs_linepool_release(mcls
.mcls_conti_dat
.s
, mcls
.mcls_conti_dat
.l
);
213 mx_fs_linepool_release(mcls
.mcls_dat
.s
, mcls
.mcls_dat
.l
);
215 n_string_gut(&mcls
.mcls_hdl_buf
);
222 a_mailcap__load_file(struct a_mailcap_load_stack
*mclsp
){
223 enum{a_NONE
, a_CONTI
= 1u<<0, a_EOF
= 1u<<1, a_NEWCONTI
= 1u<<2};
232 if(fgetline(&mclsp
->mcls_dat
.s
, &mclsp
->mcls_dat
.l
, NIL
, &len
,
233 mclsp
->mcls_fp
, TRU1
) == NIL
){
234 if(ferror(mclsp
->mcls_fp
)){
235 emsg
= N_("I/O error");
244 mclsp
->mcls_dat
.s
[--len
] = '\0';
246 /* Is it a comment? Must be in first column, cannot be continued */
247 if(!(f
& a_CONTI
) && len
> 0 && mclsp
->mcls_dat
.s
[0] == '#')
250 /* Is it a continuation line? It really is for an uneven number of \ */
252 if(len
> 0 && mclsp
->mcls_dat
.s
[len
- 1] == '\\'){
257 else for(j
= 1, i
= len
- 1; i
-- > 0; ++j
)
258 if(mclsp
->mcls_dat
.s
[i
] != '\\'){
267 /* Necessary to create/append to continuation line storage? */
268 if(f
& (a_CONTI
| a_NEWCONTI
)){
269 if(mclsp
->mcls_conti_dat
.s
== NIL
){
270 mclsp
->mcls_conti_dat
= mclsp
->mcls_dat
;
271 mclsp
->mcls_conti_len
= len
;
272 mx_fs_linepool_aquire(&mclsp
->mcls_dat
.s
, &mclsp
->mcls_dat
.l
);
274 if(!mx_fs_linepool_book(&mclsp
->mcls_conti_dat
.s
,
275 &mclsp
->mcls_conti_dat
.l
, mclsp
->mcls_conti_len
,
278 su_mem_copy(&mclsp
->mcls_conti_dat
.s
[mclsp
->mcls_conti_len
],
279 mclsp
->mcls_dat
.s
, len
+1);
280 mclsp
->mcls_conti_len
+= len
;
291 x
= a_mailcap__parse_line(mclsp
, mclsp
->mcls_conti_dat
.s
,
292 mclsp
->mcls_conti_len
);
293 /* Release the buffer to the linepool, like that we can swap it in
294 * again the next time, shall this be necessary! */
295 mx_fs_linepool_release(mclsp
->mcls_conti_dat
.s
,
296 mclsp
->mcls_conti_dat
.l
);
297 mclsp
->mcls_conti_dat
.s
= NIL
;
302 case TRUM1
: goto jenomem
;
305 if((f
^= a_CONTI
) & a_EOF
)
307 }else switch(a_mailcap__parse_line(mclsp
, mclsp
->mcls_dat
.s
, len
)){
310 case TRUM1
: goto jenomem
;
316 return (emsg
== NIL
);
319 emsg
= N_("out of memory");
322 su_state_err(su_STATE_ERR_OVERFLOW
, (su_STATE_ERR_PASS
|
323 su_STATE_ERR_NOERRNO
), _("$MAILCAPS: line too long"));
324 emsg
= N_("line too long");
326 n_err(_("$MAILCAPS: %s while loading %s\n"),
327 V_(emsg
), mclsp
->mcls_name_quoted
);
332 a_mailcap__parse_line(struct a_mailcap_load_stack
*mclsp
, char *dp
, uz dl
){
334 union {void *v
; struct a_mailcap_hdl
*mch
; struct a_mailcap_hdl
**pmch
;} p
;
335 char *cp
, *cp2
, *key
;
340 su_mem_set(&mclsp
->mcls_hdl
, 0, sizeof(mclsp
->mcls_hdl
));
341 n_string_trunc(&mclsp
->mcls_hdl_buf
, 0);
350 if(n_str_trim(&s
, n_STR_TRIM_BOTH
)->l
== 0){
351 if(n_poption
& n_PO_D_V
)
352 n_err(_("$MAILCAPS: %s: line empty after whitespace removal "
353 "(invalid RFC 1524 syntax)\n"), mclsp
->mcls_name_quoted
);
355 }else if(s
.l
>= S32_MAX
){
356 /* As stated, the sum must fit in S32_MAX */
360 (dp
= s
.s
)[s
.l
] = '\0';
366 while((cp
= su_cs_sep_escable_c(&dp
, ';', FAL0
)) != NIL
){
367 /* We do not allow empty fields, but there may be a trailing semicolon */
368 if(*cp
== '\0' && dp
== NIL
)
371 /* First: TYPE/SUBTYPE; separate them first */
374 key
= UNCONST(char*,N_("no MIME TYPE"));
376 }else if((cp2
= su_cs_find_c(cp
, '/')) == NIL
){
378 n_err(_("$MAILCAPS: %s: missing SUBTYPE, assuming /* (any): %s\n"),
379 mclsp
->mcls_name_quoted
, cp
);
380 cp2
= UNCONST(char*,n_star
);
387 /* And unite for the real one */
388 key
= savecatsep(cp
, '/', cp2
);
389 if(!mx_mimetype_is_valid(key
, TRU1
, TRU1
)){
391 key
= UNCONST(char*,N_("invalid MIME type"));
394 mclsp
->mcls_type_subtype
= key
;
396 if((p
.v
= su_cs_dict_lookup(a_mailcap_dp
, key
)) != NIL
){
397 while(p
.mch
->mch_next
!= NIL
)
398 p
.mch
= p
.mch
->mch_next
;
399 p
.pmch
= &p
.mch
->mch_next
;
402 /* The mandatory view command */
404 s
.l
= su_cs_len(s
.s
= cp
);
405 n_str_trim(&s
, n_STR_TRIM_BOTH
);
406 if((rv
= a_mailcap__parse_value(a_MAILCAP_SF_CMD
, mclsp
, &s
)))
409 /* An optional field */
412 if(n_poption
& n_PO_D_V
)
413 n_err(_("$MAILCAPS: %s: ignoring empty optional field: %s\n"),
414 mclsp
->mcls_name_quoted
, key
);
415 }else if((cp2
= su_cs_find_c(cp
, '=')) != NIL
){
417 if((rv
= a_mailcap__parse_kv(mclsp
, cp
, cp2
)))
419 }else if(a_mailcap__parse_flag(mclsp
, cp
))
424 rv
= a_mailcap__parse_create_hdl(mclsp
, p
.pmch
);
431 n_err(_("$MAILCAPS: %s: skip due to error: %s: %s\n"),
432 mclsp
->mcls_name_quoted
, V_(key
), cp
);
438 a_mailcap__parse_kv(struct a_mailcap_load_stack
*mclsp
, char *kp
, char *vp
){
440 #define a_X(X,Y) FIELD_INITI(su_CONCAT(a_MAILCAP_SF_, X) - 1) su_STRING(Y)
441 static char const sfa
[][16] = {
442 a_X(COMPOSE
, compose
),
443 a_X(COMPOSETYPED
, composetyped
),
444 a_X(DESCRIPTION
, description
),
446 a_X(NAMETEMPLATE
, nametemplate
),
449 a_X(X11_BITMAP
, x11
-bitmap
)
455 char const *emsg
, (*sfapp
)[16];
459 /* Trim key and value */
464 s
.l
= su_cs_len(s
.s
= *cpp
);
465 if(n_str_trim(&s
, n_STR_TRIM_BOTH
)->l
== 0){
466 emsg
= (emsg
== R(char*,1)) ? N_("ignored: empty key")
467 : N_("ignored: empty value");
470 (*cpp
= s
.s
)[s
.l
] = '\0';
472 if(emsg
== R(char*,1)){
481 for(sfapp
= &sfa
[0]; sfapp
< &sfa
[NELEM(sfa
)]; ++sfapp
){
482 if(!su_cs_cmp_case(kp
, *sfapp
)){
486 i
= P2UZ(sfapp
- &sfa
[0]) + 1;
488 if((n_poption
& n_PO_D_V
) && mclsp
->mcls_hdl
.mch_sfields
[i
] != 0)
489 n_err(_("$MAILCAPS: %s: %s: multiple %s fields\n"),
490 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, kp
);
495 case a_MAILCAP_SF_DESCRIPTION
:
496 /* This is "optionally quoted" */
500 if(s
.s
[s
.l
- 1] == '"')
503 emsg
= N_("unclosed quoted description");
507 case a_MAILCAP_SF_NAMETEMPLATE
:{
510 if((cp
= vp
)[0] != '%' || cp
[1] != 's'){
512 emsg
= N_("unsatisfied constraints, ignoring nametemplate");
515 for(cp
+= 2; (c
= *cp
++) != '\0';)
516 if(!su_cs_is_alnum(c
) && c
!= '_' && c
!= '.')
522 mclsp
->mcls_hdl
.mch_sfields
[i
] = mclsp
->mcls_hdl_buf
.s_len
;
523 if((rv
= a_mailcap__parse_value(i
, mclsp
, &s
)))
524 mclsp
->mcls_hdl
.mch_sfields
[i
] = 0;
532 if((rv
= (kp
[0] != 'x' && kp
[0] != '\0' && kp
[1] != '-')) ||
533 (n_poption
& n_PO_D_V
)){
534 emsg
= N_("ignored unknown string/command");
544 /* I18N: FILENAME: TYPE/SUBTYPE: ERROR MSG: key = value */
545 n_err(_("$MAILCAPS: %s: %s: %s: %s = %s\n"),
546 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, V_(emsg
), kp
, vp
);
551 a_mailcap__parse_value(u32 sfield
, struct a_mailcap_load_stack
*mclsp
,
557 if(S(uz
,S32_MAX
) - mclsp
->mcls_hdl_buf
.s_len
<= s
->l
+1){
564 /* Take over unless we see a format, then branch to more expensive code */
565 for(cp2
= cp3
= s
->s
;;){
578 if((*cp3
++ = c
) == '\0')
582 n_string_push_c(n_string_push_buf(&mclsp
->mcls_hdl_buf
,
583 s
->s
, P2UZ(cp3
- s
->s
)), '\0');
595 lbuf
= n_lofi_alloc(s
->l
* 2);
596 i
= P2UZ(cp3
- s
->s
);
597 su_mem_copy(lbuf
, s
->s
, i
);
606 switch((c
= *cp2
++)){
608 if(su_cs_find_c(cp2
, '}') == NIL
){
609 n_err(_("$MAILCAPS: %s: %s: unclosed %%{ format: %s\n"),
610 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, s
->s
);
611 /* We need to skip the entire thing if we are incapable to
612 * satisfy the format request when invoking a command */
613 if(a_MAILCAP_SFIELD_SUPPORTED(sfield
)){
622 if(sfield
== a_MAILCAP_SF_TEST
){ /* XXX only view/quote */
623 n_err(_("$MAILCAPS: %s: %s: %%s format cannot be used in "
624 "the \"test\" field\n"),
625 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, s
->s
);
629 /* xxx Very primitive user-used-false-quotes check */
630 if((n_poption
& n_PO_D_V
) && (*cp2
== '"' || *cp2
== '\''))
631 n_err(_("$MAILCAPS: %s: %s: (maybe!) "
632 "%%s must not be quoted: %s\n"),
633 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, s
->s
);
635 mclsp
->mcls_hdl
.mch_flags
|= a_MAILCAP_F_HAS_S_FORMAT
;
639 mclsp
->mcls_hdl
.mch_sfield_has_format
|= 1u << sfield
;
646 n_err(_("$MAILCAPS: %s: %s: unsupported format %%%c\n"),
647 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, c
);
648 /* We need to skip the entire thing if we are incapable to satisfy
649 * the format request when invoking a command */
650 if(a_MAILCAP_SFIELD_SUPPORTED(sfield
)){
654 /* Since it is actually ignored, do not care, do not quote */
655 mclsp
->mcls_hdl
.mch_sfield_has_format
|= 1u << sfield
;
660 n_err(_("$MAILCAPS: %s: %s: invalid format %%%c, "
661 "should be quoted: \\%%%c\n"),
662 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, c
, c
);
670 if((*cp3
++ = c
) == '\0')
674 n_string_push_c(n_string_push_buf(&mclsp
->mcls_hdl_buf
,
675 lbuf
, P2UZ(cp3
- lbuf
)), '\0');
684 a_mailcap__parse_flag(struct a_mailcap_load_stack
*mclsp
, char *flag
){
685 struct a_mailcap_flags
const *fap
;
691 for(fap
= &a_mailcap_flags
[0];
692 fap
< &a_mailcap_flags
[NELEM(a_mailcap_flags
)]; ++fap
)
693 if(!su_cs_cmp_case(flag
, fap
->mcf_name
)){
694 if((n_poption
& n_PO_D_V
) &&
695 (mclsp
->mcls_hdl
.mch_flags
& fap
->mcf_flags
))
696 n_err(_("$MAILCAPS: %s: %s: multiple %s flags\n"),
697 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, flag
);
698 mclsp
->mcls_hdl
.mch_flags
|= fap
->mcf_flags
;
702 if((rv
= (flag
[0] != 'x' && flag
[0] != '\0' && flag
[1] != '-')) ||
703 (n_poption
& n_PO_D_V
))
704 n_err(_("$MAILCAPS: %s: %s: ignored unknown flag: %s\n"),
705 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, flag
);
713 a_mailcap__parse_create_hdl(struct a_mailcap_load_stack
*mclsp
,
714 struct a_mailcap_hdl
**ins_or_nil
){
715 struct a_mailcap_hdl
*mchp
;
723 /* Flag implications */
725 f
= mclsp
->mcls_hdl
.mch_flags
;
727 if(f
& (mx_MIMETYPE_HDL_TMPF_FILL
| mx_MIMETYPE_HDL_TMPF_UNLINK
))
728 f
|= mx_MIMETYPE_HDL_TMPF
;
730 if(f
& mx_MIMETYPE_HDL_ASYNC
){
731 if(f
& mx_MIMETYPE_HDL_COPIOUSOUTPUT
){
732 emsg
= N_("cannot use x-mailx-async and copiousoutput");
735 if(f
& mx_MIMETYPE_HDL_TMPF_UNLINK
){
736 emsg
= N_("cannot use x-mailx-async and x-mailx-tmpfile-unlink");
741 if(f
& mx_MIMETYPE_HDL_NEEDSTERM
){
742 if(f
& mx_MIMETYPE_HDL_COPIOUSOUTPUT
){
743 emsg
= N_("cannot use needsterminal and copiousoutput");
746 if(f
& mx_MIMETYPE_HDL_ASYNC
){
747 emsg
= N_("cannot use needsterminal and x-mailx-async");
752 /* Mailcap implications */
754 if(mclsp
->mcls_hdl
.mch_sfields
[a_MAILCAP_SF_NAMETEMPLATE
] != 0){
755 if(f
& a_MAILCAP_F_HAS_S_FORMAT
)
756 f
|= mx_MIMETYPE_HDL_TMPF_NAMETMPL
|
757 mx_MIMETYPE_HDL_TMPF_NAMETMPL_SUFFIX
;
759 n_err(_("$MAILCAPS: %s: %s: no %%s format, ignoring nametemplate\n"),
760 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
);
763 if(f
& mx_MIMETYPE_HDL_TMPF
){
764 /* Not with any %s */
765 if(f
& a_MAILCAP_F_HAS_S_FORMAT
){
766 emsg
= N_("cannot use x-mailx-tmpfile if formats use %s");
771 mclsp
->mcls_hdl
.mch_flags
= f
;
773 /* Since all strings altogether fit in S32_MAX, allocate one big chunk */
776 mchp
= S(struct a_mailcap_hdl
*,su_CALLOC(sizeof(*mchp
) +
777 mclsp
->mcls_hdl_buf
.s_len
+1));
781 su_mem_copy(mchp
, &mclsp
->mcls_hdl
, sizeof(mclsp
->mcls_hdl
));
782 su_mem_copy(&mchp
[1], n_string_cp(&mclsp
->mcls_hdl_buf
),
783 mclsp
->mcls_hdl_buf
.s_len
+1);
787 if(ins_or_nil
!= NIL
)
789 else if(su_cs_dict_insert(a_mailcap_dp
, mclsp
->mcls_type_subtype
, mchp
) > 0)
798 n_err(_("$MAILCAPS: %s: %s: %s\n"),
799 mclsp
->mcls_name_quoted
, mclsp
->mcls_type_subtype
, V_(emsg
));
804 a_mailcap_gut(boole gut_dp
){
807 if(a_mailcap_dp
!= NIL
){
808 struct su_cs_dict_view csdv
;
810 su_CS_DICT_FOREACH(a_mailcap_dp
, &csdv
){
811 struct a_mailcap_hdl
*mchp
, *tmp
;
813 for(mchp
= S(struct a_mailcap_hdl
*,su_cs_dict_view_data(&csdv
));
816 mchp
= mchp
->mch_next
;
822 su_cs_dict_gut(a_mailcap_dp
);
825 su_cs_dict_clear(a_mailcap_dp
);
831 static struct n_strlist
*
832 a_mailcap_dump(char const *cmdname
, char const *key
, void const *dat
){
833 /* XXX real strlist + str_to_fmt() */
835 #define a_X(X,Y) FIELD_INITI(su_CONCAT(a_MAILCAP_SF_, X)) Y
836 static char const sfa
[][20] = {
838 a_X(COMPOSE
, " compose = "),
839 a_X(COMPOSETYPED
, " composetyped = \0"),
840 a_X(DESCRIPTION
, " description = "),
841 a_X(EDIT
, " edit = "),
842 a_X(NAMETEMPLATE
, " nametemplate = "),
843 a_X(PRINT
, " print = "),
844 a_X(TEST
, " test = "),
845 a_X(X11_BITMAP
, " x11-bitmap = ")
849 struct n_string s_b
, *s
;
850 struct a_mailcap_hdl
const *mchp
;
851 struct n_strlist
*slp
;
855 s
= n_string_book(n_string_creat_auto(&s_b
), 127);
856 s
= n_string_resize(s
, n_STRLIST_PLAIN_SIZE());
858 for(mchp
= S(struct a_mailcap_hdl
const*,dat
); mchp
!= NIL
;
859 mchp
= mchp
->mch_next
){
863 if(S(void const*,mchp
) != dat
)
864 s
= n_string_push_c(s
, '\n');
866 lo
= i
= su_cs_len(key
);
867 s
= n_string_push_buf(s
, key
, i
);
869 buf
= S(char const*,&mchp
[1]);
870 for(i
= a_MAILCAP_SF_CMD
; i
< NELEM(sfa
); ++i
)
871 if(i
== a_MAILCAP_SF_CMD
|| mchp
->mch_sfields
[i
] > 0)
872 a_mailcap__dump_kv(i
, s
, &lo
, sfa
[i
], &buf
[mchp
->mch_sfields
[i
]]);
874 if(mchp
->mch_flags
!= 0){
875 a_MAILCAP_DUMP_SEP_INJ(boole any
= FAL0
, (void)0);
877 for(i
= 0; i
< NELEM(a_mailcap_flags
); ++i
){
878 if(mchp
->mch_flags
& a_mailcap_flags
[i
].mcf_flags
){
881 s
= n_string_push_c(s
, ';');
883 j
= su_cs_len(a_mailcap_flags
[i
].mcf_name
);
884 if(a_MAILCAP_DUMP_SEP_INJ(!any
, FAL0
) || lo
+ j
>= 76){
885 s
= n_string_push_buf(s
, "\\\n ", sizeof("\\\n ") -1);
889 s
= n_string_push_c(s
, ' ');
890 s
= n_string_push_buf(s
, a_mailcap_flags
[i
].mcf_name
, j
);
892 a_MAILCAP_DUMP_SEP_INJ(any
= TRU1
, (void)0);
898 s
= n_string_push_c(s
, '\n');
902 slp
= R(struct n_strlist
*,S(void*,s
->s_dat
));
903 /* xxx Should we assert alignment constraint of slp is satisfied?
904 * xxx Should be, heap memory with alignment < sizeof(void*) bitter? */
906 slp
->sl_len
= s
->s_len
;
907 n_string_drop_ownership(s
);
914 a_mailcap__dump_kv(u32 sfield
, struct n_string
*s
, uz
*llp
, char const *pre
,
921 prel
= su_cs_len(pre
);
923 s
= n_string_push_c(s
, ';');
927 if(vp
[i
] == '\0' && vp
[i
+ 1] == '\0')
929 /* An empty command is an error if no other field follows, a condition we do
930 * not know here, so put something; instead of a dummy field, put success */
931 if(i
== 0 && sfield
== a_MAILCAP_SF_CMD
){
936 if(a_MAILCAP_DUMP_SEP_INJ(sfield
!= a_MAILCAP_SF_CMD
|| lo
+ prel
+ i
>= 75,
937 lo
+ prel
+ i
>= 72)){
938 s
= n_string_push_buf(s
, "\\\n ", sizeof("\\\n ") -1);
942 quote
= (sfield
== a_MAILCAP_SF_DESCRIPTION
);
945 s
= n_string_push_buf(s
, pre
, prel
);
947 if(quote
&& i
> 0 && (su_cs_is_space(vp
[0]) || su_cs_is_space(vp
[i
- 1])))
948 s
= n_string_push_c(s
, '"');
951 s
= a_mailcap__dump_quote(s
, vp
, quote
);
953 s
= n_string_push_c(s
, '"');
960 static struct n_string
*
961 a_mailcap__dump_quote(struct n_string
*s
, char const *cp
, boole quotequote
){
966 if((c
= *cp
++) == '\0'){
967 if((c
= *cp
++) == '\0')
969 s
= n_string_push_c(s
, '%');
970 }else if(c
== ';' || c
== '\\' || c
== '%' || (c
== '"' && quotequote
))
971 s
= n_string_push_c(s
, '\\');
972 s
= n_string_push_c(s
, c
);
980 a_mailcap_expand_formats(char const *format
, struct mimepart
const *mpp
,
982 struct n_string s_b
, *s
= &s_b
;
986 s
= n_string_creat_auto(s
);
987 s
= n_string_book(s
, 128);
992 if((c
= *cp
++) == '\0'){
993 if((c
= *cp
++) == '\0')
1001 tmp
= su_cs_find_c(cp
, '}');
1002 ASSERT(tmp
!= NIL
); /* (parser verified) */
1003 xp
= savestrbuf(cp
, P2UZ(tmp
- cp
));
1007 s
= n_string_push_c(s
, '\'');
1008 if((xp
= mime_param_get(xp
, mpp
->m_ct_type
)) != NIL
){
1009 /* XXX Maybe we should simply shell quote that thing? */
1010 while((c
= *xp
++) != '\0'){
1012 s
= n_string_push_c(s
, c
);
1014 s
= n_string_push_cp(s
, "'\"'\"'");
1017 s
= n_string_push_c(s
, '\'');
1020 /* For that we leave the actual expansion up to $SHELL.
1021 * See XXX comment in mx_mailcap_handler(), however */
1022 s
= n_string_push_cp(s
,
1023 "\"${" n_PIPEENV_FILENAME_TEMPORARY
"}\"");
1026 s
= n_string_push_cp(s
, ct
);
1030 s
= n_string_push_c(s
, c
);
1033 cp
= n_string_cp(s
);
1034 n_string_drop_ownership(s
);
1041 c_mailcap(void *vp
){
1053 if(su_cs_starts_with_case("show", *argv
))
1057 if(su_cs_starts_with_case("load", *argv
))
1059 if(su_cs_starts_with_case("clear", *argv
)){
1060 a_mailcap_gut(TRU1
);
1065 mx_cmd_print_synopsis(mx_cmd_firstfit("mailcap"), NIL
);
1069 return (vp
== NIL
? n_EXIT_ERR
: n_EXIT_OK
);
1072 if(a_mailcap_dp
== NIL
)
1076 struct n_strlist
*slp
;
1079 if(!(mx_xy_dump_dict("mailcap", a_mailcap_dp
, &slp
, NIL
,
1081 mx_page_or_print_strlist("mailcap", slp
, TRU1
)))
1088 mx_mailcap_handler(struct mx_mimetype_handler
*mthp
, char const *ct
,
1089 enum sendaction action
, struct mimepart
const *mpp
){
1090 union {void *v
; char const *c
; struct a_mailcap_hdl
*mch
;} p
;
1092 struct a_mailcap_hdl
*mchp
;
1098 /* For now we support that only, too */
1099 ASSERT_NYD(action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
1100 action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
1101 action
== SEND_TODISP_PARTS
);
1103 if(ok_blook(mailcap_disable
))
1106 if(a_mailcap_dp
== NIL
)
1108 if(su_cs_dict_count(a_mailcap_dp
) == 0)
1111 /* Walk over the list of handlers and check whether one fits */
1113 p
.v
= su_cs_dict_lookup(a_mailcap_dp
, ct
);
1115 for(; p
.mch
!= NIL
; p
.mch
= p
.mch
->mch_next
){
1118 f
= p
.mch
->mch_flags
;
1120 if(f
& a_MAILCAP_F_IGNORE
)
1123 if(action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
){
1124 /*if(f & mx_MIMETYPE_HDL_ASYNC)
1126 if(!(f
& mx_MIMETYPE_HDL_COPIOUSOUTPUT
))
1128 }else if(action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
){
1129 if(f
& mx_MIMETYPE_HDL_NOQUOTE
)
1131 /*if(f & mx_MIMETYPE_HDL_ASYNC)
1133 if(f
& mx_MIMETYPE_HDL_NEEDSTERM
) /* XXX for now */
1135 if(!(f
& mx_MIMETYPE_HDL_COPIOUSOUTPUT
)) /* xxx for now */
1141 /* Flags seem to fit, do we need to test? */
1142 if(p
.mch
->mch_sfields
[a_MAILCAP_SF_TEST
] != 0){
1143 if(!(f
& a_MAILCAP_F_TESTONCE
) || !(f
& a_MAILCAP_F_TEST_ONCE_DONE
)){
1144 struct mx_child_ctx cc
;
1146 mx_child_ctx_setup(&cc
);
1147 cc
.cc_flags
= mx_CHILD_RUN_WAIT_LIFE
;
1148 cc
.cc_fds
[0] = cc
.cc_fds
[1] = mx_CHILD_FD_NULL
;
1149 cc
.cc_cmd
= ok_vlook(SHELL
);
1150 cc
.cc_args
[0] = "-c";
1151 cc
.cc_args
[1] = &R(char const*,&p
.mch
[1]
1152 )[p
.mch
->mch_sfields
[a_MAILCAP_SF_TEST
]];
1153 if(p
.mch
->mch_sfield_has_format
& (1u << a_MAILCAP_SF_TEST
))
1154 cc
.cc_args
[1] = a_mailcap_expand_formats(cc
.cc_args
[1],
1157 if(mx_child_run(&cc
) && cc
.cc_exit_status
== n_EXIT_OK
)
1158 f
|= a_MAILCAP_F_TEST_ONCE_SUCCESS
;
1160 if(f
& a_MAILCAP_F_TESTONCE
){
1161 f
|= a_MAILCAP_F_TEST_ONCE_DONE
;
1162 p
.mch
->mch_flags
= f
;
1166 if(!(f
& a_MAILCAP_F_TEST_ONCE_SUCCESS
))
1170 /* That one shall be it */
1171 if(f
& a_MAILCAP_F_HAS_S_FORMAT
){
1172 f
|= mx_MIMETYPE_HDL_TMPF
| mx_MIMETYPE_HDL_TMPF_FILL
;
1173 if(!(f
& mx_MIMETYPE_HDL_ASYNC
))
1174 f
|= mx_MIMETYPE_HDL_TMPF_UNLINK
;
1176 f
|= mx_MIMETYPE_HDL_CMD
;
1177 mthp
->mth_flags
= f
;
1179 /* XXX We could use a different policy where the handler has a callback
1180 * XXX mechanism that is called when the handler's environment is fully
1181 * XXX setup; mailcap could use that to expand mth_shell_cmd.
1182 * XXX For now simply embed MAILX_FILENAME_TEMPORARY, and leave expansion
1183 * XXX up to the shell */
1184 mthp
->mth_shell_cmd
= &R(char const*,&p
.mch
[1]
1185 )[p
.mch
->mch_sfields
[a_MAILCAP_SF_CMD
]];
1186 if(p
.mch
->mch_sfield_has_format
& (1u << a_MAILCAP_SF_CMD
))
1187 mthp
->mth_shell_cmd
= a_mailcap_expand_formats(mthp
->mth_shell_cmd
,
1190 if(p
.mch
->mch_sfields
[a_MAILCAP_SF_NAMETEMPLATE
] != 0){
1191 mthp
->mth_tmpf_nametmpl
= &R(char const*,&p
.mch
[1]
1192 )[p
.mch
->mch_sfields
[a_MAILCAP_SF_NAMETEMPLATE
]];
1193 ASSERT(mthp
->mth_tmpf_nametmpl
[0] == '\0' &&
1194 mthp
->mth_tmpf_nametmpl
[1] == 's');
1195 mthp
->mth_tmpf_nametmpl
+= 2;
1202 /* Direct match, otherwise try wildcard match? */
1203 if(!wildcard
&& (p
.c
= su_cs_find_c(ct
, '/')) != NIL
){
1208 cp
= n_lofi_alloc((i
= P2UZ(p
.c
- ct
)) + 2 +1);
1210 su_mem_copy(cp
, ct
, i
);
1214 p
.v
= su_cs_dict_lookup(a_mailcap_dp
, cp
);
1224 return (mchp
== NIL
? FAL0
1225 : (mchp
->mch_flags
& a_MAILCAP_F_LAST_RESORT
? TRUM1
: TRU1
));
1228 #include "su/code-ou.h"
1229 #endif /* mx_HAVE_MAILCAP */