Bump S-nail v14.9.25 ("Lubimy Gorod"), 2024-06-27
[s-mailx.git] / src / mx / mailcap.c
blobcf99228682201a6dfde064c1bce5acfb4b350167
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.
24 #undef su_FILE
25 #define su_FILE mailcap
26 #define mx_SOURCE
27 #define mx_SOURCE_MAILCAP
29 #ifndef mx_HAVE_AMALGAMATION
30 # include "mx/nail.h"
31 #endif
33 su_EMPTY_FILE()
34 #ifdef mx_HAVE_MAILCAP
35 #include "su/cs.h"
36 #include "su/cs-dict.h"
37 #include "su/mem.h"
39 #include "mx/child.h"
40 #include "mx/cmd.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 |\
53 su_CS_DICT_ERR_PASS)
54 #define a_MAILCAP_CSD_TRESHOLD_SHIFT 3
56 /* Must be alphabetical */
57 enum a_mailcap_sfields{
58 a_MAILCAP_SF_CMD,
59 a_MAILCAP_SF_COMPOSE,
60 a_MAILCAP_SF_COMPOSETYPED,
61 a_MAILCAP_SF_DESCRIPTION,
62 a_MAILCAP_SF_EDIT,
63 a_MAILCAP_SF_NAMETEMPLATE,
64 a_MAILCAP_SF_PRINT,
65 a_MAILCAP_SF_TEST,
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");
88 struct a_mailcap_hdl{
89 struct a_mailcap_hdl *mch_next;
90 BITENUM_IS(u32,mx_mimetype_handler_flags) mch_flags;
91 u8 mch__pad[2];
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. */
103 FILE *mcls_fp;
104 char const *mcls_type_subtype;
105 struct str mcls_dat;
106 struct str mcls_conti_dat;
107 uz mcls_conti_len;
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;
116 char mcf_name[28];
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,
142 char *dp, uz dl);
143 static boole a_mailcap__parse_kv(struct a_mailcap_load_stack *mclsp,
144 char *kp, char *vp);
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,
148 char *flag);
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);
154 /* */
155 static struct n_strlist *a_mailcap_dump(char const *cmdname, char const *key,
156 void const *dat);
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);
167 static void
168 a_mailcap_create(void){
169 struct a_mailcap_load_stack mcls;
170 char *cp_base, *cp;
171 NYD_IN;
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')
178 goto jleave;
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))
187 ) == NIL)
188 continue;
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){
192 s32 eno;
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));
197 continue;
200 if(!a_mailcap__load_file(&mcls))
201 cp = NIL;
203 mx_fs_close(mcls.mcls_fp);
205 if(cp == NIL){
206 a_mailcap_gut(FAL0);
207 break;
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);
217 jleave:
218 NYD_OU;
221 static boole
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};
224 char const *emsg;
225 uz len;
226 u32 f;
227 NYD2_IN;
229 emsg = NIL;
231 for(f = a_NONE;;){
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");
236 goto jerr;
238 f |= a_EOF;
239 if(f & a_CONTI)
240 goto jconti_do;
241 break;
243 ASSERT(len > 0);
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] == '#')
248 continue;
250 /* Is it a continuation line? It really is for an uneven number of \ */
251 f &= ~a_NEWCONTI;
252 if(len > 0 && mclsp->mcls_dat.s[len - 1] == '\\'){
253 uz i, j;
255 if(len == 1)
256 continue;
257 else for(j = 1, i = len - 1; i-- > 0; ++j)
258 if(mclsp->mcls_dat.s[i] != '\\'){
259 if(j & 1){
260 f |= a_NEWCONTI;
261 --len;
263 break;
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);
273 }else{
274 if(!mx_fs_linepool_book(&mclsp->mcls_conti_dat.s,
275 &mclsp->mcls_conti_dat.l, mclsp->mcls_conti_len,
276 MAX(len, 256)))
277 goto jetoolong;
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;
282 f |= a_CONTI;
284 if(f & a_NEWCONTI)
285 continue;
287 jconti_do:
288 /* C99 */{
289 boole x;
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;
299 switch(x){
300 case FAL0: break;
301 case TRU1: break;
302 case TRUM1: goto jenomem;
305 if((f ^= a_CONTI) & a_EOF)
306 break;
307 }else switch(a_mailcap__parse_line(mclsp, mclsp->mcls_dat.s, len)){
308 case FAL0: break;
309 case TRU1: break;
310 case TRUM1: goto jenomem;
314 jleave:
315 NYD2_OU;
316 return (emsg == NIL);
318 jenomem:
319 emsg = N_("out of memory");
320 goto jerr;
321 jetoolong:
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");
325 jerr:
326 n_err(_("$MAILCAPS: %s while loading %s\n"),
327 V_(emsg), mclsp->mcls_name_quoted);
328 goto jleave;
331 static boole
332 a_mailcap__parse_line(struct a_mailcap_load_stack *mclsp, char *dp, uz dl){
333 struct str s;
334 union {void *v; struct a_mailcap_hdl *mch; struct a_mailcap_hdl **pmch;} p;
335 char *cp, *cp2, *key;
336 uz rnd;
337 boole rv;
338 NYD2_IN;
340 su_mem_set(&mclsp->mcls_hdl, 0, sizeof(mclsp->mcls_hdl));
341 n_string_trunc(&mclsp->mcls_hdl_buf, 0);
343 rv = TRU1;
345 if(dl == 0)
346 goto jleave;
348 s.s = dp;
349 s.l = dl;
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);
354 goto jleave;
355 }else if(s.l >= S32_MAX){
356 /* As stated, the sum must fit in S32_MAX */
357 rv = TRUM1;
358 goto jleave;
360 (dp = s.s)[s.l] = '\0';
362 rnd = 0;
363 UNINIT(key, NIL);
364 UNINIT(p.v, NIL);
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)
369 break;
371 /* First: TYPE/SUBTYPE; separate them first */
372 if(++rnd == 1){
373 if(*cp == '\0'){
374 key = UNCONST(char*,N_("no MIME TYPE"));
375 goto jerr;
376 }else if((cp2 = su_cs_find_c(cp, '/')) == NIL){
377 jesubtype:
378 n_err(_("$MAILCAPS: %s: missing SUBTYPE, assuming /* (any): %s\n"),
379 mclsp->mcls_name_quoted, cp);
380 cp2 = UNCONST(char*,n_star);
381 }else{
382 *cp2++ = '\0';
383 if(*cp2 == '\0')
384 goto jesubtype;
387 /* And unite for the real one */
388 key = savecatsep(cp, '/', cp2);
389 if(!mx_mimetype_is_valid(key, TRU1, TRU1)){
390 cp = key;
391 key = UNCONST(char*,N_("invalid MIME type"));
392 goto jerr;
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 */
403 else if(rnd == 2){
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)))
407 goto jleave;
409 /* An optional field */
410 else{
411 if(*cp == '\0'){
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){
416 *cp2++ = '\0';
417 if((rv = a_mailcap__parse_kv(mclsp, cp, cp2)))
418 goto jleave;
419 }else if(a_mailcap__parse_flag(mclsp, cp))
420 goto jleave;
424 rv = a_mailcap__parse_create_hdl(mclsp, p.pmch);
426 jleave:
427 NYD2_OU;
428 return rv;
430 jerr:
431 n_err(_("$MAILCAPS: %s: skip due to error: %s: %s\n"),
432 mclsp->mcls_name_quoted, V_(key), cp);
433 rv = TRU1;
434 goto jleave;
437 static boole
438 a_mailcap__parse_kv(struct a_mailcap_load_stack *mclsp, char *kp, char *vp){
439 #undef a_X
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),
445 a_X(EDIT, edit),
446 a_X(NAMETEMPLATE, nametemplate),
447 a_X(PRINT, print),
448 a_X(TEST, test),
449 a_X(X11_BITMAP, x11-bitmap)
451 #undef a_X
453 struct str s;
454 char **cpp;
455 char const *emsg, (*sfapp)[16];
456 boole rv;
457 NYD2_IN;
459 /* Trim key and value */
460 rv = TRU1;
461 emsg = R(char*,1);
462 cpp = &kp;
463 jredo:
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");
468 goto jerr;
470 (*cpp = s.s)[s.l] = '\0';
472 if(emsg == R(char*,1)){
473 emsg = R(char*,-1);
474 cpp = &vp;
475 goto jredo;
478 emsg = NIL;
480 /* Find keyword */
481 for(sfapp = &sfa[0]; sfapp < &sfa[NELEM(sfa)]; ++sfapp){
482 if(!su_cs_cmp_case(kp, *sfapp)){
483 uz i;
485 ASSERT(s.s == vp);
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);
492 switch(i){
493 default:
494 break;
495 case a_MAILCAP_SF_DESCRIPTION:
496 /* This is "optionally quoted" */
497 if(*vp == '"'){
498 s.s = ++vp;
499 --s.l;
500 if(s.s[s.l - 1] == '"')
501 s.s[--s.l] = '\0';
502 else
503 emsg = N_("unclosed quoted description");
505 break;
507 case a_MAILCAP_SF_NAMETEMPLATE:{
508 char *cp, c;
510 if((cp = vp)[0] != '%' || cp[1] != 's'){
511 jentempl:
512 emsg = N_("unsatisfied constraints, ignoring nametemplate");
513 goto jerr;
515 for(cp += 2; (c = *cp++) != '\0';)
516 if(!su_cs_is_alnum(c) && c != '_' && c != '.')
517 goto jentempl;
519 break;
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;
526 if(emsg != NIL)
527 goto jerr;
528 goto jleave;
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");
535 goto jerr;
538 rv = FAL0;
539 jleave:
540 NYD2_OU;
541 return rv;
543 jerr:
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);
547 goto jleave;
550 static boole
551 a_mailcap__parse_value(u32 sfield, struct a_mailcap_load_stack *mclsp,
552 struct str *s){
553 char *cp2, *cp3, c;
554 boole rv;
555 NYD2_IN;
557 if(S(uz,S32_MAX) - mclsp->mcls_hdl_buf.s_len <= s->l +1){
558 rv = TRUM1;
559 goto jleave;
562 rv = FAL0;
564 /* Take over unless we see a format, then branch to more expensive code */
565 for(cp2 = cp3 = s->s;;){
566 c = *cp2++;
567 if(c == '\\')
568 c = *cp2++;
569 else if(c == '%'){
570 if(*cp2 == '%')
571 ++cp2;
572 else{
573 --cp2;
574 goto jfmt;
578 if((*cp3++ = c) == '\0')
579 break;
582 n_string_push_c(n_string_push_buf(&mclsp->mcls_hdl_buf,
583 s->s, P2UZ(cp3 - s->s)), '\0');
585 jleave:
586 NYD2_OU;
587 return rv;
589 jfmt:{
590 char *lbuf;
592 /* C99 */{
593 uz i;
595 lbuf = n_lofi_alloc(s->l * 2);
596 i = P2UZ(cp3 - s->s);
597 su_mem_copy(lbuf, s->s, i);
598 cp3 = &lbuf[i];
601 for(;;){
602 c = *cp2++;
603 if(c == '\\')
604 c = *cp2++;
605 else if(c == '%'){
606 switch((c = *cp2++)){
607 case '{':
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)){
614 rv = TRU1;
615 goto jfmt_leave;
617 goto jquote;
619 /* FALLTHRU */
620 if(0){
621 case 's':
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);
626 rv = TRU1;
627 goto jfmt_leave;
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;
637 /* FALLTHRU */
638 case 't':
639 mclsp->mcls_hdl.mch_sfield_has_format |= 1u << sfield;
640 *cp3++ = '\0';
641 break;
643 case 'n':
644 /* FALLTHRU */
645 case 'F':
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)){
651 rv = TRU1;
652 goto jfmt_leave;
654 /* Since it is actually ignored, do not care, do not quote */
655 mclsp->mcls_hdl.mch_sfield_has_format |= 1u << sfield;
656 *cp3++ = '\0';
657 break;
659 default:
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);
663 jquote:
664 --cp2;
665 c = '%';
666 break;
670 if((*cp3++ = c) == '\0')
671 break;
674 n_string_push_c(n_string_push_buf(&mclsp->mcls_hdl_buf,
675 lbuf, P2UZ(cp3 - lbuf)), '\0');
677 jfmt_leave:
678 n_lofi_free(lbuf);
680 goto jleave;
683 static boole
684 a_mailcap__parse_flag(struct a_mailcap_load_stack *mclsp, char *flag){
685 struct a_mailcap_flags const *fap;
686 boole rv;
687 NYD2_IN;
689 rv = FAL0;
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;
699 goto jleave;
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);
707 jleave:
708 NYD2_OU;
709 return rv;
712 static boole
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;
716 char const *emsg;
717 u32 f;
718 boole rv;
719 NYD2_IN;
721 rv = TRU1;
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");
733 goto jerr;
735 if(f & mx_MIMETYPE_HDL_TMPF_UNLINK){
736 emsg = N_("cannot use x-mailx-async and x-mailx-tmpfile-unlink");
737 goto jerr;
741 if(f & mx_MIMETYPE_HDL_NEEDSTERM){
742 if(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT){
743 emsg = N_("cannot use needsterminal and copiousoutput");
744 goto jerr;
746 if(f & mx_MIMETYPE_HDL_ASYNC){
747 emsg = N_("cannot use needsterminal and x-mailx-async");
748 goto jerr;
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;
758 else
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");
767 goto jerr;
771 mclsp->mcls_hdl.mch_flags = f;
773 /* Since all strings altogether fit in S32_MAX, allocate one big chunk */
774 rv = TRUM1;
776 mchp = S(struct a_mailcap_hdl*,su_CALLOC(sizeof(*mchp) +
777 mclsp->mcls_hdl_buf.s_len +1));
778 if(mchp == NIL)
779 goto jleave;
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);
785 rv = FAL0;
787 if(ins_or_nil != NIL)
788 *ins_or_nil = mchp;
789 else if(su_cs_dict_insert(a_mailcap_dp, mclsp->mcls_type_subtype, mchp) > 0)
790 rv = TRUM1;
792 jleave:
793 NYD2_OU;
794 return rv;
796 jerr:
797 UNUSED(emsg);
798 n_err(_("$MAILCAPS: %s: %s: %s\n"),
799 mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, V_(emsg));
800 goto jleave;
803 static void
804 a_mailcap_gut(boole gut_dp){
805 NYD2_IN;
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));
814 mchp != NIL;){
815 tmp = mchp;
816 mchp = mchp->mch_next;
817 su_FREE(tmp);
821 if(gut_dp){
822 su_cs_dict_gut(a_mailcap_dp);
823 a_mailcap_dp = NIL;
824 }else
825 su_cs_dict_clear(a_mailcap_dp);
828 NYD2_OU;
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() */
834 #undef a_X
835 #define a_X(X,Y) FIELD_INITI(su_CONCAT(a_MAILCAP_SF_, X)) Y
836 static char const sfa[][20] = {
837 a_X(CMD, " "),
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 = ")
847 #undef a_X
849 struct n_string s_b, *s;
850 struct a_mailcap_hdl const *mchp;
851 struct n_strlist *slp;
852 NYD2_IN;
853 UNUSED(cmdname);
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){
860 uz i, lo, lx;
861 char const *buf;
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){
879 uz j;
881 s = n_string_push_c(s, ';');
882 ++lo;
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);
886 lo = 1;
888 lx = s->s_len;
889 s = n_string_push_c(s, ' ');
890 s = n_string_push_buf(s, a_mailcap_flags[i].mcf_name, j);
891 lo += s->s_len - lx;
892 a_MAILCAP_DUMP_SEP_INJ(any = TRU1, (void)0);
898 s = n_string_push_c(s, '\n');
900 n_string_cp(s);
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? */
905 slp->sl_next = NIL;
906 slp->sl_len = s->s_len;
907 n_string_drop_ownership(s);
909 NYD2_OU;
910 return slp;
913 static void
914 a_mailcap__dump_kv(u32 sfield, struct n_string *s, uz *llp, char const *pre,
915 char const *vp){
916 boole quote;
917 uz lo, prel, i, lx;
918 NYD2_IN;
920 lo = *llp;
921 prel = su_cs_len(pre);
923 s = n_string_push_c(s, ';');
924 ++lo;
926 for(i = 0;; ++i)
927 if(vp[i] == '\0' && vp[i + 1] == '\0')
928 break;
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){
932 vp = ":\0\0";
933 i = 1;
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);
939 lo = 1;
942 quote = (sfield == a_MAILCAP_SF_DESCRIPTION);
944 lx = s->s_len;
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, '"');
949 else
950 quote = FAL0;
951 s = a_mailcap__dump_quote(s, vp, quote);
952 if(quote)
953 s = n_string_push_c(s, '"');
955 lo += s->s_len - lx;
956 *llp = lo;
957 NYD2_OU;
960 static struct n_string *
961 a_mailcap__dump_quote(struct n_string *s, char const *cp, boole quotequote){
962 char c;
963 NYD2_IN;
965 for(;;){
966 if((c = *cp++) == '\0'){
967 if((c = *cp++) == '\0')
968 break;
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);
975 NYD2_OU;
976 return s;
979 static char const *
980 a_mailcap_expand_formats(char const *format, struct mimepart const *mpp,
981 char const *ct){
982 struct n_string s_b, *s = &s_b;
983 char const *cp, *xp;
984 NYD2_IN;
986 s = n_string_creat_auto(s);
987 s = n_string_book(s, 128);
989 for(cp = format;;){
990 char c;
992 if((c = *cp++) == '\0'){
993 if((c = *cp++) == '\0')
994 break;
996 switch(c){
997 case '{':
998 /* C99 */{
999 char const *tmp;
1001 tmp = su_cs_find_c(cp, '}');
1002 ASSERT(tmp != NIL); /* (parser verified) */
1003 xp = savestrbuf(cp, P2UZ(tmp - cp));
1004 cp = ++tmp;
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'){
1011 if(c != '\'')
1012 s = n_string_push_c(s, c);
1013 else
1014 s = n_string_push_cp(s, "'\"'\"'");
1017 s = n_string_push_c(s, '\'');
1018 break;
1019 case '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 "}\"");
1024 break;
1025 case 't':
1026 s = n_string_push_cp(s, ct);
1027 break;
1029 }else
1030 s = n_string_push_c(s, c);
1033 cp = n_string_cp(s);
1034 n_string_drop_ownership(s);
1036 NYD2_OU;
1037 return cp;
1041 c_mailcap(void *vp){
1042 boole load_only;
1043 char **argv;
1044 NYD_IN;
1046 argv = vp;
1048 load_only = FAL0;
1049 if(*argv == NIL)
1050 goto jlist;
1051 if(argv[1] != NIL)
1052 goto jerr;
1053 if(su_cs_starts_with_case("show", *argv))
1054 goto jlist;
1056 load_only = TRU1;
1057 if(su_cs_starts_with_case("load", *argv))
1058 goto jlist;
1059 if(su_cs_starts_with_case("clear", *argv)){
1060 a_mailcap_gut(TRU1);
1061 goto jleave;
1064 jerr:
1065 mx_cmd_print_synopsis(mx_cmd_firstfit("mailcap"), NIL);
1066 vp = NIL;
1067 jleave:
1068 NYD_OU;
1069 return (vp == NIL ? n_EXIT_ERR : n_EXIT_OK);
1071 jlist:
1072 if(a_mailcap_dp == NIL)
1073 a_mailcap_create();
1075 if(!load_only){
1076 struct n_strlist *slp;
1078 slp = NIL;
1079 if(!(mx_xy_dump_dict("mailcap", a_mailcap_dp, &slp, NIL,
1080 &a_mailcap_dump) &&
1081 mx_page_or_print_strlist("mailcap", slp, TRU1)))
1082 vp = NIL;
1084 goto jleave;
1087 boole
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;
1091 boole wildcard;
1092 struct a_mailcap_hdl *mchp;
1093 NYD_IN;
1094 UNUSED(mpp);
1096 mchp = NIL;
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))
1104 goto jleave;
1106 if(a_mailcap_dp == NIL)
1107 a_mailcap_create();
1108 if(su_cs_dict_count(a_mailcap_dp) == 0)
1109 goto jleave;
1111 /* Walk over the list of handlers and check whether one fits */
1112 wildcard = FAL0;
1113 p.v = su_cs_dict_lookup(a_mailcap_dp, ct);
1114 jagain:
1115 for(; p.mch != NIL; p.mch = p.mch->mch_next){
1116 u32 f;
1118 f = p.mch->mch_flags;
1120 if(f & a_MAILCAP_F_IGNORE)
1121 continue;
1123 if(action == SEND_TODISP || action == SEND_TODISP_ALL){
1124 /*if(f & mx_MIMETYPE_HDL_ASYNC)
1125 * continue;*/
1126 if(!(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT))
1127 continue;
1128 }else if(action == SEND_QUOTE || action == SEND_QUOTE_ALL){
1129 if(f & mx_MIMETYPE_HDL_NOQUOTE)
1130 continue;
1131 /*if(f & mx_MIMETYPE_HDL_ASYNC)
1132 * continue;*/
1133 if(f & mx_MIMETYPE_HDL_NEEDSTERM) /* XXX for now */
1134 continue;
1135 if(!(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT)) /* xxx for now */
1136 continue;
1137 }else{
1138 /* `mimeview' */
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],
1155 mpp, ct);
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))
1167 continue;
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,
1188 mpp, ct);
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;
1198 mchp = p.mch;
1199 goto jleave;
1202 /* Direct match, otherwise try wildcard match? */
1203 if(!wildcard && (p.c = su_cs_find_c(ct, '/')) != NIL){
1204 char *cp;
1205 uz i;
1207 wildcard = TRU1;
1208 cp = n_lofi_alloc((i = P2UZ(p.c - ct)) + 2 +1);
1210 su_mem_copy(cp, ct, i);
1211 cp[i++] = '/';
1212 cp[i++] = '*';
1213 cp[i++] = '\0';
1214 p.v = su_cs_dict_lookup(a_mailcap_dp, cp);
1216 n_lofi_free(cp);
1218 if(p.v != NIL)
1219 goto jagain;
1222 jleave:
1223 NYD_OU;
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 */
1230 /* s-it-mode */