about summary refs log tree commit diff
diff options
context:
space:
mode:
-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;
+}