aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Neukirchen <chneukirchen@gmail.com>2016-08-01 14:55:34 +0200
committerChristian Neukirchen <chneukirchen@gmail.com>2016-08-01 14:55:34 +0200
commit3316cfbc2200503353b0659a3dfb928283633b5d (patch)
tree07a64eaa484bb3c46e69778abd27c1d8fd8ea05d
parent0ce958a22c82d45b4b0ce77dbcac2fd6f6822965 (diff)
downloadmblaze-3316cfbc2200503353b0659a3dfb928283633b5d.tar.gz
add msed
-rw-r--r--Makefile3
-rw-r--r--README10
-rw-r--r--man/mintro.72
-rw-r--r--msed.c293
4 files changed, 303 insertions, 5 deletions
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 <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
+#include <unistd.h>
+
+#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;
+}