/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2003-2021 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see . */ /* MH mhl command */ #include /* *********************** Compiler for MHL formats *********************** */ struct mhl_ctx { mu_stream_t input; mu_linetrack_t trk; struct mu_locus_range loc; char *bufptr; size_t bufsize; size_t buflen; char *curptr; mu_list_t formlist; /* error stream state */ int errsaved; struct mu_locus_range errloc; int errmode; }; static int mhl_ctx_init (struct mhl_ctx *ctx, char const *filename) { int rc; rc = mu_file_stream_create (&ctx->input, filename, MU_STREAM_READ); if (rc) { mu_error (_("cannot open format file %s: %s"), filename, mu_strerror (rc)); return -1; } mu_linetrack_create (&ctx->trk, filename, 2); mu_locus_range_init (&ctx->loc); if ((rc = mu_list_create (&ctx->formlist)) != 0) { mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc); mu_stream_unref (ctx->input); return -1; } ctx->bufptr = ctx->curptr = NULL; ctx->bufsize = ctx->buflen = 0; if (mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE, &ctx->errloc) == 0) { if (mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_MODE, &ctx->errmode) == 0) { int mode = ctx->errmode | MU_LOGMODE_LOCUS; mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE, &mode); ctx->errsaved = 1; } } return 0; } static void mhl_ctx_deinit (struct mhl_ctx *ctx) { if (ctx->errsaved) { mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE, &ctx->errloc); mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE, &ctx->errmode); mu_locus_range_deinit (&ctx->errloc); } mu_stream_destroy (&ctx->input); mu_linetrack_destroy (&ctx->trk); mu_locus_range_deinit (&ctx->loc); free (ctx->bufptr); } static void mhl_ctx_advance (struct mhl_ctx *ctx, size_t len) { if (len) { mu_linetrack_advance (ctx->trk, &ctx->loc, ctx->curptr, len); ctx->curptr += len; if (ctx->errsaved) mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE, &ctx->loc); } } static inline void mhl_ctx_advance_to (struct mhl_ctx *ctx, char const *ptr) { mhl_ctx_advance (ctx, ptr - ctx->curptr); } static inline int mhl_ctx_leng (struct mhl_ctx *ctx) { return ctx->buflen - (ctx->curptr - ctx->bufptr); } static int mhl_ctx_getln (struct mhl_ctx *ctx) { int rc; char *p; if (ctx->buflen) { /* Advance to new line */ mu_linetrack_advance (ctx->trk, &ctx->loc, "\n", 1); if (ctx->errsaved) mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE, &ctx->loc); } rc = mu_stream_getline (ctx->input, &ctx->bufptr, &ctx->bufsize, &ctx->buflen); if (rc) { mu_error (_("error reading: %s"), mu_strerror (rc)); return -1; } ctx->curptr = ctx->bufptr; if (ctx->buflen == 0) return 1; p = mu_str_stripws (ctx->bufptr); if (!p) return 1; mhl_ctx_advance_to (ctx, p); return 0; } static inline int mhl_ctx_looking_at (struct mhl_ctx *ctx, char const *pat) { size_t len = strlen (pat); if (ctx->buflen < len) return 0; return memcmp (ctx->curptr, pat, len) == 0; } static inline int mhl_ctx_eol (struct mhl_ctx *ctx) { return ctx->curptr[0] == 0; } enum mhl_type { stmt_cleartext, stmt_component, stmt_variable }; enum mhl_datatype { dt_flag, dt_integer, dt_string, dt_format }; typedef union mhl_value { char *str; int num; mh_format_t fmt; } mhl_value_t; typedef struct mhl_variable { int id; char *name; int type; } mhl_variable_t; typedef struct mhl_stmt mhl_stmt_t; typedef struct mhl_stmt_variable mhl_stmt_variable_t; typedef struct mhl_stmt_component mhl_stmt_component_t; struct mhl_stmt_variable { mhl_variable_t *id; mhl_value_t value; }; struct mhl_stmt_component { char *name; mu_list_t format; }; struct mhl_stmt { enum mhl_type type; union { char *cleartext; mhl_stmt_variable_t variable; mhl_stmt_component_t component; } v; }; static mhl_variable_t *variable_lookup (char *name); static mhl_stmt_t * stmt_alloc (enum mhl_type type) { mhl_stmt_t *p = mu_alloc (sizeof (*p)); p->type = type; return p; } static int looking_at_compdecl (struct mhl_ctx *ctx, char **compname) { char *p; for (p = ctx->curptr; *p && !mu_isspace (*p); p++) { if (*p == ':') { int len = p - ctx->curptr; *compname = mu_alloc (len + 1); memcpy (*compname, ctx->curptr, len); (*compname)[len] = 0; mhl_ctx_advance_to (ctx, p + 1); return 0; } } return 1; } static void parse_variable (struct mhl_ctx *ctx, mu_list_t formlist); static void parse_cleartext (struct mhl_ctx *ctx) { mhl_stmt_t *stmt = stmt_alloc (stmt_cleartext); mhl_ctx_advance (ctx, 1); stmt->v.cleartext = mu_strdup (ctx->curptr); mu_rtrim_class (stmt->v.cleartext, MU_CTYPE_ENDLN); mu_list_append (ctx->formlist, stmt); } static void parse_component (struct mhl_ctx *ctx, char *compname) { int rc; mhl_stmt_t *stmt = stmt_alloc (stmt_component); stmt->v.component.name = compname; if ((rc = mu_list_create (&stmt->v.component.format)) != 0) { mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc); exit (1); /* FIXME */ } parse_variable (ctx, stmt->v.component.format); mu_list_append (ctx->formlist, stmt); } static void parse_variable (struct mhl_ctx *ctx, mu_list_t formlist) { struct mu_wordsplit ws; int wsflags; int expect_comma = 0; int rc; if (mhl_ctx_eol (ctx)) return; if (mhl_ctx_looking_at (ctx, "ignores=") && !mhl_ctx_looking_at (ctx, "ignores=\"")) { /* A hack to allow for traditional use of "ignores=", i.e. as a single statement on a line, without double-quotes around the argument */ wsflags = MU_WRDSF_NOCMD | MU_WRDSF_NOVAR | MU_WRDSF_NOSPLIT; } else { ws.ws_delim = ","; wsflags = MU_WRDSF_DEFFLAGS|MU_WRDSF_DELIM| MU_WRDSF_WS|MU_WRDSF_RETURN_DELIMS; } wsflags |= MU_WRDSF_INCREMENTAL; if (mu_wordsplit (ctx->curptr, &ws, wsflags)) { mu_error ("mu_wordsplit(%s): %s", ctx->curptr, mu_wordsplit_strerror (&ws)); exit (1); } expect_comma = 0; do { char *name = ws.ws_wordv[ws.ws_wordc - 1]; if (expect_comma) { if (strcmp (name, ",")) { mu_error (_("expected ',', but found \"%s\""), name); } mhl_ctx_advance (ctx, 1); } else { mhl_stmt_t *stmt; char *value = NULL; mhl_variable_t *var; mhl_ctx_advance (ctx, strlen (name)); value = strchr (name, '='); if (value) *value++ = 0; stmt = stmt_alloc (stmt_variable); var = variable_lookup (name); if (!var) { mu_error (_("unknown variable: %s"), name); exit (1); } if ((var->type == dt_flag && value) || (var->type != dt_flag && !value)) { mu_error (_("wrong datatype for %s"), var->name); exit (1); } switch (var->type) { case dt_string: stmt->v.variable.value.str = mu_strdup (value); break; case dt_integer: stmt->v.variable.value.num = strtoul (value, NULL, 0); break; case dt_format: { struct mu_locus_point pt = MU_LOCUS_POINT_INITIALIZER; mu_locus_point_copy (&pt, &ctx->loc.beg); /* Adjust locus FIXME: It assumes the value is quoted */ pt.mu_col += value - name + 1; if (mh_format_string_parse (&stmt->v.variable.value.fmt, value, &ctx->loc.beg, MH_FMT_PARSE_DEFAULT)) { exit (1); } mu_locus_point_deinit (&pt); } break; case dt_flag: stmt->v.variable.value.num = strcmp (var->name, name) == 0; break; } stmt->v.variable.id = var; mu_list_append (formlist, stmt); } expect_comma = !expect_comma; } while ((rc = mu_wordsplit (NULL, &ws, MU_WRDSF_INCREMENTAL)) == 0); mu_wordsplit_free (&ws); } static int parse_line (struct mhl_ctx *ctx) { char *compname; if (mhl_ctx_looking_at (ctx, ":")) parse_cleartext (ctx); else if (looking_at_compdecl (ctx, &compname) == 0) parse_component (ctx, compname); else parse_variable (ctx, ctx->formlist); return 0; } mu_list_t mhl_format_compile (char *name) { int rc; struct mhl_ctx ctx; mu_list_t formlist; rc = mhl_ctx_init (&ctx, name); if (rc) return NULL; while (mhl_ctx_getln (&ctx) == 0) { if (!mhl_ctx_looking_at (&ctx, ";")) parse_line (&ctx); } formlist = ctx.formlist; ctx.formlist = NULL; mhl_ctx_deinit (&ctx); return formlist; } /* ********************** Destroy compiled MHL format ********************** */ static void _destroy_value (enum mhl_datatype type, mhl_value_t *val) { switch (type) { case dt_string: free (val->str); break; case dt_flag: case dt_integer: break; case dt_format: mh_format_free (val->fmt); break; default: abort (); } } static int _destroy_stmt (void *item, void *data) { mhl_stmt_t *stmt = item; switch (stmt->type) { case stmt_cleartext: free (stmt->v.cleartext); break; case stmt_component: free (stmt->v.component.name); mhl_format_destroy (&stmt->v.component.format); break; case stmt_variable: _destroy_value (stmt->v.variable.id->type, &stmt->v.variable.value); break; default: abort (); } return 0; } void mhl_format_destroy (mu_list_t *fmt) { mu_list_foreach (*fmt, _destroy_stmt, NULL); mu_list_destroy (fmt); } /* *************************** Runtime functions *************************** */ /* Integer variables */ #define I_WIDTH 0 #define I_LENGTH 1 #define I_OFFSET 2 #define I_OVERFLOWOFFSET 3 #define I_COMPWIDTH 4 #define I_MAX 5 /* Boolean (flag) variables */ #define B_UPPERCASE 0 #define B_CLEARSCREEN 1 #define B_BELL 2 #define B_NOCOMPONENT 3 #define B_CENTER 4 #define B_LEFTADJUST 5 #define B_COMPRESS 6 #define B_SPLIT 7 #define B_NEWLINE 8 #define B_ADDRFIELD 9 #define B_DATEFIELD 10 #define B_DECODE 12 #define B_DISABLE_BODY 13 #define B_MAX 14 /* String variables */ #define S_OVERFLOWTEXT 0 #define S_COMPONENT 1 #define S_IGNORES 2 #define S_MAX 3 /* Format variables */ #define F_FORMATFIELD 0 #define F_MAX 1 static mhl_variable_t vartab[] = { /* Integer variables */ { I_WIDTH, "width", dt_integer }, { I_LENGTH, "length", dt_integer }, { I_OFFSET, "offset", dt_integer }, { I_OVERFLOWOFFSET, "overflowoffset", dt_integer }, { I_COMPWIDTH, "compwidth", dt_integer }, /* Boolean (flag) variables */ { B_UPPERCASE, "uppercase", dt_flag }, { B_CLEARSCREEN, "clearscreen", dt_flag }, { B_BELL, "bell", dt_flag }, { B_NOCOMPONENT, "nocomponent", dt_flag }, { B_CENTER, "center", dt_flag }, { B_LEFTADJUST, "leftadjust", dt_flag }, { B_COMPRESS, "compress", dt_flag }, { B_SPLIT, "split", dt_flag }, { B_NEWLINE, "newline", dt_flag }, { B_ADDRFIELD, "addrfield", dt_flag }, { B_DATEFIELD, "datefield", dt_flag }, { B_DECODE, "decode", dt_flag }, /* String variables */ { S_OVERFLOWTEXT, "overflowtext", dt_string }, { S_COMPONENT, "component", dt_string }, { S_IGNORES, "ignores", dt_string }, /* Format variables */ { F_FORMATFIELD, "formatfield", dt_format }, { 0 } }; static mhl_variable_t * variable_lookup (char *name) { mhl_variable_t *p; for (p = vartab; p->name; p++) { if (p->type == dt_flag && memcmp (name, "no", 2) == 0 && strcmp (p->name, name + 2) == 0) return p; if (strcmp (p->name, name) == 0) return p; } return NULL; } struct eval_env { mu_message_t msg; mu_stream_t output; mu_list_t printed_fields; /* A list of printed header names */ int pos; int nlines; int ivar[I_MAX]; int bvar[B_MAX]; char *svar[S_MAX]; mh_format_t fvar[F_MAX]; char *prefix; }; static int eval_stmt (void *item, void *data); static void newline (struct eval_env *env); static void goto_offset (struct eval_env *env, int count); static void print (struct eval_env *env, char *str, int nloff); static int _comp_name (void const *item, void const *date) { return mu_c_strcasecmp (item, date); } int header_is_printed (struct eval_env *env, const char *name) { return mu_list_locate (env->printed_fields, (void*) name, NULL) == 0; } int want_header (struct eval_env *env, const char *name) { const char *p, *str; for (str = env->svar[S_IGNORES], p = name; *str; str++) { if (p) { if (*p == 0 && *str == ',') break; if (mu_tolower (*p) == mu_tolower (*str)) p++; else p = NULL; } else if (*str == ',') p = name; } if (p && *p == 0) return 0; return 1; } static int eval_var (struct eval_env *env, mhl_stmt_variable_t *var) { switch (var->id->type) { case dt_flag: env->bvar[var->id->id] = var->value.num; break; case dt_integer: env->ivar[var->id->id] = var->value.num; break; case dt_string: env->svar[var->id->id] = var->value.str; break; case dt_format: env->fvar[var->id->id] = var->value.fmt; break; default: abort (); } return 0; } static void ovf_print (struct eval_env *env, char *str, int size, int nloff) { int ovf = 0; while (size) { int len = size; if (ovf) { goto_offset (env, env->ivar[I_OVERFLOWOFFSET]); if (env->svar[S_OVERFLOWTEXT]) { int l = strlen (env->svar[S_OVERFLOWTEXT]); mu_stream_write (env->output, env->svar[S_OVERFLOWTEXT], l, NULL); env->pos += l; } } else { if (env->prefix && !env->bvar[B_NOCOMPONENT]) { goto_offset (env, env->ivar[I_OFFSET]); mu_stream_write (env->output, env->prefix, strlen (env->prefix), NULL); env->pos += strlen (env->prefix); } goto_offset (env, nloff); } if (env->pos + len > env->ivar[I_WIDTH]) { ovf = 1; len = env->ivar[I_WIDTH] - env->pos; } mu_stream_write (env->output, str, len, NULL); env->pos += len; if (env->pos >= env->ivar[I_WIDTH]) newline (env); str += len; size -= len; } } static void print (struct eval_env *env, char *str, int nloff) { do { if (*str == '\n') { newline (env); str++; } else if (*str) { size_t size = strcspn (str, "\n"); ovf_print (env, str, size, nloff); str += size; if (*str == '\n') { newline (env); str = mu_str_skip_class (str + 1, MU_CTYPE_SPACE); } } } while (*str); } static void newline (struct eval_env *env) { mu_stream_write (env->output, "\n", 1, NULL); env->pos = 0; if (env->ivar[I_LENGTH] && ++env->nlines >= env->ivar[I_LENGTH]) { /* FIXME: Better to write it directly on the terminal */ if (env->bvar[B_BELL]) mu_stream_write (env->output, "\a", 1, NULL); if (env->bvar[B_CLEARSCREEN]) mu_stream_write (env->output, "\f", 1, NULL); env->nlines = 0; } } static void goto_offset (struct eval_env *env, int count) { for (; env->pos < count; env->pos++) mu_stream_write (env->output, " ", 1, NULL); } int print_header_value (struct eval_env *env, char *val) { char *p; if (env->fvar[F_FORMATFIELD]) { if (mh_format_str (env->fvar[F_FORMATFIELD], val, env->ivar[I_WIDTH], &p) == 0) val = p; } if (env->bvar[B_DECODE]) { if (mh_decode_2047 (val, &p) == 0) val = p; } if (env->bvar[B_UPPERCASE]) { for (p = val; *p; p++) *p = mu_toupper (*p); } if (env->bvar[B_COMPRESS]) for (p = val; *p; p++) if (*p == '\n') *p = ' '; if (env->bvar[B_LEFTADJUST]) { for (p = val; *p && mu_isspace (*p); p++) ; } else p = val; print (env, p, env->ivar[I_COMPWIDTH]); return 0; } void print_component_name (struct eval_env *env) { if (!env->bvar[B_NOCOMPONENT]) { print (env, env->svar[S_COMPONENT], 0); if (mu_c_strcasecmp (env->svar[S_COMPONENT], "body")) print (env, ": ", 0); } } int eval_component (struct eval_env *env, char *name) { mu_header_t hdr; char *val; mu_message_get_header (env->msg, &hdr); if (mu_header_aget_value (hdr, name, &val)) return 0; print_component_name (env); mu_list_append (env->printed_fields, name); print_header_value (env, val); free (val); if (env->bvar[B_NEWLINE]) newline (env); return 0; } int eval_body (struct eval_env *env) { mu_stream_t input = NULL; mu_stream_t dstr = NULL; char buf[128]; /* FIXME: Fixed size. Bad */ size_t n; mu_body_t body = NULL; int nl; if (env->bvar[B_DISABLE_BODY]) return 0; env->prefix = env->svar[S_COMPONENT]; mu_message_get_body (env->msg, &body); mu_body_get_streamref (body, &input); if (env->bvar[B_DECODE]) { mu_header_t hdr; char *encoding = NULL; mu_message_get_header (env->msg, &hdr); mu_header_aget_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, &encoding); if (encoding) { int rc = mu_filter_create (&dstr, input, encoding, MU_FILTER_DECODE, MU_STREAM_READ); if (rc == 0) input = dstr; free (encoding); } } while (mu_stream_readline (input, buf, sizeof buf, &n) == 0 && n > 0) { goto_offset (env, env->ivar[I_OFFSET]); print (env, buf, 0); nl = buf[n-1] == '\n'; } mu_stream_destroy (&input); if (!nl && env->bvar[B_NEWLINE]) newline (env); return 0; } int eval_extras (struct eval_env *env) { mu_header_t hdr; size_t i, num; char *str; print_component_name (env); mu_message_get_header (env->msg, &hdr); mu_header_get_field_count (hdr, &num); for (i = 1; i <= num; i++) { if (mu_header_aget_field_name (hdr, i, &str)) break; if (want_header (env, str) && !header_is_printed (env, str)) { goto_offset (env, env->ivar[I_OFFSET]); print (env, str, 0); print (env, ": ", 0); free (str); mu_header_aget_field_value (hdr, i, &str); print_header_value (env, str); if (i < num && env->bvar[B_NEWLINE]) newline (env); } free (str); } if (env->bvar[B_NEWLINE]) newline (env); return 0; } int eval_comp (struct eval_env *env, char *compname, mu_list_t format) { struct eval_env lenv = *env; mu_list_foreach (format, eval_stmt, &lenv); goto_offset (&lenv, lenv.ivar[I_OFFSET]); if (!lenv.svar[S_COMPONENT]) lenv.svar[S_COMPONENT] = compname; if (strcmp (compname, "extras") == 0) eval_extras (&lenv); else if (strcmp (compname, "body") == 0) eval_body (&lenv); else eval_component (&lenv, compname); env->pos = lenv.pos; env->nlines = lenv.nlines; return 0; } static int eval_stmt (void *item, void *data) { mhl_stmt_t *stmt = item; struct eval_env *env = data; switch (stmt->type) { case stmt_cleartext: print (env, stmt->v.cleartext, 0); newline (env); break; case stmt_component: eval_comp (env, stmt->v.component.name, stmt->v.component.format); break; case stmt_variable: eval_var (env, &stmt->v.variable); break; default: abort (); } return 0; } int mhl_format_run (mu_list_t fmt, int width, int length, int flags, mu_message_t msg, mu_stream_t output) { int rc; struct eval_env env; /* Initialize the environment */ memset (&env, 0, sizeof (env)); env.bvar[B_NEWLINE] = 1; mu_list_create (&env.printed_fields); mu_list_set_comparator (env.printed_fields, _comp_name); env.ivar[I_WIDTH] = width ? width : mh_width (); env.ivar[I_LENGTH] = length; env.bvar[B_CLEARSCREEN] = flags & MHL_CLEARSCREEN; env.bvar[B_BELL] = flags & MHL_BELL; env.bvar[B_DECODE] = flags & MHL_DECODE; env.bvar[B_DISABLE_BODY] = flags & MHL_DISABLE_BODY; env.pos = 0; env.nlines = 0; env.msg = msg; env.output = output; rc = mu_list_foreach (fmt, eval_stmt, &env); mu_list_destroy (&env.printed_fields); return rc; }