From 3316cfbc2200503353b0659a3dfb928283633b5d Mon Sep 17 00:00:00 2001 From: Christian Neukirchen Date: Mon, 1 Aug 2016 14:55:34 +0200 Subject: [PATCH] add msed --- Makefile | 3 +- README | 10 +- man/mintro.7 | 2 + msed.c | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 msed.c diff --git a/Makefile b/Makefile index c627112..2a2ecec 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS=-g -O1 -Wall -Wno-switch -Wextra -fstack-protector-strong -D_FORTIFY_SOURCE=2 -ALL = maddr mdeliver mdirs mflag mhdr minc mlist mmime mpick mscan mseq mshow msort mthread +ALL = maddr mdeliver mdirs mflag mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread all: $(ALL) @@ -14,6 +14,7 @@ mlist: mlist.o mmime: mmime.o mpick: mpick.o blaze822.o seq.o rfc2047.c mymemmem.o mscan: mscan.o blaze822.o seq.o rfc2047.o mymemmem.o +msed: msed.o blaze822.o seq.o mymemmem.o mseq: mseq.o seq.o mshow: mshow.o blaze822.o seq.o rfc2045.o rfc2047.c mymemmem.o filter.o msort: msort.o blaze822.o seq.o mystrverscmp.o mymemmem.o diff --git a/README b/README index dae51b4..054d7a1 100644 --- a/README +++ b/README @@ -24,6 +24,7 @@ DESCRIPTION mpick(1) to filter mail mrepl(1) to reply to mail mscan(1) to generate single line summaries of mail + msed(1) to manipulate mail headers mseq(1) to manipulate mail sequences mshow(1) to render mail and extract attachments msort(1) to sort mail @@ -52,10 +53,11 @@ PRINCIPLES nonconforming, messages. Santoku is written in portable C, using only POSIX functions (apart from - a tiny Linux-only optimization). It supports MIME and more than 7-bit - messages (everything the host iconv(3) can decode). It assumes you work - in a UTF-8 environment. Santoku works well together with other Unix mail - tools such as offlineimap(1), mairix(1), or mu(1). + a tiny Linux-only optimization), and has no external dependencies. It + supports MIME and more than 7-bit messages (everything the host iconv(3) + can decode). It assumes you work in a UTF-8 environment. Santoku works + well together with other Unix mail tools such as offlineimap(1), + mairix(1), or mu(1). EXAMPLES Santoku tools are designed to be composed together into a pipe. It is diff --git a/man/mintro.7 b/man/mintro.7 index 4152f0c..aeed142 100644 --- a/man/mintro.7 +++ b/man/mintro.7 @@ -46,6 +46,8 @@ to filter mail to reply to mail .It Xr mscan 1 to generate single line summaries of mail +.It Xr msed 1 +to manipulate mail headers .It Xr mseq 1 to manipulate mail sequences .It Xr mshow 1 diff --git a/msed.c b/msed.c new file mode 100644 index 0000000..43e0c35 --- /dev/null +++ b/msed.c @@ -0,0 +1,293 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "blaze822.h" + +static char *expr; + +char * +subst(char *str, char *srch, char *repl, char *flags) +{ + static char buf[4096]; + char *bufe = buf + sizeof buf; + + int iflag = !!strchr(flags, 'i'); + int gflag = !!strchr(flags, 'g'); + +#define APP(o,l) do {if(bufe-b<(ssize_t)l) return str; memcpy(b,str+i+o,l); b+=l;} while(0) +#define APPC(c) do {if(b>=bufe) return str; *b++=c;} while(0) + + regex_t srchrx; + regmatch_t pmatch[10]; + if (regcomp(&srchrx, srch, iflag ? REG_ICASE : 0) != 0) + return str; + + char *b = buf; + + regoff_t i = 0; + while (1) { + if (regexec(&srchrx, str+i, 9, pmatch, 0) != 0) + break; + + APP(0, pmatch[0].rm_so); + + char *t = repl; + while (*t) { + // & == \0 + if (*t == '&' || (*t == '\\' && isdigit(*(t+1)))) { + int n; + if (*t == '&') { + t++; + n = 0; + } else { + t++; + n = *t++ - '0'; + } + + APP(pmatch[n].rm_so, + pmatch[n].rm_eo - pmatch[n].rm_so); + } else if (*t == '\\' && *(t+1)) { + t++; + APPC(*t++); + } else { + APPC(*t++); + } + } + + i += pmatch[0].rm_eo; // advance to end of match + if (!gflag) + break; + } + + if (i > 0) { // any match? + APP(0, strlen(str + i)); + *b = 0; + return buf; + } + + return str; +} + +void +printhdr(char *hdr, int rest) +{ + int uc = 1; + + while (*hdr && *hdr != ':') { + putc(uc ? toupper(*hdr) : *hdr, stdout); + uc = (*hdr == '-'); + hdr++; + } + + if (rest) { + printf("%s\n", hdr); + } +} + +void +sed(char *file) +{ + struct message *msg = blaze822_file(file); + if (!msg) + return; + + char *h = 0; + while ((h = blaze822_next_header(msg, h))) { + regex_t headerrx; + char headersel[1024]; + + char *v = strchr(h, ':'); + if (!v) + continue; + v++; + while (*v && (*v == ' ' || *v == '\t')) + v++; + + v = strdup(v); + + char *e = expr; + while (*e) { + while (*e && + (*e == ' ' || *e == '\t' || *e == '\n' || *e == ';')) + e++; + + *headersel = 0; + if (*e == '/') { + e++; + char *s = e; + // parse_headers, sets headersel + while (*e && *e != '/') + e++; + snprintf(headersel, sizeof headersel, + "^(%.*s)*:", e-s, s); + for (s = headersel; *s && *(s+1); s++) + if (*s == ':') + *s = '|'; + regcomp(&headerrx, headersel, REG_EXTENDED); + if (*e) + e++; + } + + char sep; + char *s; + if (!*headersel || regexec(&headerrx, h, 0, 0, 0) == 0) { + switch (*e) { + case 'd': + free(v); + v = 0; + break; + case 'a': + // skipped here; + sep = *++e; + if (!sep) { + fprintf(stderr, "unterminated a command\n"); + exit(1); + } + while (*e && *e != sep) + e++; + break; + if (!(*e == ' ' || *e == ';' || *e == '\n' || !*e)) { + fprintf(stderr, "unterminated a command\n"); + exit(1); + } + + case 'c': + sep = *++e; + s = ++e; + while (*e && *e != sep) + e++; + free(v); + v = strndup(s, e-s); + break; + case 's': + sep = *++e; + s = ++e; + while (*e && *e != sep) + e++; + char *t = ++e; + while (*e && *e != sep) + e++; + char *u = ++e; + while (*e == 'i' || *e == 'g') + e++; + + if (!(*e == ' ' || *e == ';' || *e == '\n' || !*e)) { + fprintf(stderr, "unterminated s command\n"); + exit(1); + } + + // XXX stack allocate + char *from = strndup(s, t-s-1); + char *to = strndup(t, u-t-1); + char *flags = strndup(u, e-u); + + char *ov = v; + v = strdup(subst(ov, from, to, flags)); + free(ov); + + free(from); + free(to); + free(flags); + + break; + default: + fprintf(stderr, "unknown command: '%c'\n", *e); + exit(1); + } + } + while (*e && *e != ';') + e++; + } + if (v) { + printhdr(h, 0); + printf(": %s\n", v); + free(v); + } + } + + // loop, do all a// + + char *hs, *he; + char *e = expr; + while (*e) { + while (*e && + (*e == ' ' || *e == '\t' || *e == '\n' || *e == ';')) + e++; + + hs = he = 0; + if (*e == '/') { + e++; + hs = e; + // parse_headers, sets headersel + while (*e && *e != '/') + e++; + he = e; + if (*e) + e++; + } + + char sep; + char *s; + switch (*e) { + case 'a': + sep = *++e; + if (!sep) { + fprintf(stderr, "unterminated a command\n"); + exit(1); + } + + s = ++e; + while (*e && *e != sep) + e++; + if (he != hs) { + char *h = strndup(hs, he-hs); + char *v = strndup(s, e-s); + printhdr(h, 0); + printf(": %s\n", v); + } + break; + + case 'c': + case 'd': + case 's': + // ignore here; + break; + } + while (*e && *e != ';') + e++; + } + + printf("\n"); + fwrite(blaze822_body(msg), 1, blaze822_bodylen(msg), stdout); +} + +int +main(int argc, char *argv[]) +{ + int c; + while ((c = getopt(argc, argv, "")) != -1) + switch(c) { + default: + fprintf(stderr, "Usage: msed [expr] [msgs...]\n"); + exit(1); + } + + expr = argv[optind]; + optind++; + + if (argc == optind && isatty(0)) + blaze822_loop1(".", sed); + else + blaze822_loop(argc-optind, argv+optind, sed); + + return 0; +}