nmh-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Nmh-commits] [SCM] The nmh Mail Handling System branch, master, updated


From: Ken Hornstein
Subject: [Nmh-commits] [SCM] The nmh Mail Handling System branch, master, updated. 523cfc19212197befa73fc59460e93c563b76c9b
Date: Mon, 02 Apr 2012 19:39:26 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The nmh Mail Handling System".

The branch, master has been updated
       via  523cfc19212197befa73fc59460e93c563b76c9b (commit)
       via  f74055c90a34cd03c8d6c444aa2d96ddf8d3c746 (commit)
      from  de3930eed91e934f7842beb8932153ca7e568047 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.savannah.gnu.org/cgit/nmh.git/commit/?id=523cfc19212197befa73fc59460e93c563b76c9b


commit 523cfc19212197befa73fc59460e93c563b76c9b
Author: Ken Hornstein <address@hidden>
Date:   Mon Apr 2 15:39:00 2012 -0400

    Bring in replyfilter to the docs directory.

diff --git a/Makefile.am b/Makefile.am
index d7972a0..dbb5ec8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -168,7 +168,7 @@ dist_doc_DATA = COPYRIGHT VERSION docs/COMPLETION-BASH 
docs/COMPLETION-TCSH \
                docs/MAIL.FILTERING docs/MAILING-LISTS docs/README-ATTACHMENTS \
                docs/README-HOOKS docs/README-components docs/README.about \
                docs/README.SASL docs/README.developers docs/README.manpages \
-               docs/TODO
+               docs/TODO docs/replyfilter
 
 ##
 ## Our man pages
diff --git a/docs/pending-release-notes b/docs/pending-release-notes
index e91c7bf..a80ce41 100644
--- a/docs/pending-release-notes
+++ b/docs/pending-release-notes
@@ -60,3 +60,5 @@ Things to add to the release notes for the next full release:
 - Added support for readline editing and command/filename completion at
   the WhatNow? prompt
 - The hostable option of mts.conf is no longer needed and has been removed.
+- A sample mhl filter for handling MIME content inside of mhl has been
+  placed in the doc directory as "replyfilter".
diff --git a/docs/replyfilter b/docs/replyfilter
new file mode 100755
index 0000000..de81f87
--- /dev/null
+++ b/docs/replyfilter
@@ -0,0 +1,637 @@
+#!/usr/bin/perl
+#
+# replyfilter - A reply filter for nmh
+#
+# The idea behind this program is that it will act as a format filter
+# for nmh.  It will try to extract out all text/plain parts and format
+# them if necessary using a filter program.
+#
+# To use this program, configure nmh in the following way (nmh 1.5 or later):
+#
+# - Put the path to this program in your .mh_profile under formatproc:
+# 
+#   formatproc: replyfilter
+#
+# - Create a mhl reply filter that consists of the following line:
+#
+#   
body:nocomponent,format,nowrap,formatarg="%(trim{content-type})%(putstr)",formatarg="%(trim{content-transfer-encoding})%(putstr)",formatarg=">"
+#
+#   To decode this a bit:
+#
+#   body       - Output the "body" component
+#   nocomponent - Don't output a component prefix (normally here we use a
+#                component prefix of ">" as a quote character, but we're
+#                going to have replyfilter do that).
+#   nowrap     - Don't wrap lines if they exceed the column width
+#   formatarg   - Arguments to fmtproc.  The first argument is the value of
+#                the Content-type header; the second is the value of the
+#                Content-Transfer-Encoding header.  The last "formatarg"
+#                is used as your quoting prefix.  Replace it with whatever
+#                you want.
+#
+
+use Mail::Field;
+use MIME::Head;
+use MIME::QuotedPrint;
+use MIME::Base64;
+use Encode;
+
+#
+# The program we use to format "long" text
+#
+
+$filterprogram = 'par';
+
+#
+# Our output character set.  This script assumes a UTF-8 locale, but if you
+# want to run under a different locale the change it here.
+#
+
+$outcharset = 'utf-8';
+
+#
+# Maximum column width (used by the HTML converter and to decide if we need
+# to invoke the filter program
+#
+
+$maxcolwidth = 78;
+
+#
+# Out HTML converter program & arguments
+#
+
address@hidden = ('w3m', '-dump', '-cols', $maxcolwidth - 2, '-T', 'text/html',
+            '-O', $outcharset);
+
+
+die "Usage: $0 Content-type content-transfer-encoding quote-prefix\n"
+                               if $#ARGV != 2;
+
+if ($ARGV[0] ne "") {
+       $content_type = Mail::Field->new('Content-Type', $ARGV[0]);
+}
+
+$encoding = $ARGV[1] eq "" ? '7bit' : lc($ARGV[1]);
+$quoteprefix = $ARGV[2];
+
+#
+# Set up our output to be in our character set
+#
+
+binmode(STDOUT, ":encoding($outcharset)");
+
+#
+# The simplest case: if we have a single type of text/plain, send it
+# to our format subroutine.
+#
+
+if ($ARGV[0] eq "" || $content_type->type eq 'text/plain') {
+       process_text(\*STDIN, $encoding, $content_type->charset);
+       exit 0;
+}
+
+#
+# Alright, here's what we need to do.
+#
+# Find any text/plain parts and decode them.  Decode them via base64 or
+# quoted-printable, and feed them to our formatting filter when appropriate.
+# Put markers in the output for other content types.
+#
+
+($type) = (split('/', $content_type->type));
+
+if ($type eq 'multipart') {
+
+       #
+       # For multipart messages we have to do a little extra.
+       # Eat the MIME prologue (everything up until the first boundary)
+       #
+
+       $boundary = $content_type->boundary;
+
+       if ($boundary eq '') {
+               print "No boundary in Content-Type header!\n";
+               eat_part(\*STDIN);
+               exit 1;
+       }
+
+       while (<STDIN>) {
+               last if match_boundary($_, $boundary);
+       }
+
+       if (eof(STDIN)) {
+               print "Unable to find boundary in message\n";
+               exit 1;
+       }
+} else {
+       undef $boundary;
+}
+
+process_part(\*STDIN, $content_type->type, $encoding, $content_type->charset,
+            $boundary);
+
+if ($boundary) {
+       #
+       # Eat the MIME eplilog
+       #
+       eat_part(\*STDIN);
+}
+
+exit 0;
+
+#
+# Handled encoded text.  I think we can assume if the encoding is q-p
+# or base64 to feed it into a formatting filter.
+#
+
+sub process_text (*$$;$)
+{
+       my ($input, $encoding, $charset, $boundary) = @_;
+       my $text, $filterpid, $prefixpid, $finread, $finwrite;
+       my $foutread, $foutwrite, $decoder, $ret, $filterflag;
+       my @text = ( '' ), $maxline = 0;
+
+       #
+       # In the simple case, just spit out the text prefixed by the
+       # quote character
+       #
+
+       if ($encoding eq '7bit' || $encoding ne '8bit') {
+               while (<$input>) {
+                       $ret = match_boundary($_, $boundary);
+                       if (defined $ret) {
+                               return $ret;
+                       }
+                       print $quoteprefix, $_;
+               }
+               return 'EOF';
+       } else {
+               $decoder = find_decoder($encoding);
+               if (! defined $decoder) {
+                       return 'EOF';
+               }
+       }
+
+       #
+       # Okay, assume that the encoding will make it so that we MIGHT need
+       # to filter it.  Read it in; if it's too long, filter it.
+       #
+
+       while (<$input>) {
+               my $line, $len;
+
+               last if ($ret = match_boundary($_, $boundary));
+
+               $line = decode($charset, &$decoder($_));
+
+               if (substr($text[$#text], -1, 1) eq "\n") {
+                       push @text, $line;
+               } else {
+                       $text[$#text] .= $line;
+               }
+               if (($len = length($text[$#text])) > $maxline) {
+                       $maxline = $len;
+               }
+       }
+
+       if (! defined $ret) {
+               $ret = 'EOF';
+       }
+
+       if ($maxline <= $maxcolwidth) {
+               #
+               # These are short enough; just output it now as-is
+               #
+               print STDOUT @text;
+               return $ret;
+       }
+
+       #
+       # We fork a copy of ourselves to read the output from the filter
+       # program and prefix the quote character.
+       #
+
+       pipe($finread, $finwrite) || die "pipe() failed: $!\n";
+       pipe($foutread, $foutwrite) || die "pipe() (second) failed: $!\n";
+
+       binmode($finread, ":encoding($outcharset)");
+       binmode($finread, ":encoding($outcharset)");
+       binmode($foutread, ":encoding($outcharset)");
+       binmode($foutwrite, ":encoding($outcharset)");
+
+       if ($filterpid = fork) {
+               #
+               # Close the pipes in the parent that we're not using
+               #
+
+               close($finread);
+               close($foutwrite);
+       } elsif (defined $filterpid) {
+               #
+               # Close our ununsed filehandles
+               #
+
+               close($finwrite);
+               close($foutread);
+
+               #
+               # Dup() down the filehandles to standard input and output
+               #
+
+               open(STDIN, "<&", $finread) ||
+                                       die "dup(filterin) failed: $!\n";
+               open(STDOUT, ">&", $foutwrite) ||
+                                       die "dup(filterout) failed: $!\n";
+
+               #
+               # Close our copies.
+               #
+
+               close($finread);
+               close($foutwrite);
+
+               #
+               # Exec our filter
+               #
+
+               exec $filterprogram ||
+                               die "Unable to exec $filterprogram: $!\n";
+       } else {
+               die "Fork for $filterprogram failed: $!\n";
+       }
+
+       #
+       # Fork our output handler.
+       #
+
+       if ($prefixpid = fork) {
+               #
+               # We don't need these anymore
+               #
+               close($foutread);
+
+       } elsif (defined $prefixpid) {
+               #
+               # Read from foutwrite, and output (with prefix) to stdout
+               #
+
+               close($finwrite);
+
+               while (<$foutread>) {
+                       print STDOUT $quoteprefix, $_;
+               }
+
+               exit 0;
+       }
+
+       #
+       # Send our input to the filter program
+       #
+
+       print $finwrite @text;
+
+       close($finwrite);
+       waitpid $filterpid, 0;
+       warn "Filter process exited with ", ($? >> 8), "\n" if $?;
+       waitpid $prefixpid, 0;
+       warn "Pipe reader process exited with ", ($? >> 8), "\n" if $?;
+
+       return $ret;
+}
+
+#
+# Filter HTML through a converter program
+#
+
+sub process_html (*$$;$)
+{
+       my ($input, $encoding, $charset, $boundary) = @_;
+       my $filterpid, $prefixpid, $finread, $finwrite;
+       my $foutread, $foutwrite, $decoder, $ret;
+
+       if (! defined($decoder = find_decoder($encoding))) {
+               return 'EOF';
+       }
+
+       #
+       # We fork a copy of ourselves to read the output from the filter
+       # program and prefix the quote character.
+       #
+
+       pipe($finread, $finwrite) || die "pipe() failed: $!\n";
+       pipe($foutread, $foutwrite) || die "pipe() (second) failed: $!\n";
+
+       binmode($finread, ":encoding($outcharset)");
+       binmode($finread, ":encoding($outcharset)");
+       binmode($foutread, ":encoding($outcharset)");
+       binmode($foutwrite, ":encoding($outcharset)");
+
+       if ($filterpid = fork) {
+               #
+               # Close the pipes in the parent that we're not using
+               #
+
+               close($finread);
+               close($foutwrite);
+       } elsif (defined $filterpid) {
+               #
+               # Close our ununsed filehandles
+               #
+
+               close($finwrite);
+               close($foutread);
+
+               #
+               # Dup() down the filehandles to standard input and output
+               #
+
+               open(STDIN, "<&", $finread) ||
+                                       die "dup(filterin) failed: $!\n";
+               open(STDOUT, ">&", $foutwrite) ||
+                                       die "dup(filterout) failed: $!\n";
+
+               #
+               # Close our copies.
+               #
+
+               close($finread);
+               close($foutwrite);
+
+               #
+               # Exec our converter
+               #
+
+               exec (@htmlconv) ||
+                               die "Unable to exec $filterprogram: $!\n";
+       } else {
+               die "Fork for $htmlconv[0] failed: $!\n";
+       }
+
+       #
+       # Fork our output handler.
+       #
+
+       if ($prefixpid = fork) {
+               #
+               # We don't need these anymore
+               #
+               close($foutread);
+
+       } elsif (defined $prefixpid) {
+               #
+               # Read from foutwrite, and output (with prefix) to stdout
+               #
+
+               close($finwrite);
+
+               while (<$foutread>) {
+                       print STDOUT $quoteprefix, $_;
+               }
+
+               exit 0;
+       }
+
+       #
+       # Send our input to the filter program
+       #
+
+       while (<$input>) {
+               last if ($ret = match_boundary($_, $boundary));
+               print $finwrite (&$decoder($_));
+       }
+
+       if (! defined $ret) {
+               $ret = 'EOF';
+       }
+
+       close($finwrite);
+       waitpid $filterpid, 0;
+       warn "HTML converter process exited with ", scalar($? >> 8), "\n" if $?;
+       waitpid $prefixpid, 0;
+       warn "Pipe reader process exited with ", $? >> 8, "\n" if $?;
+
+       return $ret;
+}
+
+#
+# Decide what to do, based on what kind of content it is.
+#
+
+sub process_part (*$$$$;$)
+{
+       my ($input, $content_type, $encoding, $charset, $boundary, $name) = @_;
+       my ($type, $subtype) = (split('/', $content_type, -1), '');
+
+       if ($type eq 'text') {
+               #
+               # If this is a text part, right now we only deal with
+               # plain and HTML parts.
+               #
+               if ($subtype eq 'plain') {
+                       return process_text($input, $encoding, $charset,
+                                           $boundary);
+               } elsif ($subtype eq 'html') {
+                       return process_html($input, $encoding, $charset,
+                                           $boundary);
+               } else {
+                       print ">>> $content_type content\n";
+                       return eat_part($input, $boundary);
+               }
+       } elsif ($type eq 'multipart') {
+               return process_multipart($input, $subtype, $boundary);
+       } else {
+               #
+               # Other types we're not sure what to do with right now
+               # Just put a marker in there
+               #
+
+               print ">>> $content_type attachment";
+               if (defined $name) {
+                       print ", name=$name";
+               }
+               print "\n";
+
+               return eat_part($input, $boundary);
+       }
+}
+
+#
+# Process a multipart message.
+#
+# When called, we should be right after the beginning of the first
+# boundary marker.  So we should be pointed at header lines which describe
+# the content of this part
+#
+
+sub process_multipart ($$$)
+{
+       my ($input, $subtype, $boundary) = @_;
+       my $altout;
+
+       while (1) {
+               my $encoding, $type, $end, $name, $charset;
+
+               #
+               # Use the Mail::Header package to read in any headers
+               # corresponding to this part
+               #
+
+               my $head = Mail::Header->new($input, (MailFrom => 'IGNORE'));
+
+               #
+               # Extract out any Content-Type, Content-Transfer-Encoding, and
+               # Content-Disposition headers
+               #
+
+               my $ctype = Mail::Field->extract('Content-Type', $head);
+               my $cte = Mail::Field->extract('Content-Transfer-Encoding',
+                                              $head);
+               my $cdispo = Mail::Field->extract('Content-Disposition', $head);
+
+               if (defined $ctype) {
+                       $type = $ctype->type;
+                       $charset = $ctype->charset;
+               } else {
+                       $type = 'text/plain';
+                       $charset = 'us-ascii';
+               }
+
+               $encoding = defined $cte ? $cte->param('_') : '7bit';
+               $name = defined $cdispo ? $cdispo->param('filename') : undef;
+
+                #
+                # Special handling for multipart/alternative; pick
+                # the "first" one we can handle (which is usually
+                # text/plain) and silently eat the rest, but output a
+                # warning if we can't handle anything.
+                #
+
+               if ($altout) {
+                       $end = eat_part($input, $boundary);
+               } else {
+                       my $subboundary = $boundary;
+                       my $maintype = (split('/', $type))[0];
+
+                       if ($maintype eq 'multipart') {
+                               $subboundary = $ctype->boundary;
+                               #
+                               # Go until we find our beginning of this
+                               # part
+                               #
+                               my $subend = eat_part($input, $subboundary);
+
+                               if ($subend ne 'EOP') {
+                                       print ">>> WARNING: malformed ",
+                                               "nested multipart\n";
+                                       return $subend;
+                               }
+                       }
+
+                       $end = process_part($input, $type, $encoding,
+                                           $charset, $subboundary, $name);
+
+                       if ($subtype eq 'alternative' && ! defined $altout &&
+                           $type eq 'text/plain') {
+                               $altout = 1;
+                       }
+
+                        #
+                        # Since we changed the semantics of $boundary
+                        # above for nested multiparts, if we are
+                        # handling a nested multipart then find the end
+                        # of our current part
+                        #
+
+                       if ($maintype eq 'multipart') {
+                               $end = eat_part($input, $boundary);
+                       }
+
+               }
+
+               if ($end eq 'EOM' || $end eq 'EOF') {
+                       if ($subtype eq 'alternative' && !defined $altout) {
+                               print ">>>multipart/alternative: no suitable ",
+                                       "parts\n";
+                       }
+                       return $end;
+               }
+       }
+}
+
+#
+# "Eat" a MIME part; consume content until we hit the boundary or EOF
+#
+
+sub eat_part ($$)
+{
+       my ($input, $boundary) = @_;
+       my $ret;
+
+       #
+       # If we weren't given a boundary just eat input until EOF
+       #
+
+       if (! defined $boundary) {
+               while (<$input>) { }
+               return 'EOF';
+       }
+
+       #
+       # Otherwise, consume data until we hit our boundary
+       #
+
+       while (<$input>) {
+               if ($ret = match_boundary($_, $boundary)) {
+                       return $ret;
+               }
+       }
+
+       return 'EOF';
+}
+
+#
+# Return the decoder subroutine to use
+#
+
+sub find_decoder ($)
+{
+       my ($encoding) = @_;
+
+       if ($encoding eq '7bit' || $encoding eq '8bit') {
+               return \&null_decoder;
+       } elsif ($encoding eq 'base64') {
+               return \&decode_base64;
+       } elsif ($encoding eq 'quoted-printable') {
+               return \&decode_qp;
+       } else {
+               warn "Unknown encoding: $encoding\n";
+               return undef;
+       }
+}
+
+sub null_decoder ($)
+{
+       my ($input) = @_;
+
+       return $input;
+}
+
+#
+# Match a line against the boundary string
+#
+
+sub match_boundary($$)
+{
+       my ($_, $boundary) = @_;
+
+       if (substr($_, 0, 2) eq '--') {
+               s/[ \t\r\n]+\Z//;
+               if ($_ eq "--$boundary") {
+                       return 'EOP';
+               } elsif ($_ eq "--$boundary--") {
+                       return 'EOM';
+               }
+       }
+
+       return undef;
+}

http://git.savannah.gnu.org/cgit/nmh.git/commit/?id=f74055c90a34cd03c8d6c444aa2d96ddf8d3c746


commit f74055c90a34cd03c8d6c444aa2d96ddf8d3c746
Author: Ken Hornstein <address@hidden>
Date:   Mon Apr 2 15:38:40 2012 -0400

    Implement fix in 59b086daa57105e8fbd8b1e6ba60be6a845faf95 here as well.

diff --git a/uip/mhlsbr.c b/uip/mhlsbr.c
index d49314f..faa7f71 100644
--- a/uip/mhlsbr.c
+++ b/uip/mhlsbr.c
@@ -223,6 +223,22 @@ static struct triple triples[] = {
     { NULL,            0,           0 }
 };
 
+static char *addrcomps[] = {
+    "from",
+    "sender",
+    "reply-to",
+    "to",
+    "cc",
+    "bcc",
+    "resent-from",
+    "resent-sender",
+    "resent-reply-to",
+    "resent-to",
+    "resent-cc",
+    "resent-bcc",
+    NULL
+};
+
 
 static int bellflg   = 0;
 static int clearflg  = 0;
@@ -1907,10 +1923,22 @@ compileargs (struct mcomp *c1, char *nfs)
 {
     struct format *fmt;
     struct arglist *args;
+    char *ap;
+    struct comp *cptr;
     unsigned int i;
 
     i = fmt_compile(nfs, &fmt);
 
+    /*
+     * Search through and mark any components that are address components
+     */
+
+    for (ap = addrcomps; *ap; ap++) {
+       FINDCOMP (cptr, *ap);
+       if (cptr)
+           cptr->c_type |= CT_ADDR;
+    }
+
     args = (struct arglist *) mh_xmalloc(sizeof(struct arglist));
 
     if (! args)
@@ -1971,9 +1999,7 @@ checkcomp(char *name, char *buf)
            if (mh_strcasecmp(name, c->c_name) == 0) {
                found++;
                if (! c->c_text) {
-                   i = strlen(c->c_text = strdup(buf)) - 1;
-                   if (c->c_text[i] == '\n')
-                       c->c_text[i] = '\0';
+                   c->c_text = strdup(buf);
                } else {
                    i = strlen(cp = c->c_text) - 1;
                    if (cp[i] == '\n') {

-----------------------------------------------------------------------

Summary of changes:
 Makefile.am                |    2 +-
 docs/pending-release-notes |    2 +
 docs/replyfilter           |  637 ++++++++++++++++++++++++++++++++++++++++++++
 uip/mhlsbr.c               |   32 ++-
 4 files changed, 669 insertions(+), 4 deletions(-)
 create mode 100755 docs/replyfilter


hooks/post-receive
-- 
The nmh Mail Handling System



reply via email to

[Prev in Thread] Current Thread [Next in Thread]