From 4c592759a29702b8daa9e92028a5ecef95b9a393 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Mon, 17 Jul 2017 14:18:25 +0200 Subject: [PATCH 01/12] mseq: use first message of range for -c and -C. --- mseq.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mseq.c b/mseq.c index 1b27104..efe8ec0 100644 --- a/mseq.c +++ b/mseq.c @@ -251,6 +251,9 @@ stdinmode() void overridecur(char *file) { + static int once = 0; + if (once++) + return; while (*file == ' ') file++; setenv("MAILDOT", file, 1); @@ -259,6 +262,9 @@ overridecur(char *file) void setcur(char *file) { + static int once = 0; + if (once++) + return; while (*file == ' ') file++; unsetenv("MAILDOT"); From c6c8fb8d7cde685c0bd1d69e28261e05ca33b54f Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Mon, 17 Jul 2017 16:26:47 +0200 Subject: [PATCH 02/12] add NEWS.md --- NEWS.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 NEWS.md diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..cec68bf --- /dev/null +++ b/NEWS.md @@ -0,0 +1,17 @@ +## 0.2 (2017-07-17) + +* New sequence syntax `m:+n` for `n` messages after message `m`. +* Threading shortcuts `=`, `_`, `^` for `.=`, `._`, `.^`. +* Sequence related errors are now reported. +* minc and mlist normalize slashes in paths. +* mfwd now generates conforming message/rfc822 parts. +* mthread can add optional folders (e.g. your outbox) to resolve message ids. +* mcom now adds Date: just before sending or cancelling the mail. +* VIOLATIONS.md documents how mblaze works with certain common mistakes. +* Full documentation revamp by Larry Hynes. +* Fix rare crash looking for mail body. +* Numerous small bug and portability fixes. + +## 0.1 (2017-06-24) + +* Initial release From 1382543ae8c2db437e7c8c69c3e45bafc4d9eba3 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Mon, 17 Jul 2017 16:29:49 +0200 Subject: [PATCH 03/12] VERSION: 0.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 49d5957..3b04cfb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1 +0.2 From 9387d1fe07fc34c3015d4bcda466e93490ec3868 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Wed, 26 Jul 2017 17:23:01 +0200 Subject: [PATCH 04/12] add mflow --- GNUmakefile | 8 +-- README | 1 + man/mblaze.7 | 2 + man/mflow.1 | 55 +++++++++++++++ mflow.c | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 man/mflow.1 create mode 100644 mflow.c diff --git a/GNUmakefile b/GNUmakefile index 5f1d6a6..9430a18 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -17,18 +17,18 @@ PREFIX=/usr/local BINDIR=$(PREFIX)/bin MANDIR=$(PREFIX)/share/man -ALL = maddr magrep mdate mdeliver mdirs mexport mflag mgenmid mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread +ALL = maddr magrep mdate mdeliver mdirs mexport mflag mflow mgenmid mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread SCRIPT = mcolor mcom mless mmkdir mquote museragent all: $(ALL) museragent $(ALL) : % : %.o -maddr magrep mdeliver mexport mflag mgenmid mhdr mpick mscan msed mshow \ +maddr magrep mdeliver mexport mflag mflow mgenmid mhdr mpick mscan msed mshow \ msort mthread : blaze822.o mymemmem.o mytimegm.o maddr magrep mexport mflag mgenmid mhdr mlist mpick mscan msed mseq mshow msort \ mthread : seq.o slurp.o -maddr magrep mhdr mpick mscan mshow : rfc2047.o -magrep mshow : rfc2045.o +maddr magrep mflow mhdr mpick mscan mshow : rfc2047.o +magrep mflow mshow : rfc2045.o mshow : filter.o safe_u8putstr.o rfc2231.o pipeto.o mscan : pipeto.o msort : mystrverscmp.o diff --git a/README b/README index b74dc45..d228edb 100644 --- a/README +++ b/README @@ -18,6 +18,7 @@ DESCRIPTION mdirs(1) find Maildir folders mexport(1) export Maildir folders as mailboxes mflag(1) change flags (marks) of mail + mflow(1) reflow format=flowed plain text mails mfwd(1) forward mail mgenmid(1) generate Message-IDs mhdr(1) extract mail headers diff --git a/man/mblaze.7 b/man/mblaze.7 index 13b37d0..d843968 100644 --- a/man/mblaze.7 +++ b/man/mblaze.7 @@ -30,6 +30,8 @@ find Maildir folders export Maildir folders as mailboxes .It Xr mflag 1 change flags (marks) of mail +.It Xr mflow 1 +reflow format=flowed plain text mails .It Xr mfwd 1 forward mail .It Xr mgenmid 1 diff --git a/man/mflow.1 b/man/mflow.1 new file mode 100644 index 0000000..63a2a23 --- /dev/null +++ b/man/mflow.1 @@ -0,0 +1,55 @@ +.Dd July 26, 2017 +.Dt MFLOW 1 +.Os +.Sh NAME +.Nm mflow +.Nd reflow format=flowed plain text mails +.Sh SYNOPSIS +.Nm +\&< +.Ar file +.Sh DESCRIPTION +.Nm +reformats the standard input according to the rules +of RFC 3676. +.Ev PIPE_CONTENTTYPE +is inspected, making this a suitable filter +for +.Sq text/plain +messages for +.Xr mshow 1 . +Mails not using +.Sq format=flowed +are output as is. +.Pp +Text is reflowed (where allowed) to +fit the width given in the environment variable +.Ev COLUMNS , +the terminal width, or 80 characters by default. +.Pp +If defined, +the environment variable +.Ev MAXCOLUMNS +specifies the maximum line length. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr mshow 1 +.Rs +.%A R. Gellens +.%D February 2004 +.%R RFC 3676 +.%T The Text/Plain Format and DelSp Parameters +.Re +.Sh AUTHORS +.An Leah Neukirchen Aq Mt leah@vuxu.org +.Sh LICENSE +.Nm +is in the public domain. +.Pp +To the extent possible under law, +the creator of this work +has waived all copyright and related or +neighboring rights to this work. +.Pp +.Lk http://creativecommons.org/publicdomain/zero/1.0/ diff --git a/mflow.c b/mflow.c new file mode 100644 index 0000000..56af515 --- /dev/null +++ b/mflow.c @@ -0,0 +1,187 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "blaze822.h" + +int column = 0; +int maxcolumn = 80; + +void +chgquote(int quotes) +{ + static int oquotes; + + if (quotes != oquotes) { + if (column) + putchar('\n'); + column = 0; + oquotes = quotes; + } +} + +void +fixed(int quotes, char *line, size_t linelen) +{ + chgquote(quotes); + + if (linelen > (size_t)(maxcolumn - column)) { + putchar('\n'); + column = 0; + } + + if (column == 0) { + for (; column < quotes; column++) + putchar('>'); + if (quotes) + putchar(' '); + } + + fwrite(line, 1, linelen, stdout); + putchar('\n'); + column = 0; +} + +void +flowed(int quotes, char *line, ssize_t linelen) +{ + chgquote(quotes); + int done = 0; + + while (!done) { + if (column == 0) { + for (; column < quotes; column++) + putchar('>'); + column++; + if (quotes) + putchar(' '); + } + + char *eow; + if (*line == ' ') + eow = memchr(line + 1, ' ', linelen - 1); + else + eow = memchr(line, ' ', linelen); + + if (!eow) { + eow = line + linelen; + done = 1; + } + + if (column + (eow - line) > maxcolumn) { + putchar('\n'); + column = 0; + if (*line == ' ') { + line++; + linelen--; + } + } else { + fwrite(line, 1, eow - line, stdout); + column += eow - line; + linelen -= eow - line; + line = eow; + } + } +} + +int +main() +{ + char *linebuf = 0; + char *line; + size_t linelen = 0; + int quotes = 0; + + int reflow = 1; // re-evaluated on $PIPE_CONTENTTYPE + int delsp = 0; + + char *ct = getenv("PIPE_CONTENTTYPE"); + if (ct) { + char *s, *se; + blaze822_mime_parameter(ct, "format", &s, &se); + reflow = s && (strncasecmp(s, "flowed", 6) == 0); + blaze822_mime_parameter(ct, "delsp", &s, &se); + delsp = s && (strncasecmp(s, "yes", 3) == 0); + } + + char *cols = getenv("COLUMNS"); + if (cols && isdigit(*cols)) { + maxcolumn = atoi(cols); + } else { + struct winsize w; + int fd = open("/dev/tty", O_RDONLY | O_NOCTTY); + if (fd >= 0) { + if (ioctl(fd, TIOCGWINSZ, &w) == 0) + maxcolumn = w.ws_col; + close(fd); + } + } + + char *maxcols = getenv("MAXCOLUMNS"); + if (maxcols && isdigit(*maxcols)) { + int m = atoi(maxcols); + if (maxcolumn > m) + maxcolumn = m; + } + + while (1) { + errno = 0; + ssize_t rd = getdelim(&linebuf, &linelen, '\n', stdin); + if (rd == -1) { + if (errno == 0) + break; + fprintf(stderr, "mflow: error reading: %s\n", + strerror(errno)); + exit(1); + } + + line = linebuf; + + if (!reflow) { + fwrite(line, 1, rd, stdout); + continue; + } + + if (rd > 0 && line[rd-1] == '\n') + line[--rd] = 0; + if (rd > 0 && line[rd-1] == '\r') + line[--rd] = 0; + + quotes = 0; + while (*line == '>') { // measure quote depth + line++; + quotes++; + rd--; + } + + if (*line == ' ') { // space stuffing + line++; + rd--; + } + + if (strcmp(line, "-- ") == 0) { // usenet signature convention + if (column) + fixed(quotes, "", 0); // flush paragraph + fixed(quotes, line, rd); + continue; + } + + if (rd > 0 && line[rd-1] == ' ') { // flowed line + if (delsp) + line[--rd] = 0; + flowed(quotes, line, rd); + } else { + fixed(quotes, line, rd); + } + } + + if (reflow && column != 0) + putchar('\n'); +} From 3b282a4934d3a5c8aacd008279dd4e24d582e44e Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Wed, 26 Jul 2017 17:32:22 +0200 Subject: [PATCH 05/12] filter.example: use mflow --- filter.example | 1 + 1 file changed, 1 insertion(+) diff --git a/filter.example b/filter.example index c1804ac..1cdd524 100644 --- a/filter.example +++ b/filter.example @@ -1,3 +1,4 @@ +text/plain: mflow text/html: lynx -dump -stdin -nomargins ${PIPE_CHARSET:+-assume_charset $PIPE_CHARSET} application/pdf: pdftotext - - | par application: file -b - From 7fd982d172423a47f7310e250b30987f2ac7ec49 Mon Sep 17 00:00:00 2001 From: Larry Hynes Date: Thu, 27 Jul 2017 10:40:17 +0100 Subject: [PATCH 06/12] mcom: dreaft -> draft Closes: #69 [via git-merge-pr] --- mcom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcom b/mcom index 15bba03..6fe2b84 100755 --- a/mcom +++ b/mcom @@ -51,7 +51,7 @@ case "$0" in shift resume=1 if [ "$#" -gt 0 ]; then - echo "used dreaft $1" + echo "used draft $1" draft="$1" shift fi From 3b51c5938a82f831c5f734630fdebb165f747ced Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sun, 30 Jul 2017 16:44:16 +0200 Subject: [PATCH 07/12] rfc2047: blaze822_decode_b64: ensure nul-termination of result Fixes #70. --- rfc2047.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfc2047.c b/rfc2047.c index d0bd103..5f5c2f5 100644 --- a/rfc2047.c +++ b/rfc2047.c @@ -75,7 +75,7 @@ blaze822_decode_b64(char *s, char *e, char **deco, size_t *decleno) 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 }; - char *buf = malloc((e - s) / 4 * 3); + char *buf = malloc((e - s) / 4 * 3 + 1); if (!buf) return 0; @@ -118,6 +118,8 @@ error: if (c3 != '=') *buf++ = d2; } + *buf = 0; + *decleno = buf - *deco; return 1; } From 05e4d5b5fa66e4fbf4ffd7f5a950ff00312b5d42 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sat, 5 Aug 2017 17:46:25 +0200 Subject: [PATCH 08/12] mshow: fallback to text/plain when no content-type exists --- mshow.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mshow.c b/mshow.c index 8d4ae24..dc890fa 100644 --- a/mshow.c +++ b/mshow.c @@ -28,6 +28,8 @@ static char *hflag = defaulthflags; static char *xflag; static char *Oflag; +static char fallback_ct[] = "text/plain"; + struct message *filters; static int mimecount; @@ -181,7 +183,7 @@ render_mime(int depth, struct message *msg, char *body, size_t bodylen) { char *ct = blaze822_hdr(msg, "content-type"); if (!ct) - ct = "text/x-unknown"; + ct = fallback_ct; char *mt = mimetype(ct); char *tlmt = tlmimetype(ct); char *filename = mime_filename(msg); @@ -309,7 +311,7 @@ choose_alternative(struct message *msg, int depth) m++; char *ict = blaze822_hdr(imsg, "content-type"); if (!ict) - ict = "text/x-unknown"; + ict = fallback_ct; char *imt = mimetype(ict); char *s = strstr(Aflag, imt); @@ -366,7 +368,7 @@ list_mime(int depth, struct message *msg, char *body, size_t bodylen) char *ct = blaze822_hdr(msg, "content-type"); if (!ct) - ct = "text/x-unknown"; + ct = fallback_ct; char *mt = mimetype(ct); char *filename = mime_filename(msg); From 470d7f85a0aebe2e3bb6ef18006977fb2dfc379b Mon Sep 17 00:00:00 2001 From: Larry Hynes Date: Sat, 5 Aug 2017 13:35:18 +0100 Subject: [PATCH 09/12] mverify: Cant - > cannot - This works around the quoting problem inherent in using Can't and avoids using the incorrect Cant Closes: #71 [via git-merge-pr] --- contrib/mverify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/mverify b/contrib/mverify index 2bca940..740ce83 100755 --- a/contrib/mverify +++ b/contrib/mverify @@ -26,7 +26,7 @@ END { exit(system("mshow -r -O " q(msg) " " q(signed) \ " | openssl smime -verify")) } else { - print("Cant verify signatures of type " type ".") + print("Cannot verify signatures of type " type ".") exit(2) } } From 8a9825596b0cff53d3cf1475ad540f23273442bb Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sat, 5 Aug 2017 20:13:16 +0200 Subject: [PATCH 10/12] mverify: use 0+x to force number +x does not work in nawk/*BSD awk. --- contrib/mverify | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/mverify b/contrib/mverify index 740ce83..0edf234 100755 --- a/contrib/mverify +++ b/contrib/mverify @@ -8,9 +8,9 @@ mshow -t "$1" | awk -v "msg=$1" ' { match($0, "^ *"); indent = RLENGTH } $2 == "text/plain" { plain++ } -$2 == "multipart/signed" { signed = +$1; si = indent; next } -signed && !content && indent == si+2 { content = +$1; next } -signed && content && !signature && indent == si+2 { signature = +$1; type = $2 } +$2 == "multipart/signed" { signed = 0+$1; si = indent; next } +signed && !content && indent == si+2 { content = 0+$1; next } +signed && content && !signature && indent == si+2 { signature = 0+$1; type = $2 } function q(a) { gsub("\\47", "\47\\\47\47", a); return "\47"a"\47" } END { if (type == "" && plain) { // guess plain text armored signature From 6b5b42f5836388d467ab0c2adcca58bb2a6fb0f3 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sun, 6 Aug 2017 18:53:51 +0200 Subject: [PATCH 11/12] filter: don't write more than PIPE_BUF at once This could result in stalling during big writes when we can't read the output of the child in the mean time. --- filter.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/filter.c b/filter.c index 111b606..c0b1541 100644 --- a/filter.c +++ b/filter.c @@ -1,9 +1,11 @@ +#include + +#include #include #include #include #include #include -#include #include #include @@ -83,7 +85,8 @@ filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno) } if (fds[1].revents & POLLOUT) { - ssize_t ret = write(fds[1].fd, input, inlen); + ssize_t ret = write(fds[1].fd, input, + inlen > PIPE_BUF ? PIPE_BUF : inlen); if (ret > 0) { input += ret; inlen -= ret; From 13db67713f4f2f4dde8c8dfbf7b2432560ae9230 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sun, 6 Aug 2017 20:05:37 +0200 Subject: [PATCH 12/12] filter: use a non-blocking write pipe --- filter.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/filter.c b/filter.c index c0b1541..7b86106 100644 --- a/filter.c +++ b/filter.c @@ -1,5 +1,7 @@ #include +#include +#include #include #include #include @@ -34,6 +36,10 @@ filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno) if (pipe(pipe0) != 0 || pipe(pipe1) != 0) goto fail; + int got = fcntl(pipe0[1], F_GETFL); + if (got > 0) + fcntl(pipe0[1], F_SETFL, got | O_NONBLOCK); + char *argv[] = { "/bin/sh", "-c", cmd, (char *)0 }; if (!(pid = fork())) { @@ -85,13 +91,14 @@ filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno) } if (fds[1].revents & POLLOUT) { - ssize_t ret = write(fds[1].fd, input, - inlen > PIPE_BUF ? PIPE_BUF : inlen); + ssize_t ret = write(fds[1].fd, input, inlen); if (ret > 0) { input += ret; inlen -= ret; } - if (ret <= 0 || inlen == 0) + if (ret <= 0 && errno == EAGAIN) { + /* ignore */ + } else if (ret <= 0 || inlen == 0) close(fds[1].fd); } else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) { fds[1].fd = -1;