]> diplodocus.org Git - nmh/commitdiff
Merge branch 'master' into xoauth
authorDavid Levine <david.levine@gonift.com>
Sun, 13 Mar 2016 14:42:39 +0000 (10:42 -0400)
committerDavid Levine <david.levine@gonift.com>
Sun, 13 Mar 2016 14:42:39 +0000 (10:42 -0400)
114 files changed:
.gitignore
MACHINES
Makefile.am
config/config.c
configure.ac
docs/README-iCalendar [deleted file]
docs/contrib/build_nmh
docs/contrib/replaliases [new file with mode: 0644]
docs/contrib/replyfilter
docs/pending-release-notes
etc/mhical.12hour [new file with mode: 0644]
etc/mhical.24hour [new file with mode: 0644]
etc/mhl.format
etc/mhl.headers
etc/mhl.reply
etc/mhl.replywithoutbody [new file with mode: 0644]
etc/mhn.defaults.sh
etc/mhshow.header [new file with mode: 0644]
etc/scan.MMDDYY
etc/scan.YYYYMMDD
etc/scan.curses
etc/scan.default
etc/scan.highlighted
etc/scan.mailx
etc/scan.size
etc/scan.time
etc/scan.timely
etc/scan.unseen
h/fmt_compile.h
h/icalendar.h [new file with mode: 0644]
h/mh.h
h/mhparse.h
h/mime.h
h/nmh.h
h/prototypes.h
man/mh-folders.man
man/mh-format.man
man/mh-mime.man
man/mh-profile.man
man/mhbuild.man
man/mhfixmsg.man
man/mhical.man [new file with mode: 0644]
man/mhlist.man
man/mhparam.man
man/mhshow.man
man/mhstore.man
man/pick.man
man/post.man
man/repl.man
man/send.man
man/show.man
sbr/context_read.c
sbr/context_save.c
sbr/datetime.c [new file with mode: 0644]
sbr/fmt_compile.c
sbr/fmt_scan.c
sbr/getansreadline.c
sbr/icalendar.l [new file with mode: 0644]
sbr/icalparse.y [new file with mode: 0644]
sbr/lock_file.c
sbr/mime_type.c
sbr/uprf.c
test/bad-input/test-header
test/common.sh.in
test/format/test-fmtdump
test/format/test-functions
test/inc/test-deb359167
test/mhfixmsg/test-mhfixmsg
test/mhical/test-mhical [new file with mode: 0755]
test/mhlist/test-mhlist
test/mhparam/test-mhparam
test/mhshow/test-charset
test/mhshow/test-cte-binary
test/mhshow/test-msg-buffer-boundaries
test/mhshow/test-qp
test/mhshow/test-subpart
test/mhshow/test-textcharset
test/pick/test-pick
test/repl/test-convert [new file with mode: 0755]
test/repl/test-repl
test/show/test-show [new file with mode: 0755]
test/valgrind.supp
test/whatnow/test-cd
uip/fmtdump.c
uip/fmttest.c
uip/inc.c
uip/mhbuild.c
uip/mhbuildsbr.c
uip/mhcachesbr.c
uip/mhfixmsg.c
uip/mhical.c [new file with mode: 0644]
uip/mhlist.c
uip/mhlistsbr.c
uip/mhlsbr.c
uip/mhmail
uip/mhmisc.c
uip/mhn.c
uip/mhoutsbr.c
uip/mhparam.c
uip/mhparse.c
uip/mhshow.c
uip/mhshowsbr.c
uip/mhstore.c
uip/mhstoresbr.c
uip/pick.c
uip/post.c
uip/prompter.c
uip/rcvdist.c
uip/repl.c
uip/send.c
uip/sendsbr.c
uip/show.c
uip/viamail.c
uip/whatnowsbr.c

index 2703810..f251aef 100644 (file)
@@ -22,6 +22,8 @@
 /ylwrap
 /nmh-*.tar.gz
 /nmh-*.tar.gz.sig
+/sbr/icalendar.c
+/sbr/icalparse.[hc]
 
 # Removed by maintainer-clean:
 /autom4te.cache/
@@ -78,6 +80,7 @@ a.out.dSYM/
 /uip/mark
 /uip/mhbuild
 /uip/mhfixmsg
+/uip/mhical
 /uip/mhl
 /uip/mhlist
 /uip/mhlogin
index e045bdc..1f8b0c1 100644 (file)
--- a/MACHINES
+++ b/MACHINES
@@ -22,9 +22,10 @@ the exceptions noted below), using an ANSI C compiler, such as gcc:
 
 On all platforms, the following programs are required to build nmh from a
 snapshot of the source code repository:
-    autoconf
-    automake
-    flex
+    autoconf 2.68 or later
+    automake 1.12 or later
+    flex 2.5.4 or later
+    bison 2.3 or later,  Berkeley yacc 1.9 or later, or Solaris yacc 4.0
 They are not required if building from an nmh distribution (.tar.gz) file.
 
 Platform-specific notes follow.
index 59269d5..cb34b5d 100644 (file)
@@ -7,6 +7,8 @@
 ## We set this to get our autoconf macros in the m4 directory
 ACLOCAL_AMFLAGS = -I m4
 
+AM_YFLAGS = -d
+
 ##
 ## This is the default set of libraries all programs link against.  Some
 ## programs add extra libraries to this set, so they override this with
@@ -40,6 +42,7 @@ TESTS_ENVIRONMENT = MH_OBJ_DIR="@abs_builddir@" \
                    MH_TEST_DIR="@abs_builddir@/test/testdir" \
                    nmhlibexecdir="$(nmhlibexecdir)" bindir="$(bindir)" \
                    mandir="$(mandir)" nmhetcdir="$(nmhetcdir)" \
+                   nmhetcdirinst="@nmhetcdirinst@$(nmhetcdir)" \
                    supported_locks="$(supported_locks)" \
                    default_locking="${default_locking}" \
                    MULTIBYTE_ENABLED=$(MULTIBYTE_ENABLED) \
@@ -74,7 +77,7 @@ TESTS = test/ali/test-ali test/anno/test-anno \
        test/mhbuild/test-ext-params \
        test/mhbuild/test-forw test/mhbuild/test-header-encode \
        test/mhbuild/test-utf8-body \
-       test/mhfixmsg/test-mhfixmsg \
+       test/mhfixmsg/test-mhfixmsg test/mhical/test-mhical \
        test/mhl/test-mhl-flags \
        test/mhlist/test-mhlist test/mhlist/test-ext-params \
        test/mhmail/test-mhmail \
@@ -93,12 +96,13 @@ TESTS = test/ali/test-ali test/anno/test-anno \
        test/post/test-post-group test/post/test-mts test/post/test-messageid \
        test/post/test-sendfiles test/prompter/test-prompter \
        test/rcv/test-rcvdist test/rcv/test-rcvpack test/rcv/test-rcvstore \
-       test/rcv/test-rcvtty test/refile/test-refile \
+       test/rcv/test-rcvtty test/refile/test-refile test/repl/test-convert \
        test/repl/test-if-str test/repl/test-trailing-newline \
        test/repl/test-multicomp test/repl/test-repl \
        test/scan/test-scan test/scan/test-scan-multibyte \
        test/sequences/test-flist test/sequences/test-mark \
        test/sequences/test-out-of-range \
+       test/show/test-show \
        test/slocal/test-slocal \
        test/whatnow/test-attach-detach test/whatnow/test-cd \
        test/whatnow/test-ls test/whom/test-whom \
@@ -107,14 +111,20 @@ TESTS = test/ali/test-ali test/anno/test-anno \
 check_SCRIPTS = test/common.sh
 check_PROGRAMS = test/getfullname test/getcanon test/fakepop test/fakesmtp \
                 test/getcwidth test/fakehttp
-DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1
+
+## The location of installed nmhetcdir is, for all purposes except
+## distcheck, $nmhetcdir.  For distcheck, prepend $MH_INST_DIR (from
+## test/common.sh.in), which is based on $MH_TEST_DIR (from
+## $TESTS_ENVIRONMENT).
+DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \
+                           NMHETCDIRINST='$${abs_builddir}/test/testdir/inst'
 
 ##
 ## Stuff that should be cleaned via "make clean"
 ## automake 1.12.6 on FreeBSD 9 needs the sbr/dtimep.c.
 ##
 CLEANFILES = config/version.c sbr/sigmsg.h sbr/dtimep.c etc/mts.conf \
-            etc/gen-ctype-checked sbr/ctype-checked.h sbr/ctype-checked.c \
+            etc/gen-ctype-checked sbr/ctype-checked.[hc] \
             etc/mhn.defaults man/man.sed man/mh-chart.man $(man_MANS) \
             *.plist
 clean-local:
@@ -148,7 +158,7 @@ BUILT_SOURCES = sbr/sigmsg.h sbr/ctype-checked.c
 ##
 bin_PROGRAMS = uip/ali uip/anno uip/burst uip/comp uip/dist uip/flist \
               uip/fmttest uip/folder uip/forw uip/inc uip/install-mh \
-              uip/mark uip/mhbuild uip/mhfixmsg uip/mhlist uip/mhn \
+              uip/mark uip/mhbuild uip/mhfixmsg uip/mhical uip/mhlist uip/mhn \
               uip/mhparam uip/mhpath uip/mhshow uip/mhstore uip/msgchk \
               uip/new uip/packf uip/pick uip/prompter uip/refile \
               uip/repl uip/rmf uip/rmm uip/scan uip/send uip/show uip/sortm \
@@ -183,7 +193,7 @@ noinst_LIBRARIES = sbr/libmh.a mts/libmts.a
 ## them, but that might change in the future.
 ##
 noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h h/dropsbr.h \
-                h/fmt_compile.h h/fmt_scan.h h/md5.h h/mf.h \
+                h/fmt_compile.h h/fmt_scan.h h/icalendar.h h/md5.h h/mf.h \
                 h/mh.h h/mhcachesbr.h h/mhparse.h h/mime.h \
                 h/mts.h h/nmh.h h/picksbr.h h/popsbr.h h/prototypes.h \
                 h/rcvmail.h h/scansbr.h h/signals.h h/tws.h h/utils.h \
@@ -194,15 +204,17 @@ noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h h/dropsbr.h \
 ## Extra files we need to install in various places
 ##
 dist_nmhetc_DATA = etc/MailAliases etc/components etc/digestcomps \
-                   etc/distcomps etc/forwcomps etc/mhl.body etc/mhl.digest \
-                   etc/mhl.format etc/mhl.forward etc/mhl.headers \
-                   etc/mhl.reply etc/mhshow.marker etc/rcvdistcomps \
-                   etc/rcvdistcomps.outbox etc/replcomps etc/replgroupcomps \
-                   etc/scan.MMDDYY \
-                   etc/scan.YYYYMMDD etc/scan.curses etc/scan.default \
-                   etc/scan.highlighted \
-                   etc/scan.mailx etc/scan.nomime etc/scan.size etc/scan.time \
-                   etc/scan.timely etc/scan.unseen
+                  etc/distcomps etc/forwcomps \
+                  etc/mhical.12hour etc/mhical.24hour \
+                  etc/mhl.body etc/mhl.digest etc/mhl.format etc/mhl.forward \
+                  etc/mhl.headers etc/mhl.reply etc/mhl.replywithoutbody \
+                  etc/mhshow.marker etc/rcvdistcomps etc/rcvdistcomps.outbox \
+                  etc/replcomps etc/replgroupcomps \
+                  etc/scan.MMDDYY \
+                  etc/scan.YYYYMMDD etc/scan.curses etc/scan.default \
+                  etc/scan.highlighted \
+                  etc/scan.mailx etc/scan.nomime etc/scan.size etc/scan.time \
+                  etc/scan.timely etc/scan.unseen
 
 ##
 ## The same as above, but we don't include these in the distribution
@@ -219,7 +231,7 @@ dist_doc_DATA = COPYRIGHT INSTALL NEWS README VERSION \
                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/README-iCalendar docs/TODO
+               docs/TODO
 
 ##
 ## Contribs that get installed in docdir/contrib/
@@ -227,6 +239,7 @@ dist_doc_DATA = COPYRIGHT INSTALL NEWS README VERSION \
 contribdir = $(docdir)/contrib
 dist_contrib_SCRIPTS = docs/contrib/replyfilter docs/contrib/build_nmh \
                       docs/contrib/ml docs/contrib/vpick
+dist_contrib_DATA = docs/contrib/replaliases
 
 ##
 ## Our man pages
@@ -238,8 +251,8 @@ man_MANS = man/ali.1 man/anno.1 man/ap.8 man/burst.1 man/comp.1 \
           man/mh-alias.5 man/mh-chart.7 man/mh-draft.5 man/mh-folders.5 \
           man/mh-format.5 man/mh-mail.5 man/mh-mime.7 man/mh-profile.5 \
           man/mh_profile.5 man/mh-sequence.5 man/mh-tailor.5 man/mhbuild.1 \
-          man/mhfixmsg.1 man/mhl.1 man/mhlist.1 man/mhmail.1 man/mhn.1 \
-          man/mhparam.1 man/mhpath.1 man/mhshow.1 man/mhstore.1 \
+          man/mhfixmsg.1 man/mhical.1 man/mhl.1 man/mhlist.1 man/mhmail.1 \
+          man/mhn.1 man/mhparam.1 man/mhpath.1 man/mhshow.1 man/mhstore.1 \
           man/mh-mkstemp.1 man/msgchk.1 man/mts.conf.5 man/new.1 \
           man/next.1 man/nmh.7 man/packf.1 man/pick.1 man/post.8 man/prev.1 \
           man/prompter.1 man/rcvdist.1 man/rcvpack.1 man/rcvstore.1 \
@@ -258,10 +271,10 @@ man_SRCS = man/ali.man man/anno.man man/ap.man man/burst.man man/comp.man \
           man/mh-chart-gen.sh man/mh-draft.man man/mh-folders.man \
           man/mh-format.man man/mh-mail.man man/mh-mime.man \
           man/mh-profile.man man/mh_profile.man man/mh-sequence.man \
-          man/mh-tailor.man man/mhbuild.man man/mhfixmsg.man man/mhl.man \
-          man/mhlist.man man/mhmail.man man/mhn.man man/mhparam.man \
-          man/mhpath.man man/mhshow.man man/mhstore.man man/mh-mkstemp.man \
-          man/msgchk.man man/mts.conf.man man/new.man \
+          man/mh-tailor.man man/mhbuild.man man/mhfixmsg.man man/mhical.man \
+          man/mhl.man man/mhlist.man man/mhmail.man man/mhn.man \
+          man/mhparam.man man/mhpath.man man/mhshow.man man/mhstore.man \
+          man/mh-mkstemp.man man/msgchk.man man/mts.conf.man man/new.man \
           man/next.man man/nmh.man man/packf.man man/pick.man man/post.man \
           man/prev.man man/prompter.man man/rcvdist.man man/rcvpack.man \
           man/rcvstore.man man/rcvtty.man man/refile.man man/repl.man \
@@ -273,8 +286,9 @@ man_SRCS = man/ali.man man/anno.man man/ap.man man/burst.man man/comp.man \
 ## Files we need to include in the distribution which aren't found by
 ## Automake using the automatic rules
 ##
-EXTRA_DIST = autogen.sh config/version.sh sbr/sigmsg.awk etc/mts.conf.in \
-            etc/mhn.defaults.sh etc/sendfiles $(MHNSEARCHPROG) DATE MACHINES \
+EXTRA_DIST = autogen.sh config/version.sh sbr/sigmsg.awk sbr/icalparse.h \
+            etc/mts.conf.in etc/mhn.defaults.sh etc/sendfiles \
+            $(MHNSEARCHPROG) DATE MACHINES \
             docs/ChangeLog_MH-3_to_MH-6.6 \
             docs/ChangeLog_MH-6.7.0_to_MH-6.8.4.html \
             test/README test/fakesendmail $(TESTS) test/inc/deb359167.mbox \
@@ -336,7 +350,8 @@ uip_mark_LDADD = $(LDADD) $(POSTLINK)
 
 uip_mhbuild_SOURCES = uip/mhbuild.c uip/mhbuildsbr.c uip/mhcachesbr.c \
                      uip/mhlistsbr.c uip/mhoutsbr.c uip/mhmisc.c  \
-                     uip/mhfree.c uip/mhparse.c uip/md5.c
+                     uip/mhfree.c uip/mhparse.c uip/md5.c \
+                     uip/mhstoresbr.c uip/mhshowsbr.c
 uip_mhbuild_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
 uip_mhfixmsg_SOURCES = uip/mhfixmsg.c uip/mhparse.c uip/mhcachesbr.c \
@@ -344,6 +359,9 @@ uip_mhfixmsg_SOURCES = uip/mhfixmsg.c uip/mhparse.c uip/mhcachesbr.c \
                       uip/mhshowsbr.c uip/mhlistsbr.c uip/md5.c
 uip_mhfixmsg_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
+uip_mhical_SOURCES = uip/mhical.c
+uip_mhical_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
+
 uip_mhlist_SOURCES = uip/mhlist.c uip/mhparse.c uip/mhcachesbr.c \
                     uip/mhlistsbr.c uip/mhmisc.c uip/mhfree.c uip/md5.c
 uip_mhlist_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
@@ -580,7 +598,9 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c sbr/atooi.c sbr/arglist.c \
                      sbr/getcpy.c sbr/geteditor.c sbr/getfolder.c \
                      sbr/getpass.c \
                      sbr/fmt_addr.c sbr/fmt_compile.c sbr/fmt_new.c \
-                     sbr/fmt_rfc2047.c sbr/fmt_scan.c sbr/lock_file.c \
+                     sbr/fmt_rfc2047.c sbr/fmt_scan.c \
+                     sbr/icalparse.y sbr/icalendar.l sbr/datetime.c \
+                     sbr/lock_file.c \
                      sbr/m_atoi.c sbr/m_backup.c sbr/m_convert.c \
                      sbr/m_draft.c sbr/m_getfld.c sbr/m_gmprot.c \
                      sbr/m_maildir.c sbr/m_name.c sbr/m_popen.c sbr/m_rand.c \
@@ -611,7 +631,7 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c sbr/atooi.c sbr/arglist.c \
 
 config/sbr_libmh_a-config.$(OBJEXT) sbr/sbr_libmh_a-mts.$(OBJEXT): Makefile
 
-sbr_libmh_a_CPPFLAGS = -I./sbr -DNMHETCDIR='"$(nmhetcdir)"' \
+sbr_libmh_a_CPPFLAGS = -I./sbr -DNMHETCDIR='"@nmhetcdirinst@$(nmhetcdir)"' \
                -DMAILSPOOL='"$(mailspool)"' \
                -DSENDMAILPATH='"$(sendmailpath)"' -DNMHBINDIR='"$(bindir)"' \
                -DNMHLIBEXECDIR='"$(nmhlibexecdir)"'
index a5cdfd9..919c1ee 100644 (file)
@@ -143,8 +143,9 @@ char *nmhaccessftp = "nmh-access-ftp";
 /* profile entry for external url access command */
 char *nmhaccessurl = "nmh-access-url";
 
-char *mhlibexecdir = NMHLIBEXECDIR;
+char *mhbindir = NMHBINDIR;
 char *mhetcdir = NMHETCDIR;
+char *mhlibexecdir = NMHLIBEXECDIR;
 
 /* 
  * nmh not-so constants
@@ -241,13 +242,6 @@ char *moreproc = NULL;
 
 char *mhlproc = nmhlibexecdir (/mhl);
 
-/* 
- * This is the super handy BBoard reading program, which is
- * really just the nmh shell program.
- */
-
-char *mshproc = nmhbindir (/msh);
-
 /* 
  * This program is called to pack a folder.  
  */
@@ -298,13 +292,6 @@ char *showmimeproc = nmhbindir (/mhshow);
 
 char *showproc = nmhlibexecdir (/mhl);
 
-/* 
- * This program is called by vmh as the back-end to the window management
- * protocol
- */
-
-char *vmhproc = nmhbindir (/msh);
-
 /* 
  * This program is called after comp, et. al., have built a draft
  */
index 507e4ea..548a43d 100644 (file)
@@ -93,9 +93,6 @@ AC_PREFIX_PROGRAM([mhparam])
 dnl ------------------
 dnl CHECK THE COMPILER
 dnl ------------------
-dnl We want these before the checks,
-dnl so the checks can modify their values.
-test -z "$CFLAGS"  &&  auto_cflags=1
 
 AC_PROG_CC([cc gcc])
 AM_PROG_CC_C_O
@@ -103,51 +100,32 @@ AM_PROG_CC_C_O
 dnl ----------------
 dnl CUSTOMIZE CFLAGS
 dnl ----------------
-if test -n "$auto_cflags"; then
-  dnl The user didn't specify CFLAGS, so customize them.
-  if test "$GCC" = yes; then
-    dnl Can't use -ansi with gcc 4.5.3 on Cygwin, at least through setup
-    dnl version 2.763, because it disables some features in the system
-    dnl header files and warns about them with -Wall.  Try to test for
-    dnl that generally, with gcc.
-    AC_CACHE_CHECK([whether we can use -ansi with gcc], [nmh_cv_has_dash_ansi],
-      [nmh_saved_cppflags="$CPPFLAGS"
-       nmh_saved_cflags="$CFLAGS"
-       CPPFLAGS="-D_GNU_SOURCE"
-       CFLAGS="-ansi -Wall -Werror"
-       AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include <time.h>],[tzset();])],
-         [nmh_cv_has_dash_ansi=yes],
-         [nmh_cv_has_dash_ansi=no])
-       CPPFLAGS="$nmh_saved_cppflags"
-       CFLAGS="$nmh_saved_cflags"])
-    test "$nmh_cv_has_dash_ansi" = "yes"  &&  \
-      CFLAGS="${CFLAGS:+$CFLAGS }-ansi -pedantic"
-  else
-    dnl Use -v and some other options with SunStudio cc.  lex produces
-    dnl code that causes unreachable-statement warnings.  It might be
-    dnl better to use an autoconf test, except -v will probably succeed
-    dnl with many other compilers but have different meaning.
-    AS_CASE([`${CC} -V 2>&1`],
-            [cc:\ Sun\ C*], [CFLAGS=\
+dnl The user didn't specify CFLAGS, so customize them.
+if test "$GCC" != yes; then
+  dnl Use -v and some other options with SunStudio cc.  lex produces
+  dnl code that causes unreachable-statement warnings.  It might be
+  dnl better to use an autoconf test, except -v will probably succeed
+  dnl with many other compilers but have different meaning.
+  AS_CASE([`${CC} -V 2>&1`],
+          [cc:\ Sun\ C*], [CFLAGS=\
 "${CFLAGS:+$CFLAGS }-v -errtags=yes -erroff=E_STATEMENT_NOT_REACHED"])
-  fi
-
-  AC_CACHE_CHECK([whether compiler supports -Wall], [nmh_cv_has_wall],
-                 [nmh_saved_cflags="$CFLAGS"
-                 CFLAGS="$CFLAGS -Wall -Werror"
-                 AC_TRY_COMPILE([],[],nmh_cv_has_wall=yes,nmh_cv_has_wall=no)
-                 CFLAGS="$nmh_saved_cflags"])
-  test "$nmh_cv_has_wall" = "yes"  &&  CFLAGS="${CFLAGS:+$CFLAGS }-Wall"
-
-  AC_CACHE_CHECK([whether compiler supports -Wextra], [nmh_cv_has_wextra],
-                 [nmh_saved_cflags="$CFLAGS"
-                 CFLAGS="$CFLAGS -Wextra -Werror"
-                 AC_TRY_COMPILE([],[],nmh_cv_has_wextra=yes,
-                                nmh_cv_has_wextra=no)
-                 CFLAGS="$nmh_saved_cflags"])
-  test "$nmh_cv_has_wextra" = "yes"  &&  CFLAGS="${CFLAGS:+$CFLAGS }-Wextra"
 fi
 
+AC_CACHE_CHECK([whether compiler supports -Wall], [nmh_cv_has_wall],
+               [nmh_saved_cflags="$CFLAGS"
+               CFLAGS="$CFLAGS -Wall -Werror"
+               AC_TRY_COMPILE([],[],nmh_cv_has_wall=yes,nmh_cv_has_wall=no)
+               CFLAGS="$nmh_saved_cflags"])
+test "$nmh_cv_has_wall" = "yes"  &&  CFLAGS="${CFLAGS:+$CFLAGS }-Wall"
+
+AC_CACHE_CHECK([whether compiler supports -Wextra], [nmh_cv_has_wextra],
+               [nmh_saved_cflags="$CFLAGS"
+               CFLAGS="$CFLAGS -Wextra -Werror"
+               AC_TRY_COMPILE([],[],nmh_cv_has_wextra=yes,
+                              nmh_cv_has_wextra=no)
+               CFLAGS="$nmh_saved_cflags"])
+test "$nmh_cv_has_wextra" = "yes"  &&  CFLAGS="${CFLAGS:+$CFLAGS }-Wextra"
+
 if test "$GCC" = yes; then
   dnl Do this regardless of auto_cflags value.
   dnl Enable _FORTIFY_SOURCE checking, which is supported by
@@ -215,11 +193,12 @@ AC_HEADER_ASSERT
 dnl ------------------
 dnl CHECK FOR PROGRAMS
 dnl ------------------
-AC_PROG_MAKE_SET       dnl Does make define $MAKE
-AC_PROG_INSTALL                dnl Check for BSD compatible `install'
-AC_PROG_RANLIB         dnl Check for `ranlib'
+AC_PROG_MAKE_SET        dnl Does make define $MAKE
+AC_PROG_INSTALL         dnl Check for BSD compatible `install'
+AC_PROG_RANLIB          dnl Check for `ranlib'
 AC_PROG_AWK             dnl Check for mawk,gawk,nawk, then awk
-AC_PROG_SED            dnl Check for Posix-compliant sed
+AC_PROG_SED             dnl Check for Posix-compliant sed
+AC_PROG_YACC            dnl Check for yacc/bison
 AM_PROG_LEX             dnl Check for lex/flex
 
 AM_PROG_AR
@@ -315,7 +294,7 @@ AS_IF([test x"$DISABLE_SETGID_MAIL" != x -a x"$DISABLE_SETGID_MAIL" != x0],
 
 dnl If mailspool is not world-writable and dotlockfile is not setgid,
 dnl we need to #define MAILGROUP to 1 and make inc setgid.
-if test x"$LOCKTYPE" = x"dot" -a x"$nmh_cv_mailspool_world_writable" = x"no" -a x"$nmh_cv_dotlockfile_setgid" != x"yes" ; then
+if test x"$with_locking" = x"dot" -a x"$nmh_cv_mailspool_world_writable" = x"no" -a x"$nmh_cv_dotlockfile_setgid" != x"yes" ; then
   dnl do we really need both of these?
   AC_DEFINE([MAILGROUP],[1],
     [Define to 1 if you need to make `inc' set-group-id because your mail spool is not world writable. There are no guarantees as to the safety of doing this, but this #define will add some extra security checks.])dnl
@@ -540,6 +519,13 @@ AS_IF([test "$LEX" = flex],
 '; sed "s/\(   \)int i;/\1yy_size_t i;/" $@ >$@.tmp && mv -f $@.tmp $@'])
        AC_SUBST([LFLAGS])])
 
+dnl ------------------
+dnl FOR INTERNAL USE by the NMH test suite
+dnl ------------------
+AC_ARG_VAR([NMHETCDIRINST], [for internal use by nmh test suite])
+AS_IF([test -n "$NMHETCDIRINST"], [nmhetcdirinst=$NMHETCDIRINST]
+      AC_SUBST([nmhetcdirinst]))
+
 dnl ----------------
 dnl CHECK STRUCTURES
 dnl ----------------
diff --git a/docs/README-iCalendar b/docs/README-iCalendar
deleted file mode 100644 (file)
index ea275a5..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-To view an iCalendar (text/calendar, .ics, RFC 5545) attachment with
-mhshow or mhn, all I had to do was install calcurse
-(http://www.calcurse.org/), set it up once, and add this to my
-profile:
-
-#: With mhshow, the following just views the calendar attachment.
-#: With mhn, it inserts it into my personal calcurse calendar.
-#: calcurse must be set up before or at first use.  To set up beforehand,
-#:   either run it without any arguments and then "qqqy", or:
-#:   $ echo qqqy | calcurse
-#: The range of 60 is in days:  anything after that range (or before the
-#:   current date) won't be shown.
-#: The grep of TZID works around a current (v. 3.1.4) calcurse
-#:   deficiency:  it doesn't support timezones.
-#: The | cat below allows a sequence of shell commands.
-mhshow-show-text/calendar:
-  %lcalcurse -c /dev/null --read-only --import '%F' --range=60 --todo | cat;
-  calcurse --gc;
-  grep TZID: '%F'
-mhn-show-text/calendar:
-  %lcalcurse --import '%F' | cat;
-  grep TZID: '%F'
-
-
-To create and send out a calendar request:
-  1) Launch calcurse and create the calendar request.
-  2) Export the calendar request to a file, either using the
-     "x" command from within interactive calcurse or using the
-     -x/--export command-line option.  The output filename
-     should have an extension of .ics if you'll use send -attach.
-  3) For compatibility with MS Outlook, edit the calendar
-     request file and insert this line into the VCALENDAR
-     section:
-METHOD:REQUEST
-     and insert this line into the VEVENT section:
-UID:<uid string>
-     where <uid string> should be unique.
-
-     If you insert the following 5 lines just before the
-     END:VEVENT line, it will enable alarms for recipients who
-     have support for them:
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:REMINDER
-TRIGGER;RELATED=START:-PT15M
-END:VALARM
-  4) Attach the calendar request file to a message.  With
-     mhbuild, be sure to specify a Content-Disposition type
-     of "inline", using {inline}.  The send -attach support
-     handles this properly.
-
-
-To respond to a calendar request:
-  1) Store the calendar request in a file using, e.g.,
-     mhstore -type text/calendar
-  2) Edit the calendar request, changing the METHOD and your
-     ATTENDEE lines to look like:
-METHOD:REPLY
-ATTENDEE;PARTSTAT=<action>;MAILTO:<your email address>
-     where <action> is one of ACCEPTED, DECLINED, TENTATIVE,
-     or DELEGATED (or COMPLETED or IN-PROCESS for to-do's).
-  3) Send that edited request by attaching it to a reply to
-     the meeting requestor; see last step under "To create and
-     send out a calendar request" above.
index 499fade..ae728d8 100755 (executable)
@@ -84,9 +84,7 @@ if [ $# -gt 0 ]; then
 fi
 
 #### Check to see that we're in a nmh source directory.
-if grep 'the authors of nmh' COPYRIGHT >/dev/null; then
-  :
-else
+if ! grep 'the authors of nmh' COPYRIGHT >/dev/null; then
   echo "$0: not in nmh source directory"
   exit 1
 fi
@@ -250,9 +248,7 @@ if [ -f Makefile ]; then
 fi
 
 /bin/rm -f "$logfile"
-if [ -f configure  -a  -f Makefile.in ]; then
-  :
-else
+if [ ! -f configure  -o  ! -f Makefile.in ]; then
   [ $verbose -ge 1 ]  &&  echo autoconfiguring . . .
   ./autogen.sh >>"$logfile" 2>&1
 fi
@@ -263,7 +259,8 @@ fi
 ####
 [ $verbose -ge 1 ]  &&  echo configuring . . .
 echo ./configure $config_opts ${smtpservers:+"$smtpservers"} >>"$logfile" 2>&1
-./configure $config_opts ${smtpservers:+"$smtpservers"} >>"$logfile" 2>&1
+./configure CFLAGS="${CFLAGS:+$CFLAGS }-g -O2 -ansi -pedantic" \
+            $config_opts ${smtpservers:+"$smtpservers"} >>"$logfile" 2>&1
 status=$?
 
 if [ $status -eq 0 ]; then
diff --git a/docs/contrib/replaliases b/docs/contrib/replaliases
new file mode 100644 (file)
index 0000000..6848dc2
--- /dev/null
@@ -0,0 +1,81 @@
+#### replaliases
+####
+#### convenience functions for various repl(1) commands
+#### They're functions instead of aliases for portability.
+
+#### If using par (see mhn.defaults), it helps to have its PARINIT
+#### environment variable set.  If you really want it to be null,
+#### either comment this out or set it to, e.g., ' '.
+#### Removed "R" from PARINIT recommendation in par(1) man page so
+#### that it doesn't consider words that are too long to be an error.
+if [ -z "$PARINIT" ]; then
+    PARINIT='rTbgq B=.,?_A_a Q=_s>|'
+    export PARINIT
+fi
+
+#### Reply, including text/html (converted to text/plain) and
+#### text/plain parts.
+####
+#### Optional arguments:
+####   -h to disable conversion of text/html parts
+####   -p to disable conversion of text/plain parts
+#### All other arguments are passed to repl.
+#### The -p argument can be useful with improperly structured
+#### messages, such as those that use multipart/related when they
+#### should have used multipart/alternative.
+rt() {
+    if [ "$1" = -h ]; then
+        shift
+        repl -filter mhl.replywithoutbody -convertargs text/plain '' "$@"
+    elif [ "$1" = -p ]; then
+        shift
+        repl -filter mhl.replywithoutbody -convertargs text/html '' "$@"
+    else
+        repl -filter mhl.replywithoutbody \
+             -convertargs text/html '' -convertargs text/plain '' "$@"
+    fi
+}
+
+
+#### Add -editor mhbuild to above.  Useful only when attachments
+#### won't be added to the message.
+####
+#### To ease editing at the What now? prompt, add a line like this to
+#### your .mh_profile:
+####     mhbuild-next: $EDITOR
+#### assuming that your EDTIOR environment variable is set; if not,
+#### replace $EDITOR above with the name of your editor.  Without that
+#### profile entry, enter "e[dit] $EDITOR" at the What now? prompt.
+rtm() {
+    rt -editor mhbuild "$@"
+}
+
+
+#### accept a calendar request
+calaccept() {
+    repl -noformat -editor mhbuild \
+         -convertargs text/calendar '-reply accept -contenttype' "$@"
+}
+
+#### decline a calendar request
+caldecline() {
+    repl -noformat -editor mhbuild \
+         -convertargs text/calendar '-reply decline -contenttype' "$@"
+}
+
+#### reply as tentative to a calendar request
+caltentative() {
+    repl -noformat -editor mhbuild \
+         -convertargs text/calendar '-reply tentative -contenttype' "$@"
+}
+
+#### cancel a calendar request
+calcancel() {
+    repl -noformat -editor mhbuild \
+         -convertargs text/calendar '-cancel -contenttype' "$@"
+}
+
+
+# Local Variables:
+# mode: sh
+# End:
index c7d4643..78c5c4a 100755 (executable)
@@ -163,7 +163,7 @@ exit 0;
 # or base64 to feed it into a formatting filter.
 #
 
-sub process_text (*$$;$)
+sub process_text
 {
        my ($input, $encoding, $charset, $boundary) = @_;
        my $text, $filterpid, $prefixpid, $finread, $finwrite;
@@ -347,7 +347,7 @@ sub process_text (*$$;$)
 # Filter HTML through a converter program
 #
 
-sub process_html (*$$;$)
+sub process_html
 {
        my ($input, $encoding, $charset, $boundary) = @_;
        my $filterpid, $prefixpid, $finread, $finwrite;
@@ -407,7 +407,7 @@ sub process_html (*$$;$)
 
                my @conv = (@htmlconv, $charset);
                exec (@conv) ||
-                               die "Unable to exec $filterprogram: $!\n";
+                               die "Unable to exec $htmlconv[0]: $!\n";
        } else {
                die "Fork for $htmlconv[0] failed: $!\n";
        }
@@ -462,7 +462,7 @@ sub process_html (*$$;$)
 # Decide what to do, based on what kind of content it is.
 #
 
-sub process_part (*$$$$;$)
+sub process_part
 {
        my ($input, $content_type, $encoding, $charset, $boundary, $name) = @_;
        my ($type, $subtype) = (split('/', $content_type, -1), '');
@@ -508,7 +508,7 @@ sub process_part (*$$$$;$)
 # the content of this part
 #
 
-sub process_multipart ($$$)
+sub process_multipart
 {
        my ($input, $subtype, $boundary) = @_;
        my $altout;
@@ -607,7 +607,7 @@ sub process_multipart ($$$)
 # "Eat" a MIME part; consume content until we hit the boundary or EOF
 #
 
-sub eat_part ($$)
+sub eat_part
 {
        my ($input, $boundary) = @_;
        my $ret;
@@ -638,7 +638,7 @@ sub eat_part ($$)
 # Return the decoder subroutine to use
 #
 
-sub find_decoder ($)
+sub find_decoder
 {
        my ($encoding) = @_;
 
@@ -654,7 +654,7 @@ sub find_decoder ($)
        }
 }
 
-sub null_decoder ($)
+sub null_decoder
 {
        my ($input) = @_;
 
@@ -665,7 +665,7 @@ sub null_decoder ($)
 # Match a line against the boundary string
 #
 
-sub match_boundary($$)
+sub match_boundary
 {
        my ($line, $boundary) = @_;
 
index fe12e69..5e70513 100644 (file)
@@ -25,9 +25,24 @@ NEW FEATURES
 - When building from source, configure will derive ${prefix} from an existing
   nmh installation if it finds one in your $PATH.
 - Added getmymbox and getmyaddr mh-format(5) function escapes.
-- The -changecur and -nochangecur switches have been added to mhfixmsg(1).
+- New -[no]changecur, -fixtype, -decodetypes, and -[no]crlflinebreaks switches
+  have been added to mhfixmsg(1).
 - mhfixmsg now removes an extraneous trailing semicolon from header
   parameter lists.
+- Added -convertargs switch to repl(1), to pass arguments to programs
+  specified in the user's profile or mhn.defaults to convert message
+  content.
+- Added mhical(1), to display, reply to, and cancel iCalendar (RFC 5545)
+  event requests.
+- added multiply format function
+- "mhparam bindir" prints the path to the directory containing the public
+  executables (${bindir}).
+- new "-prefer" switch for mhshow (and mhlist and mhshow), to allow specifying
+  the preferred content types to show, if present in a multipart alternative.
+- mh-format now has %(kilo) and %(kibi) functions, to allow printing
+  numbers as, for example, "10K", or "2.3Mi"
+- Support for the -sendmail flag to send/post to change the sendmail
+  binary when using the sendmail/pipe MTS.
 
 -----------------
 OBSOLETE FEATURES
diff --git a/etc/mhical.12hour b/etc/mhical.12hour
new file mode 100644 (file)
index 0000000..2ed1bd5
--- /dev/null
@@ -0,0 +1,49 @@
+%; mhical.12hour
+%; Form file for mhical that directs it to use 12-hour instead of 24-hour
+%; time format.
+%;
+%; See mhical.24hour for list of supported components.
+%;
+%; Here's what it does to the %{dtstart} special component:
+%;
+%; 1) Enable bold, and if the terminal supports color, green attributes.
+%; 2) If the time zone is explicit, coerce the date to the local time zone;
+%;    otherwise, assume that it's a floating time.
+%; 3) Output the date in "ddd DD MM YYYY" format.
+%; 4) Convert hours from 24- to 12-hour clock and output them as soon as
+%;    they are calculated.  Store AM or PM in the str register.
+%; 5) Output :minutes.
+%; 6) Output AM or PM.
+%; 7) If the time zone is explicit, output it.
+%; 8) Reset all terminal attributes.
+%; The timezone is not output for the dtend component because it is
+%; almost always (except across a daylight saving time transition)
+%; the same as for the dtstart component.
+%;
+%<(nonnull{method})Method: %(putstr{method})\n%>\
+%<(nonnull{organizer})Organizer: %(putstr{organizer})\n%>\
+%<(nonnull{summary})Summary: %(putstr{summary})\n%>\
+%<(nonnull{description})Description: %(putstr{description})\n%>\
+%<(nonnull{location})Location: %(putstr{location})\n%>\
+%<(nonnull{dtstart})At: \
+%<(hascolor)%(zputlit(fgcolor green))%>%(zputlit(bold))\
+%(void(szone{dtstart}))%<(gt 0)%(date2local{dtstart})%>\
+%(day{dtstart}), %02(mday{dtstart}) %(month{dtstart}) %(year{dtstart}) \
+%(void(hour{dtstart}))%<(eq 0)%(num 12)%(void(lit AM))\
+%?(eq 12)%(num 12)%(void(lit PM))\
+%?(gt 12)%2(modulo 12)%(void(lit PM))\
+%|%2(putnumf)%(void(lit AM))%>\
+:%02(min{dtstart}) \
+%(putlit)%; AM or PM
+%(void(szone{dtstart}))%<(gt 0) %(tzone{dtstart})%>\
+%(zputlit(resetterm))\n%>\
+%<(nonnull{dtstart})To: \
+%(void(szone{dtend}))%<(gt 0)%(date2local{dtend})%>\
+%(day{dtend}), %02(mday{dtend}) %(month{dtend}) %(year{dtend}) \
+%(void(hour{dtend}))%<(eq 0)%(num 12)%(void(lit AM))\
+%?(eq 12)%(num 12)%(void(lit PM))\
+%?(gt 12)%2(modulo 12)%(void(lit PM))\
+%|%2(putnumf)%(void(lit AM))%>\
+:%02(min{dtend}) \
+%(putlit)\n%>%; AM or PM
+%<(nonnull{attendees})%(putstr{attendees})\n%>\
diff --git a/etc/mhical.24hour b/etc/mhical.24hour
new file mode 100644 (file)
index 0000000..a12fc0e
--- /dev/null
@@ -0,0 +1,50 @@
+%; mhical.24hour
+%; Form file for mhical that directs it to use 24-hour time format.
+%;
+%; Default form file for mhical.  The following components
+%; are supported:
+%;     method
+%;     organizer
+%;     summary
+%;     description
+%;     location
+%;     dtstart
+%;     dtend
+%;     attendees
+%; Each corresponds to the iCalendar property of the same name as
+%; defined in RFC 5545, with the exception of "attendees".  That is a
+%; comma-delimited list of the common name (CN), if available, or
+%; email address of Attendee properties.
+%; The component names listed above are case-sensitive because
+%; fmt_findcomp() is case-sensitive.
+%;
+%; Here's what it does to the %{dtstart} special component for the
+%; event:
+%; 1) Enable bold, and if the terminal supports color, green attributes.
+%; 2) If the time zone is explicit, coerce the date to the local time zone;
+%;    otherwise, assume that it's a floating time.
+%; 3) Output the date in "ddd DD MM YYYY" format.
+%; 4) Output the time in "HH:MM" format.
+%; 5) If the time zone is explicit, output it.
+%; 6) Reset all terminal attributes.
+%; The timezone is not output for the dtend component because it is
+%; almost always (except across a daylight saving time transition)
+%; the same as for the dtstart component.
+%;
+%<(nonnull{method})Method: %(putstr{method})\n%>\
+%<(nonnull{organizer})Organizer: %(putstr{organizer})\n%>\
+%<(nonnull{summary})Summary: %(putstr{summary})\n%>\
+%<(nonnull{description})Description: %(putstr{description})\n%>\
+%<(nonnull{location})Location: %(putstr{location})\n%>\
+%<(nonnull{dtstart})At: \
+%<(hascolor)%(zputlit(fgcolor green))%>%(zputlit(bold))\
+%(void(szone{dtstart}))%<(gt 0)%(date2local{dtstart})%>\
+%(day{dtstart}), %02(mday{dtstart}) %(month{dtstart}) %(year{dtstart}) \
+%02(hour{dtstart}):%02(min{dtstart})\
+%(void(szone{dtstart}))%<(gt 0) %(tzone{dtstart})%>\
+%(zputlit(resetterm))\n%>\
+%<(nonnull{dtstart})To: \
+%(void(szone{dtend}))%<(gt 0)%(date2local{dtend})%>\
+%(day{dtend}), %02(mday{dtend}) %(month{dtend}) %(year{dtend}) \
+%02(hour{dtend}):%02(min{dtend})\n%>\
+%<(nonnull{attendees})%(putstr{attendees})\n%>\
index 7f8a567..289f0b8 100644 (file)
@@ -9,7 +9,7 @@ ignores=msgid,message-id,received,content-type,content-transfer-encoding,content
 Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%>"
 To:
 cc:
-From:decode
+From:formatfield="%(unquote(decode{text}))"
 Subject:decode
 :
 extras:nocomponent
index 8fee181..27a08d7 100644 (file)
@@ -10,7 +10,7 @@ ignores=msgid,message-id,received,content-type,content-transfer-encoding,content
 Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%>"
 To:
 cc:
-From:decode
+From:formatfield="%(unquote(decode{text}))"
 Subject:decode
 :
 extras:nocomponent
index 3498263..dffc0d2 100644 (file)
@@ -2,5 +2,5 @@
 ;
 ; default message filter for `repl' (repl -format)
 ;
-from:nocomponent,formatfield="%(decode(friendly{text})) writes:"
+from:nocomponent,formatfield="%(unquote(decode(friendly{text}))) writes:"
 body:component="> ",overflowtext="> ",overflowoffset=0
diff --git a/etc/mhl.replywithoutbody b/etc/mhl.replywithoutbody
new file mode 100644 (file)
index 0000000..41421ea
--- /dev/null
@@ -0,0 +1,5 @@
+; mhl.replywithoutbody
+;
+; message filter for `repl' (repl -format) that excludes the message body
+;
+from:nocomponent,formatfield="%(unquote(decode(friendly{text}))) writes:"
index d9d35b0..57d5b2e 100755 (executable)
@@ -25,31 +25,29 @@ fi
 TMP=/tmp/nmh_temp.$$
 trap "rm -f $TMP" 0 1 2 3 13 15
 
-PGM=`$SEARCHPROG "$SEARCHPATH" w3m`
-if [ ! -z "$PGM" ]; then
-    echo 'mhfixmsg-format-text/html: charset=%{charset}; '"\
-$PGM "'-dump ${charset:+-I "$charset"} -O utf-8 -T text/html %F' >> $TMP
+if [ ! -z `$SEARCHPROG "$SEARCHPATH" par` ]; then
+    #### The widths here correspond to those for the text browsers below.
+    textfmt=' | par 64'
+    replfmt=" | sed 's/^\(.\)/> \1/; s/^$/>/;' | par 64"
+elif [ ! -z `$SEARCHPROG "$SEARCHPATH" fmt` ]; then
+    textfmt=' | fmt'
+    replfmt=" | fmt | sed 's/^\(.\)/> \1/; s/^$/>/;'"
 else
-    PGM=`$SEARCHPROG "$SEARCHPATH" lynx`
-    if [ ! -z "$PGM" ]; then
-        #### lynx indents with 3 spaces, remove them and any trailing spaces.
-        echo 'mhfixmsg-format-text/html: charset=%{charset}; '"\
-$PGM "'-child -dump -force_html ${charset:+--assume_charset "$charset"} %F | '"\
-expand | sed -e 's/^   //' -e 's/  *$//'" >> $TMP
-    else
-        PGM=`$SEARCHPROG "$SEARCHPATH" elinks`
-        if [ ! -z "$PGM" ]; then
-          echo "mhfixmsg-format-text/html: $PGM -dump -force-html \
--no-numbering -eval 'set document.browse.margin_width = 0' %F" >> $TMP
-    fi
-  fi
+    textfmt=
+    replfmt=
 fi
+[ ! -z `$SEARCHPROG "$SEARCHPATH" iconv` ]  &&
+    charsetconv=' | iconv -f ${charset:-us-ascii} -t utf-8'"${textfmt}"  ||
+    charsetconv=
 
-echo "mhstore-store-text: %m%P.txt" >> $TMP
-echo "mhstore-store-text/html: %m%P.html" >> $TMP
-echo "mhstore-store-text/richtext: %m%P.rt" >> $TMP
-echo "mhstore-store-video/mpeg: %m%P.mpg" >> $TMP
-echo "mhstore-store-application/PostScript: %m%P.ps" >> $TMP
+cat >>"$TMP" <<'EOF'
+mhstore-store-text: %m%P.txt
+mhstore-store-text/calendar: %m%P.ics
+mhstore-store-text/html: %m%P.html
+mhstore-store-text/richtext: %m%P.rt
+mhstore-store-video/mpeg: %m%P.mpg
+mhstore-store-application/PostScript: %m%P.ps
+EOF
 
 PGM=`$SEARCHPROG "$SEARCHPATH" xwud`
 if [ ! -z "$PGM" ]; then
@@ -129,9 +127,11 @@ fi
 #### mhbuild-disposition-<type>[/<subtype>] entries are used by the
 #### WhatNow attach for deciding whether the Content-Disposition
 #### should be 'attachment' or 'inline'.  Only those values are
-#### supported.
+#### supported.  mhbuild-convert-text/html is defined below.
 ####
-cat <<EOF >> ${TMP}
+cat <<EOF >>${TMP}
+mhbuild-convert-text/calendar: mhical -infile %F -contenttype
+mhbuild-convert-text: charset=%{charset}; iconv -f \${charset:-us-ascii} -t utf-8 %F${replfmt}
 mhbuild-disposition-text/calendar: inline
 mhbuild-disposition-message/rfc822: inline
 EOF
@@ -181,6 +181,9 @@ else
     fi
 fi
 
+echo "mhshow-show-text/calendar: mhical -infile %F" >> $TMP
+echo "mhfixmsg-format-text/calendar: mhical -infile %F" >> $TMP
+
 PGM=`$SEARCHPROG "$SEARCHPATH" ivs_replay`
 if [ ! -z "$PGM" ]; then
        echo "mhshow-show-application/x-ivs: %l$PGM -o %F" >> $TMP
@@ -283,17 +286,35 @@ PGM=`$SEARCHPROG "$SEARCHPATH" w3m`
 if [ ! -z "$PGM" ]; then
     echo 'mhshow-show-text/html: charset=%{charset}; '"\
 %l$PGM"' -dump ${charset:+-I "$charset"} -T text/html %F' >> $TMP
+    echo 'mhfixmsg-format-text/html: charset=%{charset}; '"\
+$PGM "'-dump ${charset:+-I "$charset"} -O utf-8 -T text/html %F' >> $TMP
+    echo 'mhbuild-convert-text/html: charset=%{charset}; '"\
+$PGM "'-dump ${charset:+-I "$charset"} -O utf-8 -T text/html %F '"\
+${replfmt}" >> $TMP
 else
     PGM=`$SEARCHPROG "$SEARCHPATH" lynx`
     if [ ! -z "$PGM" ]; then
        echo 'mhshow-show-text/html: charset=%{charset}; '"\
 %l$PGM"' -child -dump -force-html ${charset:+--assume_charset "$charset"} %F' >> $TMP
+        #### lynx indents with 3 spaces, remove them and any trailing spaces.
+        echo 'mhfixmsg-format-text/html: charset=%{charset}; '"\
+$PGM "'-child -dump -force_html ${charset:+--assume_charset "$charset"} %F | '"\
+expand | sed -e 's/^   //' -e 's/  *$//'" >> $TMP
+        echo 'mhbuild-convert-text/html: charset=%{charset}; '"\
+$PGM "'-child -dump -force_html ${charset:+--assume_charset "$charset"} '"\
+%F${replfmt}" >> $TMP
     else
-      PGM=`$SEARCHPROG "$SEARCHPATH" elinks`
-      if [ ! -z "$PGM" ]; then
-          echo "mhshow-show-text/html: $PGM -dump -force-html \
+        PGM=`$SEARCHPROG "$SEARCHPATH" elinks`
+        if [ ! -z "$PGM" ]; then
+            echo "mhshow-show-text/html: %l$PGM -dump -force-html \
 -eval 'set document.browse.margin_width = 0' %F" >> $TMP
-      fi
+            echo "mhfixmsg-format-text/html: $PGM -dump -force-html \
+-no-numbering -eval 'set document.browse.margin_width = 0' %F" >> $TMP
+            echo "mhbuild-convert-text/html: $PGM -dump -force-html \
+-no-numbering -eval 'set document.browse.margin_width = 0' %F${replfmt}" >> $TMP
+        else
+            echo 'mhbuild-convert-text/html: cat %F' >> $TMP
+        fi
     fi
 fi
 
diff --git a/etc/mhshow.header b/etc/mhshow.header
new file mode 100644 (file)
index 0000000..2f9bafe
--- /dev/null
@@ -0,0 +1,5 @@
+%;
+%; This is provided as reference only; it shows the default content marker
+%; used by mhshow when it decides to not display content.
+%;
+[ Message %{folder}%<{folder}:%>%(msg) ]
index dd20b29..f4a20bd 100644 (file)
@@ -5,6 +5,6 @@
 %;
 %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\
 %02(mon{date})/%02(mday{date})/%(void(year{date}))%02(modulo 100)%<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}>>%>
index 741681d..97016c2 100644 (file)
@@ -5,6 +5,6 @@
 %;
 %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\
 %4(year{date})-%02(mon{date})-%02(mday{date})%<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}>>%>
index 913f2c0..53f6bb6 100644 (file)
@@ -37,8 +37,8 @@
 %;
 %; If the message is from me, print out who the message was sent to.
 %; Otherwise, print out who it was from.
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %;
 %; Print out the subject and any of th mesage body that will fit on the
 %; rest of the line.  After that, print the terminal reset code to reset
index f898e86..adca1fc 100644 (file)
@@ -6,6 +6,6 @@
 %;
 %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\
 %02(mon{date})/%02(mday{date})%<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}>>%>
index 4bffe58..0e46f15 100644 (file)
@@ -36,6 +36,6 @@
 %<(unseen)%(zputlit(lit \e[36;40m))%>\
 %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\
 %02(mon{date})/%02(mday{date})%<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}>>%>%(zputlit(lit \e[m))
index 27d18d3..942e265 100644 (file)
@@ -2,8 +2,8 @@
 %<{status} %|N%>\
 %<{replied}R%?{encrypted}E%| %>\
 %4(msg) \
-%<(mymbox{from})%<{to}To: %13(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%> \
+%<(mymbox{from})%<{to}To: %13(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%> \
 %3(day{date}) %3(month{date}) %02(mday{date}) \
 %02(hour{date}):%02(min{date}) \
 %(decode{subject})
index a6b6698..9b48078 100644 (file)
@@ -1,6 +1,6 @@
 %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\
 %02(mon{date})/%02(mday{date})%<{date} %|*%>\
 %5(size) \
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}%>
index ee54a52..ff37628 100644 (file)
@@ -2,6 +2,6 @@
 %02(mon{date})/%02(mday{date}) \
 %02(hour{date}):%02(min{date})%3(tzone{date})\
 %<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}%>
index 06c068c..de09b8b 100644 (file)
@@ -5,6 +5,6 @@
 %?(gt 86400) %(day{date}) %|\
 %02(hour{date}):%02(min{date})%>\
 %<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}%>
index d1cd195..1b254d4 100644 (file)
@@ -1,5 +1,5 @@
 %4(msg)%<(cur)+%| %>%<(unseen)U%| %>%<{replied}-%?{encrypted}E%| %>\
 %02(mon{date})/%02(mday{date})%<{date} %|*%>\
-%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\
-%<(zero)%17(decode(friendly{from}))%>  \
+%<(mymbox{from})%<{to}To:%14(unquote(decode(friendly{to})))%>%>\
+%<(zero)%17(unquote(decode(friendly{from})))%>  \
 %(decode{subject})%<{body}<<%{body}>>%>
index 9e94b3c..f34613a 100644 (file)
  */
 
 /* types that output text */
-#define FT_COMP         1       /* the text of a component                 */
-#define FT_COMPF        2       /* comp text, filled                       */
-#define FT_LIT          3       /* literal text                            */
-#define FT_LITF         4       /* literal text, filled                    */
-#define FT_CHAR         5       /* a single ascii character                */
-#define FT_NUM          6       /* "value" as decimal number               */
-#define FT_NUMF         7       /* "value" as filled dec number            */
-#define FT_STR          8       /* "str" as text                           */
-#define FT_STRF         9       /* "str" as text, filled                   */
-#define FT_STRFW        10      /* "str" as text, filled, width in "value" */
-#define FT_STRLIT       11      /* "str" as text, no space compression     */
-#define FT_STRLITZ      12      /* literal text with zero display width    */
-#define FT_PUTADDR      13      /* split and print address line            */
+#define FT_COMP          1       /* the text of a component                 */
+#define FT_COMPF         2       /* comp text, filled                       */
+#define FT_LIT           3       /* literal text                            */
+#define FT_LITF          4       /* literal text, filled                    */
+#define FT_CHAR          5       /* a single ascii character                */
+#define FT_NUM           6       /* "value" as decimal number               */
+#define FT_NUMF          7       /* "value" as filled dec number            */
+#define FT_STR           8       /* "str" as text                           */
+#define FT_STRF          9       /* "str" as text, filled                   */
+#define FT_STRFW         10      /* "str" as text, filled, width in "value" */
+#define FT_STRLIT        11      /* "str" as text, no space compression     */
+#define FT_STRLITZ       12      /* literal text with zero display width    */
+#define FT_PUTADDR       13      /* split and print address line            */
 
 /* types that modify the "str" or "value" registers                     */
-#define FT_LS_COMP      14      /* set "str" to component text          */
-#define FT_LS_LIT       15      /* set "str" to literal text            */
-#define FT_LS_GETENV    16      /* set "str" to getenv(text)            */
-#define FT_LS_CFIND     17      /* set "str" to context_find(text)      */
-#define FT_LS_DECODECOMP 18     /* set "str" to decoded component text  */
-#define FT_LS_DECODE    19      /* decode "str" as RFC-2047 header      */
-#define FT_LS_TRIM      20      /* trim trailing white space from "str" */
-#define FT_LV_COMP      21      /* set "value" to comp (as dec. num)    */
-#define FT_LV_COMPFLAG  22      /* set "value" to comp flag word        */
-#define FT_LV_LIT       23      /* set "value" to literal num           */
-#define FT_LV_DAT       24      /* set "value" to dat[n]                */
-#define FT_LV_STRLEN    25      /* set "value" to length of "str"       */
-#define FT_LV_PLUS_L    26      /* set "value" += literal               */
-#define FT_LV_MINUS_L   27      /* set "value" -= literal               */
-#define FT_LV_DIVIDE_L  28      /* set "value" to value / literal       */
-#define FT_LV_MODULO_L  29      /* set "value" to value % literal       */
-#define FT_LV_CHAR_LEFT 30      /* set "value" to char left in output   */
+#define FT_LS_COMP       14      /* set "str" to component text          */
+#define FT_LS_LIT        15      /* set "str" to literal text            */
+#define FT_LS_GETENV     16      /* set "str" to getenv(text)            */
+#define FT_LS_CFIND      17      /* set "str" to context_find(text)      */
+#define FT_LS_DECODECOMP 18      /* set "str" to decoded component text  */
+#define FT_LS_DECODE     19      /* decode "str" as RFC-2047 header      */
+#define FT_LS_TRIM       20      /* trim trailing white space from "str" */
+#define FT_LV_COMP       21      /* set "value" to comp (as dec. num)    */
+#define FT_LV_COMPFLAG   22      /* set "value" to comp flag word        */
+#define FT_LV_LIT        23      /* set "value" to literal num           */
+#define FT_LV_DAT        24      /* set "value" to dat[n]                */
+#define FT_LV_STRLEN     25      /* set "value" to length of "str"       */
+#define FT_LV_PLUS_L     26      /* set "value" += literal               */
+#define FT_LV_MINUS_L    27      /* set "value" -= literal               */
+#define FT_LV_MULTIPLY_L 28      /* set "value" to value * literal       */
+#define FT_LV_DIVIDE_L   29      /* set "value" to value / literal       */
+#define FT_LV_MODULO_L   30      /* set "value" to value % literal       */
+#define FT_LV_CHAR_LEFT  31      /* set "value" to char left in output   */
 
-#define FT_LS_MONTH     31      /* set "str" to tws month                   */
-#define FT_LS_LMONTH    32      /* set "str" to long tws month              */
-#define FT_LS_ZONE      33      /* set "str" to tws timezone                */
-#define FT_LS_DAY       34      /* set "str" to tws weekday                 */
-#define FT_LS_WEEKDAY   35      /* set "str" to long tws weekday            */
-#define FT_LS_822DATE   36      /* set "str" to 822 date str                */
-#define FT_LS_PRETTY    37      /* set "str" to pretty (?) date str         */
-#define FT_LS_KILO      38      /* set "str" to "<value>[KMGT]"             */
-#define FT_LS_KIBI      39      /* set "str" to "<value>[KMGT]"             */
-#define FT_LV_SEC       40      /* set "value" to tws second                */
-#define FT_LV_MIN       41      /* set "value" to tws minute                */
-#define FT_LV_HOUR      42      /* set "value" to tws hour                  */
-#define FT_LV_MDAY      43      /* set "value" to tws day of month          */
-#define FT_LV_MON       44      /* set "value" to tws month                 */
-#define FT_LV_YEAR      45      /* set "value" to tws year                  */
-#define FT_LV_YDAY      46      /* set "value" to tws day of year           */
-#define FT_LV_WDAY      47      /* set "value" to tws weekday               */
-#define FT_LV_ZONE      48      /* set "value" to tws timezone              */
-#define FT_LV_CLOCK     49      /* set "value" to tws clock                 */
-#define FT_LV_RCLOCK    50      /* set "value" to now - tws clock           */
-#define FT_LV_DAYF      51      /* set "value" to tws day flag              */
-#define FT_LV_DST       52      /* set "value" to tws daylight savings flag */
-#define FT_LV_ZONEF     53      /* set "value" to tws timezone flag         */
+#define FT_LS_MONTH      32      /* set "str" to tws month                   */
+#define FT_LS_LMONTH     33      /* set "str" to long tws month              */
+#define FT_LS_ZONE       34      /* set "str" to tws timezone                */
+#define FT_LS_DAY        35      /* set "str" to tws weekday                 */
+#define FT_LS_WEEKDAY    36      /* set "str" to long tws weekday            */
+#define FT_LS_822DATE    37      /* set "str" to 822 date str                */
+#define FT_LS_PRETTY     38      /* set "str" to pretty (?) date str         */
+#define FT_LS_KILO       39      /* set "str" to "<value>[KMGT]"             */
+#define FT_LS_KIBI       40      /* set "str" to "<value>[KMGT]"             */
+#define FT_LV_SEC        41      /* set "value" to tws second                */
+#define FT_LV_MIN        42      /* set "value" to tws minute                */
+#define FT_LV_HOUR       43      /* set "value" to tws hour                  */
+#define FT_LV_MDAY       44      /* set "value" to tws day of month          */
+#define FT_LV_MON        45      /* set "value" to tws month                 */
+#define FT_LV_YEAR       46      /* set "value" to tws year                  */
+#define FT_LV_YDAY       47      /* set "value" to tws day of year           */
+#define FT_LV_WDAY       48      /* set "value" to tws weekday               */
+#define FT_LV_ZONE       49      /* set "value" to tws timezone              */
+#define FT_LV_CLOCK      50      /* set "value" to tws clock                 */
+#define FT_LV_RCLOCK     51      /* set "value" to now - tws clock           */
+#define FT_LV_DAYF       52      /* set "value" to tws day flag              */
+#define FT_LV_DST        53      /* set "value" to tws daylight savings flag */
+#define FT_LV_ZONEF      54      /* set "value" to tws timezone flag         */
 
-#define FT_LS_PERS      54      /* set "str" to person part of addr    */
-#define FT_LS_MBOX      55      /* set "str" to mbox part of addr      */
-#define FT_LS_HOST      56      /* set "str" to host part of addr      */
-#define FT_LS_PATH      57      /* set "str" to route part of addr     */
-#define FT_LS_GNAME     58      /* set "str" to group part of addr     */
-#define FT_LS_NOTE      59      /* set "str" to comment part of addr   */
-#define FT_LS_ADDR      60      /* set "str" to mbox@host              */
-#define FT_LS_822ADDR   61      /* set "str" to 822 format addr        */
-#define FT_LS_FRIENDLY  62      /* set "str" to "friendly" format addr */
-#define FT_LV_HOSTTYPE  63      /* set "value" to addr host type       */
-#define FT_LV_INGRPF    64      /* set "value" to addr in-group flag   */
-#define FT_LS_UNQUOTE   65      /* remove RFC 2822 quotes from "str"   */
-#define FT_LV_NOHOSTF   66      /* set "value" to addr no-host flag */
+#define FT_LS_PERS       55      /* set "str" to person part of addr    */
+#define FT_LS_MBOX       56      /* set "str" to mbox part of addr      */
+#define FT_LS_HOST       57      /* set "str" to host part of addr      */
+#define FT_LS_PATH       58      /* set "str" to route part of addr     */
+#define FT_LS_GNAME      59      /* set "str" to group part of addr     */
+#define FT_LS_NOTE       60      /* set "str" to comment part of addr   */
+#define FT_LS_ADDR       61      /* set "str" to mbox@host              */
+#define FT_LS_822ADDR    62      /* set "str" to 822 format addr        */
+#define FT_LS_FRIENDLY   63      /* set "str" to "friendly" format addr */
+#define FT_LV_HOSTTYPE   64      /* set "value" to addr host type       */
+#define FT_LV_INGRPF     65      /* set "value" to addr in-group flag   */
+#define FT_LS_UNQUOTE    66      /* remove RFC 2822 quotes from "str"   */
+#define FT_LV_NOHOSTF    67      /* set "value" to addr no-host flag */
 
 /* Date Coercion */
-#define FT_LOCALDATE    67      /* Coerce date to local timezone */
-#define FT_GMTDATE      68      /* Coerce date to gmt            */
+#define FT_LOCALDATE     68      /* Coerce date to local timezone */
+#define FT_GMTDATE       69      /* Coerce date to gmt            */
 
 /* pre-format processing */
-#define FT_PARSEDATE    69      /* parse comp into a date (tws) struct */
-#define FT_PARSEADDR    70      /* parse comp into a mailaddr struct   */
-#define FT_FORMATADDR   71      /* let external routine format addr    */
-#define FT_CONCATADDR   72      /* formataddr w/out duplicate removal  */
-#define FT_MYMBOX       73      /* do "mymbox" test on comp            */
-#define FT_GETMYMBOX    74      /* return "mymbox" mailbox string      */
-#define FT_GETMYADDR    75      /* return "mymbox" addr string         */
+#define FT_PARSEDATE     70      /* parse comp into a date (tws) struct */
+#define FT_PARSEADDR     71      /* parse comp into a mailaddr struct   */
+#define FT_FORMATADDR    72      /* let external routine format addr    */
+#define FT_CONCATADDR    73      /* formataddr w/out duplicate removal  */
+#define FT_MYMBOX        74      /* do "mymbox" test on comp            */
+#define FT_GETMYMBOX     75      /* return "mymbox" mailbox string      */
+#define FT_GETMYADDR     76      /* return "mymbox" addr string         */
 
 /* conditionals & control flow (must be last) */
-#define FT_SAVESTR      76      /* save current str reg               */
-#define FT_DONE         77      /* stop formatting                    */
-#define FT_PAUSE        78      /* pause                              */
-#define FT_NOP          79      /* nop                                */
-#define FT_GOTO         80      /* (relative) goto                    */
-#define FT_IF_S_NULL    81      /* test if "str" null                 */
-#define FT_IF_S         82      /* test if "str" non-null             */
-#define FT_IF_V_EQ      83      /* test if "value" = literal          */
-#define FT_IF_V_NE      84      /* test if "value" != literal         */
-#define FT_IF_V_GT      85      /* test if "value" > literal          */
-#define FT_IF_MATCH     86      /* test if "str" contains literal     */
-#define FT_IF_AMATCH    87      /* test if "str" starts with literal  */
-#define FT_S_NULL       88      /* V = 1 if "str" null                */
-#define FT_S_NONNULL    89      /* V = 1 if "str" non-null            */
-#define FT_V_EQ         90      /* V = 1 if "value" = literal         */
-#define FT_V_NE         91      /* V = 1 if "value" != literal        */
-#define FT_V_GT         92      /* V = 1 if "value" > literal         */
-#define FT_V_MATCH      93      /* V = 1 if "str" contains literal    */
-#define FT_V_AMATCH     94      /* V = 1 if "str" starts with literal */
+#define FT_SAVESTR       77      /* save current str reg               */
+#define FT_DONE          78      /* stop formatting                    */
+#define FT_PAUSE         79      /* pause                              */
+#define FT_NOP           80      /* nop                                */
+#define FT_GOTO          81      /* (relative) goto                    */
+#define FT_IF_S_NULL     82      /* test if "str" null                 */
+#define FT_IF_S          83      /* test if "str" non-null             */
+#define FT_IF_V_EQ       84      /* test if "value" = literal          */
+#define FT_IF_V_NE       85      /* test if "value" != literal         */
+#define FT_IF_V_GT       86      /* test if "value" > literal          */
+#define FT_IF_MATCH      87      /* test if "str" contains literal     */
+#define FT_IF_AMATCH     88      /* test if "str" starts with literal  */
+#define FT_S_NULL        89      /* V = 1 if "str" null                */
+#define FT_S_NONNULL     90      /* V = 1 if "str" non-null            */
+#define FT_V_EQ          91      /* V = 1 if "value" = literal         */
+#define FT_V_NE          92      /* V = 1 if "value" != literal        */
+#define FT_V_GT          93      /* V = 1 if "value" > literal         */
+#define FT_V_MATCH       94      /* V = 1 if "str" contains literal    */
+#define FT_V_AMATCH      95      /* V = 1 if "str" starts with literal */
 
-#define IF_FUNCS FT_S_NULL      /* start of "if" functions */
+#define IF_FUNCS FT_S_NULL       /* start of "if" functions */
diff --git a/h/icalendar.h b/h/icalendar.h
new file mode 100644 (file)
index 0000000..a1a0122
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * icalendar.h -- data structures and common code for icalendar scanner,
+ *                parser, and application code
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh.  See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+/*
+ * Types used in struct contentline below.
+ */
+typedef struct value_list {
+    char *value;
+    struct value_list *next;
+} value_list;
+
+typedef struct param_list {
+    char *param_name;          /* Name of property parameter. */
+    struct value_list *values; /* List of its values. */
+    struct param_list *next;   /* Next node in list of property parameters. */
+} param_list;
+
+typedef enum cr_indicator {
+    CR_UNSET = 0,
+    LF_ONLY,
+    CR_BEFORE_LF
+} cr_indicator;
+
+/*
+ * Each (unfolded) line in the .ics file is represented by a struct
+ * contentline.
+ */
+typedef struct contentline {
+    char *name;                /* The name of the property, calprops,
+                                  or BEGIN. */
+    struct param_list *params; /* List parameters. */
+    char *value;               /* Everything after the ':'. */
+
+    char *input_line;          /* The (unfolded) input line. */
+    size_t input_line_len;     /* Amount of text stored in input_line. */
+    size_t input_line_size;    /* Size of allocated input_line. */
+
+    cr_indicator cr_before_lf; /* To support CR before LF.  If the first
+                                  line of the input has a CR before its LF,
+                                  assume that all output lines need to.
+                                  Only used in root node. */
+
+    struct contentline *next;  /* Next node in list of content lines. */
+    struct contentline *last;  /* Last node of list.  Only used in root node. */
+} contentline;
+
+
+/*
+ * List of vevents, each of which is a list of contentlines.
+ */
+typedef struct vevent {
+    contentline *contentlines;
+    struct vevent *next;
+    /* The following is only used in the root node. */
+    struct vevent *last;
+} vevent;
+
+
+/*
+ * Exported functions.
+ */
+void remove_contentline (contentline *);
+contentline *add_contentline (contentline *, const char *);
+void add_param_name (contentline *, char *);
+void add_param_value (contentline *, char *);
+void remove_value (value_list *);
+struct contentline *find_contentline (contentline *, const char *,
+                                      const char *);
+void free_contentlines (contentline *);
+
+typedef struct tzdesc *tzdesc_t;
+tzdesc_t load_timezones (const contentline *);
+void free_timezones (tzdesc_t);
+char *format_datetime (tzdesc_t, const contentline *);
+
+/*
+ * The following provide access to, and by, the iCalendar parser.
+ */
+
+/* This YYSTYPE definition prevents problems with Solaris yacc, which
+   has declarations of two variables of type YYSTYPE * in one
+   statement. */
+typedef char *charptr;
+#define YYSTYPE charptr
+
+extern int icaldebug;
+int icalparse (void);
+extern vevent vevents;
+int icallex (void);
+
+/* And this is for the icalendar scanner. */
+extern YYSTYPE icallval;
+
+/*
+ * For directing the scanner to use a files other than stdin/stdout.
+ * These don't use the accessors provided by modern flex because
+ * flex 2.5.4 doesn't supply them.
+ */
+void icalset_inputfile (FILE *);
+void icalset_outputfile (FILE *);
diff --git a/h/mh.h b/h/mh.h
index 71315b6..8dcad65 100644 (file)
--- a/h/mh.h
+++ b/h/mh.h
@@ -377,7 +377,6 @@ typedef struct m_getfld_state *m_getfld_state_t;
 #define        MS_UNKNOWN      1       /* type not known yet         */
 #define        MS_MBOX         2       /* Unix-style "from" lines    */
 #define        MS_MMDF         3       /* string mmdlm2              */
-#define        MS_MSH          4       /* whacko msh                 */
 
 #define        NOUSE   0               /* draft being re-used */
 
index 371e671..5d7668f 100644 (file)
@@ -6,7 +6,7 @@
 
 #define        NPARTS  50
 #define        NTYPES  20
-#define        NPARMS  10
+#define        NPREFS  20
 
 /*
  * Abstract type for header fields
@@ -193,6 +193,10 @@ struct Content {
 /* Structure for text content */
 struct text {
     int        tx_charset;             /* flag for character set */
+    int lf_line_endings;        /* Whether to use CR LF (0) or LF (1) line
+                                   endings.  The meaning of 0 was selected so
+                                   that CR LF is the default, in accordance
+                                   with RFC 2046, Sec. 4.1.1, Par. 1. */
 };
 
 /*
@@ -338,6 +342,8 @@ void close_encoding (CT);
 void free_content (CT);
 char *ct_type_str (int);
 char *ct_subtype_str (int, int);
+int ct_str_type (const char *);
+int ct_str_subtype (int, const char *);
 const struct str2init *get_ct_init (int);
 const char *ce_str (int);
 const struct str2init *get_ce_method (const char *);
@@ -500,8 +506,7 @@ char *get_param_value(PM pm, char replace);
  * markerform  - The name of a file containg mh-format(5) code used to
  *               display markers about non-displayed MIME parts.
  */
-void show_all_messages(CT *cts, int concat, int textonly, int inlineonly,
-                      char *markerform);
+void show_all_messages(CT *cts, int concat, int textonly, int inlineonly);
 
 /*
  * Display (or store) a single MIME part using the specified command
index 9fcaf5d..9cac94e 100644 (file)
--- a/h/mime.h
+++ b/h/mime.h
 #define        DESCR_FIELD     "Content-Description"
 #define        DISPO_FIELD     "Content-Disposition"
 #define        MD5_FIELD       "Content-MD5"
-#define PSEUDOHEADER_PREFIX            "Nmh-"
-#define        ATTACH_FIELD                    PSEUDOHEADER_PREFIX "Attach"
-#define        ATTACH_FIELD_ALT                "Attach"
+#define        PSEUDOHEADER_PREFIX        "Nmh-"
+#define        ATTACH_FIELD               PSEUDOHEADER_PREFIX "Attach"
+#define        ATTACH_FIELD_ALT           "Attach"
+#define        MHBUILD_FILE_PSEUDOHEADER  PSEUDOHEADER_PREFIX "mhbuild-file-"
+#define        MHBUILD_ARGS_PSEUDOHEADER  PSEUDOHEADER_PREFIX "mhbuild-args-"
 
 #define        isatom(c)   (isascii((unsigned char) c) \
                      && !isspace ((unsigned char) c) \
diff --git a/h/nmh.h b/h/nmh.h
index ca32f58..2468919 100644 (file)
--- a/h/nmh.h
+++ b/h/nmh.h
@@ -74,5 +74,3 @@
 
 #define DEFAULT_PAGER "more"
 #define DEFAULT_EDITOR "vi"
-
-#include <signal.h>
index 5c104a7..33ac013 100644 (file)
@@ -384,7 +384,7 @@ int unputenv (char *);
  *
  */
 void unquote_string(const char *input, char *output);
-int uprf (char *, char *);
+int uprf (const char *, const char *);
 int vfgets (FILE *, char **);
 
 /*
index 0fbdddd..1876107 100644 (file)
@@ -1,4 +1,4 @@
-.TH MH-FOLDERS %manext5% "July 22, 2014" "%nmhversion%"
+.TH MH-FOLDERS %manext5% "February 25, 2016" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -153,6 +153,18 @@ platform, and it is for popular platforms.  That default should also
 be the same as used by the
 .B mail
 program, if provided on the platform.
+.PP
+.SS Naming
+.B nmh
+folders can be given arbitrary names, with one exception:
+folders should not be given all-numeric names.  This
+limitation results from
+.B nmh
+messages themselves being stored
+in numerically named files -- allowing folders to be named
+similarly would make
+.B nmh
+slower, and introduce usage ambiguities.
 .SH FILES
 .fc ^ ~
 .nf
index 1e2f41a..baad070 100644 (file)
@@ -1,4 +1,4 @@
-.TH MH-FORMAT %manext5% "October 27, 2014" "%nmhversion%"
+.TH MH-FORMAT %manext5% "January 10, 2015" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -137,7 +137,7 @@ For string valued functions or components, the condition is true
 if the function return or component value is 
 a non-empty string, and false for an empty string.
 .PP
-The `%?' control escape is optional, and may there may be more
+The `%?' control escape is optional, and there may be more
 than one `%?' control escape in a conditional block.
 The `%|' control escape
 is also optional, but may be included at most once.
@@ -268,6 +268,7 @@ match       literal boolean \fIstr\fR contains \fIarg\fR
 amatch literal boolean \fIstr\fR starts with \fIarg\fR
 plus   literal integer \fIarg\fR plus \fInum\fR
 minus  literal integer \fIarg\fR minus \fInum\fR
+multiply       literal integer \fInum\fR multiplied by \fIarg\fR
 divide literal integer \fInum\fR divided by \fIarg\fR
 modulo literal integer \fInum\fR modulo \fIarg\fR
 num    literal integer Set \fInum\fR to \fIarg\fR.
index 51f0607..7a4bc81 100644 (file)
@@ -1,4 +1,4 @@
-.TH MH\-MIME %manext7% "March 16, 2014" "%nmhversion%"
+.TH MH\-MIME %manext7% "December 14, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -141,8 +141,11 @@ command at the \*(lqWhat now?\*(rq prompt to process them.
 When replying to messages using
 .IR repl (1)
 the traditional MH method of including the original text in the reply does
-not interoperate with MIME messages.  As of this writing there is no
-native solution for addressing this issue, but the contrib directory
+not interoperate with MIME messages.  The
+.B \-convertargs
+switch to
+.IR repl (1)
+provides one solution.  Another solution:  the contrib directory
 .RI ( %docdir%/contrib )
 contains a Perl program called
 .B replyfilter
index 81e2ccb..cdd7812 100644 (file)
@@ -1,4 +1,4 @@
-.TH MH-PROFILE %manext5% "April 18, 2014" "%nmhversion%"
+.TH MH-PROFILE %manext5% "December 21, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -44,6 +44,15 @@ context, and indicates what the default value is.  Note that a profile
 component can only appear once.  Multiple appearances with trigger a
 warning that all appearances after the first are ignored.
 .PP
+Some MH programs, including
+.BR mhbuild ,
+.BR mhshow ,
+and
+.BR mhstore ,
+have specific profile components that are described in their respective
+man pages.  Each component name begins with the name of the program and
+is followed by a dash.
+.PP
 .BR Path :
 Mail
 .RS 5
@@ -1045,6 +1054,9 @@ if annotations are to occur.
 .SH "SEE ALSO"
 .IR environ (5),
 .IR mh-sequence (5),
+.IR mhbuild (1),
+.IR mhshow (1),
+.IR mhstore (1),
 .IR nmh (7)
 .SH HISTORY
 The
index 4b261f3..21f7d9c 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHBUILD %manext1% "March 13, 2014" "%nmhversion%"
+.TH MHBUILD %manext1% "December 14, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -19,7 +19,7 @@ mhbuild \- translate MIME composition draft
 .RB [ \-verbose " | " \-noverbose ]
 .RB [ \-disposition " | " \-nodisposition ]
 .RB [ \-check " | " \-nocheck ]
-.RB [ \-headerencoding 
+.RB [ \-headerencoding
 .IR encoding\-algorithm
 .RB " | " \-autoheaderencoding ]
 .RB [ \-maxunencoded
@@ -119,6 +119,160 @@ to supply the disposition value.  The only supported values are
 .I attachment
 and
 .IR inline.
+.SS "Convert Interface"
+.nr item 1 1
+The \*(lqconvert\*(rq interface is a powerful mechanism that supports
+replying to MIME messages.  These placeholders are used in the following
+description:
+.IP TYPE
+content type/subtype
+.IP CONVERTER
+external program, and any fixed arguments, to convert content, such as
+from a request to a reply
+.IP ARGSTRING
+arguments to pass from
+.B repl
+to
+.I CONVERTER
+.IP FILE
+full path of message being replied to
+.PP
+.RE
+The convert support is based on pseudoheaders of the form
+.PP
+.RS 5
+  Nmh-mhbuild-file-TYPE: FILE
+  Nmh-mhbuild-args-TYPE: ARGSTRING
+.RE
+.PP
+in the draft.  For each such pseudoheader, mhbuild looks in the
+profile and mhn.defaults for this corresponding TYPE entry to find the
+converter that supports it:
+.PP
+.RS 5
+.RI mhbuild-convert- TYPE :
+.I CONVERTER
+.RE
+.PP
+It's a fatal error if no such entry is found for TYPE.  An empty
+entry, e.g.,
+.PP
+.RS 5
+mhbuild-convert-text/html:
+.RE
+.PP
+excludes parts of that TYPE from the draft.  The mhn.defaults file
+contains default
+.B mhbuild-convert-text/html
+and
+.BR mhbuild-convert-text/plain
+entries.  Profile entries can be used to override corresponding
+mhn.defaults entries, as usual.
+.PP
+For each
+.I TYPE
+part in
+.IR FILE ,
+.B mhbuild
+runs
+.I CONVERTER ARGSTRING
+on the content of the part.
+.PP
+Each part in
+.I FILE
+that has no corresponding TYPE entry in the profile or mhn.defaults is
+excluded from the draft; the user can include them using mhbuild
+directives as usual.
+.PP
+.B repl
+inserts Nmh-mhbuild-text/html: and Nmh-mhbuild-text/plain:
+pseudoheaders in every draft.  The user can prevent insertion of
+content parts of either of those types by putting corresponding empty
+entries in their profile.
+.PP
+Only the highest precedence alternative with a supported
+.I TYPE
+of a multipart/alternative part is used.
+.PP
+mhn.defaults.sh selects the text/html-to-text/plain converter at
+install time.  It includes
+.BR iconv "(1),"
+and
+.BR par (1)
+or
+.BR fmt "(1),"
+in the pipeline only if found.
+.PP
+Some content types require the addition of parameters to the
+Content-Type header, such as
+.I method=REPLY
+for text/calendar.  mhbuild looks for a Content-Type header, followed
+by a blank line, at the beginning of the converter output.  If one is
+found, it is used for the corresponding part in the reply draft.
+.PP
+The \*(lqconvert\*(rq interface doesn't support different
+.IR ARGSTRING s
+or different converters for different parts of the same
+.IR  TYPE .
+That would require associating parts by part number with the
+.IR ARGSTRING s
+or converters.  Instead, that can be done (currently, without using
+the convert support), with
+.B mhbuild
+directives as described below, e.g.,
+.PP
+.RS 5
+#text/html; charset=utf-8 *8bit | mhstore -noverbose -part 42.7 -outfile - | w3m -dump -cols 64 -T text/html -O utf-8
+.RE
+.PP
+The only way to mix
+.B convert
+pseudoheaders and mhbuild directives is to insert the directives before
+.B mhbuild
+is run, which is typically done by entering
+.I mime
+at the \*(lqWhat now?\*(rq prompt, or with an
+.B \-editor mhbuild
+switch.
+.PP
+These (optional) setup steps can make the convert support
+easier to use:
+.IP \n[item]. 3
+If the
+.BR par (1)
+program is installed on your system, it will be set by default
+(in mhn.defaults) to filter the converter output.  It helps to
+set the
+.B $PARINIT
+environment variable, as described in its man page.
+.IP \n+[item]. 3
+Add this line to your profile:
+.PP
+.RS 5
+.nf
+mhbuild-next: $EDITOR
+.fi
+.RE
+.PP
+.RS 3
+assuming that your EDTIOR environment variable is set; if not, replace
+$EDITOR with the name of your editor.  Without that profile entry, a
+response of \*(lqe[dit]\*(rq at the What now? prompt will require
+specification of your editor if an
+.B \-editor mhbuild
+switch is used.
+.RE
+.IP \n+[item]. 3
+If using
+.BR repl ,
+source the Bourne-shell compatible functions in
+.IR %docdir%/contrib/replaliases .
+.br
+That script also sets the
+.B $PARINIT
+environment variable if it was not set.
+.RE
+.PP
 .SS "Translating the Composition File"
 .B mhbuild
 is essentially a filter to aid in the composition of MIME
@@ -180,7 +334,7 @@ errors:
 .nf
 #off
 #include <stdio.h>
-printf("Hello, World!);
+printf("Hello, World!");
 #pop
 .fi
 .RE
@@ -522,7 +676,7 @@ The
 switch will indicate which algorithm to use when encoding any message headers
 that contain 8\-bit characters.  The valid arguments are
 .I base64
-for based\-64 encoding and 
+for based\-64 encoding and
 .I quoted
 for quoted\-printable encoding.  The
 .B \-autoheaderencoding
@@ -610,7 +764,7 @@ to translate the composition file into MIME format.
 .PP
 Normally it is an error to invoke
 .B mhbuild
-on file that already in MIME format.  The 
+on file that already in MIME format.  The
 .B \-auto
 switch will cause
 .B mhbuild
@@ -770,7 +924,10 @@ is checked.
 .SH "SEE ALSO"
 .IR mhlist (1),
 .IR mhshow (1),
-.IR mhstore (1)
+.IR mhstore (1),
+.IR fmt (1),
+.IR iconv (1),
+.IR par (1)
 .PP
 .I "Proposed Standard for Message Encapsulation"
 (RFC 934),
index 3bb8e84..eb9595d 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHFIXMSG %manext1% "November 30, 2014" "%nmhversion%"
+.TH MHFIXMSG %manext1% "March 12, 2016" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -16,6 +16,9 @@ mhfixmsg \- rewrite MIME messages with various transformations
 .RB [ \-decodetext
 8bit/7bit |
 .BR \-nodecodetext ]
+.RB [ \-decodetypes
+.IR "type/[subtype][,...]" ]
+.RB [ \-crlflinebreaks " | " \-nocrlflinebreaks ]
 .RB [ \-textcharset
 .I charset
 .RB "| " \-notextcharset ]
@@ -23,6 +26,8 @@ mhfixmsg \- rewrite MIME messages with various transformations
 .RB [ \-replacetextplain " | " \-noreplacetextplain ]
 .RB [ \-fixboundary " | " \-nofixboundary ]
 .RB [ \-fixcte " | " \-nofixcte ]
+.RB [ \-fixtype
+.IR mimetype ]
 .RB [ \-outfile
 .IR outfile ]
 .RB [ \-rmmproc
@@ -49,7 +54,9 @@ messages.
 .B mhfixmsg
 passes non-MIME messages through without any transformations.  If no
 transformations apply to a MIME message, the original message or file
-is not modified or removed.
+is not modified or removed.  Thus,
+.B mhfixmsg
+can safely be run multiple times on a message.
 .PP
 The
 .B \-decodetext
@@ -70,6 +77,34 @@ linefeed character is removed from text parts encoded in ASCII,
 ISO-8859-x, UTF-8, or Windows-12xx.
 .PP
 The
+.B \-decodetypes
+switch specifies the message parts, by type and optionally subtype,
+to which
+.B \-decodetext
+applies.  Its argument is a comma-separated list of type/subtype
+elements.  If an element does not contain a subtype, then
+.B \-decodetext
+applies to all subtypes of the type.  The default is
+.B \-decodetypes
+.IR text ;
+it can be overridden, e.g., with
+.B \-decodetypes
+.I text/plain
+to restrict
+.B \-decodetext
+to just text/plain parts.
+.PP
+By default, carriage return characters are preserved or inserted at
+the end of each line of text content.  The
+.B \-crlflinebreaks
+switch selects this behavior and is enabled by default.  The
+.B \-nocrlflinebreaks
+switch causes carriage return characters to be stripped from, and not
+inserted in, text content when it is decoded and encoded.  Note that
+its use can cause the generation of MIME messages that do not conform
+with RFC 2046, Â§4.1.1, paragraph 1.
+.PP
+The
 .B \-textcharset
 switch specifies that all text/plain parts of the message(s)
 should be converted to
@@ -102,6 +137,17 @@ inserts that text/plain part at the beginning of the containing
 multipart/alternative part, if present.  If not, it creates a
 multipart/alternative part.
 .PP
+With the
+.B \-reformat
+switch, multipart/related parts are handled differently than
+multipart/alternative.  If the multipart/related has only a single
+part that is not text/plain and can be converted to text/plain, a
+text/plain part is added and the type of the part is changed to
+multipart/alternative.  If the multipart/related has more than one
+part but does not have a text/plain part,
+.B mhfixmsg
+tries to add one.
+.PP
 The
 .B \-replacetextplain
 switch broadens the applicability of
@@ -142,13 +188,21 @@ The
 switch enables a transformation to change the
 Content-Transfer-Encoding from an invalid value to 8bit in message
 parts with a Content-Type of multipart, as required by RFC 2045,
-Section 6.4.  That condition is indicated by a \*(lqmust be encoded in
+§6.4.  That condition is indicated by a \*(lqmust be encoded in
 7bit, 8bit, or binary\*(rq error message from
 .B mhlist
 and other
 .B nmh
 programs that parse MIME messages.
 .PP
+The
+.B \-fixtype
+switch ensures that each part of the message has the correct MIME type
+shown in its Content-Type header.  It may be repeated.  It is
+typically used to replace \*(lqapplication/octet-stream\*(rq with a
+more descriptive MIME type.  It may not be used for multipart and
+message types.
+.PP
 .B mhfixmsg
 applies one transformation unconditionally:  it removes an extraneous
 trailing semicolon from the parameter lists of MIME header fields.
@@ -234,15 +288,17 @@ content type and/or encoding as follows:
 .PP
 .RS 5
 .nf
-.ta \w'\-fixboundary 'u
-\-decodetext   base64 and quoted-printable encoded text parts
-\-textcharset  text/plain parts
-\-reformat     text parts that are not text/plain
-\-fixboundary  outermost multipart part
-\-fixcte       multipart part
+.ta \w'\-crlflinebreaks 'u
+\-decodetext         base64 and quoted-printable encoded text parts
+\-decodetypes        limits parts to which -decodetext applies
+\-crlflinebreaks     text parts
+\-textcharset        text/plain parts
+\-reformat           text parts that are not text/plain
+\-fixboundary        outermost multipart part
+\-fixcte             multipart part
+\-fixtype            all except multipart and message parts
 .fi
 .RE
-.PP
 .SS "Backup of Original Message/File"
 If it applies any transformations to a message or file,
 and the
@@ -265,7 +321,6 @@ switch disables the use of any
 profile component and negates all prior
 .B \-rmmproc
 switches.
-.PP
 .SS "Integration with inc"
 .B mhfixmsg
 can be used as an add-hook, as described in %docdir%/README-HOOKS.
@@ -291,6 +346,7 @@ which
 sets with the newly incorporated messages.  Those could then be
 supplied to
 .BR mhfixmsg .
+An example is shown below.
 .SS "Integration with procmail"
 By way of example, here is an excerpt from a procmailrc file
 that filters messages through
@@ -321,7 +377,92 @@ STORE = %nmhlibexecdir%/rcvstore
 | tee `$MKSTEMP` | $MHFIXMSG | $STORE +nmh-workers
 .fi
 .RE
+.SH "EXAMPLES"
+.PP
+.SS Basic usage
+To run
+.B mhfixmsg
+on the current message in the current folder, with default transformations to
+fix MIME boundaries and Content-Transfer-Encoding, to decode text and
+application/ics content parts to 8 bit, and to add a corresponding text/plain
+part where lacking:
+.PP
+.RS
+.nf
+mhfixmsg -verbose
+.fi
+.RE
+.SS Specified folder and messages
+To run
+.B mhfixmsg
+on specified messages, without its informational output:
+.PP
+.RS
+.nf
+mhfixmsg +inbox last:4
+.fi
+.RE
+.SS View without modification
+By default,
+.B mhfixmsg
+transforms the message in place.  To view the MIME structure that would result from running
+.B mhfixmsg
+on the current message, without modifying the message:
+.PP
+.RS
+.nf
+mhfixmsg -outfile - | mhlist -file -
+.fi
+.RE
+.SS Search message without modification
+To search the current message, which possibly contains base64 or quoted printable encoded text parts,
+without modifiying it, use the -outfile switch:
+.PP
+.RS
+.nf
+mhfixmsg -outfile - | grep \fIpattern\fR
+.fi
+.RE
+.PP
+-outfile can be abbreviated in usual MH fashion, e.g., to -o.  The search will be
+on the entire message, not just text parts.
+.SS Translate text/plain parts to UTF-8
+To translate all text/plain parts in the current message to UTF-8, in addition
+to all of the default transformations:
 .PP
+.RS
+.nf
+mhfixmsg -textcharset utf-8
+.fi
+.RE
+.SS Fix all messages in a folder
+To run
+.B mhfixmsg
+on all of the messages in a folder, using a Bourne shell loop:
+.PP
+.RS
+.nf
+for msg in `pick +folder`; do mhfixmsg $msg; done
+.fi
+.RE
+.PP
+.B mhfixmsg
+can be run on more than one message, but on a large number of messages
+may attempt to open too many files.
+.SS Run on newly incorporated messages
+To run
+.B mhfixmsg
+on messages as they are incorporated:
+.PP
+.RS
+.nf
+inc  &&  mhfixmsg -nochangecur unseen
+.fi
+.RE
+.PP
+This assumes that the Unseen-Sequence profile entry is set to
+.BR unseen ,
+as shown in the mh\-profile(5) man page.
 .SH FILES
 .B mhfixmsg
 looks for mhn.defaults in multiple locations: absolute pathnames are
@@ -365,6 +506,8 @@ is checked.
 .RB ` +folder "' defaults to the current folder"
 .RB ` msgs "' defaults to cur"
 .RB ` "\-decodetext 8bit"'
+.RB ` "\-decodetypes text,application/ics"'
+.RB ` \-crlflinebreaks '
 .RB ` \-notextcharset '
 .RB ` \-reformat '
 .RB ` \-noreplacetextplain '
@@ -382,3 +525,17 @@ switch is enabled.  If the
 .B \-file
 switch or an absolute pathname is used, the context will not be
 modified.
+.SH BUGS
+.B mhfixmsg
+opens files internally for decoding and character set conversion, and apparently does not
+close them expeditiously.  Until that is resolved, it is recommended that
+.B mhfixmsg
+not be run on a large number of messages at once, as noted in the EXAMPLES above.
+.PP
+As noted in the DESCRIPTION above,
+.B mhfixmsg
+will not decode to binary content.  This restriction should be removed at some point.  It's
+not due to any issue in
+.BR mhfixmsg ,
+but rather an observation of incorrect behavior by other nmh tools on messages with binary
+content.
diff --git a/man/mhical.man b/man/mhical.man
new file mode 100644 (file)
index 0000000..8ee1c52
--- /dev/null
@@ -0,0 +1,171 @@
+.TH MHICAL %manext1% "January 4, 2015" "%nmhversion%"
+.\"
+.\" %nmhwarning%
+.\"
+.SH NAME
+mhical \- manipulates an iCalendar event request
+.SH SYNOPSIS
+.HP 5
+.na
+.B mhical
+.RB [ \-form
+.IR formatfile ]
+.RB [ \-format
+.IR formatstring ]
+.RB [[ \-reply
+.IR "accept" " | " "decline" " | " "tentative" "] |"
+.BR \-cancel ]
+.RB [ \-contenttype ]
+.RB [ \-infile
+.IR infile ]
+.RB [ \-outfile
+.IR outfile ]
+.RB [ \-unfold ]
+.RB [ \-debug ]
+.RB [ \-version ]
+.RB [ \-help ]
+.ad
+.SH DESCRIPTION
+.B mhical
+manipulates an iCalendar (.ics) event request, to display it, generate
+a reply to it, or cancel it.  iCalendar event requests and replies are
+defined by RFC 5545.
+.PP
+.SH DISPLAY
+The default operation is to display the iCalendar event request in a
+human-readable format.
+.SS "Format Program Selection"
+For the display operation, the
+.B \-format
+.I string
+and
+.B \-form
+.I formatfile
+switches may be used to specify a format string or a format file to read.
+If given a format string, it must be specified as a single argument to
+the
+.B \-format
+switch.  If given a format file name with
+.BR \-form ,
+the file is searched for using the normal
+.B nmh
+rules: absolute pathnames are accessed directly, tilde expansion is
+done on usernames, and files are searched for in the user's
+.I Mail
+directory as specified in their profile.  If not found there, the directory
+.RI \*(lq %nmhetcdir% \*(rq
+is checked.
+.B mhical
+defaults to using a format file named
+.BR mhical.24hour ,
+and will use the one installed in the
+.RI \*(lq %nmhetcdir% \*(rq
+directory if not found elsewhere.
+.PP
+The following format components are supported:
+.PP
+.RS 5
+.fc ^ ~
+.nf
+.ta \w'description 'u
+.BR ^method~^
+.BR ^organizer~^
+.BR ^summary~^
+.BR ^description~^
+.BR ^location~^
+.BR ^dtstart~^
+.BR ^dtend~^
+.BR ^attendees~^
+.fi
+.RE
+.PP
+Those format names are case-sensitive.  Each corresponds to the
+iCalendar property of the same name as defined in RFC 5545, with the
+exception of
+.BR attendees .
+That is a comma-delimited list of the common name (CN), if available,
+or email address of Attendee properties.  A maximum of 20 will be
+displayed.
+.SS Timezone
+.B mhical
+will display the event with times converted to the timezone specified
+by the
+.B TZ
+environment variable, if it is set, see tzset(3).  If not set, its
+behavior is implementation defined, but may use the user's local
+timezone.
+.SH REPLY
+The
+.B \-reply
+switch generates a reply from the event request.  The required
+action parameter must be one of
+.IR "accept" ,
+.IR "decline" ", or"
+.IR "tentative" .
+Delegation is not supported.
+.SH CANCEL
+The
+.B \-cancel
+switch generates an iCalendar event that can be used to cancel
+the event request.
+.SH INPUT/OUTPUT
+By default,
+.B mhical
+reads from standard input and writes to standard output.  The
+.B \-infile
+and
+.BR \-outfile ,
+respectively, switches can be used to override these defaults.
+.PP
+.SH MISCELLANEOUS SWITCHES
+The
+.B \-contenttype
+switch instructs
+.B mhical
+to insert a Content-Type header at the beginning of its output,
+for use by
+.BR mhbuild .
+It can only be used with
+.B \-reply
+and
+.BR \-cancel .
+.PP
+The
+.B \-unfold
+switch echoes the event request, but with all lines unfolded.
+.PP
+The
+.B \-debug
+switch reveals minute details of the parse process.
+.SH FILES
+.B mhical
+looks for format files in multiple locations:  absolute pathnames are
+accessed directly, tilde expansion is done on usernames, and files are
+searched for in the user's
+.I Mail
+directory as specified in their profile.  If not found there, the directory
+.RI \*(lq %nmhetcdir% \*(rq
+is checked.
+.PP
+.fc ^ ~
+.nf
+.ta \w'%nmhetcdir%/mhical.24hour  'u
+^%nmhetcdir%/mhical.24hour~^The default display template
+^%nmhetcdir%/mhical.12hour~^Display template that uses 12-hour clock
+.fi
+.fi
+.SH "SEE ALSO"
+.IR mhbuild (1),
+.IR mh\-format (5),
+.IR tzset (3),
+.IR repl (1)
+.SH DEFAULTS
+.nf
+.RB ` \-form "' defaults to mhical.24hour"
+.RB ` \-infile "' defaults to standard input"
+.RB ` \-outfile "' defaults to standard output"
+.fi
+.SH BUGS
+.B mhical
+supports only a very limited subset of RRULE formats.  Specifically,
+only a frequency of YEARLY and an interval of 1 are supported.
index a75e50d..ee70811 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHLIST %manext1% "August 20, 2014" "%nmhversion%"
+.TH MHLIST %manext1% "February 6, 2015" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -18,6 +18,9 @@ mhlist \- list information about MIME messages
 .RB [ \-type
 .IR content ]
 \&...
+.RB [ \-prefer
+.IR content ]
+\&...
 .RB [ \-headers " | " \-noheaders ]
 .RB [ \-realsize " | " \-norealsize ]
 .RB [ \-rcache
@@ -95,24 +98,46 @@ By default,
 .B mhlist
 will list information about the entire
 message (all of its parts).  By using the
-.B \-part
+.BR \-part ,
+.BR \-type ,
 and
-.B \-type
-switches, you may limit the scope of this command to particular
-subparts (of a multipart content) and/or particular content types.
-.PP
-A part specification consists of a series of numbers separated by dots.
-For example, in a multipart content containing three parts, these
-would be named as 1, 2, and 3, respectively.  If part 2 was also a
-multipart content containing two parts, these would be named as 2.1 and
-2.2, respectively.  Note that the
+.B \-prefer
+switches, you may limit and reorder the set of parts to be listed,
+based on part number and/or content type.
+.PP
+A part specification consists of a series of numbers separated by
+dots.  For example, in a multipart content containing three parts,
+these would be named as 1, 2, and 3, respectively.  If part 2 was also
+a multipart content containing two parts, these would be named as 2.1
+and 2.2, respectively.  Note that the
 .B \-part
-switch is effective for only
-messages containing a multipart content.  If a message has some other
-kind of content, or if the part is itself another multipart content, the
+switch is effective for only messages containing a multipart content. 
+If a message has some other kind of content, or if the part is itself
+another multipart content, the
 .B \-part
 switch will not prevent the content from being acted upon.
 .PP
+The
+.B \-type
+switch can also be used to restrict (or, when used in conjunction with
+.BR \-part ,
+to further restrict) the selection of parts according to content type.
+One or more
+.B \-type
+switches part will only select the first match
+from a multipart/alternative, even if there is more than one
+subpart that matches (one of) the given content type(s).
+.PP
+Using either
+.B \-part
+or
+.B -type
+switches alone will cause either to select
+the part(s) they match.  Using them together will select only
+the part(s) matched by both (sets of) switches.  In other
+words, the result is the intersection, and not the union, of their
+separate match results.
+.PP
 A content specification consists of a content type and a subtype.
 The initial list of \*(lqstandard\*(rq content types and subtypes can
 be found in RFC 2046.
@@ -152,9 +177,29 @@ switch must
 be used twice: once for message/external-body and once for the content
 externally referenced.
 .PP
-The parts of a multipart/alternative part are listed in the reverse
-order of their placement in the message.  The listing therefore is
-in decreasing order of preference, as defined in RFC 2046.
+By default, the parts of a multipart/alternative part are listed in
+the reverse order of their placement in the message.  The listing
+therefore is in decreasing order of preference, as defined in RFC
+2046.  The
+.B \-prefer
+switch can be used (one or more times, in order of descending
+preference) to let MH know which content types from a
+multipart/alternative MIME part are preferred by the user, in order to
+override the default preference order.  Thus, when viewed by
+.BR mhlist ,
+the ordering of multipart/alternative parts will appear to change when
+invoked with or without various
+.B \-prefer
+switches.
+The
+.B \-prefer
+switch is functionally most important for
+.IR mhshow ,
+but is also implemented in
+.B mhlist
+and
+.B mhstore
+to make common part numbering possible across all three programs.
 .SS "Checking the Contents"
 The
 .B \-check
index dfa5aac..3df2a9e 100644 (file)
@@ -48,6 +48,7 @@ displayed and other arguments are ignored.
 can provide other information, such as the
 .B nmh
 version identifier, the locations of the nmh
+.IR bindir ,
 .I etcdir
 and
 .I libexecdir
index 1010425..c2bba35 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHSHOW %manext1% "April 9, 2014" "%nmhversion%"
+.TH MHSHOW %manext1% "February 8, 2015" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -18,9 +18,13 @@ mhshow \- display MIME messages
 .RB [ \-type
 .IR content ]
 \&...
+.RB [ \-prefer
+.IR content ]
+\&...
 .RB [ \-concat " | " \-noconcat ]
 .RB [ \-textonly " | " \-notextonly ]
 .RB [ \-inlineonly " | " \-noinlineonly ]
+.RB [ \-header " | " \-noheader ]
 .RB [ \-form
 .IR formfile ]
 .RB [ \-markform
@@ -56,14 +60,13 @@ and
 .B \-noinlineonly
 switches.
 In addition, by using the
-.B \-part
+.BR \-part ,
+.BR \-type ,
 and
-.B \-type
-switches, you may
-further limit the scope of
-.B mhshow
-to particular subparts (of a
-multipart content) and/or particular content types.  The inclusion of any
+.B \-prefer
+switches, you may limit and reorder the set of parts to be displayed,
+based on part number and/or content type.
+The inclusion of any
 .B \-part
 or
 .B \-type
@@ -72,6 +75,22 @@ switches will override the default settings of
 and
 .BR \-inlineonly.
 .PP
+The
+.B \-header
+switch control whether
+.B mhshow
+will print a message separator header before each message that it
+displays.  The header format can be controlled using
+.B \-headerform
+to specify a file containing
+.IR mh\-format (5)
+instructions.  A copy of the built-in default
+headerform can be found in %nmhetcdir%/mhshow.header, for reference.
+In addition to the normal set of
+.IR mh\-format (5)
+instructions, a "%{folder}" escape provides a
+string representing the current folder.
+.PP
 By default
 .B mhshow
 will concatenate all content under one pager.  If you which each part to
@@ -99,18 +118,54 @@ mail drop format to a folder of
 messages, see
 .IR inc (1)).
 .PP
+The
+.B \-part
+switch can be used (one or more times) to restrict the
+set of subparts that will be displayed.  (Obviously with no
+.B \-part
+switches, all parts will be considered.)  If a
+.B \-part
+switch specifies a specific subpart (i.e., a "leaf" in the tree of
+MIME parts), then that part will always be displayed.  If a
+.B \-part
+switch references a multipart/alternative part, then (in
+the absence of a
+.B \-type
+switch) only the default subpart of that multipart will be displayed.
+.PP
 A part specification consists of a series of numbers separated by dots.
 For example, in a multipart content containing three parts, these
 would be named as 1, 2, and 3, respectively.  If part 2 was also a
 multipart content containing two parts, these would be named as 2.1 and
 2.2, respectively.  Note that the
 .B \-part
-switch is effective for only
+switch is effective only for
 messages containing a multipart content.  If a message has some other
 kind of content, or if the part is itself another multipart content, the
 .B \-part
 switch will not prevent the content from being acted upon.
 .PP
+The
+.B \-type
+switch can also be used to restrict (or, when used in conjunction with
+.BR \-part ,
+to further restrict) the display of parts according to content type.
+One or more
+.B \-type
+switches part will only select the first match
+from a multipart/alternative, even if there is more than one
+subpart that matches (one of) the given content type(s).
+.PP
+Using either
+.B \-part
+or
+.B -type
+switches alone will cause either to select
+the part(s) they match.  Using them together will select only
+the part(s) matched by both (sets of) switches.  In other
+words, the result is the intersection, and not the union, of their
+separate match results.
+.PP
 A content specification consists of a content type and a subtype.
 The initial list of \*(lqstandard\*(rq content types and subtypes can
 be found in RFC 2046.
@@ -137,12 +192,50 @@ A legal MIME message must contain a subtype specification.
 To specify a content, regardless of its subtype, just use the
 name of the content, e.g., \*(lqaudio\*(rq.  To specify a specific
 subtype, separate the two with a slash, e.g., \*(lqaudio/basic\*(rq.
-Note that regardless of the values given to the `\-type' switch, a
+Note that regardless of the values given to the
+.B \-type
+switch, a
 multipart content (of any subtype listed above) is always acted upon.
-Further note that if the `\-type' switch is used, and it is desirable to
-act on a message/external-body content, then the `\-type' switch must
+Further note that if the
+.B \-type
+switch is used, and it is desirable to
+act on a message/external-body content, then the
+.B \-type
+switch must
 be used twice: once for message/external-body and once for the content
 externally referenced.
+.PP
+In the absence of
+.BR \-prefer ,
+.B mhshow
+will select the "best" displayable subpart from
+multipart/alternative content.  The
+.B \-prefer
+switch can be used (one or more times, in order of descending
+preference) to let MH know which content types from a
+multipart/alternative MIME part are preferred by the user, in order to
+override the default selection for display.  For example, mail is
+often sent containing both plaintext and HTML-formatted versions of
+the same content, and the HTML version is usually indicated to be the
+"best" format for viewing.  Using \*(lq-prefer text/plain\*(rq will
+cause the plaintext version to be displayed if possible, but still
+allow display of the HTML part if there is no plaintext subpart
+available.  Using \*(lq-prefer text/plain -prefer image/png\*(rq
+would add a preference for PNG images, which might or might not
+ever appear in the same multipart/alternative section with text/plain.
+Implementation note:  RFC 2046 requires that the subparts
+of a multipart/alternative be ordered according to "faithfulness to
+the original content", and MH by default selects the subpart ranked
+most "faithful" by that ordering.  The
+.B \-prefer
+switch reorders the alternative parts (only internally, never changing
+the message file) to move the user's preferred part(s) to the "most
+faithful" position.  Thus, when viewed by
+.BR mhlist ,
+the ordering of multipart/alternative parts will appear to change when
+invoked with or without various
+.B \-prefer
+switches.
 .SS "Unseen Sequence"
 If the profile entry \*(lqUnseen\-Sequence\*(rq is present and
 non\-empty, then
@@ -345,7 +438,7 @@ was built with
 .IR iconv (3),
 then all text/plain parts of the message(s) will be displayed using
 the character set of the current locale.  See the
-.BR mhparam (1)
+.IR mhparam (1)
 man page for how determine whether your
 .B nmh
 installation includes
@@ -604,8 +697,9 @@ next.  The default marker can be changed using the
 .B \-markform
 switch to specify a file containing
 .IR mh\-format (5)
-instructions to use when displaying the content marker.  In addition to
-the normal set of
+instructions to use when displaying the content marker.  A copy of the
+default markform can be found in %nmhetcdir%/mhshow.marker, for
+reference.  In addition to the normal set of
 .IR mh\-format (5)
 instructions, the following
 .I component
@@ -655,6 +749,7 @@ is checked.
 ^%nmhetcdir%/mhn.defaults~^System default MIME profile entries
 ^%nmhetcdir%/mhl.headers~^The headers template
 ^%nmhetcdir%/mhshow.marker~^Example content marker
+^%nmhetcdir%/mhshow.header~^Example message separator header
 .fi
 .SH "PROFILE COMPONENTS"
 .fc ^ ~
index fbdd370..fb0f57d 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHSTORE %manext1% "March 2, 2014" "%nmhversion%"
+.TH MHSTORE %manext1% "February 6, 2015" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -20,6 +20,9 @@ mhstore \- store contents of MIME messages into files
 .RB [ \-type
 .IR content ]
 \&...
+.RB [ \-prefer
+.IR content ]
+\&...
 .RB [ \-auto " | " \-noauto ]
 .RB [ \-clobber
 .IR always " | " auto " | " suffix " | " ask " | " never ]
@@ -48,13 +51,12 @@ By default,
 will store all the parts of each message.
 Each part will be store in a separate file.  The header fields of
 the message are not stored.  By using the
-.B \-part
+.BR \-part ,
+.BR \-type ,
 and
-.B \-type
-switches, you may limit the scope of
-.B mhstore
-to particular
-subparts (of a multipart content) and/or particular content types.
+.B \-prefer
+switches, you may limit and reorder the set of parts to be stored,
+based on part number and/or content type.
 .PP
 The
 .B \-file
@@ -80,21 +82,40 @@ messages, see
 .PP
 A part specification consists of a series of numbers separated by
 dots.  For example, in a multipart content containing three parts,
-these would be named as 1, 2, and 3, respectively.  If part 2 was
-also a multipart content containing two parts, these would be named
-as 2.1 and 2.2, respectively.  Note that the
+these would be named as 1, 2, and 3, respectively.  If part 2 was also
+a multipart content containing two parts, these would be named as 2.1
+and 2.2, respectively.  Note that the
 .B \-part
-switch is
-effective for only messages containing a multipart content.  If a
-message has some other kind of content, or if the part is itself
+switch is effective for only messages containing a multipart content. 
+If a message has some other kind of content, or if the part is itself
 another multipart content, the
 .B \-part
-switch will not prevent
-the content from being acted upon.
+switch will not prevent the content from being acted upon.
+.PP
+The
+.B \-type
+switch can also be used to restrict (or, when used in conjunction with
+.BR \-part ,
+to further restrict) the selection of parts according to content type.
+One or more
+.B \-type
+switches part will only select the first match
+from a multipart/alternative, even if there is more than one
+subpart that matches (one of) the given content type(s).
+.PP
+Using either
+.B \-part
+or
+.B -type
+switches alone will cause either to select
+the part(s) they match.  Using them together will select only
+the part(s) matched by both (sets of) switches.  In other
+words, the result is the intersection, and not the union, of their
+separate match results.
 .PP
 A content specification consists of a content type and a subtype.
-The initial list of \*(lqstandard\*(rq content types and subtypes
-can be found in RFC 2046.
+The initial list of \*(lqstandard\*(rq content types and subtypes can
+be found in RFC 2046.
 .PP
 A list of commonly used contents is briefly reproduced here:
 .PP
@@ -129,6 +150,26 @@ desirable to act on a message/external-body content, then the
 .B \-type
 switch must be used twice: once for message/external-body
 and once for the content externally referenced.
+.PP
+The
+.B \-prefer
+switch will alter the part ordering of multipart/alternative MIME sections
+in order to override the sender-imposed default ordering.
+The
+.B \-prefer
+switch is functionally most important for
+.BR mhshow ,
+but is also implemented in
+.B mhlist
+and
+.B mhstore
+to make common part numbering possible across all three programs.
+See
+.IR mhlist (1)
+and
+.IR mhshow (1)
+for more information on
+.BR \-prefer.
 .SS "Checking the Contents"
 The
 .B \-check
index 000da22..9d7cdcf 100644 (file)
@@ -10,6 +10,8 @@ pick \- search for messages by content
 .B pick
 .RI [ +folder ]
 .RI [ msgs ]
+.RB [ \-reverse
+\&...]
 .RB [ \-and
 \&...]
 .RB [ \-or
@@ -177,6 +179,20 @@ will
 also honor a specification of the form \*(lq\-dd\*(rq, which means
 \*(lqdd days ago\*(rq.
 .PP
+Use the
+.BR \-reverse
+switch to make
+.B pick
+find matching messages in reverse order, working from the highest message
+number down to the lowest.  This can be useful in for searching recent
+messages in large folders, for example,
+.PP
+.RS 5
+.nf
+pick\0\-reverse\0\-from\0frated\0|\0xargs\0\-n1\0scan
+.fi
+.RE
+.PP
 .B Pick
 supports complex boolean operations on the searching primitives
 with the
index a9745ee..fe13538 100644 (file)
@@ -24,6 +24,8 @@ post \- deliver a message
 .IR columns ]
 .RB [ \-mts
 .IR smtp " | " sendmail/smtp " | " sendmail/pipe ]
+.RB [ \-sendmail
+.IR program ]
 .RB [ \-server
 .IR servername ]
 .RB [ \-port
@@ -184,6 +186,14 @@ but can be overriiden here with the
 .B \-mts
 switch.
 .PP
+If nmh is using as its mail transport system
+.BR sendmail/pipe ,
+the
+.B \-sendmail
+switch can be used to override the default
+.B sendmail
+program.
+.PP
 If nmh is using the SMTP MTA, the
 .B \-server
 and the
index 208676e..4de6a70 100644 (file)
@@ -1,4 +1,4 @@
-.TH REPL %manext1% "November 4, 2014" "%nmhversion%"
+.TH REPL %manext1% "December 14, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -36,6 +36,8 @@ all/to/cc/me]
 .RB [ \-editor
 .IR editor ]
 .RB [ \-noedit ]
+.RB [ \-convertargs
+.IR "type argstring" ]
 .RB [ \-whatnowproc
 .IR program ]
 .RB [ \-nowhatnowproc ]
@@ -234,6 +236,25 @@ and
 .B \-noatfile
 options.
 .PP
+The
+.B \-convertargs
+switch directs
+.B repl
+to pass the arguments for
+.I type
+to
+.BR mhbuild .
+Both arguments are required;
+.I type
+must be non-empty while
+.I argstring
+can be empty, e.g., '' in a shell command line.  The
+.B \-convertargs
+switch can be used multiple times.
+.I %docdir%/contrib/replaliases
+shows the use of
+.BR "repl \-convertargs" .
+.PP
 Although
 .B repl
 uses a forms file to direct it how to construct
@@ -260,7 +281,9 @@ which message filter file to use.
 .PP
 If the switch
 .B \-noformat
-is given (it is the default), then the message
+is given (it is the default) and the
+.B \-filter
+switch is not used, then the message
 to which you are replying is not included in the body of the draft.
 .PP
 If the switch
@@ -514,6 +537,8 @@ is checked.
 .IR send (1),
 .IR whatnow (1),
 .IR mh\-format (5)
+.PP
+.I %docdir%/contrib/replaliases
 .SH DEFAULTS
 .nf
 .RB ` +folder "' defaults to the current folder"
index 29e6e24..281fe8d 100644 (file)
@@ -34,6 +34,8 @@ send \- send a message
 .RB [ \-watch " | " \-nowatch ]
 .RB [ \-mts
 .IR smtp " | " sendmail/smtp " | " sendmail/pipe ]
+.RB [ \-sendmail
+.IR program ]
 .RB [ \-server
 .IR servername ]
 .RB [ \-port
@@ -369,6 +371,14 @@ but can be overriiden here with the
 .B \-mts
 switch.
 .PP
+If nmh is using as its mail transport system
+.BR sendmail/pipe ,
+the
+.B \-sendmail
+switch can be used to override the default
+.B sendmail
+program.
+.PP
 If nmh is using the SMTP MTA, the
 .B \-server
 and the
index a248e6a..57832c3 100644 (file)
@@ -1,4 +1,4 @@
-.TH SHOW %manext1% "June 6, 2012" "%nmhversion%"
+.TH SHOW %manext1% "February 8, 2015" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -43,7 +43,11 @@ manual page for the details about this
 command.  This default can be changed by defining the
 .I showproc
 profile component.  Any switches not recognized by
-.B show
+.BR show ,
+as well as
+.B \-header
+and
+.BR \-noheader ,
 are
 passed along to that program.  To override the default and the
 .I showproc
index 8f63fed..8034068 100644 (file)
@@ -65,6 +65,8 @@ context_read (void)
     if ((cp = getenv("MH")) && *cp != '\0') {
        defpath = path(cp, TFILE);
 
+        /* defpath is an absolute path; make sure that always MH is, too. */
+       setenv("MH", defpath, 1);
        if (stat(defpath, &st) != -1 && (st.st_mode & S_IFREG) == 0)
                adios((char *)0, "`%s' specified by your MH environment variable is not a normal file", cp);
 
index 7080959..27d55e9 100644 (file)
@@ -7,19 +7,18 @@
  * complete copyright information.
  */
 
-#include <h/mh.h>
-#include <h/signals.h>
-
 /*
- * static prototypes
+ * This function used to support setuid/setgid programs by writing
+ * the file as the user.  But that code, m_chkids(), was removed
+ * because there no longer are setuid/setgid programs in nmh.
  */
-static int m_chkids(void);
 
+#include <h/mh.h>
+#include <h/signals.h>
 
 void
 context_save (void)
 {
-    int action;
     register struct node *np;
     FILE *out;
     sigset_t set, oset;
@@ -33,9 +32,6 @@ context_save (void)
        return;
     ctxflags &= ~CTXMOD;
 
-    if ((action = m_chkids ()) > 0)
-       return;                 /* child did it for us */
-
     /* block a few signals */
     sigemptyset (&set);
     sigaddset (&set, SIGHUP);
@@ -57,48 +53,4 @@ context_save (void)
     lkfclosedata (out, ctxpath);
 
     sigprocmask (SIG_SETMASK, &oset, &set); /* reset the signal mask */
-
-    if (action == 0)
-       /* This must be _exit(), not exit(), because the child didn't
-          call unregister_for_removal() in m_chkids(). */
-       _exit (0);              /* we are child, time to die */
-}
-
-/*
- * This hack brought to you so we can handle set[ug]id MH programs.
- * If we return -1, then no fork is made, we update .mh_profile
- * normally, and return to the caller normally.  If we return 0,
- * then the child is executing, .mh_profile is modified after
- * we set our [ug]ids to the norm.  If we return > 0, then the
- * parent is executed and .mh_profile has already be modified.
- * We can just return to the caller immediately.
- */
-
-static int
-m_chkids (void)
-{
-    int i;
-    pid_t pid;
-
-    if (getuid () == geteuid ())
-       return (-1);
-
-    for (i = 0; (pid = fork ()) == -1 && i < 5; i++)
-       sleep (5);
-
-    switch (pid) {
-       case -1:
-           break;
-
-       case 0:
-           /* It's not necessary to call unregister_for_removal(0)
-               because the child calls _exit() in context_save(). */
-           break;
-
-       default:
-           pidwait (pid, -1);
-           break;
-    }
-
-    return pid;
 }
diff --git a/sbr/datetime.c b/sbr/datetime.c
new file mode 100644 (file)
index 0000000..4fb7bac
--- /dev/null
@@ -0,0 +1,490 @@
+/*
+ * datetime.c -- functions for manipulating RFC 5545 date-time values
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh.
+ * See the COPYRIGHT file in the root directory of the nmh
+ * distribution for complete copyright information.
+ */
+
+#include "h/mh.h"
+#include "h/icalendar.h"
+#include <h/fmt_scan.h>
+#include "h/tws.h"
+#include "h/utils.h"
+
+/*
+ * This doesn't try to support all of the myriad date-time formats
+ * allowed by RFC 5545.  It is only used for viewing date-times,
+ * so that shouldn't be a problem:  if a particular format can't
+ * be handled by this code, just present it to the user in its
+ * original form.
+ *
+ * And, this assumes a valid iCalendar input file.  E.g, it
+ * doesn't check that each BEGIN has a matching END and vice
+ * versa.  That should be done in the parser, though it currently
+ * isn't.
+ */
+
+typedef struct tzparams {
+    /* Pointers to values in parse tree.
+     * TZOFFSETFROM is used to calculate the absolute time at which
+     * the transition to a given observance takes place.
+     * TZOFFSETTO is the timezone offset from UTC.  Both are in HHmm
+     * format. */
+    char *offsetfrom, *offsetto;
+    const char *dtstart;
+    const char *rrule;
+
+    /* This is only used to make sure that timezone applies.  And not
+       always, because if the timezone DTSTART is before the epoch, we
+       don't try to compare to it. */
+    time_t start_dt; /* in seconds since epoch */
+} tzparams;
+
+struct tzdesc {
+    char *tzid;
+
+    /* The following are translations of the pieces of RRULE and DTSTART
+       into seconds from beginning of year. */
+    tzparams standard_params;
+    tzparams daylight_params;
+
+    struct tzdesc *next;
+};
+
+/*
+ * Parse a datetime of the form YYYYMMDDThhmmss and a string
+ * representation of the timezone in units of [+-]hhmm and load the
+ * struct tws.
+ */
+static int
+parse_datetime (const char *datetime, const char *zone, int dst,
+                struct tws *tws) {
+    char utc_indicator;
+    int form_1 = 0;
+    int items_matched;
+
+    memset(tws, 0, sizeof *tws);
+    items_matched =
+        sscanf (datetime, "%4d%2d%2dT%2d%2d%2d%c",
+                &tws->tw_year, &tws->tw_mon, &tws->tw_mday,
+                &tws->tw_hour, &tws->tw_min, &tws->tw_sec,
+                &utc_indicator);
+    tws->tw_flags = TW_NULL;
+
+    if (items_matched == 7) {
+        /* The 'Z' must be capital according to RFC 5545 Sec. 3.3.5. */
+        if (utc_indicator != 'Z') {
+            advise (NULL, "%s has invalid timezone indicator of 0x%x",
+                    datetime, utc_indicator);
+            return NOTOK;
+        }
+    } else if (zone == NULL) {
+        form_1 = 1;
+    }
+
+    /* items_matched of 3 is for, e.g., 20151230.  Assume that means
+       the entire day.  The time fields of the tws struct were
+       initialized to 0 by the memset() above. */
+    if (items_matched >= 6  ||  items_matched == 3) {
+        int offset = atoi (zone ? zone : "0");
+
+        /* struct tws defines tw_mon over [0, 11]. */
+        --tws->tw_mon;
+
+        /* Fill out rest of tws, i.e., its tw_wday and tw_flags. */
+        set_dotw (tws);
+        /* set_dotw() sets TW_SIMP.  Replace that with TW_SEXP so that
+           dasctime() outputs the dotw before the date instead of after. */
+        tws->tw_flags &= ~TW_SDAY, tws->tw_flags |= TW_SEXP;
+
+        /* For the call to dmktime():
+           - don't need tw_yday
+           - tw_clock must be 0 on entry, and is set by dmktime()
+           - the only flag in tw_flags used is TW_DST
+         */
+        tws->tw_yday = tws->tw_clock = 0;
+        tws->tw_zone = 60 * (offset / 100)  +  offset % 100;
+        if (dst) {
+            tws->tw_zone -= 60;  /* per dlocaltime() */
+            tws->tw_flags |= TW_DST;
+        }
+        /* dmktime() just sets tws->tw_clock. */
+        (void) dmktime (tws);
+
+        if (! form_1) {
+            /* Set TW_SZEXP so that dasctime outputs timezone, except
+               with local time (Form #1). */
+            tws->tw_flags |= TW_SZEXP;
+
+            /* Convert UTC time to time in local timezone.  However,
+               don't try for years before 1970 because dlocatime()
+               doesn't handle them well.  dlocaltime() will succeed if
+               tws->tw_clock is nonzero. */
+            if (tws->tw_year >= 1970  &&  tws->tw_clock > 0) {
+                const int was_dst = tws->tw_flags & TW_DST;
+
+                *tws = *dlocaltime (&tws->tw_clock);
+                if (was_dst  &&  ! (tws->tw_flags & TW_DST)) {
+                    /* dlocaltime() changed the DST flag from 1 to 0,
+                       which means the time is in the hour (assumed to
+                       be one hour) that is lost in the transition to
+                       DST.  So per RFC 5545 Sec. 3.3.5, "the
+                       DATE-TIME value is interpreted using the UTC
+                       offset before the gap in local times."  In
+                       other words, add an hour to it.
+                       No adjustment is necessary for the transition
+                       from DST to standard time, because dasctime()
+                       shows the first occurrence of the time. */
+                    tws->tw_clock += 3600;
+                    *tws = *dlocaltime (&tws->tw_clock);
+                }
+            }
+        }
+
+        return OK;
+    } else {
+        return NOTOK;
+    }
+}
+
+tzdesc_t
+load_timezones (const contentline *clines) {
+    tzdesc_t timezones = NULL, timezone = NULL;
+    int in_vtimezone, in_standard, in_daylight;
+    tzparams *params = NULL;
+    const contentline *node;
+
+    /* Interpret each VTIMEZONE section. */
+    in_vtimezone = in_standard = in_daylight = 0;
+    for (node = clines; node; node = node->next) {
+        /* node->name will be NULL if the line was "deleted". */
+        if (! node->name) { continue; }
+
+        if (in_daylight  ||  in_standard) {
+            if (! strcasecmp ("END", node->name)  &&
+                ((in_standard  &&  ! strcasecmp ("STANDARD", node->value))  ||
+                 (in_daylight  &&  ! strcasecmp ("DAYLIGHT", node->value)))) {
+                struct tws tws;
+
+                if (in_standard) { in_standard = 0; }
+                else if (in_daylight) { in_daylight = 0; }
+                if (parse_datetime (params->dtstart, params->offsetfrom,
+                                    in_daylight ? 1 : 0,
+                                    &tws) == OK) {
+                    if (tws.tw_year >= 1970) {
+                        /* dmktime() falls apart for, e.g., the year 1601. */
+                        params->start_dt = tws.tw_clock;
+                    }
+                } else {
+                    advise (NULL, "failed to parse start time %s for %s",
+                            params->dtstart,
+                            in_standard ? "standard" : "daylight");
+                    return NULL;
+                }
+                params = NULL;
+            } else if (! strcasecmp ("DTSTART", node->name)) {
+                /* Save DTSTART for use after getting TZOFFSETFROM. */
+                params->dtstart = node->value;
+            } else if (! strcasecmp ("TZOFFSETFROM", node->name)) {
+                params->offsetfrom = node->value;
+            } else if (! strcasecmp ("TZOFFSETTO", node->name)) {
+                params->offsetto = node->value;
+            } else if (! strcasecmp ("RRULE", node->name)) {
+                params->rrule = node->value;
+            }
+        } else if (in_vtimezone) {
+            if (! strcasecmp ("END", node->name)  &&
+                ! strcasecmp ("VTIMEZONE", node->value)) {
+                in_vtimezone = 0;
+            } else if (! strcasecmp ("BEGIN", node->name)  &&
+                ! strcasecmp ("STANDARD", node->value)) {
+                in_standard = 1;
+                params = &timezone->standard_params;
+            } else if (! strcasecmp ("BEGIN", node->name)  &&
+                ! strcasecmp ("DAYLIGHT", node->value)) {
+                in_daylight = 1;
+                params = &timezone->daylight_params;
+            } else if (! strcasecmp ("TZID", node->name)) {
+                /* See comment below in format_datetime() about removing any enclosing quotes from a
+                   timezone identifier. */
+                char *buf = mh_xmalloc(strlen(node->value) + 1);
+                unquote_string(node->value, buf);
+                timezone->tzid = buf;
+            }
+        } else {
+            if (! strcasecmp ("BEGIN", node->name)  &&
+                ! strcasecmp ("VTIMEZONE", node->value)) {
+
+                in_vtimezone = 1;
+                timezone = mh_xcalloc (1, sizeof (struct tzdesc));
+                if (timezones) {
+                    tzdesc_t t;
+
+                    for (t = timezones; t && t->next; t = t->next) { continue; }
+                    /* The loop terminated at, not after, the last
+                       timezones node. */
+                    t->next = timezone;
+                } else {
+                    timezones = timezone;
+                }
+            }
+        }
+    }
+
+    return timezones;
+}
+
+void
+free_timezones (tzdesc_t timezone) {
+    tzdesc_t next;
+
+    for ( ; timezone; timezone = next) {
+        free (timezone->tzid);
+        next = timezone->next;
+        free (timezone);
+    }
+}
+
+/*
+ * Convert time to local timezone, accounting for daylight saving time:
+ * - Detect which type of datetime the node contains:
+ *     Form #1: DATE WITH LOCAL TIME
+ *     Form #2: DATE WITH UTC TIME
+ *     Form #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE
+ * - Convert value to local time in seconds since epoch.
+ * - If there's a DST in the timezone, convert its start and end
+ *   date-times to local time in seconds, also.  Then determine
+ *   if the value is between them, and therefore DST.  Otherwise, it's
+ *   not.
+ * - Format the time value.
+ */
+
+/*
+ * Given a recurrence rule and year, calculate its time in seconds
+ * from 01 January UTC of the year.
+ */
+time_t
+rrule_clock (const char *rrule, const char *starttime, const char *zone,
+             unsigned int year) {
+    time_t clock = 0;
+
+    if (nmh_strcasestr (rrule, "FREQ=YEARLY;INTERVAL=1")  ||
+        (nmh_strcasestr (rrule, "FREQ=YEARLY")  &&  nmh_strcasestr(rrule, "INTERVAL") == NULL)) {
+        struct tws *tws;
+        const char *cp;
+        int wday = -1, month = -1;
+        int specific_day = 1; /* BYDAY integer (prefix) */
+        char buf[32];
+        int day;
+
+        if ((cp = nmh_strcasestr (rrule, "BYDAY="))) {
+            cp += 6;
+            /* BYDAY integers must be ASCII. */
+            if (*cp == '+') { ++cp; } /* +n specific day; don't support '-' */
+            else if (*cp == '-') { goto fail; }
+
+            if (isdigit ((unsigned char) *cp)) { specific_day = *cp++ - 0x30; }
+
+            if (! strncasecmp (cp, "SU", 2)) { wday = 0; }
+            else if (! strncasecmp (cp, "MO", 2)) { wday = 1; }
+            else if (! strncasecmp (cp, "TU", 2)) { wday = 2; }
+            else if (! strncasecmp (cp, "WE", 2)) { wday = 3; }
+            else if (! strncasecmp (cp, "TH", 2)) { wday = 4; }
+            else if (! strncasecmp (cp, "FR", 2)) { wday = 5; }
+            else if (! strncasecmp (cp, "SA", 2)) { wday = 6; }
+        }
+        if ((cp = nmh_strcasestr (rrule, "BYMONTH="))) {
+            month = atoi (cp + 8);
+        }
+
+        for (day = 1; day <= 7; ++day) {
+            /* E.g, 11-01-2014 02:00:00-0400 */
+            snprintf (buf, sizeof buf, "%02d-%02d-%04u %.2s:%.2s:%.2s%s",
+                      month, day + 7 * (specific_day-1), year,
+                      starttime, starttime + 2, starttime + 4,
+                      zone ? zone : "0000");
+            if ((tws = dparsetime (buf))) {
+                if (! (tws->tw_flags & (TW_SEXP|TW_SIMP))) { set_dotw (tws); }
+
+                if (tws->tw_wday == wday) {
+                    /* Found the day specified in the RRULE. */
+                    break;
+                }
+            }
+        }
+
+        if (day <= 7) {
+            clock = tws->tw_clock;
+        }
+    }
+
+fail:
+    if (clock == 0) {
+        admonish (NULL,
+                  "Unsupported RRULE format: %s, assume local timezone",
+                  rrule);
+    }
+
+    return clock;
+}
+
+char *
+format_datetime (tzdesc_t timezones, const contentline *node) {
+    param_list *p;
+    char *dt_timezone = NULL;
+    int dst = 0;
+    struct tws tws[2]; /* [standard, daylight] */
+    tzdesc_t tz;
+    char *tp_std, *tp_dst, *tp_dt;
+
+    /* Extract the timezone, if specified (RFC 5545 Sec. 3.3.5 Form #3). */
+    for (p = node->params; p && p->param_name; p = p->next) {
+        if (! strcasecmp (p->param_name, "TZID")  &&  p->values) {
+            /* Remove any enclosing quotes from the timezone identifier.  I don't believe that it's
+               legal for it to be quoted, according to RFC 5545 Â§ 3.2.19:
+                   tzidparam  = "TZID" "=" [tzidprefix] paramtext
+                   tzidprefix = "/"
+               where paramtext includes SAFE-CHAR, which specifically excludes DQUOTE.  But we'll
+               be generous and strip quotes. */
+            char *buf = mh_xmalloc(strlen(p->values->value) + 1);
+            unquote_string(p->values->value, buf);
+            dt_timezone = buf;
+            break;
+        }
+    }
+
+    if (! dt_timezone) {
+        /* Form #1: DATE WITH LOCAL TIME, i.e., no time zone, or
+           Form #2: DATE WITH UTC TIME */
+        if (parse_datetime (node->value, NULL, 0, &tws[0]) == OK) {
+            return strdup (dasctime (&tws[0], 0));
+        } else {
+            advise (NULL, "unable to parse datetime %s", node->value);
+            return NULL;
+        }
+    }
+
+    /*
+     * must be
+     * Form #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE
+     */
+
+    /* Find the corresponding tzdesc. */
+    for (tz = timezones; dt_timezone && tz; tz = tz->next) {
+        /* Property parameter values are case insenstive (RFC 5545
+           Sec. 2) and time zone identifiers are property parameters
+           (RFC 5545 Sec. 3.8.2.4), though it would seem odd to use
+           different case in the same file for identifiers that are
+           supposed to be the same. */
+        if (tz->tzid  &&  ! strcasecmp (dt_timezone, tz->tzid)) { break; }
+    }
+
+    if (tz) {
+        free(dt_timezone);
+    } else {
+        advise (NULL, "did not find VTIMEZONE section for %s", dt_timezone);
+        free(dt_timezone);
+        return NULL;
+    }
+
+    /* Determine if it's Daylight Saving. */
+    tp_std = strchr (tz->standard_params.dtstart, 'T');
+    tp_dt = strchr (node->value, 'T');
+
+    if (tz->daylight_params.dtstart) {
+        tp_dst = strchr (tz->daylight_params.dtstart, 'T');
+    } else {
+        /* No DAYLIGHT section. */
+        tp_dst = NULL;
+        dst = 0;
+    }
+
+    if (tp_std  &&  tp_dt) {
+        time_t transition[2] = { 0, 0 }; /* [standard, daylight] */
+        time_t dt[2]; /* [standard, daylight] */
+        unsigned int year;
+        char buf[5];
+
+        /* Datetime is form YYYYMMDDThhmmss.  Extract year. */
+        memcpy (buf, node->value, sizeof buf - 1);
+        buf[sizeof buf - 1] = '\0';
+        year = atoi (buf);
+
+        if (tz->standard_params.rrule) {
+            /* +1 to skip the T before the time */
+            transition[0] =
+                rrule_clock (tz->standard_params.rrule, tp_std + 1,
+                             tz->standard_params.offsetfrom, year);
+        }
+        if (tp_dst  &&  tz->daylight_params.rrule) {
+            /* +1 to skip the T before the time */
+            transition[1] =
+                rrule_clock (tz->daylight_params.rrule, tp_dst + 1,
+                             tz->daylight_params.offsetfrom, year);
+        }
+
+        if (transition[0] < transition[1]) {
+            advise (NULL, "format_datetime() requires that daylight "
+                    "saving time transition precede standard time "
+                    "transition");
+            return NULL;
+        }
+
+        if (parse_datetime (node->value, tz->standard_params.offsetto,
+                            0, &tws[0]) == OK) {
+            dt[0] = tws[0].tw_clock;
+        } else {
+            advise (NULL, "unable to parse datetime %s", node->value);
+            return NULL;
+        }
+
+        if (tp_dst) {
+            if (dt[0] < transition[1]) {
+                dst = 0;
+            } else {
+                if (parse_datetime (node->value,
+                                    tz->daylight_params.offsetto, 1,
+                                    &tws[1]) == OK) {
+                    dt[1] = tws[1].tw_clock;
+                } else {
+                    advise (NULL, "unable to parse datetime %s",
+                            node->value);
+                    return NULL;
+                }
+
+                dst = dt[1] > transition[0]  ?  0  :  1;
+            }
+        }
+
+        if (dst) {
+            if (tz->daylight_params.start_dt > 0  &&
+                dt[dst] < tz->daylight_params.start_dt) {
+                advise (NULL, "date-time of %s is before VTIMEZONE start "
+                        "of %s", node->value,
+                        tz->daylight_params.dtstart);
+                return NULL;
+            }
+        } else {
+            if (tz->standard_params.start_dt > 0  &&
+                dt[dst] < tz->standard_params.start_dt) {
+                advise (NULL, "date-time of %s is before VTIMEZONE start "
+                        "of %s", node->value,
+                        tz->standard_params.dtstart);
+                return NULL;
+            }
+        }
+    } else {
+        if (! tp_std) {
+            advise (NULL, "unsupported date-time format: %s",
+                    tz->standard_params.dtstart);
+            return NULL;
+        }
+        if (! tp_dt) {
+            advise (NULL, "unsupported date-time format: %s", node->value);
+            return NULL;
+        }
+    }
+
+    return strdup (dasctime (&tws[dst], 0));
+}
index dd43dc8..50722a7 100644 (file)
@@ -177,6 +177,7 @@ static struct ftable functable[] = {
      { "localmbox",  TF_LMBOX, FT_LS_LIT,      0,              TFL_PUTS },
      { "plus",       TF_NUM,   FT_LV_PLUS_L,   0,              TFL_PUTN },
      { "minus",      TF_NUM,   FT_LV_MINUS_L,  0,              TFL_PUTN },
+     { "multiply",   TF_NUM,   FT_LV_MULTIPLY_L, 0,            TFL_PUTN },
      { "divide",     TF_NUM,   FT_LV_DIVIDE_L, 0,              TFL_PUTN },
      { "modulo",     TF_NUM,   FT_LV_MODULO_L, 0,              TFL_PUTN },
      { "charleft",   TF_NONE,  FT_LV_CHAR_LEFT, 0,             TFL_PUTN },
index 5732af9..b0beeb2 100644 (file)
@@ -93,13 +93,18 @@ cpnumber(charstring_t dest, int num, unsigned int wid, char fill, size_t max) {
            charstring_push_back (rev, '?');
        } else if (num < 0  &&  wid > 0) {
            /* Shouldn't need the wid > 0 check, that's why the condition
-               at the top checks wid < max-1 when num < 0. */
-           charstring_push_back (rev, '-');
+              at the top checks wid < max-1 when num < 0. */
            --wid;
+           if (fill == ' ') {
+               charstring_push_back (rev, '-');
+           }
        }
        while (wid-- > 0  &&  fill != 0) {
            charstring_push_back (rev, fill);
        }
+       if (num < 0  &&  fill == '0') {
+           charstring_push_back (rev, '-');
+       }
 
        {
            /* Output the string in reverse. */
@@ -691,7 +696,8 @@ fmt_scan (struct format *format, charstring_t scanlp, int width, int *dat,
            if (str) {
                    char *xp;
 
-                   strncpy(buffer, str, sizeof(buffer));
+                   if (str != buffer)
+                       strncpy(buffer, str, sizeof(buffer));
                    buffer[sizeof(buffer)-1] = '\0';
                    str = buffer;
                    while (isspace((unsigned char) *str))
@@ -740,6 +746,9 @@ fmt_scan (struct format *format, charstring_t scanlp, int width, int *dat,
        case FT_LV_MINUS_L:
            value = fmt->f_value - value;
            break;
+       case FT_LV_MULTIPLY_L:
+           value *= fmt->f_value;
+           break;
        case FT_LV_DIVIDE_L:
            if (fmt->f_value)
                value = value / fmt->f_value;
@@ -881,7 +890,8 @@ fmt_scan (struct format *format, charstring_t scanlp, int width, int *dat,
                goto unfriendly;
            if ((str = mn->m_pers) == NULL) {
                if ((str = mn->m_note)) {
-                   strncpy (buffer, str, sizeof(buffer));
+                   if (str != buffer)
+                       strncpy (buffer, str, sizeof(buffer));
                    buffer[sizeof(buffer)-1] = '\0';
                    str = buffer;
                    if (*str == '(')
@@ -925,7 +935,8 @@ fmt_scan (struct format *format, charstring_t scanlp, int width, int *dat,
                /* UNQUOTEs RFC-2822 quoted-string and quoted-pair */
        case FT_LS_UNQUOTE:
            if (str) {          
-               strncpy(buffer, str, sizeof(buffer));
+               if (str != buffer)
+                   strncpy(buffer, str, sizeof(buffer));
                /* strncpy doesn't NUL-terminate if it fills the buffer */
                buffer[sizeof(buffer)-1] = '\0';
                unquote_string(buffer, buffer2);
index 3282e49..5d80dd9 100644 (file)
@@ -8,8 +8,6 @@
  */
 
 #include <h/mh.h>
-#include <h/signals.h>
-#include <setjmp.h>
 
 #ifdef READLINE_SUPPORT
 #include <readline/readline.h>
diff --git a/sbr/icalendar.l b/sbr/icalendar.l
new file mode 100644 (file)
index 0000000..016c6eb
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * icalendar.l -- icalendar (RFC 5545) scanner
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh.  See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+/* See porting notes at end of this file. */
+
+%{
+#include "h/mh.h"
+#include "h/icalendar.h"
+#include "sbr/icalparse.h"
+
+static char *unfold (char *, size_t *);
+static void destroy_icallex ();
+%}
+
+/*
+ * These flex options aren't used:
+ *   8bit not needed
+ *   case-insensitive not needed
+ *   align not used because this isn't performance critical
+ */
+%option outfile="lex.yy.c" prefix="ical"
+%option perf-report warn
+%option never-interactive noinput noyywrap
+
+              /*
+               * From RFC 5545 Â§ 3.1.
+               */
+name          {iana-token}|{x-name}
+iana-token    ({ALPHA}|{DIGIT}|-)+
+x-name        X-({vendorid}-)?({ALPHA}|{DIGIT}|-)+
+vendorid      ({ALPHA}|{DIGIT}){3,}
+param-name    {iana-token}|{x-name}
+param-value   {paramtext}|{quoted-string}
+paramtext     {SAFE-CHAR}*
+value         {VALUE-CHAR}*
+quoted-string {DQUOTE}{QSAFE-CHAR}*{DQUOTE}
+QSAFE-CHAR    {WSP}|[\x21\x23-\x7E]|{NON-US-ASCII}
+SAFE-CHAR     {WSP}|[\x21\x23-\x2B\x2D-\x39\x3C-\x7E]|{NON-US-ASCII}
+VALUE-CHAR    {WSP}|[\x21-\x7E]|{NON-US-ASCII}
+              /* The following is a short-cut definition that admits more
+                 that the UNICODE characters permitted by RFC 5545. */
+NON-US-ASCII  [\x80-\xF8]{2,4}
+              /* The following excludes HTAB, unlike {CTL}. */
+CONTROL       [\x00-\x08\x0A-\x1F\x7F]
+EQUAL         =
+              /* Solaris lex requires that the , be escaped. */
+COMMA         \,
+              /*
+               * From RFC 5545 Â§ 2.1.
+               */
+COLON         :
+SEMICOLON     ;
+
+              /*
+               * From RFC 5545 Â§ 3.3.11.
+               */
+text          ({TSAFE-CHAR}|:|{DQUOTE}|{ESCAPED-CHAR})*
+ESCAPED-CHAR  \\\\|\\;|\\,|\\N|\\n
+TSAFE-CHAR    {WSP}|[\x21\x23-\x2B\x2D-\x39\x3C-\x5B\x5D-\x7E]|{NON-US-ASCII|
+
+              /*
+               * Core rules (definitions) from RFC 5234 Appendix B.1.
+               */
+ALPHA         [\x41-\x5A\x61-\x7A]
+BIT           [01]
+CHAR          [\x01-\x7F]
+CR            \x0D
+              /* Variance from RFC 5234:  the {CR} is required in
+                 CRLF, but it is optional below to support Unix
+                 filesystem convention. */
+CRLF          ({CR}?{LF})+
+CTL           [\x00-\x1F\x7F]
+DIGIT         [\x30-\x39]
+DQUOTE        \x22
+HEXDIG        {DIGIT}|[A-F]
+HTAB          \x09
+LF            \x0A
+LWSP          ({WSP}|({CRLF}{WSP}))*
+OCTET         [\x00-\xFF]
+SP            \x20
+VCHAR         [\x21-\x7E]
+WSP           {SP}|{HTAB}
+
+/*
+ * Our definitions.
+ */
+fold                 {CRLF}{WSP}
+folded-name          {name}({fold}+{iana-token})+
+folded-param-name    {param-name}({fold}+{iana-token})+
+folded-quoted-string {DQUOTE}{QSAFE-CHAR}*{fold}+{QSAFE-CHAR}*{DQUOTE}
+folded-param-value   {paramtext}({fold}{paramtext}*)+|{folded-quoted-string}
+folded-value         {VALUE-CHAR}*({fold}{VALUE-CHAR}*)+
+
+%s s_name s_colon s_value s_semicolon s_param_name s_equal s_comma
+
+%%
+
+<INITIAL>
+{CRLF} {
+    /* Eat any leading newlines. */
+}
+
+<INITIAL>
+{folded-name} {
+    /* flex 2.5.4 defines icalleng as an int instead of a size_t,
+       so copy it. */
+    size_t len = icalleng;
+    unfold (icaltext, &len);
+    icalleng = len;
+
+    icallval = strdup (icaltext);
+    /* yy_push_state (s_name);         * s_name */
+    BEGIN (s_name);                   /* s_name */
+    return ICAL_NAME;
+}
+
+<INITIAL>
+{name} {
+    icallval = strdup (icaltext);
+    /* yy_push_state (s_name);         * s_name */
+    BEGIN (s_name);                   /* s_name */
+    return ICAL_NAME;
+}
+
+<s_name>
+{COLON} {
+    /* Don't need to strdup a single character. */
+    icallval = icaltext;
+    /* yy_pop_state ();                * INITIAL */
+    /* yy_push_state (s_colon);        * s_colon */
+    BEGIN (s_colon);                  /* s_colon */
+    return ICAL_COLON;
+}
+
+<s_colon>
+{folded-value} {
+    /* flex 2.5.4 defines icalleng as an int instead of a size_t,
+       so copy it. */
+    size_t len = icalleng;
+    unfold (icaltext, &len);
+    icalleng = len;
+
+    icallval = strdup (icaltext);
+    /* yy_pop_state ();                * INITIAL */
+    /* yy_push_state (s_value);        * s_value */
+    BEGIN (s_value);                  /* s_value */
+    return ICAL_VALUE;
+}
+
+<s_colon>
+{value} {
+    icallval = strdup (icaltext);
+    /* yy_pop_state ();                * INITIAL */
+    /* yy_push_state (s_value);        * s_value */
+    BEGIN (s_value);                  /* s_value */
+    return ICAL_VALUE;
+}
+
+<s_name>
+{SEMICOLON} {
+    /* Don't need to strdup a single character. */
+    icallval = icaltext;
+    /* yy_push_state (s_semicolon);    * s_name, s_semicolon */
+    BEGIN (s_semicolon);              /* s_name, s_semicolon */
+    return ICAL_SEMICOLON;
+}
+
+<s_semicolon>
+{folded-param-name} {
+    /* flex 2.5.4 defines icalleng as an int instead of a size_t,
+       so copy it. */
+    size_t len = icalleng;
+    unfold (icaltext, &len);
+    icalleng = len;
+
+    icallval = strdup (icaltext);
+    /* yy_pop_state ();                * s_name */
+    /* yy_push_state (s_param_name);   * s_name, s_param_name */
+    BEGIN (s_param_name);             /* s_name, s_param_name */
+    return ICAL_PARAM_NAME;
+}
+
+<s_semicolon>
+{param-name} {
+    icallval = strdup (icaltext);
+    /* yy_pop_state ();                * s_name */
+    /* yy_push_state (s_param_name);   * s_name, s_param_name */
+    BEGIN (s_param_name);             /* s_name, s_param_name */
+    return ICAL_PARAM_NAME;
+}
+
+<s_param_name>
+{EQUAL} {
+    /* Don't need to strdup a single character. */
+    icallval = icaltext;
+    /* yy_pop_state ();                * s_name */
+    /* yy_push_state (s_equal);        * s_name, s_equal */
+    BEGIN (s_equal);                  /* s_name, s_equal */
+    return ICAL_EQUAL;
+}
+
+<s_equal,s_comma>
+{folded-param-value} {
+    /* flex 2.5.4 defines icalleng as an int instead of a size_t,
+       so copy it. */
+    size_t len = icalleng;
+    unfold (icaltext, &len);
+    icalleng = len;
+
+    icallval = strdup (icaltext);
+    /* yy_pop_state ();                * s_name */
+    BEGIN (s_name);                   /* s_name */
+    return ICAL_PARAM_VALUE;
+}
+
+<s_equal,s_comma>
+{param-value} {
+    icallval = strdup (icaltext);
+    /* yy_pop_state ();                * s_name */
+    BEGIN (s_name);                   /* s_name */
+    return ICAL_PARAM_VALUE;
+}
+
+<s_name>
+{COMMA} {
+    /* Don't need to strdup a single character. */
+    icallval = icaltext;
+    /* yy_push_state (s_comma);        * s_name, s_comma */
+    BEGIN (s_comma);                  /* s_name, s_comma */
+    return ICAL_COMMA;
+}
+
+<s_value>
+{CRLF} {
+    /* Use start condition to ensure that all newlines are where expected. */
+    icallval = icaltext;
+    /* yy_pop_state ();                * INITIAL */
+    BEGIN (INITIAL);                  /* INITIAL */
+    return ICAL_CRLF;
+}
+
+<s_colon>
+{CRLF} {
+    /* Null value. */
+    icallval = strdup ("");
+    /* yy_pop_state ();                * INITIAL */
+    /* yy_push_state (s_value);        * s_value */
+    BEGIN (s_value);                  /* s_value */
+    /* Push the newline back so it can be handled in the proper state. */
+    unput ('\n');
+    return ICAL_VALUE;
+}
+
+. {
+    /* By default, flex will just pass unmatched text.  Catch it instead. */
+    advise (NULL, "unexpected input: |%s|\n", icaltext);
+}
+
+<<EOF>> {
+    destroy_icallex ();
+    yyterminate ();
+}
+
+%%
+
+static char *
+unfold (char *text, size_t *leng) {
+    /* It's legal to shorten text and modify leng (because we don't
+       use yymore()). */
+    char *cp;
+
+    /* First squash any CR-LF-WSP sequences. */
+    while ((cp = strstr (text, "\r\n "))  ||  (cp = strstr (text, "\r\n\t"))) {
+        /* Subtract any characters prior to fold sequence and 3 for
+           the fold sequence, and add 1 for the terminating null. */
+        (void) memmove (cp, cp + 3, *leng - (cp - text) - 3 + 1);
+        *leng -= 3;
+    }
+
+    /* Then squash any LF-WSP sequences. */
+    while ((cp = strstr (text, "\n "))  ||  (cp = strstr (text, "\n\t"))) {
+        /* Subtract any characters prior to fold sequence and 2 for
+           the fold sequence, and add 1 for the terminating null. */
+        (void) memmove (cp, cp + 2, *leng - (cp - text) - 2 + 1);
+        *leng -= 2;
+    }
+
+    return text;
+}
+
+
+/*
+ * To clean up memory, call the function provided by modern
+ * versions of flex.  Older versions don't have it, and of
+ * course this won't do anything if the scanner was built
+ * with something other than flex.
+ */
+static void
+destroy_icallex () {
+#if defined FLEX_SCANNER  &&  defined YY_FLEX_SUBMINOR_VERSION
+    /* Hack:  rely on fact that the the YY_FLEX_SUBMINOR_VERSION
+       #define was added to flex (flex.skl v. 2.163) after
+       #yylex_destroy() was added. */
+    icallex_destroy ();
+#endif /* FLEX_SCANNER  &&  YY_CURRENT_BUFFER_LVALUE */
+}
+
+/*
+ * See comment in h/icalendar.h about having to provide these
+ * because flex 2.5.4 doesn't.
+ */
+void
+icalset_inputfile (FILE *file) {
+    yyin = file;
+}
+
+void
+icalset_outputfile (FILE *file) {
+    yyout = file;
+}
+
+/*
+ * Porting notes
+ * -------------
+ * POSIX lex only supports an entry point name of yylex().  nmh
+ * programs can contain multiple scanners (see sbr/dtimep.l), so
+ * nmh requires the use of flex to build them.
+ * In addition, if there is a need to port this to Solaris lex:
+ *  - Use the lex -e or -w option.
+ *  - Comment out all of the %options.
+ *  - Comment out the <<EOF>> rule.
+ *  - The start condition and pattern must be on the same line.
+ *  - Comments must be inside rules, not just before them.
+ *  - Don't use start condition stack.  In the code, above BEGIN's are
+ *    used instead, and the contents of an imaginary start condition
+ *    stack are shown after each.  The stack operations are also shown
+ *    in comments.
+ */
diff --git a/sbr/icalparse.y b/sbr/icalparse.y
new file mode 100644 (file)
index 0000000..f2e7b37
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * icalparse.y -- icalendar (RFC 5545) parser
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh.  See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+%{
+    /*
+     * Use these yy* #defines, instead of the -p command line
+     * option, to allow multiple parsers in a program.  yyval
+     * is generated by Solaris yacc and is of type YYSTYPE.
+     * All other yy* symbols are data of a built-in type and
+     * are initialized by yyparse(), so they can be shared
+     * between different parsers.
+     */
+#define yydebug icaldebug
+#define yyerror icalerror
+#define yylex   icallex
+#define yylval  icallval
+#define yyval   icalval
+#define yyparse icalparse
+#define YYDEBUG 1
+#define YYERROR_DEBUG
+#define YYERROR_VERBOSE
+#define YY_NO_LEAKS
+
+    /*
+     * To quiet compile warnings with Solaris yacc:
+#ifdef sun
+# define lint 1
+# undef YYDEBUG
+#endif
+    */
+
+#include "h/mh.h"
+#include "h/icalendar.h"
+#include "h/utils.h"
+
+static char *append (contentline *, const char *, const size_t);
+static void new_input_line (contentline **);
+static void new_vevent (vevent *);
+static void free_param_names (param_list *);
+static void free_param_values (value_list *);
+static int icalerror (const char *);
+%}
+
+%token ICAL_NAME ICAL_COLON ICAL_VALUE ICAL_CRLF ICAL_SEMICOLON
+%token ICAL_PARAM_NAME ICAL_EQUAL ICAL_PARAM_VALUE ICAL_COMMA
+
+%start contentline_list
+
+%%
+
+    /* Instead of rigorous definition, cheat based on fact that every
+       icalbody line looks like a contentline.  And we don't need to
+       parse values. */
+contentline_list
+    : contentline
+    | contentline_list contentline
+
+    /* contentline = name *(";" param ) ":" value CRLF */
+contentline
+    : ICAL_NAME {
+          new_input_line (&vevents.last->contentlines);
+          append (vevents.last->contentlines->last, $1, strlen ($1));
+          vevents.last->contentlines->last->name = $1;
+      } ICAL_COLON {
+          append (vevents.last->contentlines->last, $3, strlen ($3));
+      } ICAL_VALUE {
+          append (vevents.last->contentlines->last, $5, strlen ($5));
+          vevents.last->contentlines->last->value = $5;
+      } ICAL_CRLF {
+          append (vevents.last->contentlines->last, $7, strlen ($7));
+          if (vevents.last->contentlines->cr_before_lf == CR_UNSET) {
+              vevents.last->contentlines->cr_before_lf =
+                  $7[0] == '\r'  ?  CR_BEFORE_LF  :  LF_ONLY;
+          }
+          /* END:VEVENT doesn't have a param_list so we don't need
+             to check for it below. */
+          if (vevents.last->contentlines->last->name  &&
+              vevents.last->contentlines->last->value  &&
+              ! strcasecmp (vevents.last->contentlines->last->name, "END")  &&
+              ! strcasecmp (vevents.last->contentlines->last->value,
+                            "VEVENT")) {
+              new_vevent (&vevents);
+          }
+      }
+    | ICAL_NAME {
+          new_input_line (&vevents.last->contentlines);
+          append (vevents.last->contentlines->last, $1, strlen ($1));
+          vevents.last->contentlines->last->name = $1;
+      } param_list ICAL_COLON {
+          append (vevents.last->contentlines->last, $4, strlen ($4));
+      } ICAL_VALUE {
+          append (vevents.last->contentlines->last, $6, strlen ($6));
+          vevents.last->contentlines->last->value = $6;
+      } ICAL_CRLF {
+          append (vevents.last->contentlines->last, $8, strlen ($8));
+          if (vevents.last->contentlines->cr_before_lf == CR_UNSET) {
+              vevents.last->contentlines->cr_before_lf =
+                  $8[0] == '\r'  ?  CR_BEFORE_LF  :  LF_ONLY;
+          }
+      }
+
+param_list
+    : ICAL_SEMICOLON {
+          append (vevents.last->contentlines->last, $1, strlen ($1));
+      } param
+    | param_list ICAL_SEMICOLON {
+          append (vevents.last->contentlines->last, $2, strlen ($2));
+      } param
+
+    /* param = param-name "=" param-value *("," param-value) */
+param
+    : ICAL_PARAM_NAME {
+          append (vevents.last->contentlines->last, $1, strlen ($1));
+          add_param_name (vevents.last->contentlines->last, $1);
+      } ICAL_EQUAL {
+          append (vevents.last->contentlines->last, $3, strlen ($3));
+      } param_value_list
+
+param_value_list
+    : ICAL_PARAM_VALUE {
+          append (vevents.last->contentlines->last, $1, strlen ($1));
+          add_param_value (vevents.last->contentlines->last, $1);
+      }
+    | param_value_list ICAL_COMMA {
+          append (vevents.last->contentlines->last, $2, strlen ($2));
+      } ICAL_PARAM_VALUE {
+          append (vevents.last->contentlines->last, $4, strlen ($4));
+          add_param_value (vevents.last->contentlines->last, $4);
+      }
+
+%%
+
+/*
+ * Remove the contentline node (by setting its name to NULL).
+ */
+void
+remove_contentline (contentline *node) {
+    free (node->name);
+    node->name = NULL;
+}
+
+contentline *
+add_contentline (contentline *node, const char *name) {
+    contentline *new_node = mh_xcalloc (1, sizeof (contentline));
+
+    new_node->name  = strdup (name);
+    new_node->next = node->next;
+    node->next = new_node;
+
+    return new_node;
+}
+
+/*
+ * Remove the value from a value_list.
+ */
+void
+remove_value (value_list *node) {
+    free (node->value);
+    node->value = NULL;
+}
+
+/*
+ * Find the contentline with the specified name, and optionally,
+ * the specified value and/or parameter name.
+ */
+contentline *
+find_contentline (contentline *contentlines, const char *name,
+                  const char *val) {
+    contentline *node;
+
+    for (node = contentlines; node; node = node->next) {
+        /* node->name will be NULL if the line was "deleted". */
+        if (node->name  &&  ! strcasecmp (name, node->name)) {
+            if (val  &&  node->value) {
+                if (! strcasecmp (val, node->value)) {
+                    return node;
+                }
+            } else {
+                return node;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static char *
+append (contentline *cline, const char *src, const size_t src_len) {
+    if (src_len > 0) {
+        const size_t len = cline->input_line_len + src_len;
+
+        while (len >= cline->input_line_size) {
+            cline->input_line_size = cline->input_line_size == 0
+                ?  (BUFSIZ>=8192 ? BUFSIZ : 8192)
+                :  2 * cline->input_line_size;
+            cline->input_line =
+                mh_xrealloc (cline->input_line, cline->input_line_size);
+        }
+
+        memcpy (cline->input_line + cline->input_line_len, src, src_len);
+        cline->input_line[len] = '\0';
+        cline->input_line_len = len;
+    }
+
+    return cline->input_line;
+}
+
+static void
+new_input_line (contentline **cline) {
+    contentline *new_node = mh_xcalloc (1, sizeof (contentline));
+
+    if (*cline) {
+        /* Append the new node to the end of the list. */
+        (*cline)->last->next = new_node;
+    } else {
+        /* First line:  save the root node in *cline. */
+        *cline = new_node;
+    }
+
+    /* Only maintain the pointer to the last node in the root node. */
+    (*cline)->last = new_node;
+}
+
+static void
+new_vevent (vevent *event) {
+    vevent *new_node = mh_xcalloc (1, sizeof (vevent)), *node;
+
+    /* Append the new node to the end of the list. */
+    for (node = event; node->next; node = node->next) { continue; }
+    event->last =  node->next = new_node;
+}
+
+void
+add_param_name (contentline *cline, char *name) {
+    param_list *new_node = mh_xcalloc (1, sizeof (param_list));
+    param_list *p;
+
+    new_node->param_name = name;
+
+    if (cline->params) {
+        for (p = cline->params; p->next; p = p->next) { continue; }
+        /* The loop terminated at, not after, the last node. */
+        p->next = new_node;
+    } else {
+        cline->params = new_node;
+    }
+}
+
+/*
+ * Add a value to the last parameter seen.
+ */
+void
+add_param_value (contentline *cline, char *value) {
+    value_list *new_node = mh_xcalloc (1, sizeof (value_list));
+    param_list *p;
+    value_list *v;
+
+    new_node->value = value;
+
+    if (cline->params) {
+        for (p = cline->params; p->next; p = p->next) { continue; }
+        /* The loop terminated at, not after, the last param_list node. */
+
+        if (p->values) {
+            for (v = p->values; v->next; v = v->next) { continue; }
+            /* The loop terminated at, not after, the last value_list node. */
+            v->next = new_node;
+        } else {
+            p->values = new_node;
+        }
+    } else {
+        /* Never should get here because a param value is always
+           preceded by a param name. */
+        free (new_node);
+    }
+}
+
+void
+free_contentlines (contentline *root) {
+    contentline *i, *next;
+
+    for (i = root; i; i = next) {
+        free (i->name);
+        if (i->params) {
+            free_param_names (i->params);
+        }
+        free (i->value);
+        free (i->input_line);
+        next = i->next;
+        free (i);
+    }
+}
+
+static void
+free_param_names (param_list *p) {
+    param_list *next;
+
+    for ( ; p; p = next) {
+        free (p->param_name);
+        free_param_values (p->values);
+        next = p->next;
+        free (p);
+    }
+}
+
+static void
+free_param_values (value_list *v) {
+    value_list *next;
+
+    for ( ; v; v = next) {
+        free (v->value);
+        next = v->next;
+        free (v);
+    }
+}
+
+static int
+icalerror (const char *error) {
+    if (! strcmp ("syntax error, unexpected $end, expecting ICAL_NAME",
+                  error)) {
+        /* Empty input: produce no output. */
+    } else {
+        adios (NULL, "%s", error);
+    }
+
+    return 0;  /* The return value isn't used anyway. */
+}
+
+/*
+ * In case YYDEBUG is disabled above; mhical refers to icaldebug.
+ */
+#if ! defined YYDEBUG  ||  ! YYDEBUG
+int icaldebug;
+#endif /* ! YYDEBUG */
index f4d3902..a8a9811 100644 (file)
@@ -561,7 +561,7 @@ lockit (struct lockinfo *li)
     curlock = li->curlock;
 
     if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
-        advise(NULL, "unable to create temporary file in %s", get_temp_dir());
+        advise(NULL, "unable to create temporary file in %s", li->tmplock);
        return -1;
     }
 
index 06a6156..23ccf03 100644 (file)
@@ -37,14 +37,15 @@ mime_type(const char *file_name) {
             if ((mimeencoding = get_file_info(MIMEENCODINGPROC, file_name))) {
                 content_type = concat(mimetype, "; charset=", mimeencoding,
                                       NULL);
+                free (mimetype);
             } else {
-                content_type = strdup(mimetype);
+                content_type = mimetype;
             }
         } else {
-            content_type = strdup(mimetype);
+            content_type = mimetype;
         }
 #else  /* MIMEENCODINGPROC */
-        content_type = strdup(mimetype);
+        content_type = mimetype;
 #endif /* MIMEENCODINGPROC */
     }
 #endif /* MIMETYPEPROC */
index 500b036..dddacaf 100644 (file)
@@ -11,7 +11,7 @@
 
 
 int
-uprf (char *c1, char *c2)
+uprf (const char *c1, const char *c2)
 {
     int c, mask;
 
index 1716ff1..eeacc83 100755 (executable)
@@ -38,7 +38,7 @@ cat >"$expected" <<EOF
   11  12/18 foo@example.edu    test<<This is a multi-part message in MIME format
 EOF
 run_prog scan -width 80 last > "$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : scan with bogus header field
 
 # check show (mhl)
 cat >"$expected" <<EOF
@@ -55,10 +55,11 @@ This is a multi-part message in MIME format.
 I am a stupid spammer.
 EOF
 run_prog show last > "$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : "show (mhl) with bogus header field"
 
 # check mhshow
 cat >"$expected" <<EOF
+[ Message inbox:11 ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -72,7 +73,7 @@ This is a multi-part message in MIME format.
 I am a stupid spammer.
 EOF
 run_prog mhshow last > "$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : mhshow with bogus header field
 
 # check m_getfld() handling of empty header field
 msgfile="$MH_TEST_DIR/Mail/inbox/12"
@@ -86,8 +87,8 @@ scan: eof encountered in field "Received"
 EOF
 
 run_prog scan -width 13 last >"$actual" 2>"$actual_err"
-check "$expected" "$actual"
-check "$expected_err" "$actual_err"
+check "$expected" "$actual" : m_getfld, empty header output
+check "$expected_err" "$actual_err" : m_getfld, empty header error
 
 # check m_getfld() handling of excessively long header field name
 msgfile="$MH_TEST_DIR/Mail/inbox/13"
@@ -100,7 +101,7 @@ cat >"$expected" <<EOF
 EOF
 
 run_prog scan -width 13 last >"$actual" 2>"$actual_err"
-check "$expected" "$actual"
+check "$expected" "$actual" : m_getfld with excessively long header field name
 # Cygwin has a BUFSIZ of 1024 so the error message gets truncated.
 # Deal with that by grepping to verify that scan showed the proper error.
 verify_string_in_file() {
@@ -135,8 +136,8 @@ scan: eol encountered in field "If a header field name has at least 512 characte
 EOF
 
 run_prog scan -width 13 last >"$actual" 2>"$actual_err"
-check "$expected" "$actual"
-check "$expected_err" "$actual_err"
+check "$expected" "$actual" : m_getfld with long header, no colon output
+check "$expected_err" "$actual_err" : m_getfld with long header, no colon error
 
 
 exit $failed
index 004c8b1..3a21658 100644 (file)
@@ -1,27 +1,26 @@
-# Common helper routines for test shell scripts -- intended to be sourced by them
+# Common helper routines for test shell scripts -- to be sourced by them
 # @configure_input@
 
 
-#### The following exported variables are set by "make check".  Ensure
+#### The following variables are set by "make check".  Ensure
 #### that they are set here so that individual tests can be run
 #### outside of make.  Requires that MH_OBJ_DIR be set on entry.
 test -z "$MH_TEST_DIR"  &&  MH_TEST_DIR="$MH_OBJ_DIR/test/testdir"
+test -z "$MH_INST_DIR"  &&  MH_INST_DIR="${MH_TEST_DIR}/inst"
 test -z "$prefix"  &&  prefix=@prefix@
 test -z "$datarootdir"  &&  datarootdir=@datarootdir@
 test -z "$exec_prefix"  &&  exec_prefix=@exec_prefix@
-test -z "$nmhexecdir"  &&  nmhexecdir="@libexecdir@/nmh"
 test -z "$bindir"  &&  bindir="@bindir@"
 test -z "$mandir"  &&  mandir="@mandir@"
 test -z "$nmhetcdir"  &&  nmhetcdir="@sysconfdir@/nmh"
+#### The following doesn't support running the distcheck version of
+#### test-mhparam standalone, but only via make distcheck.
+test -z "$nmhetcdirinst"  &&  nmhetcdirinst="@nmhetcdirinst@$nmhetcdir"
+test -z "$nmhlibexecdir"  &&  nmhlibexecdir="@libexecdir@/nmh"
 test -z "$supported_locks"  &&  supported_locks="@supported_locks@"
 test -z "$default_locking"  &&  default_locking="@default_locking@"
 test -z "$MULTIBYTE_ENABLED"  &&  MULTIBYTE_ENABLED="@MULTIBYTE_ENABLED@"
 test -z "$ICONV_ENABLED"  &&  ICONV_ENABLED="@ICONV_ENABLED@"
-export MH_TEST_DIR nmhexecdir bindir mandir nmhetcdir
-export MULTIBYTE_ENABLED ICONV_ENABLED
-
-test -z "$MH_INST_DIR"  &&  MH_INST_DIR="${MH_TEST_DIR}/inst"
-export MH_INST_DIR
 
 unset MAILDROP MHBUILD MHCONTEXT MHMTSUSERCONF MHN MHSHOW MHSTORE
 unset MHLDEBUG MHPDEBUG MHWDEBUG PAGER
@@ -33,6 +32,10 @@ unset LANG LC_ALL LC_CTYPE
 MHTMPDIR=$MH_TEST_DIR/Mail
 export MHTMPDIR
 
+#### If you're reading this .... you can set MH_TEST_NOCLEANUP to prevent
+#### the test suite from cleaning up the results of a test run, if you need
+#### to do manual debugging on a test.
+
 output_md5()
 {
   #### Output just the checksum.  If the filename needs to appear on
@@ -156,23 +159,26 @@ prepare_space() {
 
 #### check() requires two arguments, each the name of a file to be
 #### diff'ed.
-#### If the contents are same, the second file is removed.  And the
-####   first file is removed unless there's an optional argument with
-####   a value of 'keep first'.
-#### If different, global variable "failed" is incremented.
-#### If there's an optional 'ignore space' argument, spacing differences
-####   will not be considered signficant, emulating GNU diff -w.  It
-####   is assumed that the first file has already been run through
-####   prepare_space.
+#### If the contents are same, the second file is removed.  If different,
+#### global variable "failed" is incremented.
+#### Optional arguments:
+####   'keep first' -- first file is removed unless this is present.
+####   'ignore space' -- spacing differences will not be considered
+####        signficant, emulating GNU diff -w.  It is assumed that the
+####        first file has already been run through prepare_space.
+####   ':' <test name> -- will print '<test name>' in the failure message,
+####        to make it easier to tell which of multiple tests has failed.
 check() {
     first=$1; shift
     second=$1; shift
     keepfirst=
     ignorespace=
+    label=test
     while [ $# -gt 0 ]; do
         case $1 in
             'keep first')   keepfirst=1 ;;
             'ignore space') ignorespace=1 ;;
+           ':') shift; label=\'"$*"\'; break ;;
             *) echo "$0: invalid check() argument \"$1\" in test suite" >&2 ;;
         esac
         shift
@@ -194,7 +200,7 @@ check() {
         #### POSIX diff should support -c.
         diff -c "$first" "$second" || true
         echo
-        echo "$0: test failed, outputs are in $first and $second."
+        echo "$0: $label failed, outputs are in $first and $second."
         failed=`expr ${failed:-0} + 1`
         #### Set return status of the function.
         [ $failed -eq 0 ]
@@ -247,7 +253,7 @@ setup_test ()
 {
   MH="${MH_TEST_DIR}/Mail/.mh_profile"
   MHMTSCONF="${MH_INST_DIR}${nmhetcdir}/mts.conf"
-  MH_LIBEXEC_DIR="${MH_INST_DIR}${nmhexecdir}"
+  MH_LIBEXEC_DIR="${MH_INST_DIR}${nmhlibexecdir}"
   export MH MHMTSCONF MH_LIBEXEC_DIR
 
   #
@@ -278,7 +284,7 @@ setup_test ()
   export PATH
 
   # clean old test data
-  trap "cd $MH_TEST_DIR; rm -rf '$MH_TEST_DIR/Mail'" 0
+  trap "test -z $MH_TEST_NOCLEANUP && (cd $MH_TEST_DIR; rm -rf '$MH_TEST_DIR/Mail')" 0
   # setup test data
   mkdir -p "$MH_TEST_DIR/Mail" || exit 1
   cat > "$MH" <<EOF || exit 1
@@ -290,17 +296,26 @@ mhbuild: -nocontentid
 mhlproc: ${MH_LIBEXEC_DIR}/mhl
 moreproc: cat
 postproc: ${MH_LIBEXEC_DIR}/post
+showmimeproc: ${MH_INST_DIR}${bindir}/mhshow
 showproc: ${MH_LIBEXEC_DIR}/mhl
+#: The following aren't currently used by the test suite, but are
+#: defined here in case they are in the future:
+mailproc: ${MH_INST_DIR}${bindir}/mhmail
+rcvstoreproc: ${MH_LIBEXEC_DIR}/rcvstore
+sendproc: ${MH_INST_DIR}${bindir}/send
+whatnowproc: ${MH_INST_DIR}${bindir}/whatnow
+whomproc: ${MH_INST_DIR}${bindir}/whom
+#: incproc and packproc are defined in config.c but not used by any code.
 EOF
 
-  for f in MailAliases components digestcomps distcomps forwcomps mhl.body \
-           mhl.digest mhl.format mhl.forward mhl.headers mhl.reply \
-           mhn.defaults rcvdistcomps replcomps replgroupcomps scan.MMDDYY \
-           scan.YYYYMMDD scan.curses scan.default scan.highlighted scan.mailx \
-          scan.nomime scan.size scan.time scan.timely scan.unseen
-  do
-    cp "${MH_INST_DIR}${nmhetcdir}/${f}" "${MH_TEST_DIR}/Mail" || exit 1
-  done
+  if test -z '@nmhetcdirinst@'; then
+    #### This isn't used with make distcheck, so that we can use it to
+    #### be sure that etc files are not used from an existing nmh
+    #### installation.
+    #### posh doesn't like "${MH_INST_DIR}${nmhetcdir}"/*, so cd to
+    #### the directory and provide an argument without quotes to cp.
+    (cd "${MH_INST_DIR}${nmhetcdir}/"  &&  cp * "${MH_TEST_DIR}/Mail")
+  fi
 
   folder -create +inbox > /dev/null
   # create 10 basic messages
index b10374c..73fd14d 100755 (executable)
@@ -107,10 +107,69 @@ L10:      CHAR '\n'
 EOF
 
 run_prog $fmtdump >$actual 2>&1
-check $expected $actual 'keep first'
+check $expected $actual
 
 # check -form
-run_prog $fmtdump -form "$MH_TEST_DIR/Mail/scan.default" >$actual 2>&1
+cat >$expected <<EOF
+       LV_DAT, value dat[0]
+       NUMF, width 4, fill ' '
+       LV_DAT, value dat[1]
+       IF_V_NE 0 continue else goto L0
+       CHAR '+'
+       GOTO L1
+L0:    CHAR ' '
+L1:    LS_COMP, comp "replied"
+       IF_S continue else goto L2
+       CHAR '-'
+       GOTO L3
+L2:    LS_COMP, comp "encrypted"
+       IF_S continue else goto L4
+       CHAR 'E'
+L3:    GOTO L5
+L4:    CHAR ' '
+L5:    PARSEDATE, c_name "date", c_type <DATE>
+       LV_MON, c_name "date", c_type <DATE>
+       NUMF, width 2, fill '0'
+       CHAR '/'
+       PARSEDATE, c_name "date", c_type <DATE>
+       LV_MDAY, c_name "date", c_type <DATE>
+       NUMF, width 2, fill '0'
+       LS_COMP, comp "date", c_type <DATE>
+       IF_S continue else goto L6
+       CHAR ' '
+       GOTO L7
+L6:    CHAR '*'
+L7:    MYMBOX, c_name "from", c_type <ADDR>
+       LV_COMPFLAG, comp "from", c_type <ADDR>
+       IF_V_NE 0 continue else goto L8
+       LS_COMP, comp "to", c_type <ADDR>
+       IF_S continue else goto L8
+       LIT "To:"
+       PARSEADDR, c_name "to", c_type <ADDR>
+       LS_FRIENDLY, c_name "to", c_type <ADDR>
+       FT_LS_DECODE
+       
+       STRF, width 14, fill ' '
+L8:    IF_V_EQ 0 continue else goto L9
+       PARSEADDR, c_name "from", c_type <ADDR>
+       LS_FRIENDLY, c_name "from", c_type <ADDR>
+       FT_LS_DECODE
+       
+       STRF, width 17, fill ' '
+L9:    LIT "  "
+       LS_COMP, comp "subject"
+       FT_LS_DECODE
+       STR
+       LS_COMP, comp "body"
+       IF_S continue else goto L10
+       LIT "<<"
+       COMP, comp "body"
+       LIT ">>"
+L10:   CHAR '\n'
+       DONE
+EOF
+
+run_prog $fmtdump -form "$MH_INST_DIR${nmhetcdir}/scan.default" >$actual 2>&1
 check $expected $actual
 
 # check -format
index 9cfaeee..805ddfe 100755 (executable)
@@ -12,18 +12,33 @@ fi
 . "$MH_OBJ_DIR/test/common.sh"
 
 setup_test
-expected="$MH_TEST_DIR/$$.expected"
-actual="$MH_TEST_DIR/$$.actual"
+expected="$MH_TEST_DIR/test-functions$$.expected"
+actual="$MH_TEST_DIR/test-functions$$.actual"
 
 # check sday when day of week is specified
-echo 1 >"$expected"
+printf '1\n' >"$expected"
 fmttest -raw -format '%(sday{text})' 'Fri Sep 12 20:02 2014' >"$actual"
 check "$expected" "$actual"
 
 # check sday when day of week is not specified
-echo 0 >"$expected"
+printf '0\n' >"$expected"
 fmttest -raw -format '%(sday{text})' 'Sep 12 20:02 2014' >"$actual"
 check "$expected" "$actual"
 
+# check negative number, without padding
+printf '%s\n' ' -42' >"$expected"
+fmttest -raw -format '%4(minus -42)' 0 >"$actual"
+check "$expected" "$actual"
+
+# check negative number, with padding
+# Output was "0-42" with nmh 1.6 and earlier.
+printf '%s\n' -042 >"$expected"
+fmttest -raw -format '%04(minus -42)' 0 >"$actual"
+check "$expected" "$actual"
+
+# check multiply
+printf '%s\n' 42 >"$expected"
+fmttest -raw -format '%(void(num 7))%(multiply 6)' 0 >"$actual"
+check "$expected" "$actual"
 
 exit $failed
index e0360e3..225a2b4 100755 (executable)
@@ -36,4 +36,4 @@ fi
 
 chmod 755 ${MH_INST_DIR}${bindir}/inc
 
-valgrind --error-exitcode=1 --quiet inc -silent -file "$TESTMBOX"
+valgrind --error-exitcode=1 --quiet --suppressions="${srcdir}/test/valgrind.supp" inc -silent -file "$TESTMBOX"
index cc69aea..61821c2 100755 (executable)
@@ -12,7 +12,9 @@ if test -z "${MH_OBJ_DIR}"; then
     MH_OBJ_DIR=`cd $srcdir && pwd`; export MH_OBJ_DIR
 fi
 
-. "${srcdir}/test/post/test-post-common.sh"
+. "${MH_OBJ_DIR}/test/common.sh"
+
+setup_test
 
 #### Make sure that html-to-text conversion is what we expect.
 require_locale en_US.utf-8 en_US.utf8
@@ -23,10 +25,12 @@ expected_err="$MH_TEST_DIR/test-mhfixmsg$$.expected_err"
 actual="$MH_TEST_DIR/test-mhfixmsg$$.actual"
 actual_err="$MH_TEST_DIR/test-mhfixmsg$$.actual_err"
 
-if grep mhfixmsg-format-text/html "${MH_TEST_DIR}/Mail/mhn.defaults" \
+mhn_defaults="$MH_INST_DIR${nmhetcdir}/mhn.defaults"
+
+if grep mhfixmsg-format-text/html "${mhn_defaults}" \
   >/dev/null; then
   can_reformat_texthtml=1
-  if grep 'mhfixmsg-format-text/html.*w3m' "${MH_TEST_DIR}/Mail/mhn.defaults" \
+  if grep 'mhfixmsg-format-text/html.*w3m' "${mhn_defaults}" \
     >/dev/null; then
     #### w3m uses $HOME/.w3m/, and creates it if it doesn't exist.  To
     #### support testing with non-writeable $HOME, and to not leave
@@ -42,17 +46,19 @@ fi
 
 
 # check -help
-# Verified behavior consistent with compiled sendmail.
 cat >"$expected" <<EOF
 Usage: mhfixmsg [+folder] [msgs] [switches]
   switches are:
   -decodetext 8bit|7bit
   -nodecodetext
+  -decodetypes
+  -[no]crlflinebreaks
   -[no]textcharset
   -[no]reformat
   -[no]replacetextplain
   -[no]fixboundary
   -[no]fixcte
+  -fixtype mimetype
   -file file
   -outfile file
   -rmmproc program
@@ -68,7 +74,6 @@ check "$expected" "$actual"
 
 
 # check -version
-# Verified same behavior as compiled mhfixmsg.
 case `mhfixmsg -version` in
   mhfixmsg\ --*) ;;
   *) printf '%s: mhfixmsg -version generated unexpected output\n' "$0" >&2
@@ -415,11 +420,11 @@ else
 fi
 
 
-# check -decode (enabled by default)
+# check -decodetext (enabled by default)
 cat >"$expected" <<EOF
 To: recipient@example.com
 From: sender@example.com
-Subject: mhfixmsg decode test
+Subject: mhfixmsg decode test 1
 MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
 
@@ -436,7 +441,7 @@ EOF
 cat >`mhpath new` <<EOF
 To: recipient@example.com
 From: sender@example.com
-Subject: mhfixmsg decode test
+Subject: mhfixmsg decode test 1
 MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
 
@@ -454,7 +459,7 @@ run_prog mhfixmsg last -outfile "$actual"
 check "$expected" "$actual"
 
 
-# check -decode with more complicated content structure
+# check -decodetext with more complicated content structure
 cat >$expected <<EOF
 To: recipient@example.com
 From: sender@example.com
@@ -556,7 +561,7 @@ run_prog mhfixmsg last -outfile "$actual"
 check "$expected" "$actual"
 
 
-# check attempted -decode of binary text
+# check attempted -decodetext of binary text
 #### Generated the encoded text below with:
 ####   $ printf '\x0\xbd\xb2=\xbc\n' | base64
 cat >`mhpath new` <<EOF
@@ -580,7 +585,97 @@ run_prog mhfixmsg last
 check `mhpath last` "$expected" 'keep first'
 
 
-# check -decode of binary text
+# check for successful decode of a different part with attempted -decodetext
+# of binary text
+#### Generated the encoded text below with:
+####   $ printf '\x0\xbd\xb2=\xbc\n' | base64
+cat >$expected <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg successful decode of text/plain with failed binary decode
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: 8bit
+
+This is a text plain part
+
+------- =_aaaaaaaaaa0
+Content-Type: text/html; charset="ascii"
+Content-Transfer-Encoding: base64
+Content-Disposition: inline
+
+PGh0bWw+PGhlYWQ+PHRpdGxlPmxvbmcgbGluZTwvdGl0bGU+PC9oZWFkPjxib2R5PlRoaXMgbGlu
+ZSBpcyBncmVhdGVyIHRoYW4gOTk4IGNoYXJhY3RlcnMgaW4gbGVuZ3RoLCBzbyB0aGlzIHBhcnQg
+c2hvdWxkIG5vdCBiZSBkZWNvZGVkLiAgVGhpcyBsaW5lIGlzIGdyZWF0ZXIgdGhhbiA5OTggY2hh
+cmFjdGVycyBpbiBsZW5ndGgsIHNvIHRoaXMgcGFydCBzaG91bGQgbm90IGJlIGRlY29kZWQuICBU
+aGlzIGxpbmUgaXMgZ3JlYXRlciB0aGFuIDk5OCBjaGFyYWN0ZXJzIGluIGxlbmd0aCwgc28gdGhp
+cyBwYXJ0IHNob3VsZCBub3QgYmUgZGVjb2RlZC4gIFRoaXMgbGluZSBpcyBncmVhdGVyIHRoYW4g
+OTk4IGNoYXJhY3RlcnMgaW4gbGVuZ3RoLCBzbyB0aGlzIHBhcnQgc2hvdWxkIG5vdCBiZSBkZWNv
+ZGVkLiAgVGhpcyBsaW5lIGlzIGdyZWF0ZXIgdGhhbiA5OTggY2hhcmFjdGVycyBpbiBsZW5ndGgs
+IHNvIHRoaXMgcGFydCBzaG91bGQgbm90IGJlIGRlY29kZWQuICBUaGlzIGxpbmUgaXMgZ3JlYXRl
+ciB0aGFuIDk5OCBjaGFyYWN0ZXJzIGluIGxlbmd0aCwgc28gdGhpcyBwYXJ0IHNob3VsZCBub3Qg
+YmUgZGVjb2RlZC4gIFRoaXMgbGluZSBpcyBncmVhdGVyIHRoYW4gOTk4IGNoYXJhY3RlcnMgaW4g
+bGVuZ3RoLCBzbyB0aGlzIHBhcnQgc2hvdWxkIG5vdCBiZSBkZWNvZGVkLiAgVGhpcyBsaW5lIGlz
+IGdyZWF0ZXIgdGhhbiA5OTggY2hhcmFjdGVycyBpbiBsZW5ndGgsIHNvIHRoaXMgcGFydCBzaG91
+bGQgbm90IGJlIGRlY29kZWQuICBUaGlzIGxpbmUgaXMgZ3JlYXRlciB0aGFuIDk5OCBjaGFyYWN0
+ZXJzIGluIGxlbmd0aCwgc28gdGhpcyBwYXJ0IHNob3VsZCBub3QgYmUgZGVjb2RlZC4gIFRoaXMg
+bGluZSBpcyBncmVhdGVyIHRoYW4gOTk4IGNoYXJhY3RlcnMgaW4gbGVuZ3RoLCBzbyB0aGlzIHBh
+cnQgc2hvdWxkIG5vdCBiZSBkZWNvZGVkLiAgVGhpcyBsaW5lIGlzIGdyZWF0ZXIgdGhhbiA5OTgg
+Y2hhcmFjdGVycyBpbiBsZW5ndGgsIHNvIHRoaXMgcGFydCBzaG91bGQgbm90IGJlIGRlY29kZWQu
+ICA8L2JvZHk+PC9odG1sPg==
+
+------- =_aaaaaaaaaa0--
+EOF
+
+cat >`mhpath new` <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg successful decode of text/plain with failed binary decode
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: base64
+
+VGhpcyBpcyBhIHRleHQgcGxhaW4gcGFydAo=
+
+------- =_aaaaaaaaaa0
+Content-Type: text/html; charset="ascii"
+Content-Transfer-Encoding: base64
+Content-Disposition: inline
+
+PGh0bWw+PGhlYWQ+PHRpdGxlPmxvbmcgbGluZTwvdGl0bGU+PC9oZWFkPjxib2R5PlRoaXMgbGlu
+ZSBpcyBncmVhdGVyIHRoYW4gOTk4IGNoYXJhY3RlcnMgaW4gbGVuZ3RoLCBzbyB0aGlzIHBhcnQg
+c2hvdWxkIG5vdCBiZSBkZWNvZGVkLiAgVGhpcyBsaW5lIGlzIGdyZWF0ZXIgdGhhbiA5OTggY2hh
+cmFjdGVycyBpbiBsZW5ndGgsIHNvIHRoaXMgcGFydCBzaG91bGQgbm90IGJlIGRlY29kZWQuICBU
+aGlzIGxpbmUgaXMgZ3JlYXRlciB0aGFuIDk5OCBjaGFyYWN0ZXJzIGluIGxlbmd0aCwgc28gdGhp
+cyBwYXJ0IHNob3VsZCBub3QgYmUgZGVjb2RlZC4gIFRoaXMgbGluZSBpcyBncmVhdGVyIHRoYW4g
+OTk4IGNoYXJhY3RlcnMgaW4gbGVuZ3RoLCBzbyB0aGlzIHBhcnQgc2hvdWxkIG5vdCBiZSBkZWNv
+ZGVkLiAgVGhpcyBsaW5lIGlzIGdyZWF0ZXIgdGhhbiA5OTggY2hhcmFjdGVycyBpbiBsZW5ndGgs
+IHNvIHRoaXMgcGFydCBzaG91bGQgbm90IGJlIGRlY29kZWQuICBUaGlzIGxpbmUgaXMgZ3JlYXRl
+ciB0aGFuIDk5OCBjaGFyYWN0ZXJzIGluIGxlbmd0aCwgc28gdGhpcyBwYXJ0IHNob3VsZCBub3Qg
+YmUgZGVjb2RlZC4gIFRoaXMgbGluZSBpcyBncmVhdGVyIHRoYW4gOTk4IGNoYXJhY3RlcnMgaW4g
+bGVuZ3RoLCBzbyB0aGlzIHBhcnQgc2hvdWxkIG5vdCBiZSBkZWNvZGVkLiAgVGhpcyBsaW5lIGlz
+IGdyZWF0ZXIgdGhhbiA5OTggY2hhcmFjdGVycyBpbiBsZW5ndGgsIHNvIHRoaXMgcGFydCBzaG91
+bGQgbm90IGJlIGRlY29kZWQuICBUaGlzIGxpbmUgaXMgZ3JlYXRlciB0aGFuIDk5OCBjaGFyYWN0
+ZXJzIGluIGxlbmd0aCwgc28gdGhpcyBwYXJ0IHNob3VsZCBub3QgYmUgZGVjb2RlZC4gIFRoaXMg
+bGluZSBpcyBncmVhdGVyIHRoYW4gOTk4IGNoYXJhY3RlcnMgaW4gbGVuZ3RoLCBzbyB0aGlzIHBh
+cnQgc2hvdWxkIG5vdCBiZSBkZWNvZGVkLiAgVGhpcyBsaW5lIGlzIGdyZWF0ZXIgdGhhbiA5OTgg
+Y2hhcmFjdGVycyBpbiBsZW5ndGgsIHNvIHRoaXMgcGFydCBzaG91bGQgbm90IGJlIGRlY29kZWQu
+ICA8L2JvZHk+PC9odG1sPg==
+
+------- =_aaaaaaaaaa0--
+EOF
+
+run_prog mhfixmsg -noreformat last
+check `mhpath last` "$expected"
+
+
+# check -decodetext of binary text
 printf "%s\x0d\xbd\xb2=\xbc%s" "To: recipient@example.com
 From: sender@example.com
 Subject: mhfixmsg binary decode test
@@ -834,6 +929,68 @@ else
 fi
 
 
+# check that text/plain part is added to text/html in multipart/related
+# when it should be
+cat >"$expected" <<EOF
+MIME-Version: 1.0
+Date: Tue, 26 Feb 2013 18:07:20 -0600
+Subject: multipart/related, not /alternative
+Content-Type: multipart/alternative; boundary="----=_Part_90310_101292502.1"
+
+------=_Part_90310_101292502.1
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+This is the real content.
+
+------=_Part_90310_101292502.1
+Content-Type: text/html; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
+    <title>HTML Content</title>
+  </head>
+  <body>
+    This is the real content.
+  </body>
+</html>
+
+------=_Part_90310_101292502.1--
+EOF
+
+cat >`mhpath new` <<EOF
+MIME-Version: 1.0
+Date: Tue, 26 Feb 2013 18:07:20 -0600
+Subject: multipart/related, not /alternative
+Content-Type: multipart/related;
+        type="text/html"; boundary="----=_Part_90310_101292502.1"
+
+------=_Part_90310_101292502.1
+Content-Type: text/html; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
+    <title>HTML Content</title>
+  </head>
+  <body>
+    This is the real content.
+  </body>
+</html>
+
+------=_Part_90310_101292502.1--
+EOF
+
+if [ $can_reformat_texthtml -eq 1 ]; then
+  #### lynx inserts multiple blank lines, so squeeze them.
+  run_prog mhfixmsg last -outfile - | squeeze_lines >"$actual"
+  check "$expected" "$actual" 'ignore space'
+else
+  rm -f "$expected"
+fi
+
+
 # check handling of rfc822 message type
 cat >"$expected" <<EOF
 From: Test <test@example.com>
@@ -897,7 +1054,7 @@ run_test 'mhfixmsg last -outfile '"$actual" ''
 check "$expected" "$actual"
 
 
-# check stripping of CRs from ASCII text context
+# check not stripping of CRs from ASCII text context (default)
 cat >"$expected" <<EOF
 MIME-Version: 1.0
 From: sender@example.com
@@ -905,8 +1062,8 @@ To: recipient@example.com
 Subject: message with CR's
 Date: Mon, 29 Apr 2013 11:51:45 -0400
 
-There are two CR-LF pairs at the end of this sentence.
-
+There are two CR-LF pairs at the end of this sentence.\r
+\r
 EOF
 
 cat >`mhpath new` <<'EOF'
@@ -924,6 +1081,33 @@ run_prog mhfixmsg last -outfile "$actual"
 check "$expected" "$actual"
 
 
+# check stripping of CRs from ASCII text context
+cat >"$expected" <<EOF
+MIME-Version: 1.0
+From: sender@example.com
+To: recipient@example.com
+Subject: message with CR's
+Date: Mon, 29 Apr 2013 11:51:45 -0400
+
+There were two CR-LF pairs at the end of this sentence.
+
+EOF
+
+cat >`mhpath new` <<'EOF'
+MIME-Version: 1.0
+From: sender@example.com
+To: recipient@example.com
+Subject: message with CR's
+Date: Mon, 29 Apr 2013 11:51:45 -0400
+
+There were two CR-LF pairs at the end of this sentence.\r
+\r
+EOF
+
+run_prog mhfixmsg last -outfile "$actual" -nocrlflinebreaks
+check "$expected" "$actual"
+
+
 # check -replacetextplain
 prepare_space >"$expected" <<EOF
 To: recipient@example.com
@@ -1110,6 +1294,291 @@ if test -f '${MH_TEST_DIR}/Mail/inbox/20.backup'; then
 fi
 
 
+# check -fixtype
+cat >"$expected" <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg with incorrect Content-Type
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; name="plaintext.txt"
+
+this is plain text
+
+------- =_aaaaaaaaaa0--
+EOF
+
+cat >`mhpath new` <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg with incorrect Content-Type
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: application/octet-stream; name="plaintext.txt"
+
+this is plain text
+
+------- =_aaaaaaaaaa0--
+EOF
+
+mhfixmsg -fixtype application/octet-stream last
+check "$expected" `mhpath last`
+
+
+# check default -decodetypes
+cat >$expected <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 3
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: 8bit
+
+This is the text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: text/html; charset="iso-8859-1"; name="test2.txt"
+Content-Disposition: attachment; filename="test2.txt"
+Content-Transfer-Encoding: 8bit
+
+<html>
+<head>
+    This was the text/html part.
+</head>
+</body>
+</html>
+
+------- =_aaaaaaaaaa0--
+EOF
+
+cat >`mhpath new` <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 3
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: quoted-printable
+
+This is the text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: text/html; charset="iso-8859-1"; name="test2.txt"
+Content-Disposition: attachment; filename="test2.txt"
+Content-Transfer-Encoding: base64
+
+PGh0bWw+CjxoZWFkPgogICAgVGhpcyB3YXMgdGhlIHRleHQvaHRtbCBwYXJ0Lgo8L2hlYWQ+Cjwv
+Ym9keT4KPC9odG1sPgo=
+
+------- =_aaaaaaaaaa0--
+EOF
+run_prog mhfixmsg last -outfile "$actual" -noreformat
+check "$expected" "$actual"
+
+
+# check -decodetypes text/plain
+cat >$expected <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 4
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: 8bit
+
+This is the text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: text/html; charset="iso-8859-1"; name="test2.txt"
+Content-Disposition: attachment; filename="test2.txt"
+Content-Transfer-Encoding: base64
+
+PGh0bWw+DQo8aGVhZD4NCiAgICBUaGlzIGlzIHRoZSB0ZXh0L2h0bWwgcGFydC4NCjwvaGVhZD4N
+CjwvYm9keT4NCjwvaHRtbD4NCg==
+
+------- =_aaaaaaaaaa0--
+EOF
+
+cat >`mhpath new` <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 4
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: quoted-printable
+
+This is the text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: text/html; charset="iso-8859-1"; name="test2.txt"
+Content-Disposition: attachment; filename="test2.txt"
+Content-Transfer-Encoding: base64
+
+PGh0bWw+DQo8aGVhZD4NCiAgICBUaGlzIGlzIHRoZSB0ZXh0L2h0bWwgcGFydC4NCjwvaGVhZD4N
+CjwvYm9keT4NCjwvaHRtbD4NCg==
+
+------- =_aaaaaaaaaa0--
+EOF
+run_prog mhfixmsg last -outfile "$actual" -decodetypes text/plain -noreformat
+check "$expected" "$actual"
+
+
+# check -decodetypes attachment/ics
+cat >$expected <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 5
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: 8bit
+
+This is the text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: application/ics; charset="iso-8859-1"; name="invite.ics"
+Content-Disposition: attachment; filename="invite.ics"
+Content-Transfer-Encoding: 8bit
+
+BEGIN:VCALENDAR\r
+VERSION:2.0\r
+PRODID:test-mhical\r
+BEGIN:VEVENT\r
+DTSTAMP:20150101T162400Z\r
+DTSTART:20150105T160000\r
+DTEND:20150105T163000\r
+SUMMARY:4 pm meeting\r
+END:VEVENT\r
+END:VCALENDAR\r
+
+------- =_aaaaaaaaaa0--
+EOF
+
+cat >`mhpath new` <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 5
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: quoted-printable
+
+This is the=
+ text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: application/ics; charset="iso-8859-1"; name="invite.ics"
+Content-Disposition: attachment; filename="invite.ics"
+Content-Transfer-Encoding: base64
+
+QkVHSU46VkNBTEVOREFSDQpWRVJTSU9OOjIuMA0KUFJPRElEOnRlc3QtbWhpY2FsDQpCRUdJTjpW
+RVZFTlQNCkRUU1RBTVA6MjAxNTAxMDFUMTYyNDAwWg0KRFRTVEFSVDoyMDE1MDEwNVQxNjAwMDAN
+CkRURU5EOjIwMTUwMTA1VDE2MzAwMA0KU1VNTUFSWTo0IHBtIG1lZXRpbmcNCkVORDpWRVZFTlQN
+CkVORDpWQ0FMRU5EQVINCg==
+
+------- =_aaaaaaaaaa0--
+EOF
+run_prog mhfixmsg last -outfile "$actual" -noreformat
+check "$expected" "$actual"
+
+
+# check -decodetypes attachment/ics and -nocrlflinebreaks
+cat >$expected <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 5
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: 8bit
+
+This is the text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: application/ics; charset="iso-8859-1"; name="invite.ics"
+Content-Disposition: attachment; filename="invite.ics"
+Content-Transfer-Encoding: 8bit
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:test-mhical
+BEGIN:VEVENT
+DTSTAMP:20150101T162400Z
+DTSTART:20150105T160000
+DTEND:20150105T163000
+SUMMARY:4 pm meeting
+END:VEVENT
+END:VCALENDAR
+
+------- =_aaaaaaaaaa0--
+EOF
+
+cat >`mhpath new` <<EOF
+To: recipient@example.com
+From: sender@example.com
+Subject: mhfixmsg decode test 5
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
+
+------- =_aaaaaaaaaa0
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="test1.txt"
+Content-MD5: kq+Hnc2SD/eKwAnkFBDuEA==
+Content-Transfer-Encoding: quoted-printable
+
+This is the=
+ text/plain part.
+
+------- =_aaaaaaaaaa0
+Content-Type: application/ics; charset="iso-8859-1"; name="invite.ics"
+Content-Disposition: attachment; filename="invite.ics"
+Content-Transfer-Encoding: base64
+
+QkVHSU46VkNBTEVOREFSDQpWRVJTSU9OOjIuMA0KUFJPRElEOnRlc3QtbWhpY2FsDQpCRUdJTjpW
+RVZFTlQNCkRUU1RBTVA6MjAxNTAxMDFUMTYyNDAwWg0KRFRTVEFSVDoyMDE1MDEwNVQxNjAwMDAN
+CkRURU5EOjIwMTUwMTA1VDE2MzAwMA0KU1VNTUFSWTo0IHBtIG1lZXRpbmcNCkVORDpWRVZFTlQN
+CkVORDpWQ0FMRU5EQVINCg==
+
+------- =_aaaaaaaaaa0--
+EOF
+run_prog mhfixmsg last -outfile "$actual" -nocrlflinebreaks -noreformat
+check "$expected" "$actual"
+
+
 # make sure there are no tmp files left over
 find "$MH_TEST_DIR/Mail" \( -name 'mhfix*' -o -name ',mhfix*' \) -print \
   >"$actual"
diff --git a/test/mhical/test-mhical b/test/mhical/test-mhical
new file mode 100755 (executable)
index 0000000..fb80250
--- /dev/null
@@ -0,0 +1,800 @@
+#!/bin/sh
+######################################################
+#
+# Test mhical
+#
+######################################################
+
+set -e
+
+if test -z "${MH_OBJ_DIR}"; then
+    srcdir=`dirname $0`/../..
+    MH_OBJ_DIR=`cd $srcdir && pwd`; export MH_OBJ_DIR
+fi
+
+. "${MH_OBJ_DIR}/test/common.sh"
+
+setup_test
+
+#### Make sure that html-to-text conversion is what we expect.
+require_locale en_US.utf-8 en_US.utf8
+LC_ALL=en_US.UTF-8; export LC_ALL
+
+#### Disable colorized output.
+TERM=dumb; export TERM
+
+expected="$MH_TEST_DIR/test-mhical$$.expected"
+expected_err="$MH_TEST_DIR/test-mhical$$.expected_err"
+actual="$MH_TEST_DIR/test-mhical$$.actual"
+actual_err="$MH_TEST_DIR/test-mhical$$.actual_err"
+
+
+# check -help
+cat >"$expected" <<EOF
+Usage: mhical [switches]
+  switches are:
+  -reply accept|decline|tentative
+  -cancel
+  -form formatfile
+  -(forma)t string
+  -infile
+  -outfile
+  -[no]contenttype
+  -unfold
+  -debug
+  -version
+  -help
+EOF
+
+run_prog mhical -help >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+# check -version
+case `mhical -version` in
+  mhical\ --*) ;;
+  *) printf '%s: mhical -version generated unexpected output\n' "$0" >&2
+     failed=`expr ${failed:-0} + 1`;;
+esac
+
+
+# check display with timezone that only has standard time
+cat >"$expected" <<'EOF'
+Summary: Santa Watch
+Description: See Santa here first!
+At: Wed, 24 Dec 2014 12:00 +0000
+To: Fri, 25 Dec 2015 11:59
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:test-mhical
+
+BEGIN:VTIMEZONE
+TZID:MHT-12
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:+1200
+TZOFFSETTO:+1200
+END:STANDARD
+END:VTIMEZONE
+
+BEGIN:VEVENT
+DTSTAMP:20141224T140426Z
+DTSTART;TZID=MHT-12:20141225T000000
+DTEND;TZID=MHT-12:20151225T235959
+SUMMARY:Santa Watch
+DESCRIPTION: See Santa here first!
+END:VEVENT
+
+END:VCALENDAR
+EOF
+
+TZ=UTC mhical <"$MH_TEST_DIR/test1.ics" >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check display with 24 hour time format and -outfile
+cat >"$expected" <<'EOF'
+Summary: 4 pm meeting
+At: Mon, 05 Jan 2015 16:00
+To: Mon, 05 Jan 2015 16:30
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:test-mhical
+
+BEGIN:VEVENT
+DTSTAMP:20150101T162400Z
+DTSTART:20150105T160000
+DTEND:20150105T163000
+SUMMARY:4 pm meeting
+END:VEVENT
+
+END:VCALENDAR
+EOF
+
+mhical -outfile "$MH_TEST_DIR/test1.txt" <"$MH_TEST_DIR/test1.ics"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check display with 12 hour time format and -infile
+cat >"$expected" <<'EOF'
+Summary: 4 pm meeting
+At: Mon, 05 Jan 2015  4:00 PM
+To: Mon, 05 Jan 2015  4:30 PM
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:test-mhical
+
+BEGIN:VEVENT
+DTSTAMP:20150101T162800Z
+DTSTART:20150105T160000
+DTEND:20150105T163000
+SUMMARY:4 pm meeting
+END:VEVENT
+
+END:VCALENDAR
+EOF
+
+mhical -form mhical.12hour -infile "$MH_TEST_DIR/test1.ics" \
+       >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check display with DST
+cat >"$expected" <<'EOF'
+Method: REQUEST
+Organizer: Requester
+Summary: Big Meeting
+Location: The Office
+At: Mon, 05 Jan 2015 08:00 -0500
+To: Mon, 05 Jan 2015 09:00
+Attendees: Requestee
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:Microsoft Exchange Server 2010
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=FALSE;CN=Requestee
+ :MAILTO:requestee@example.com
+DESCRIPTION;LANGUAGE=en-US:\n\n
+SUMMARY;LANGUAGE=en-US:Big Meeting
+DTSTART;TZID=Eastern Standard Time:20150105T080000
+DTEND;TZID=Eastern Standard Time:20150105T090000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20141231T235959Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:The Office
+X-MICROSOFT-CDO-APPT-SEQUENCE:0
+X-MICROSOFT-CDO-OWNERAPPTID:-0123456789
+X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
+X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
+X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
+X-MICROSOFT-CDO-IMPORTANCE:1
+X-MICROSOFT-CDO-INSTTYPE:0
+X-MICROSOFT-DISALLOW-COUNTER:FALSE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+TZ=EST mhical <"$MH_TEST_DIR/test1.ics" >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check timezone boundary at transition to daylight saving time
+# The default mhical display format doesn't show the timezone for the
+# To: time, but it is different than that of the At: time.
+cat >"$expected" <<'EOF'
+Summary: EST to EDT
+At: Sun, 09 Mar 2014 01:59 -0500
+To: Sun, 09 Mar 2014 03:30
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:test-mhical
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20150101T000000Z
+DTSTART;TZID=Eastern Standard Time:20140309T015959
+DTEND;TZID=Eastern Standard Time:20140309T023000
+Summary: EST to EDT
+END:VEVENT
+END:VCALENDAR
+EOF
+
+TZ=EST5EDT mhical <"$MH_TEST_DIR/test1.ics" >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check -format, and that timezone is correct in end time
+cat >"$expected" <<'EOF'
+Sun, 09 Mar 2014 03:30:00 -0400
+EOF
+
+TZ=EST5EDT mhical -format '%(pretty{dtend})' \
+    -infile "$MH_TEST_DIR/test1.ics" -outfile "$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check timezone boundary at transition from daylight saving time
+cat >"$expected" <<'EOF'
+Summary: EDT to EST
+At: Sun, 02 Nov 2014 01:59 -0400
+To: Sun, 02 Nov 2014 02:00
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:test-mhical
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20150101T000000Z
+DTSTART;TZID=Eastern Standard Time:20141102T015959
+DTEND;TZID=Eastern Standard Time:20141102T020000
+Summary: EDT to EST
+END:VEVENT
+END:VCALENDAR
+EOF
+
+TZ=EST5EDT mhical <"$MH_TEST_DIR/test1.ics" >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check -format, and that timezone is correct in end time
+cat >"$expected" <<'EOF'
+Sun, 02 Nov 2014 02:00:00 -0500
+EOF
+
+TZ=EST5EDT mhical -format '%(pretty{dtend})' \
+    -infile "$MH_TEST_DIR/test1.ics" -outfile "$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+printf 'Local-Mailbox: Requestee2 <requestee2@example.com>\n' >> "$MH"
+
+# check accept of request
+cat >"$expected" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REPLY
+PRODID:nmh mhical v0.1
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;CN=Requestee2:MAILTO:requestee2@example.com
+SUMMARY;LANGUAGE=en-US:Accepted: test request
+DTSTART;TZID=Eastern Standard Time:20150105T090000
+DTEND;TZID=Eastern Standard Time:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+END:VCALENDAR
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:test-mhical
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee2
+ :MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART;TZID=Eastern Standard Time:20150105T090000
+DTEND;TZID=Eastern Standard Time:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+mhical -reply accept <"$MH_TEST_DIR/test1.ics" | egrep -v '^DTSTAMP:' \
+       >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+# check accept of multiple vevent requests in single vcalendar
+cat >"$expected" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REPLY
+PRODID:nmh mhical v0.1
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;CN=Requestee2:MAILTO:requestee2@example.com
+SUMMARY;LANGUAGE=en-US:Accepted: test request
+DTSTART;TZID=Eastern Standard Time:20150105T090000
+DTEND;TZID=Eastern Standard Time:20150105T093000
+UID:0123456790
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;CN=Requestee2:MAILTO:requestee2@example.com
+SUMMARY;LANGUAGE=en-US:Accepted: test request
+DTSTART;TZID=Eastern Standard Time:20150105T130000
+DTEND;TZID=Eastern Standard Time:20150105T134500
+UID:0123456791
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+END:VCALENDAR
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:test-mhical
+VERSION:2.0
+
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee2
+ :MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART;TZID=Eastern Standard Time:20150105T090000
+DTEND;TZID=Eastern Standard Time:20150105T093000
+UID:0123456790
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee2
+ :MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART;TZID=Eastern Standard Time:20150105T130000
+DTEND;TZID=Eastern Standard Time:20150105T134500
+UID:0123456791
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+
+END:VCALENDAR
+EOF
+
+mhical -reply accept <"$MH_TEST_DIR/test1.ics" | egrep -v '^DTSTAMP:' \
+       >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check decline of request
+cat >"$expected" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REPLY
+PRODID:nmh mhical v0.1
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;PARTSTAT=DECLINED;CN=Requestee2:MAILTO:requestee2@example.com
+SUMMARY;LANGUAGE=en-US:Declined: test request
+DTSTART:20150105T090000
+DTEND:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+END:VCALENDAR
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:test-mhical
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee2
+ :MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART:20150105T090000
+DTEND:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+mhical -reply decline <"$MH_TEST_DIR/test1.ics" | egrep -v '^DTSTAMP:' \
+       >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check response of tentative to request, and -nocontenttype
+cat >"$expected" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REPLY
+PRODID:nmh mhical v0.1
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;PARTSTAT=TENTATIVE;CN=Requestee2:MAILTO:requestee2@example.com
+SUMMARY;LANGUAGE=en-US:Tentative: test request
+DTSTART:20150105T090000
+DTEND:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+END:VCALENDAR
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:test-mhical
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee2
+ :MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART:20150105T090000
+DTEND:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+mhical -reply tentative -contenttype -nocontenttype \
+       -infile "$MH_TEST_DIR/test1.ics" | egrep -v '^DTSTAMP:' \
+       >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# check cancel request, and -contenttype
+cat >"$expected" <<'EOF'
+Content-Type: text/calendar; method="CANCEL"; charset="UTF-8"
+
+BEGIN:VCALENDAR
+METHOD:CANCEL
+PRODID:nmh mhical v0.1
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER;CN=Requestee2:MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:Cancelled:test request
+DTSTART:20150105T090000
+DTEND:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CANCELLED
+SEQUENCE:1
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+END:VCALENDAR
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:test-mhical
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER;CN=Requestee2:MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART:20150105T090000
+DTEND:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+mhical -cancel -contenttype <"$MH_TEST_DIR/test1.ics" | egrep -v '^DTSTAMP:' \
+       >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# Check TZID name wrapped with quotes, this used to cause a segfault.
+cat >"$expected" <<'EOF'
+Method: REQUEST
+Summary: Quoted timezone ID
+At: Wed, 01 Jan 2014 00:00
+To: Wed, 01 Jan 2014 01:00
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+PRODID:Zimbra-Calendar-Provider
+VERSION:2.0
+METHOD:REQUEST
+SUMMARY:Quoted timezone ID
+BEGIN:VTIMEZONE
+TZID:Etc/GMT
+BEGIN:STANDARD
+DTSTART:19710101T000000
+TZOFFSETTO:-0000
+TZOFFSETFROM:-0000
+TZNAME:GMT
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTART:20140101T000000
+DTEND;TZID="Etc/GMT":20140101T010000
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+EOF
+
+TZ=UTC mhical <"$MH_TEST_DIR/test1.ics" >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+
+
+# Check start datetime without a time, and missing end datetime.
+cat >"$expected" <<'EOF'
+Method: PUBLISH
+Summary: Test datetime without time
+At: Wed, 30 Dec 2015 00:00
+To: Wed, 30 Dec 2015 23:59
+EOF
+
+cat >"$MH_TEST_DIR/test1.ics" <<'EOF'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:icalendar-ruby
+CALSCALE:GREGORIAN
+METHOD:PUBLISH
+BEGIN:VEVENT
+DTSTAMP:20151208T204350Z
+UID:0123456789
+DTSTART;VALUE=DATE:20151230
+CLASS:PRIVATE
+SUMMARY:Test datetime without time
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+TZ=UTC mhical <"$MH_TEST_DIR/test1.ics" >"$MH_TEST_DIR/test1.txt"
+check "$expected" "$MH_TEST_DIR/test1.txt"
+rm -f "$MH_TEST_DIR/test1.ics"
+
+
+exit $failed
index d14475c..e5fc895 100755 (executable)
@@ -220,6 +220,7 @@ cat > $expected <<EOF
 EOF
 
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat > $msgfile <<EOF
 To: recipient@example.com, recipient@example.com, recipient@example.com,
     recipient@example.com, recipient@example.com, recipient@example.com,
@@ -363,5 +364,150 @@ EOF
 run_prog mhlist last > $actual 2>&1
 check $expected $actual
 
+##  now check mhlist output
+
+msgfile=`mhpath new`
+msgnum=`basename $msgfile`
+cat > $msgfile <<EOF
+To: example@example.org
+From: someone <someone@example.com>
+Subject: mhlist test
+Date: Thu, 29 Jan 2015 18:12:21 +0000 (GMT)
+Content-Type: multipart/mixed; boundary="BoundaryMixed"
+
+--BoundaryMixed
+Content-type: multipart/alternative;
+ boundary="BoundaryAlternative1"
+
+--BoundaryAlternative1
+Content-type: text/enriched; CHARSET=US-ASCII
+Content-transfer-encoding: 7bit
+
+This is supposedly enriched.
+
+--BoundaryAlternative1
+Content-type: text/plain; charset=ISO-8859-1; format=flowed
+Content-transfer-encoding: 8bit
+
+This is the body text/plain part.
+
+--BoundaryAlternative1
+Content-type: text/html; CHARSET=US-ASCII
+Content-transfer-encoding: 7bit
+
+<html><body><div><span style="line-height: 1.5;">
+This is the text/html body part.
+</span></div></body></html>
+
+--BoundaryAlternative1--
+
+--BoundaryMixed
+Content-type: multipart/alternative;
+ boundary="BoundaryAlternative2"
+
+--BoundaryAlternative2
+Content-type: audio/wav
+Content-transfer-encoding: 8bit
+
+pretend wav audio
+
+--BoundaryAlternative2
+Content-type: audio/mp3
+Content-transfer-encoding: 8bit
+
+pretend mp3 audio
+
+--BoundaryAlternative2
+Content-type: audio/basic
+Content-transfer-encoding: 8bit
+
+pretend basic audio
+
+--BoundaryAlternative2--
+
+--BoundaryMixed--
+EOF
+
+# Write the expected output.
+cat > $expected <<EOF
+ msg part  type/subtype              size description
+  13       multipart/mixed           1038
+     1     multipart/alternative      518
+     1.1   text/html                  111
+     1.2   text/plain                  34
+     1.3   text/enriched               29
+     2     multipart/alternative      326
+     2.1   audio/basic                 20
+     2.2   audio/mp3                   18
+     2.3   audio/wav                   18
+EOF
+
+# check it -- vanilla output
+run_prog mhlist $msgnum > $actual 2>&1
+check "$expected" "$actual"
+
+# Write the expected output.
+cat > $expected <<EOF
+ msg part  type/subtype              size description
+  13       multipart/mixed           1038
+     1     multipart/alternative      518
+     1.1   text/html                  111
+     1.2   text/plain                  34
+     1.3   text/enriched               29
+     2     multipart/alternative      326
+EOF
+
+# check it -- just part 1
+run_prog mhlist -part 1 $msgnum > $actual 2>&1
+check "$expected" "$actual"
+
+# Write the expected output.
+cat > $expected <<EOF
+ msg part  type/subtype              size description
+  13       multipart/mixed           1038
+     1     multipart/alternative      518
+     2     multipart/alternative      326
+EOF
+
+# check it -- ask for audio/basic in part 1, should get nothing
+run_prog mhlist -part 1 -type audio/basic $msgnum > $actual 2>&1
+check "$expected" "$actual"
+
+# Write the expected output.
+cat > $expected <<EOF
+ msg part  type/subtype              size description
+  13       multipart/mixed           1038
+     1     multipart/alternative      518
+     1.1   text/plain                  34
+     1.2   text/html                  111
+     1.3   text/enriched               29
+     2     multipart/alternative      326
+     2.1   audio/wav                   18
+     2.2   audio/basic                 20
+     2.3   audio/mp3                   18
+EOF
+
+# check it -- bump plain and wav to the top of their parts
+run_prog mhlist -prefer text/plain -prefer audio/wav $msgnum > $actual 2>&1
+check "$expected" "$actual"
+
+# Write the expected output.
+cat > $expected <<EOF
+ msg part  type/subtype              size description
+  13       multipart/mixed           1038
+     1     multipart/alternative      518
+     1.1   text/plain                  34
+     1.2   text/enriched               29
+     1.3   text/html                  111
+     2     multipart/alternative      326
+     2.1   audio/basic                 20
+     2.2   audio/mp3                   18
+     2.3   audio/wav                   18
+EOF
+
+# check it -- bump plain and enriched to the top.  add a bogus type as well.
+run_prog mhlist -prefer foo/bar -prefer text/plain -prefer text/enriched $msgnum > $actual 2>&1
+check "$expected" "$actual"
+
 
 exit $failed
index c315778..29dd11f 100755 (executable)
@@ -84,19 +84,19 @@ $MH_INST_DIR$bindir/refile
 700
 $bindir/inc
 more
-$bindir/mhmail
-$MH_INST_DIR$nmhexecdir/mhl
+$MH_INST_DIR$bindir/mhmail
+$MH_INST_DIR$nmhlibexecdir/mhl
 cat
 600
 $bindir/packf
-$MH_INST_DIR$nmhexecdir/post
-$bindir/send
-$bindir/mhshow
-$MH_INST_DIR$nmhexecdir/mhl
+$MH_INST_DIR$nmhlibexecdir/post
+$MH_INST_DIR$bindir/send
+$MH_INST_DIR$bindir/mhshow
+$MH_INST_DIR$nmhlibexecdir/mhl
 nmh-`cat ${srcdir}/VERSION`
-$bindir/whatnow
-$bindir/whom
-$nmhetcdir
+$MH_INST_DIR$bindir/whatnow
+$MH_INST_DIR$bindir/whom
+$nmhetcdirinst
 $MH_LIBEXEC_DIR
 fcntl
 ${default_locking}
index e0a0b6e..6c3c763 100755 (executable)
@@ -43,6 +43,7 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 EOF
 
 cat > $expected <<EOF
+[ Message inbox:11 ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -56,6 +57,6 @@ This is a test
 EOF
 
 run_prog mhshow $msgnum > $actual 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : bogus quoted-printable data
 
 exit $failed
index fdf1ca7..f872e3b 100755 (executable)
@@ -35,6 +35,7 @@ EOF
 
 # check it
 cat > $expected <<EOF
+[ Message inbox:11 ]
 Date:    Tue, 05 Mar 2002 18:20:35 +0000
 To:      bar@example.edu
 From:    foo@example.edu
@@ -47,7 +48,7 @@ Content-Disposition: inline
 This is a test; this is the body.
 EOF
 run_prog mhshow $msgnum > $actual 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : C-T-E binary text
 
 exit $failed
 
index 47005cc..ca1e7cd 100755 (executable)
@@ -153,12 +153,13 @@ EOF
 
 # Write the expected output.
 cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 [ part 1.1 - text/plain -   29B  ]
 This is the text/plain part.
 EOF
 
 run_prog mhshow -part 1.1 -form mhl.null $msgnum >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check number 1
 
 # check a boundary condition in m_getfld():  its internal message
 # buffer holds exactly up to the end of a header field name in a
@@ -293,12 +294,13 @@ EOF
 
 # Write the expected output.
 cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 [ part 1.1 - text/plain -   29B  ]
 This is the text/plain part.
 EOF
 
 run_prog mhshow -part 1.1 -form mhl.null $msgnum >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check number 2
 
 # check a boundary condition in m_getfld():  its internal message
 # buffer holds exactly up to the end of a message part boundary
@@ -434,12 +436,13 @@ EOF
 
 # Write the expected output.
 cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 [ part 1.1 - text/plain - the text/plain part  29B  ]
 This is the text/plain part.
 EOF
 
 run_prog mhshow -part 1.1 -form mhl.null $msgnum >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check number 3
 
 
 exit $failed
index dae70a5..1b78c8e 100755 (executable)
@@ -47,6 +47,7 @@ EOF
 
 # check it
 cat > $expected <<EOF
+[ Message inbox:11 ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -69,6 +70,6 @@ just a newline
 =\b ^H (backspace) character, probably erased = in diff output
 EOF
 run_prog mhshow $msgnum > $actual 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : bogus quoted-printable data
 
 exit $failed
index 9739c48..27fb907 100755 (executable)
@@ -51,12 +51,134 @@ EOF
 
 # Write the expected output.
 cat > $expected <<EOF
+[ Message inbox:11 ]
 [ part 1.1 - text/plain -   29B  ]
 This is the text/plain part.
 EOF
 
 # check it
 run_prog mhshow -part 1.1 -form mhl.null $msgnum > $actual 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : part 1.1
+
+
+# tests of -part/-type/-prefer
+
+cat > $msgfile <<EOF
+To: example@example.org
+From: someone <someone@example.com>
+Subject: mhshow -part/-type/-prefer test
+Date: Thu, 29 Jan 2015 18:12:21 +0000 (GMT)
+Content-Type: multipart/mixed; boundary="BoundaryMixed"
+
+--BoundaryMixed
+Content-type: multipart/alternative;
+ boundary="BoundaryAlternative1"
+
+--BoundaryAlternative1
+Content-type: text/plain; charset=US-ASCII; format=flowed
+Content-transfer-encoding: 8bit
+
+This is the body text/plain part.
+--BoundaryAlternative1
+Content-type: multipart/related;
+ boundary="BoundaryAlternative2"; type="text/html"
+
+--BoundaryAlternative2
+Content-type: text/html; CHARSET=US-ASCII
+Content-transfer-encoding: quoted-printable
+
+<html><body><div><span style=3D"line-height: 1.5;">
+This is the text/html body part.
+</span></div></body></html>
+
+--BoundaryAlternative2--
+
+--BoundaryAlternative1--
+
+--BoundaryMixed
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+This is the final text/plain signature part.
+
+--BoundaryMixed--
+EOF
+
+# Write the expected output.
+cat > $expected <<EOF
+[ Message inbox:11 ]
+[ part 1.1.1 - text/html -   111B  ]
+This is the text/html body part.
+EOF
+
+# check it
+# The sed command removes blank lines and leading space that lynx inserts.
+run_prog mhshow -part 1.1.1 -form mhl.null $msgnum |
+    sed '/^$/D; s/^ *//;' > $actual 2>&1
+check "$expected" "$actual" : part 1.1.1
+
+# Write the expected output.
+cat > $expected <<EOF
+[ Message inbox:11 ]
+[ part 1.2 - text/plain -   33B  ]
+This is the body text/plain part.
+EOF
+
+# check it
+run_prog mhshow -part 1.2 -form mhl.null $msgnum > $actual 2>&1
+check "$expected" "$actual" : part 1.2
+
+# Write the expected output.
+cat > $expected <<EOF
+[ Message inbox:11 ]
+[ part 1.1 - text/plain -   33B  ]
+This is the body text/plain part.
+EOF
+
+# check it
+run_prog mhshow -prefer text/plain -part 1 -form mhl.null $msgnum > $actual 2>&1
+check "$expected" "$actual" : part conflicting text/plain, part 1 switches
+
+# Write the expected output.
+cat > $expected <<EOF
+[ Message inbox:11 ]
+[ part 2 - text/plain -   93B  ]
+_______________________________________________
+This is the final text/plain signature part.
+EOF
+
+# check it
+run_prog mhshow -part 2 -form mhl.null $msgnum > $actual 2>&1
+check "$expected" "$actual" : part 2
+
+# Write the expected output.
+cat > $expected <<EOF
+[ Message inbox:11 ]
+[ part 1.2 - text/plain -   33B  ]
+This is the body text/plain part.
+[ part 2 - text/plain -   93B  ]
+_______________________________________________
+This is the final text/plain signature part.
+EOF
+
+# check it
+run_prog mhshow -type text/plain -form mhl.null $msgnum > $actual 2>&1
+check "$expected" "$actual" : type text/plain
+
+# Write the expected output.
+cat > $expected <<EOF
+[ Message inbox:11 ]
+[ part 1.1 - text/plain -   33B  ]
+This is the body text/plain part.
+[ part 2 - text/plain -   93B  ]
+_______________________________________________
+This is the final text/plain signature part.
+EOF
+
+run_prog mhshow -prefer text/plain -form mhl.null $msgnum > $actual 2>&1
+check "$expected" "$actual" : prefer text/plain
 
 exit $failed
index d4d7ef0..f8d854f 100755 (executable)
@@ -28,6 +28,7 @@ actual="$MH_TEST_DIR"/$$.actual
 
 # check charset conversion
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -40,7 +41,8 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 4 =F7 2 =3D 2
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -53,7 +55,7 @@ MIME-Version: 1.0
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check charset conversion
 
 cat >>"$MH" <<'EOF'
 mhshow-show-text/plain: echo %{charset}
@@ -62,6 +64,7 @@ EOF
 
 # check expansion of %{charset} by itself
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -74,7 +77,8 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 4 =F7 2 =3D 2
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -86,11 +90,13 @@ UTF-8
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check %{charset} by itself
 
 
 # check expansion of empty %{charset} by itself
 msgfile=`mhpath new`
+omsgnum=$msgnum
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -103,7 +109,8 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 4 =F7 2 =3D 2
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -115,7 +122,7 @@ MIME-Version: 1.0
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check empty %{charset} by itself
 
 
 grep -v 'mhshow-show-text/plain:' "$MH" >"$MH.new"
@@ -125,7 +132,8 @@ mhshow-show-text/plain: charset=%{charset}; echo ${charset:+-I $charset}
 EOF
 
 # check expansion of embedded %{charset} with no text following
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$omsgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -136,12 +144,13 @@ MIME-Version: 1.0
 -I UTF-8
 EOF
 
-run_prog mhshow prev >"$actual" 2>&1
-check "$expected" "$actual"
+run_prog mhshow prev >"$actual" 2>&1   # NB:  "mhshow prev" !!!
+check "$expected" "$actual" : check embedded %{charset} with no text
 
 
 # check expansion of empty embedded %{charset} with no text following
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -153,14 +162,15 @@ MIME-Version: 1.0
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check empty embedded %{charset} with no text following
 
 
 sed -e 's%\(mhshow-show-text/plain:.*\)%\1 file%' "$MH" >"$MH.new"
 mv -f "$MH.new" "$MH"
 
 # check expansion of embedded %{charset} with text following
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$omsgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -171,12 +181,13 @@ MIME-Version: 1.0
 -I UTF-8 file
 EOF
 
-run_prog mhshow prev >"$actual" 2>&1
-check "$expected" "$actual"
+run_prog mhshow prev >"$actual" 2>&1   # NB:  "mhshow prev" !!!
+check "$expected" "$actual" : check embedded %{charset} with text following
 
 
 # check expansion of empty embedded %{charset} with text following
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -188,7 +199,7 @@ file
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check empty embedded %{charset} with text following
 
 
 sed -e 's/charset/method/g' "$MH" >"$MH.new"
@@ -196,6 +207,7 @@ mv -f "$MH.new" "$MH"
 
 # check parameter value quoting
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -208,7 +220,10 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 4 =F7 2 =3D 2
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
+EOF
+cat >>"$expected" <<'EOF'
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -220,7 +235,7 @@ MIME-Version: 1.0
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check parameter value quoting
 
 
 sed -e 's/method/unknown/g' "$MH" >"$MH.new"
@@ -228,6 +243,7 @@ mv -f "$MH.new" "$MH"
 
 # check that unknown parameter is not expanded
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -240,7 +256,8 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 4 =F7 2 =3D 2
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -252,7 +269,7 @@ file
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : check unknown parameter is not expanded
 
 
 grep -v '^mhshow-show-text/plain: ' "$MH" >>"$MH.new"
@@ -263,6 +280,7 @@ EOF
 
 # check parameter value quoting with text following
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -274,7 +292,8 @@ Date: Sun, 18 Dec 2005 00:52:39 +0100
 This is a test.
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -285,7 +304,7 @@ MIME-Version: 1.0
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : parameter value quoting with text following
 
 #
 # test a large file that needs to be converted to UTF-8
@@ -294,6 +313,7 @@ check "$expected" "$actual"
 grep -v '^mhshow-show-text/plain: ' "$MH" >>"$MH.new"
 mv -f "$MH.new" "$MH"
 msgfile=`mhpath new`
+msgnum=`basename $msgfile`
 cat >"$msgfile" <<'EOF'
 From: foo@example.edu
 To: bar@example.edu
@@ -728,7 +748,8 @@ one of the glass buildings.
 =93How strange!=94 she exclaimed, drawing a long breath.
 EOF
 
-cat >"$expected" <<'EOF'
+cat >"$expected" <<EOF
+[ Message inbox:$msgnum ]
 Date:    Sun, 18 Dec 2005 00:52:39 +0100
 To:      bar@example.edu
 From:    foo@example.edu
@@ -1155,6 +1176,6 @@ one of the glass buildings.
 EOF
 
 run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : large file conversion to UTF-8
 
 exit $failed
index 858d8e7..ca19bb8 100755 (executable)
@@ -49,6 +49,12 @@ run_test 'pick -not -subject message.8 last:5' '7
 10
 11'
 
+# Test -reverse.
+run_test 'pick -reverse -not -subject message.8 last:5' '11
+10
+9
+7'
+
 # Test -lbrace, -rbrace.
 run_test 'pick -subject message.12 -and -subject message.3 -or -from Test4' '4'
 run_test 'pick -subject message.12 -and -lbrace -subject message.3 -rbrace' \
diff --git a/test/repl/test-convert b/test/repl/test-convert
new file mode 100755 (executable)
index 0000000..d2d398a
--- /dev/null
@@ -0,0 +1,279 @@
+#!/bin/sh
+######################################################
+#
+# Test repl -convertarg
+#
+######################################################
+
+set -e
+
+if test -z "${MH_OBJ_DIR}"; then
+    srcdir=`dirname $0`/../..
+    MH_OBJ_DIR=`cd $srcdir && pwd`; export MH_OBJ_DIR
+fi
+
+. "${srcdir}/test/post/test-post-common.sh"
+
+expected="$MH_TEST_DIR/test-convert$$.expected"
+actual=`mhpath +`/draft
+
+printf 'Local-Mailbox: recipient@example.com' >>"$MH"
+
+
+#### Make sure that this works with 7-bit encoding.
+LC_ALL=C; export LC_ALL
+
+
+# check -convertarg with multiple parts and additional text in draft
+cat >"$expected" <<'EOF'
+From: recipient@example.com
+To: sender@example.com
+cc: 
+Fcc: +outbox
+Subject: Re: test
+Comments: In-reply-to sender@example.com
+   message dated "Thu, 11 Dec 2014 08:19:02 -0600."
+MIME-Version: 1.0
+Content-Type: text/plain; charset="US-ASCII"
+
+> This is part 1.
+
+> This is part 2.
+EOF
+
+cat >`mhpath new` <<'EOF'
+From: sender@example.com
+To: recipient@example.com
+Subject: test
+Date: Thu, 11 Dec 2014 08:19:02 -0600
+Content-Type: multipart/mixed; boundary="_001_"
+MIME-Version: 1.0
+
+--_001_
+Content-Type: text/plain
+
+This is part 1.
+
+--_001_
+Content-Type: text/plain
+
+This is part 2.
+
+--_001_
+Content-Type: text/enriched
+
+This should not appear in the reply
+because the content type isn't matched.
+
+--_001_--
+EOF
+
+repl -noformat -convertarg text/plain '' -nowhatnowproc last
+mhbuild "$actual"
+check "$actual" "$expected"
+
+
+#### Make sure that this works with 8-bit encoding.
+require_locale en_US.utf-8 en_US.utf8
+LC_ALL=en_US.UTF-8; export LC_ALL
+
+
+# check -convertarg with multiple parts and no additional text in draft
+cat >"$expected" <<'EOF'
+From: recipient@example.com
+To: sender@example.com
+cc: 
+Fcc: +outbox
+Subject: Re: test
+Comments: In-reply-to sender@example.com
+   message dated "Thu, 11 Dec 2014 08:19:02 -0600."
+MIME-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+
+sender@example.com writes:
+
+> This is part 1.
+
+> This is part 2.
+EOF
+
+cat >`mhpath new` <<'EOF'
+From: sender@example.com
+To: recipient@example.com
+Subject: test
+Date: Thu, 11 Dec 2014 08:19:02 -0600
+Content-Type: multipart/mixed; boundary="_001_"
+MIME-Version: 1.0
+
+--_001_
+Content-Type: text/plain
+
+This is part 1.
+
+--_001_
+Content-Type: text/plain
+
+This is part 2.
+
+--_001_--
+EOF
+
+repl -filter mhl.replywithoutbody -convertarg text/plain '' -nowhatnowproc last
+mhbuild "$actual"
+check "$actual" "$expected"
+
+
+# check message with text part in multipart/related
+cat >"$expected" <<'EOF'
+From: recipient@example.com
+To: sender@example.com
+cc: 
+Fcc: +outbox
+Subject: Re: test with text part in multipart/related
+Comments: In-reply-to sender@example.com
+   message dated "."
+MIME-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+
+sender@example.com writes:
+
+> This is a test.
+EOF
+
+cat >`mhpath new` <<'EOF'
+From: sender@example.com
+To: recipient@example.com
+Subject: test with text part in multipart/related
+Content-Type: multipart/alternative; boundary="_001_"
+MIME-Version: 1.0
+
+--_001_
+Content-Type: multipart/related; type="text/plain"; boundary="_002_"
+
+--_002_
+Content-Type: text/plain
+
+This is a test.
+
+--_002_--
+
+--_001_--
+EOF
+
+repl -filter mhl.replywithoutbody -convertarg text/plain '' -nowhatnowproc last
+mhbuild "$actual"
+check "$actual" "$expected"
+
+
+# check reply to calendar request
+cat >"$expected" <<'EOF'
+From: recipient@example.com
+To: sender@example.com
+cc: 
+Fcc: +outbox
+Subject: Re: test iCalendar reply
+Comments: In-reply-to sender@example.com
+   message dated "."
+MIME-Version: 1.0
+Content-Type: text/calendar; method="REPLY"; charset="UTF-8"
+
+BEGIN:VCALENDAR
+METHOD:REPLY
+PRODID:nmh mhical v0.1
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;CN=Recip:MAILTO:recipient@example.com
+SUMMARY;LANGUAGE=en-US:Accepted: test request
+DTSTART;TZID=Eastern Standard Time:20150105T090000
+DTEND;TZID=Eastern Standard Time:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+END:VEVENT
+END:VCALENDAR
+EOF
+
+cat >`mhpath new` <<'EOF'
+From: sender@example.com
+To: recipient@example.com
+Subject: test iCalendar reply
+Content-Type: text/calendar; charset="UTF-8"
+MIME-Version: 1.0
+
+BEGIN:VCALENDAR
+METHOD:REQUEST
+PRODID:test-convert
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Eastern Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+ORGANIZER;CN=Requester:MAILTO:requester@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee1
+ :MAILTO:requestee1@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee2
+ :MAILTO:requestee2@example.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Requestee3
+ :MAILTO:requestee3@example.com
+SUMMARY;LANGUAGE=en-US:test request
+DTSTART;TZID=Eastern Standard Time:20150105T090000
+DTEND;TZID=Eastern Standard Time:20150105T093000
+UID:0123456789
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20150101T171600Z
+TRANSP:OPAQUE
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION;LANGUAGE=en-US:
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:REMINDER
+TRIGGER;RELATED=START:-PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+EOF
+
+actual="$MH_TEST_DIR/test-convert$$.actual"
+repl -noformat \
+     -convertargs text/calendar '-reply accept -contenttype' -nowhatnowproc last
+SIGNATURE=Recip mhbuild - <`mhpath +`/draft | egrep -v '^DTSTAMP:' >"$actual"
+check "$actual" "$expected"
+
+
+exit $failed
index e4856d0..20bb96d 100755 (executable)
@@ -35,6 +35,7 @@ Usage: repl: [+folder] [msg] [switches]
   -nodraftfolder
   -editor editor
   -noedit
+  -convertargs type argstring
   -fcc folder
   -filter filterfile
   -form formfile
diff --git a/test/show/test-show b/test/show/test-show
new file mode 100755 (executable)
index 0000000..1554094
--- /dev/null
@@ -0,0 +1,125 @@
+#!/bin/sh
+
+if test -z "${MH_OBJ_DIR}"; then
+    srcdir=`dirname "$0"`/../..
+    MH_OBJ_DIR=`cd "$srcdir" && pwd`; export MH_OBJ_DIR
+fi
+
+. "$MH_OBJ_DIR/test/common.sh"
+
+setup_test
+
+expected=$MH_TEST_DIR/$$.expected
+actual=$MH_TEST_DIR/$$.actual
+
+# Write message with a text/plain subpart.
+msgfile=`mhpath new`
+msgnum=`basename $msgfile`
+cat > $msgfile <<EOF
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="NextPart_001"
+Content-class: urn:content-classes:message
+Date: Thu, 19 May 2011 00:00:00
+From: sender@example.com
+To: recipient@example.com
+
+This is a multi-part message in MIME format.
+
+--NextPart_001
+Content-Type: multipart/related;
+    type="multipart/alternative";
+    boundary="NextPart_002"
+
+--NextPart_002
+Content-Type: text/plain;
+    charset="utf-8"
+
+This is the text/plain part.
+
+--NextPart_002--
+
+--NextPart_001--
+EOF
+
+# Test showing multiple non-MIME messages shows message numbers.
+
+cat > $expected <<EOF
+>>> inbox:1
+
+
+Date:    Fri, 29 Sep 2006 00:00:00
+To:      Some User <user@example.com>
+From:    Test1 <test1@example.com>
+Subject: Testing message 1
+
+
+This is message number 1
+
+
+
+>>> inbox:2
+
+
+Date:    Fri, 29 Sep 2006 00:00:00
+To:      Some User <user@example.com>
+From:    Test2 <test2@example.com>
+Subject: Testing message 2
+
+
+This is message number 2
+EOF
+
+show +inbox 1 2 > $actual || exit 1
+
+check "$expected" "$actual" : show two non-mime
+
+# Test showing one MIME message shows message number.
+# Format is different; why?
+
+cat > $expected <<EOF
+[ Message inbox:$msgnum ]
+Date:    Thu, 19 May 2011 00:00:00
+To:      recipient@example.com
+From:    sender@example.com
+
+MIME-Version: 1.0
+Content-class: urn:content-classes:message
+
+[ part 1.1 - text/plain -   29B  ]
+This is the text/plain part.
+EOF
+
+show +inbox $msgnum > $actual || exit 1
+
+check "$expected" "$actual" : show one MIME
+
+# Test showing multiple MIME messages shows message numbers.
+# Currently fails.
+
+cat > $expected <<EOF
+[ Message inbox:1 ]
+Date:    Fri, 29 Sep 2006 00:00:00
+To:      Some User <user@example.com>
+From:    Test1 <test1@example.com>
+Subject: Testing message 1
+
+
+[ part  - text/plain -   25B  ]
+This is message number 1
+[ Message inbox:11 ]
+Date:    Thu, 19 May 2011 00:00:00
+To:      recipient@example.com
+From:    sender@example.com
+
+MIME-Version: 1.0
+Content-class: urn:content-classes:message
+
+[ part 1.1 - text/plain -   29B  ]
+This is the text/plain part.
+EOF
+
+show +inbox 1 $msgnum > $actual || exit 1
+
+check "$expected" "$actual" : show multiple mime
+
+exit $failed
index f3198bb..afebe3f 100644 (file)
    fun:*sendmsg*
    fun:readline
 }
+
+{
+   Syscall param msgsnd(msgp->mtext) points to uninitialised byte(s) [under fakeroot]
+   Memcheck:Param
+   msgsnd(msgp->mtext)
+   ...
+   obj:*libfakeroot*.so
+}
+
+{
+ Syscall param stat(file_name) points to uninitialised byte(s) [under fakeroot]
+ Memcheck:Param
+ stat(file_name)
+ ...
+ obj:*libfakeroot*.so
+}
+
+{
+   Syscall param socketcall.connect(serv_addr.sa_data) points to uninitialised byte(s) (MacOS X)
+   Memcheck:Param
+   socketcall.connect(serv_addr.sa_data)
+   fun:connect
+   ...
+   fun:sa_dst_lookup
+   fun:sa_dst_compare_internal
+   fun:_qsort
+   fun:_gai_sort_list
+   fun:si_addrinfo
+   fun:getaddrinfo
+}
index 8b01ab6..e5111bd 100755 (executable)
@@ -37,13 +37,17 @@ actual=$MH_TEST_DIR/$$.actual
 set +e
 whatnowtest=`echo cd | whatnow -prompt ''`
 set -e
-
 case ${whatnowtest} in
     cd) cat > "$expected" <<EOF
 cd ${MH_TEST_DIR}/foo\'s\ bar
 pwd
 $SPDIR
 EOF
+    # If path to $SPDIR is too long, readline will wrap and disturb
+    # the actual output.  It obeys COLUMNS, so set that to a value
+    # that should be longer that $SPDIR plus a bit more.
+    COLUMNS=`getconf PATH_MAX . 2>/dev/null`  ||  COLUMNS=8192
+    export COLUMNS
     ;;
     "") cat > "$expected" <<EOF
 $SPDIR
index 463c7ed..a45ea80 100644 (file)
@@ -284,6 +284,7 @@ dumpone(struct format *fmt)
        case FT_LV_LIT:
        case FT_LV_PLUS_L:
        case FT_LV_MINUS_L:
+       case FT_LV_MULTIPLY_L:
        case FT_LV_DIVIDE_L:
        case FT_LV_MODULO_L:
                printf(" value %d", fmt->f_value);
@@ -370,6 +371,7 @@ f_typestr(int t)
        case FT_LV_STRLEN: return("LV_STRLEN");
        case FT_LV_PLUS_L: return("LV_PLUS_L");
        case FT_LV_MINUS_L: return("LV_MINUS_L");
+       case FT_LV_MULTIPLY_L: return("LV_MULTIPLY_L");
        case FT_LV_DIVIDE_L: return("LV_DIVIDE_L");
        case FT_LV_MODULO_L: return("LV_MODULO_L");
        case FT_LV_CHAR_LEFT: return("LV_CHAR_LEFT");
index a7742bd..712d7df 100644 (file)
@@ -920,6 +920,7 @@ dumpone(struct format *fmt)
        case FT_LV_LIT:
        case FT_LV_PLUS_L:
        case FT_LV_MINUS_L:
+       case FT_LV_MULTIPLY_L:
        case FT_LV_DIVIDE_L:
        case FT_LV_MODULO_L:
                printf(" value %d", fmt->f_value);
@@ -1044,6 +1045,7 @@ f_typestr(int t)
        case FT_LV_STRLEN: return("LV_STRLEN");
        case FT_LV_PLUS_L: return("LV_PLUS_L");
        case FT_LV_MINUS_L: return("LV_MINUS_L");
+       case FT_LV_MULTIPLY_L: return("LV_MULTIPLY_L");
        case FT_LV_DIVIDE_L: return("LV_DIVIDE_L");
        case FT_LV_MODULO_L: return("LV_MODULO_L");
        case FT_LV_CHAR_LEFT: return("LV_CHAR_LEFT");
index a4aea3e..5f5c509 100644 (file)
--- a/uip/inc.c
+++ b/uip/inc.c
@@ -8,7 +8,13 @@
  */
 
 #ifdef MAILGROUP
-/* Revised: Sat Apr 14 17:08:17 PDT 1990 (marvit@hplabs)
+/*
+ * Thu Feb 12 21:00 CST 2015            Marcin Cieslak <saper@saper.info>
+ *    Replaced setgid() calls with setegid() so that it works with dot
+ *    locking on FreeBSD.  setegid() should be supported on modern POSIX
+ *    systems.
+ *
+ * Revised: Sat Apr 14 17:08:17 PDT 1990 (marvit@hplabs)
  *    Added hpux hacks to set and reset gid to be "mail" as needed. The reset
  *    is necessary so inc'ed mail is the group of the inc'er, rather than
  *    "mail". We setgid to egid only when [un]locking the mail file. This
@@ -128,10 +134,16 @@ static FILE *pf = NULL;
  * For setting and returning to "mail" gid
  */
 #ifdef MAILGROUP
-static int return_gid;
+static gid_t return_gid;
 #define TRYDROPGROUPPRIVS() DROPGROUPPRIVS()
-#define DROPGROUPPRIVS() setgid(getgid())
-#define GETGROUPPRIVS() setgid(return_gid)
+#define DROPGROUPPRIVS() \
+    if (setegid(getgid()) != 0) { \
+        adios ("setegid", "unable to restore group to %ld", (long) getgid()); \
+    }
+#define GETGROUPPRIVS() \
+    if (setegid(return_gid) != 0) { \
+        adios ("setegid", "unable to set group to %ld", (long) return_gid); \
+    }
 #define SAVEGROUPPRIVS() return_gid = getegid()
 #else
 /* define *GROUPPRIVS() as null; this avoids having lots of "#ifdef MAILGROUP"s */
@@ -966,6 +978,7 @@ skip:
 static void
 inc_done (int status)
 {
+    done = exit;
     if (packfile && pd != NOTOK)
        mbx_close (packfile, pd);
     if (locked)
index de0fdaa..58b37c4 100644 (file)
@@ -9,7 +9,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
index e046886..b998993 100644 (file)
@@ -17,7 +17,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
@@ -48,6 +47,13 @@ struct attach_list {
     struct attach_list *next;
 };
 
+typedef struct convert_list {
+    char *type;
+    char *filename;
+    char *argstring;
+    struct convert_list *next;
+} convert_list;
+
 /*
  * Maximum size of URL token in message/external-body
  */
@@ -62,6 +68,8 @@ void content_error (char *, CT, char *, ...);
 int find_cache (CT, int, int *, char *, char *, int);
 
 /* mhfree.c */
+extern CT *cts;
+void freects_done (int) NORETURN;
 void free_ctinfo (CT);
 void free_encoding (CT, int);
 
@@ -70,6 +78,12 @@ void free_encoding (CT, int);
  */
 static int init_decoded_content (CT, const char *);
 static void setup_attach_content(CT, char *);
+static void set_disposition (CT);
+static void set_charset (CT, int);
+static void expand_pseudoheaders (CT, struct multipart *, const char *,
+                                  const convert_list *);
+static void expand_pseudoheader (CT, CT *, struct multipart *, const char *,
+                                 const char *, const char *);
 static char *fgetstr (char *, int, FILE *);
 static int user_content (FILE *, char *, CT *, const char *infilename);
 static void set_id (CT, int);
@@ -77,6 +91,7 @@ static int compose_content (CT, int);
 static int scan_content (CT, size_t);
 static int build_headers (CT, int);
 static char *calculate_digest (CT, int);
+static int extract_headers (CT, char *, FILE **);
 
 
 static unsigned char directives_stack[32];
@@ -133,6 +148,7 @@ build_mime (char *infile, int autobuild, int dist, int directives,
     HF hp;
     m_getfld_state_t gstate = 0;
     struct attach_list *attach_head = NULL, *attach_tail = NULL, *at_entry;
+    convert_list *convert_head = NULL, *convert_tail = NULL, *convert;
 
     directive_init(directives);
 
@@ -239,6 +255,86 @@ build_mime (char *infile, int autobuild, int dist, int directives,
                } else {
                    attach_head = attach_tail = entry;
                }
+           } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER, np,
+                                   strlen (MHBUILD_FILE_PSEUDOHEADER)) == 0) {
+                /* E.g.,
+                 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
+                 */
+                char *type = np + strlen (MHBUILD_FILE_PSEUDOHEADER);
+                char *filename = vp;
+
+                /* vp should begin with a space because m_getfld()
+                   includes the space after the colon in buf. */
+                while (isspace((unsigned char) *filename)) { ++filename; }
+                /* Trim trailing newline and any other whitespace. */
+                rtrim (filename);
+
+                for (convert = convert_head; convert; convert = convert->next) {
+                    if (strcasecmp (convert->type, type) == 0) { break; }
+                }
+                if (convert) {
+                    if (convert->filename  &&
+                        strcasecmp (convert->filename, filename)) {
+                        adios (NULL, "Multiple %s headers with different files"
+                               " not allowed", type);
+                    } else {
+                        convert->filename = getcpy (filename);
+                    }
+                } else {
+                    convert = mh_xcalloc (sizeof *convert, 1);
+                    convert->filename = getcpy (filename);
+                    convert->type = getcpy (type);
+
+                    if (convert_tail) {
+                        convert_tail->next = convert;
+                    } else {
+                        convert_head = convert;
+                    }
+                    convert_tail = convert;
+                }
+
+                free (vp);
+                free (np);
+            } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER, np,
+                                   strlen (MHBUILD_ARGS_PSEUDOHEADER)) == 0) {
+                /* E.g.,
+                 * Nmh-mhbuild-args-text/calendar: -reply accept
+                 */
+                char *type = np + strlen (MHBUILD_ARGS_PSEUDOHEADER);
+                char *argstring = vp;
+
+                /* vp should begin with a space because m_getfld()
+                   includes the space after the colon in buf. */
+                while (isspace((unsigned char) *argstring)) { ++argstring; }
+                /* Trim trailing newline and any other whitespace. */
+                rtrim (argstring);
+
+                for (convert = convert_head; convert; convert = convert->next) {
+                    if (strcasecmp (convert->type, type) == 0) { break; }
+                }
+                if (convert) {
+                    if (convert->argstring  &&
+                        strcasecmp (convert->argstring, argstring)) {
+                        adios (NULL, "Multiple %s headers with different "
+                               "argstrings not allowed", type);
+                    } else {
+                        convert->argstring = getcpy (argstring);
+                    }
+                } else {
+                    convert = mh_xcalloc (sizeof *convert, 1);
+                    convert->type = getcpy (type);
+                    convert->argstring = getcpy (argstring);
+
+                    if (convert_tail) {
+                        convert_tail->next = convert;
+                    } else {
+                        convert_head = convert;
+                    }
+                    convert_tail = convert;
+                }
+
+                free (vp);
+                free (np);
            } else {
                add_header (ct, np, vp);
            }
@@ -361,6 +457,40 @@ finish_field:
        free(at_prev);
     }
 
+    /*
+     * Handle the mhbuild pseudoheaders, which deal with specific
+     * content types.
+     */
+    if (convert_head) {
+        CT *ctp;
+        convert_list *next;
+
+        done = freects_done;
+
+        /* In case there are multiple calls that land here, prevent leak. */
+        for (ctp = cts; ctp && *ctp; ++ctp) { free_content (*ctp); }
+        free (cts);
+
+        /* Extract the type part (as a CT) from filename. */
+        if (! (cts = (CT *) mh_xcalloc ((size_t) 2, sizeof *cts))) {
+            adios (NULL, "out of memory");
+        } else if (! (cts[0] = parse_mime (convert_head->filename))) {
+            adios (NULL, "failed to parse %s", convert_head->filename);
+        }
+
+        expand_pseudoheaders (cts[0], m, infile, convert_head);
+
+        /* Free the convert list. */
+        for (convert = convert_head; convert; convert = next) {
+            next = convert->next;
+            free (convert->type);
+            free (convert->filename);
+            free (convert->argstring);
+            free (convert);
+        }
+        convert_head = NULL;
+    }
+
     /*
      * To allow for empty message bodies, if we've found NO content at all
      * yet cook up an empty text/plain part.
@@ -1471,24 +1601,7 @@ scan_content (CT ct, size_t maxunencoded)
      * If the content is text and didn't specify a character set,
      * we need to figure out which one was used.
      */
-
-    if (ct->c_type == CT_TEXT) {
-       t = (struct text *) ct->c_ctparams;
-       if (t->tx_charset == CHARSET_UNSPECIFIED) {
-           CI ci = &ct->c_ctinfo;
-           char *eightbitcharset = write_charset_8bit();
-
-           if (contains8bit && strcasecmp(eightbitcharset, "US-ASCII") == 0) {
-               adios(NULL, "Text content contains 8 bit characters, but "
-                     "character set is US-ASCII");
-           }
-
-           add_param(&ci->ci_first_pm, &ci->ci_last_pm, "charset",
-                       contains8bit ? eightbitcharset : "us-ascii", 0);
-
-           t->tx_charset = CHARSET_SPECIFIED;
-       }
-    }
+    set_charset (ct, contains8bit);
 
     /*
      * Decide which transfer encoding to use.
@@ -1866,7 +1979,6 @@ setup_attach_content(CT ct, char *filename)
     char *type, *simplename = r1bindex(filename, '/');
     struct str2init *s2i;
     PM pm;
-    char *cp;
 
     if (! (type = mime_type(filename))) {
        adios(NULL, "Unable to determine MIME type of \"%s\"", filename);
@@ -1939,17 +2051,26 @@ setup_attach_content(CT ct, char *filename)
     ct->c_descr = add("\n", ct->c_descr);
     ct->c_cefile.ce_file = getcpy(filename);
 
-    /*
-     * Look for mhbuild-disposition-<type>/<subtype> entry
-     * that specifies Content-Disposition type.  Only
-     * 'attachment' and 'inline' are allowed.  Default to
-     * 'attachment'.
-     */
+    set_disposition (ct);
+
+    add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
+}
 
-    cp = context_find_by_type ("disposition", ct->c_ctinfo.ci_type,
-                               ct->c_ctinfo.ci_subtype);
-    if (cp != NULL) {
-        if (strcasecmp (cp, "attachment")  &&  strcasecmp (cp, "inline")) {
+/*
+ * If disposition type hasn't already been set in ct:
+ * Look for mhbuild-disposition-<type>/<subtype> entry
+ * that specifies Content-Disposition type.  Only
+ * 'attachment' and 'inline' are allowed.  Default to
+ * 'attachment'.
+ */
+void
+set_disposition (CT ct) {
+    if (ct->c_dispo_type == NULL) {
+        char *cp = context_find_by_type ("disposition", ct->c_ctinfo.ci_type,
+                                         ct->c_ctinfo.ci_subtype);
+
+        if (cp  &&  strcasecmp (cp, "attachment")  &&
+            strcasecmp (cp, "inline")) {
             admonish (NULL, "configuration problem: %s-disposition-%s%s%s "
                       "specifies '%s' but only 'attachment' and 'inline' are "
                       "allowed", invo_name,
@@ -1958,13 +2079,396 @@ setup_attach_content(CT ct, char *filename)
                       ct->c_ctinfo.ci_subtype ? ct->c_ctinfo.ci_subtype : "",
                       cp);
         }
+
+        ct->c_dispo_type = cp  ?  getcpy (cp)  :  getcpy ("attachment");
+    }
+}
+
+/*
+ * Set text content charset if it was unspecified.  contains8bit
+ * selctions:
+ * 0: content does not contain 8-bit characters
+ * 1: content contains 8-bit characters
+ * -1: ignore content and use user's locale to determine charset
+ */
+void
+set_charset (CT ct, int contains8bit) {
+    if (ct->c_type == CT_TEXT) {
+        struct text *t;
+
+        if (ct->c_ctparams == NULL) {
+            if ((t = ct->c_ctparams =
+                 (struct text *) mh_xcalloc (1, sizeof (struct text))) ==
+                NULL) {
+                adios (NULL, "out of memory");
+            }
+            t->tx_charset = CHARSET_UNSPECIFIED;
+        } else {
+            t = (struct text *) ct->c_ctparams;
+        }
+
+        if (t->tx_charset == CHARSET_UNSPECIFIED) {
+            CI ci = &ct->c_ctinfo;
+            char *eightbitcharset = write_charset_8bit();
+            char *charset = contains8bit ? eightbitcharset : "us-ascii";
+
+            if (contains8bit == 1  &&
+                strcasecmp (eightbitcharset, "US-ASCII") == 0) {
+                adios (NULL, "Text content contains 8 bit characters, but "
+                       "character set is US-ASCII");
+            }
+
+            add_param (&ci->ci_first_pm, &ci->ci_last_pm, "charset", charset,
+                       0);
+
+            t->tx_charset = CHARSET_SPECIFIED;
+        }
+    }
+}
+
+
+/*
+ * Look at all of the replied-to message parts and expand any that
+ * are matched by a pseudoheader.  Except don't descend into
+ * message parts.
+ */
+void
+expand_pseudoheaders (CT ct, struct multipart *m, const char *infile,
+                      const convert_list *convert_head) {
+    /* text_plain_ct is used to concatenate all of the text/plain
+       replies into one part, instead of having each one in a separate
+       part. */
+    CT text_plain_ct = NULL;
+
+    switch (ct->c_type) {
+    case CT_MULTIPART: {
+        struct multipart *mp = (struct multipart *) ct->c_ctparams;
+        struct part *part;
+
+        if (ct->c_subtype == MULTI_ALTERNATE) {
+            int matched = 0;
+
+            /* The parts are in descending priority order (defined by
+               RFC 2046 Sec. 5.1.4) because they were reversed by
+               parse_mime ().  So, stop looking for matches with
+               immediate subparts after the first match of an
+               alternative. */
+            for (part = mp->mp_parts; ! matched && part; part = part->mp_next) {
+                char *type_subtype =
+                    concat (part->mp_part->c_ctinfo.ci_type, "/",
+                            part->mp_part->c_ctinfo.ci_subtype, NULL);
+
+                if (part->mp_part->c_type == CT_MULTIPART) {
+                    expand_pseudoheaders (part->mp_part, m, infile,
+                                          convert_head);
+                } else {
+                    const convert_list *c;
+
+                    for (c = convert_head; c; c = c->next) {
+                        if (! strcasecmp (type_subtype, c->type)) {
+                            expand_pseudoheader (part->mp_part, &text_plain_ct,
+                                                 m, infile,
+                                                 c->type, c->argstring);
+                            matched = 1;
+                            break;
+                        }
+                    }
+                }
+                free (type_subtype);
+            }
+        } else {
+            for (part = mp->mp_parts; part; part = part->mp_next) {
+                expand_pseudoheaders (part->mp_part, m, infile, convert_head);
+            }
+        }
+        break;
+    }
+
+    default: {
+        char *type_subtype =
+            concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
+                    NULL);
+        const convert_list *c;
+
+        for (c = convert_head; c; c = c->next) {
+            if (! strcasecmp (type_subtype, c->type)) {
+                expand_pseudoheader (ct, &text_plain_ct, m, infile, c->type,
+                                     c->argstring);
+                break;
+            }
+        }
+        free (type_subtype);
+        break;
+    }
+    }
+}
+
+
+/*
+ * Expand a single pseudoheader.  It's for the specified type.
+ */
+void
+expand_pseudoheader (CT ct, CT *text_plain_ct, struct multipart *m,
+                     const char *infile, const char *type,
+                     const char *argstring) {
+    char *reply_file;
+    FILE *reply_fp = NULL;
+    char *convert, *type_p, *subtype_p;
+    char *convert_command;
+    char *charset = NULL;
+    char *cp;
+    struct str2init *s2i;
+    CT reply_ct;
+    struct part *part;
+    int status;
+
+    type_p = getcpy (type);
+    if ((subtype_p = strchr (type_p, '/'))) {
+        *subtype_p++ = '\0';
+        convert = context_find_by_type ("convert", type_p, subtype_p);
+    } else {
+        free (type_p);
+        type_p = concat ("mhbuild-convert-", type, NULL);
+        convert = context_find (type_p);
+    }
+    free (type_p);
+
+    if (! (convert)) {
+        /* No mhbuild-convert- entry in mhn.defaults or profile
+           for type. */
+        return;
+    }
+    /* reply_file is used to pass the output of the convert. */
+    reply_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
+    convert_command =
+        concat (convert, " ", argstring ? argstring : "", " >", reply_file,
+                NULL);
+
+    /* Convert here . . . */
+    ct->c_storeproc = getcpy (convert_command);
+    ct->c_umask = ~m_gmprot ();
+
+    if ((status = show_content_aux (ct, 0, convert_command, NULL, NULL)) !=
+        OK) {
+        admonish (NULL, "store of %s content failed", type);
+    }
+    free (convert_command);
+
+    /* Fill out the the new ct, reply_ct. */
+    reply_ct = (CT) mh_xcalloc (1, sizeof *reply_ct);
+    init_decoded_content (reply_ct, infile);
+
+    if (extract_headers (reply_ct, reply_file, &reply_fp) == NOTOK) {
+        free (reply_file);
+        admonish (NULL,
+                  "failed to extract headers from convert output in %s",
+                  reply_file);
+        return;
+    }
+
+    /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
+       charset can't be handled natively. */
+    for (s2i = str2cts; s2i->si_key; s2i++) {
+        if (strcasecmp(reply_ct->c_ctinfo.ci_type, s2i->si_key) == 0) {
+            break;
+        }
+    }
+
+    if ((reply_ct->c_ctinitfnx = s2i->si_init)) {
+        (*reply_ct->c_ctinitfnx)(reply_ct);
     }
 
-    if (cp) {
-       ct->c_dispo_type = getcpy(cp);
+    if ((cp =
+         get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1))) {
+        /* The reply Content-Type had the charset. */
+        charset = cp;
     } else {
-       ct->c_dispo_type = getcpy("attachment");
+        set_charset (reply_ct, -1);
+        charset = get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1);
+        if (reply_ct->c_reqencoding == CE_UNKNOWN) {
+            /* Assume that 8bit is sufficient (for text). */
+            reply_ct->c_reqencoding =
+                strcasecmp (charset, "US-ASCII")  ?  CE_8BIT  :  CE_7BIT;
+        }
     }
 
-    add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
+    /* Concatenate text/plain parts. */
+    if (reply_ct->c_type == CT_TEXT  &&
+        reply_ct->c_subtype == TEXT_PLAIN) {
+        if (! *text_plain_ct  &&  m->mp_parts  &&  m->mp_parts->mp_part  &&
+            m->mp_parts->mp_part->c_type == CT_TEXT  &&
+            m->mp_parts->mp_part->c_subtype == TEXT_PLAIN) {
+            *text_plain_ct = m->mp_parts->mp_part;
+            /* Make sure that the charset is set in the text/plain
+               part. */
+            set_charset (*text_plain_ct, -1);
+            if ((*text_plain_ct)->c_reqencoding == CE_UNKNOWN) {
+                /* Assume that 8bit is sufficient (for text). */
+                (*text_plain_ct)->c_reqencoding =
+                    strcasecmp (charset, "US-ASCII")  ?  CE_8BIT  :  CE_7BIT;
+            }
+        }
+
+        if (*text_plain_ct) {
+            /* Only concatenate if the charsets are identical. */
+            char *text_plain_ct_charset =
+                get_param ((*text_plain_ct)->c_ctinfo.ci_first_pm, "charset",
+                           '?', 1);
+
+            if (strcasecmp (text_plain_ct_charset, charset) == 0) {
+                /* Append this text/plain reply to the first one.
+                   If there's a problem anywhere along the way,
+                   instead attach it is a separate part. */
+                int text_plain_reply =
+                    open ((*text_plain_ct)->c_cefile.ce_file,
+                          O_WRONLY | O_APPEND);
+                int addl_reply = open (reply_file, O_RDONLY);
+
+                if (text_plain_reply != NOTOK  &&  addl_reply != NOTOK) {
+                    /* Insert blank line before each addl part. */
+                    /* It would be nice not to do this for the first one. */
+                    if (write (text_plain_reply, "\n", 1) == 1) {
+                        /* Copy the text from the new reply and
+                           then free its Content struct. */
+                        cpydata (addl_reply, text_plain_reply,
+                                 (*text_plain_ct)->c_cefile.ce_file,
+                                 reply_file);
+                        if (close (text_plain_reply) == OK  &&
+                            close (addl_reply) == OK) {
+                            if (reply_fp) { fclose (reply_fp); }
+                            free (reply_file);
+                            free_content (reply_ct);
+                            return;
+                        }
+                    }
+                }
+            }
+        } else {
+            *text_plain_ct = reply_ct;
+        }
+    }
+
+    reply_ct->c_cefile.ce_file = reply_file;
+    reply_ct->c_cefile.ce_fp = reply_fp;
+    reply_ct->c_cefile.ce_unlink = 1;
+
+    /* Attach the new part to the parent mulitpart/mixed, "m". */
+    part = (struct part *) mh_xcalloc (1, sizeof *part);
+    part->mp_part = reply_ct;
+    if (m->mp_parts) {
+        struct part *p;
+
+        for (p = m->mp_parts; p && p->mp_next; p = p->mp_next) { continue; }
+        p->mp_next = part;
+    } else {
+        m->mp_parts = part;
+    }
+}
+
+
+/* Extract any Content-Type header from beginning of convert output. */
+int
+extract_headers (CT ct, char *reply_file, FILE **reply_fp) {
+    char *buffer = NULL, *cp, *end_of_header;
+    int found_header = 0;
+    struct stat statbuf;
+
+    /* Read the convert reply from the file to memory. */
+    if (stat (reply_file, &statbuf) == NOTOK) {
+        admonish (reply_file, "failed to stat");
+        goto failed_to_extract_ct;
+    }
+
+    buffer = mh_xmalloc (statbuf.st_size + 1);
+
+    if ((*reply_fp = fopen (reply_file, "r+")) == NULL  ||
+        fread (buffer, 1, (size_t) statbuf.st_size, *reply_fp) <
+            (size_t) statbuf.st_size) {
+        admonish (reply_file, "failed to read");
+        goto failed_to_extract_ct;
+    }
+    buffer[statbuf.st_size] = '\0';
+
+    /* Look for a header in the convert reply. */
+    if (strncasecmp (buffer, TYPE_FIELD, strlen (TYPE_FIELD)) == 0  &&
+        buffer[strlen (TYPE_FIELD)] == ':') {
+        if ((end_of_header = strstr (buffer, "\r\n\r\n"))) {
+            end_of_header += 2;
+            found_header = 1;
+        } else if ((end_of_header = strstr (buffer, "\n\n"))) {
+            ++end_of_header;
+            found_header = 1;
+        }
+    }
+
+    if (found_header) {
+        CT tmp_ct;
+        char *tmp_file;
+        FILE *tmp_f;
+        size_t n;
+
+        /* Truncate buffer to just the C-T. */
+        *end_of_header = '\0';
+        n = strlen (buffer);
+
+        if (get_ctinfo (buffer + 14, ct, 0) != OK) {
+            admonish (NULL, "unable to get content info for reply");
+            goto failed_to_extract_ct;
+        }
+
+        /* Hack.  Use parse_mime() to detect the type/subtype of the
+           reply, which we'll use below. */
+        tmp_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
+        if ((tmp_f = fopen (tmp_file, "w"))  &&
+            fwrite (buffer, 1, n, tmp_f) == n) {
+            fclose (tmp_f);
+        } else {
+            goto failed_to_extract_ct;
+        }
+        tmp_ct = parse_mime (tmp_file);
+
+        if (tmp_ct) {
+            /* The type and subtype were detected from the reply
+               using parse_mime() above. */
+            ct->c_type = tmp_ct->c_type;
+            ct->c_subtype = tmp_ct->c_subtype;
+            free_content (tmp_ct);
+        }
+
+        free (tmp_file);
+
+        /* Rewrite the content without the header. */
+        cp = end_of_header + 1;
+        rewind (*reply_fp);
+
+        if (fwrite (cp, 1, statbuf.st_size - (cp - buffer), *reply_fp) <
+            (size_t) (statbuf.st_size - (cp - buffer))) {
+            admonish (reply_file, "failed to write");
+            goto failed_to_extract_ct;
+        }
+
+        if (ftruncate (fileno (*reply_fp), statbuf.st_size - (cp - buffer)) !=
+            0) {
+            advise (reply_file, "ftruncate");
+            goto failed_to_extract_ct;
+        }
+    } else {
+        /* No header section, assume the reply is text/plain. */
+        ct->c_type = CT_TEXT;
+        ct->c_subtype = TEXT_PLAIN;
+        if (get_ctinfo ("text/plain", ct, 0) == NOTOK) {
+            /* This never should fail, but just in case. */
+            adios (NULL, "unable to get content info for reply");
+        }
+    }
+
+    /* free_encoding() will close reply_fp, which is passed through
+       ct->c_cefile.ce_fp. */
+    free (buffer);
+    return OK;
+
+failed_to_extract_ct:
+    if (*reply_fp) { fclose (*reply_fp); }
+    free (buffer);
+    return NOTOK;
 }
index 56ba9b7..ae563c2 100644 (file)
@@ -9,7 +9,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
@@ -38,7 +37,7 @@ char *cache_private;
 
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void content_error (char *, CT, char *, ...);
 void flush_errors (void);
index f80ff9b..d5107c6 100644 (file)
@@ -16,6 +16,9 @@
 #define MHFIXMSG_SWITCHES \
     X("decodetext 8bit|7bit", 0, DECODETEXTSW) \
     X("nodecodetext", 0, NDECODETEXTSW) \
+    X("decodetypes", 0, DECODETYPESW) \
+    X("crlflinebreaks", 0, CRLFLINEBREAKSSW) \
+    X("nocrlflinebreaks", 0, NCRLFLINEBREAKSSW) \
     X("textcharset", 0, TEXTCHARSETSW) \
     X("notextcharset", 0, NTEXTCHARSETSW) \
     X("reformat", 0, REFORMATSW) \
@@ -26,6 +29,7 @@
     X("nofixboundary", 0, NFIXBOUNDARYSW) \
     X("fixcte", 0, FIXCTESW) \
     X("nofixcte", 0, NFIXCTESW) \
+    X("fixtype mimetype", 0, FIXTYPESW) \
     X("file file", 0, FILESW) \
     X("outfile file", 0, OUTFILESW) \
     X("rmmproc program", 0, RPROCSW) \
@@ -75,9 +79,13 @@ void freects_done (int) NORETURN;
 typedef struct fix_transformations {
     int fixboundary;
     int fixcte;
+    svector_t fixtypes;
     int reformat;
     int replacetextplain;
     int decodetext;
+    char *decodetypes;
+    /* Whether to use CRLF linebreaks, per RFC 2046 Sec. 4.1.1, par.1. */
+    int lf_line_endings;
     char *textcharset;
 } fix_transformations;
 
@@ -85,12 +93,16 @@ int mhfixmsgsbr (CT *, const fix_transformations *, char *);
 static int fix_boundary (CT *, int *);
 static int get_multipart_boundary (CT, char **);
 static int replace_boundary (CT, char *, char *);
+static int fix_types (CT, svector_t, int *);
+static char *replace_substring (char **, const char *, const char *);
+static char *remove_parameter (char *, const char *);
 static int fix_multipart_cte (CT, int *);
 static int set_ce (CT, int);
 static int ensure_text_plain (CT *, CT, int *, int);
 static int find_textplain_sibling (CT, int, int *);
 static int insert_new_text_plain_part (CT, int, CT);
 static CT build_text_plain_part (CT);
+static int insert_into_new_mp_alt (CT *, int *);
 static CT divide_part (CT);
 static void copy_ctinfo (CI, CI);
 static int decode_part (CT);
@@ -100,12 +112,14 @@ static CT build_multipart_alt (CT, CT, int, int);
 static int boundary_in_content (FILE **, char *, const char *);
 static void transfer_noncontent_headers (CT, CT);
 static int set_ct_type (CT, int type, int subtype, int encoding);
-static int decode_text_parts (CT, int, int *);
+static int decode_text_parts (CT, int, const char *, int *);
+static int should_decode(const char *, const char *, const char *);
 static int content_encoding (CT, const char **);
 static int strip_crs (CT, int *);
 static int convert_charsets (CT, char *, int *);
 static int fix_always (CT, int *);
 static int write_content (CT, char *, char *, int, int);
+static void set_text_ctparams(CT, char *, int);
 static int remove_file (char *);
 static void report (char *, char *, char *, char *, ...);
 static void pipeser (int);
@@ -126,8 +140,11 @@ main (int argc, char **argv) {
     int status = OK;
     fix_transformations fx;
     fx.reformat = fx.fixcte = fx.fixboundary = 1;
+    fx.fixtypes = NULL;
     fx.replacetextplain = 0;
     fx.decodetext = CE_8BIT;
+    fx.decodetypes = "text,application/ics";  /* Default, per man page. */
+    fx.lf_line_endings = 0;
     fx.textcharset = NULL;
 
     if (nmh_init(argv[0], 1)) { return 1; }
@@ -172,6 +189,17 @@ main (int argc, char **argv) {
             case NDECODETEXTSW:
                 fx.decodetext = 0;
                 continue;
+            case DECODETYPESW:
+                if (! (cp = *argp++)  ||  *cp == '-')
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                fx.decodetypes = cp;
+                continue;
+            case CRLFLINEBREAKSSW:
+                fx.lf_line_endings = 0;
+                continue;
+            case NCRLFLINEBREAKSSW:
+                fx.lf_line_endings = 1;
+                continue;
             case TEXTCHARSETSW:
                 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
                     adios (NULL, "missing argument to %s", argp[-2]);
@@ -192,6 +220,18 @@ main (int argc, char **argv) {
             case NFIXCTESW:
                 fx.fixcte = 0;
                 continue;
+            case FIXTYPESW:
+                if (! (cp = *argp++) || (*cp == '-' && cp[1]))
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                if (! strncasecmp (cp, "multipart/", 10)  ||
+                    ! strncasecmp (cp, "message/", 8)) {
+                    adios (NULL, "-fixtype %s not allowed", cp);
+                } else if (! strchr (cp, '/')) {
+                    adios (NULL, "-fixtype requires type/subtype");
+                }
+                if (fx.fixtypes == NULL) { fx.fixtypes = svector_create (10); }
+                svector_push_back (fx.fixtypes, cp);
+                continue;
             case REFORMATSW:
                 fx.reformat = 1;
                 continue;
@@ -306,7 +346,10 @@ main (int argc, char **argv) {
         }
         ctp = cts;
 
-        if ((ct = parse_mime (file))) { *ctp++ = ct; }
+        if ((ct = parse_mime (file))) {
+            set_text_ctparams(ct, fx.decodetypes, fx.lf_line_endings);
+            *ctp++ = ct;
+        }
     } else {
         /*
          * message(s) are coming from a folder
@@ -347,7 +390,10 @@ main (int argc, char **argv) {
                 char *msgnam;
 
                 msgnam = m_name (msgnum);
-                if ((ct = parse_mime (msgnam))) { *ctp++ = ct; }
+                if ((ct = parse_mime (msgnam))) {
+                    set_text_ctparams(ct, fx.decodetypes, fx.lf_line_endings);
+                    *ctp++ = ct;
+                }
             }
         }
 
@@ -376,6 +422,7 @@ main (int argc, char **argv) {
         status = 1;
     }
 
+    if (fx.fixtypes != NULL) { svector_free (fx.fixtypes); }
     free (outfile);
     free (file);
 
@@ -414,6 +461,9 @@ mhfixmsgsbr (CT *ctp, const fix_transformations *fx, char *outfile) {
     if (status == OK  &&  fx->fixboundary) {
         status = fix_boundary (ctp, &message_mods);
     }
+    if (status == OK  && fx->fixtypes != NULL) {
+        status = fix_types (*ctp, fx->fixtypes, &message_mods);
+    }
     if (status == OK  &&  fx->fixcte) {
         status = fix_multipart_cte (*ctp, &message_mods);
     }
@@ -422,7 +472,7 @@ mhfixmsgsbr (CT *ctp, const fix_transformations *fx, char *outfile) {
             ensure_text_plain (ctp, NULL, &message_mods, fx->replacetextplain);
     }
     if (status == OK  &&  fx->decodetext) {
-        status = decode_text_parts (*ctp, fx->decodetext, &message_mods);
+        status = decode_text_parts (*ctp, fx->decodetext, fx->decodetypes, &message_mods);
     }
     if (status == OK  &&  fx->textcharset != NULL) {
         status = convert_charsets (*ctp, fx->textcharset, &message_mods);
@@ -727,6 +777,189 @@ replace_boundary (CT ct, char *file, char *boundary) {
 }
 
 
+static int
+fix_types (CT ct, svector_t fixtypes, int *message_mods) {
+    int status = OK;
+
+    switch (ct->c_type) {
+    case CT_MULTIPART: {
+        struct multipart *m = (struct multipart *) ct->c_ctparams;
+        struct part *part;
+
+        for (part = m->mp_parts; status == OK  &&  part; part = part->mp_next) {
+            status = fix_types (part->mp_part, fixtypes, message_mods);
+        }
+        break;
+    }
+
+    case CT_MESSAGE:
+        if (ct->c_subtype == MESSAGE_EXTERNAL) {
+            struct exbody *e = (struct exbody *) ct->c_ctparams;
+
+            status = fix_types (e->eb_content, fixtypes, message_mods);
+        }
+        break;
+
+    default: {
+        char **typep, *type;
+
+        if (ct->c_ctinfo.ci_type  &&  ct->c_ctinfo.ci_subtype) {
+            for (typep = svector_strs (fixtypes);
+                 typep && (type = *typep);
+                 ++typep) {
+                char *type_subtype =
+                    concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
+                            NULL);
+
+                if (! strcasecmp (type, type_subtype)  &&
+                    decode_part (ct) == OK  &&
+                    ct->c_cefile.ce_file != NULL) {
+                    char *ct_type_subtype = mime_type (ct->c_cefile.ce_file);
+                    char *cp;
+
+                    if ((cp = strchr (ct_type_subtype, ';'))) {
+                        /* Truncate to remove any parameter list from
+                           mime_type () result. */
+                        *cp = '\0';
+                    }
+
+                    if (strcasecmp (type, ct_type_subtype)) {
+                        char *ct_type, *ct_subtype;
+                        HF hf;
+
+                        /* The Content-Type header does not match the
+                           content, so update these struct Content
+                           fields to match:
+                           * c_type, c_subtype
+                           * c_ctinfo.ci_type, c_ctinfo.ci_subtype
+                           * c_ctline
+                           */
+                        /* Extract type and subtype from type/subtype. */
+                        ct_type = getcpy (ct_type_subtype);
+                        if ((cp = strchr (ct_type, '/'))) {
+                            *cp = '\0';
+                            ct_subtype = getcpy (++cp);
+                        } else {
+                            advise (NULL, "missing / in MIME type of %s %s",
+                                    ct->c_file, ct->c_partno);
+                            free (ct_type);
+                            return NOTOK;
+                        }
+
+                        ct->c_type = ct_str_type (ct_type);
+                        ct->c_subtype = ct_str_subtype (ct->c_type, ct_subtype);
+
+                        free (ct->c_ctinfo.ci_type);
+                        ct->c_ctinfo.ci_type = ct_type;
+                        free (ct->c_ctinfo.ci_subtype);
+                        ct->c_ctinfo.ci_subtype = ct_subtype;
+                        if (! replace_substring (&ct->c_ctline, type,
+                                                 ct_type_subtype)) {
+                            advise (NULL, "did not find %s in %s",
+                                    type, ct->c_ctline);
+                        }
+
+                        /* Update Content-Type header field. */
+                        for (hf = ct->c_first_hf; hf; hf = hf->next) {
+                            if (! strcasecmp (TYPE_FIELD, hf->name)) {
+                                if (replace_substring (&hf->value, type,
+                                                       ct_type_subtype)) {
+                                    ++*message_mods;
+                                    if (verbosw) {
+                                        report (NULL, ct->c_partno, ct->c_file,
+                                                "change Content-Type in header "
+                                                "from %s to %s",
+                                                type, ct_type_subtype);
+                                    }
+                                    break;
+                                } else {
+                                    advise (NULL, "did not find %s in %s",
+                                            type, hf->value);
+                                }
+                            }
+                        }
+                    }
+                    free (ct_type_subtype);
+                }
+                free (type_subtype);
+            }
+        }
+    }}
+
+    return status;
+}
+
+char *
+replace_substring (char **str, const char *old, const char *new) {
+    char *cp;
+
+    if ((cp = strstr (*str, old))) {
+        char *remainder = cp + strlen (old);
+        char *prefix, *new_str;
+
+        if (cp - *str) {
+            prefix = getcpy (*str);
+            *(prefix + (cp - *str)) = '\0';
+            new_str = concat (prefix, new, remainder, NULL);
+            free (prefix);
+        } else {
+            new_str = concat (new, remainder, NULL);
+        }
+
+        free (*str);
+
+        return *str = new_str;
+    } else {
+        return NULL;
+    }
+}
+
+/*
+ * Remove a name=value parameter, given just its name, from a header value.
+ */
+char *
+remove_parameter (char *str, const char *name) {
+    /* It looks to me, based on the BNF in RFC 2045, than there can't
+       be whitespace betwwen the parameter name and the "=", or
+       between the "=" and the parameter value. */
+    char *param_name = concat (name, "=", NULL);
+    char *cp;
+
+    if ((cp = strstr (str, param_name))) {
+        char *start, *end;
+        size_t count = 1;
+
+        /* Remove any leading spaces, before the parameter name. */
+        for (start = cp;
+             start > str && isspace ((unsigned char) *(start-1));
+             --start) {
+            continue;
+        }
+        /* Remove a leading semicolon. */
+        if (start > str  &&  *(start-1) == ';') { --start; }
+
+        end = cp + strlen (name) + 1;
+        if (*end == '"') {
+            /* Skip past the quoted value, and then the final quote. */
+            for (++end ; *end  &&  *end != '"'; ++end) { continue; }
+            ++end;
+        } else {
+            /* Skip past the value. */
+            for (++end ; *end  &&  ! isspace ((unsigned char) *end); ++end) {}
+        }
+
+        /* Count how many characters need to be moved.  Include
+           trailing null, which is accounted for by the
+           initialization of count to 1. */
+        for (cp = end; *cp; ++cp) { ++count; }
+        (void) memmove (start, end, count);
+    }
+
+    free (param_name);
+
+    return str;
+}
+
 static int
 fix_multipart_cte (CT ct, int *message_mods) {
     int status = OK;
@@ -904,51 +1137,72 @@ ensure_text_plain (CT *ct, CT parent, int *message_mods, int replacetextplain) {
             free (type_subtype);
 
             if (! has_text_plain) {
-                /* Parent is a multipart/alternative.  Insert a new
-                   text/plain subpart. */
-                const int inserted =
-                    insert_new_text_plain_part (*ct, new_subpart_number,
-                                                parent);
-                if (inserted) {
-                    ++*message_mods;
-                    if (verbosw) {
-                        report (NULL, parent->c_partno, parent->c_file,
-                                "insert text/plain part");
+                struct multipart *mp = (struct multipart *) parent->c_ctparams;
+                struct part *part;
+                int siblings = 0;
+
+                for (part = mp->mp_parts; part; part = part->mp_next) {
+                    if (*ct != part->mp_part) {
+                        ++siblings;
                     }
-                } else {
-                    status = NOTOK;
                 }
-            }
-        } else {
-            /* Slip new text/plain part into a new multipart/alternative. */
-            CT tp_part = build_text_plain_part (*ct);
-
-            if (tp_part) {
-                CT mp_alt = build_multipart_alt (*ct, tp_part, CT_MULTIPART,
-                                                 MULTI_ALTERNATE);
-                if (mp_alt) {
-                    struct multipart *mp =
-                        (struct multipart *) mp_alt->c_ctparams;
 
-                    if (mp  &&  mp->mp_parts) {
-                        mp->mp_parts->mp_part = tp_part;
-                        /* Make the new multipart/alternative the parent. */
-                        *ct = mp_alt;
+                if (siblings) {
+                    /* Parent is a multipart/related.  Insert a new
+                       text/plain subpart in a new multipart/alternative. */
+                    if (insert_into_new_mp_alt (ct, message_mods)) {
+                        /* Not an error if text/plain couldn't be added. */
+                    }
+                } else {
+                    /* There are no siblings, so insert a new text/plain
+                       subpart, and change the parent type from
+                       multipart/related to multipart/alternative. */
+                    const int inserted =
+                        insert_new_text_plain_part (*ct, new_subpart_number,
+                                                    parent);
+
+                    if (inserted) {
+                        HF hf;
+
+                        parent->c_subtype = MULTI_ALTERNATE;
+                        parent->c_ctinfo.ci_subtype = getcpy ("alternative");
+                        if (! replace_substring (&parent->c_ctline, "/related",
+                                                 "/alternative")) {
+                            advise (NULL,
+                                    "did not find multipart/related in %s",
+                                    parent->c_ctline);
+                        }
 
-                        ++*message_mods;
-                        if (verbosw) {
-                            report (NULL, (*ct)->c_partno, (*ct)->c_file,
-                                    "insert text/plain part");
+                        /* Update Content-Type header field. */
+                        for (hf = parent->c_first_hf; hf; hf = hf->next) {
+                            if (! strcasecmp (TYPE_FIELD, hf->name)) {
+                                if (replace_substring (&hf->value, "/related",
+                                                       "/alternative")) {
+                                    ++*message_mods;
+                                    if (verbosw) {
+                                        report (NULL, parent->c_partno,
+                                                parent->c_file,
+                                                "insert text/plain part");
+                                    }
+
+                                    /* Remove, e.g., type="text/html" from
+                                       multipart/alternative. */
+                                    remove_parameter (hf->value, "type");
+                                    break;
+                                } else {
+                                    advise (NULL, "did not find multipart/"
+                                                  "related in header %s",
+                                            hf->value);
+                                }
+                            }
                         }
                     } else {
-                        free_content (tp_part);
-                        free_content (mp_alt);
-                        status = NOTOK;
+                        /* Not an error if text/plain couldn't be inserted. */
                     }
-                } else {
-                    status = NOTOK;
                 }
-            } else {
+            }
+        } else {
+            if (insert_into_new_mp_alt (ct, message_mods)) {
                 status = NOTOK;
             }
         }
@@ -1062,13 +1316,14 @@ build_text_plain_part (CT encoded_part) {
         if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) {
             advise (NULL, "unable to create temporary file in %s",
                     get_temp_dir());
-        }
-        tmp_plain_file = add (tempfile, NULL);
-        if (reformat_part (tp_part, tmp_plain_file,
-                           tp_part->c_ctinfo.ci_type,
-                           tp_part->c_ctinfo.ci_subtype,
-                           tp_part->c_type) == OK) {
-            return tp_part;
+        } else {
+            tmp_plain_file = add (tempfile, NULL);
+            if (reformat_part (tp_part, tmp_plain_file,
+                               tp_part->c_ctinfo.ci_type,
+                               tp_part->c_ctinfo.ci_subtype,
+                               tp_part->c_type) == OK) {
+                return tp_part;
+            }
         }
     }
 
@@ -1080,6 +1335,43 @@ build_text_plain_part (CT encoded_part) {
 }
 
 
+/* Slip new text/plain part into a new multipart/alternative. */
+static int
+insert_into_new_mp_alt (CT *ct, int *message_mods) {
+    CT tp_part = build_text_plain_part (*ct);
+    int status = OK;
+
+    if (tp_part) {
+        CT mp_alt = build_multipart_alt (*ct, tp_part, CT_MULTIPART,
+                                         MULTI_ALTERNATE);
+        if (mp_alt) {
+            struct multipart *mp = (struct multipart *) mp_alt->c_ctparams;
+
+            if (mp  &&  mp->mp_parts) {
+                mp->mp_parts->mp_part = tp_part;
+                /* Make the new multipart/alternative the parent. */
+                *ct = mp_alt;
+
+                ++*message_mods;
+                if (verbosw) {
+                    report (NULL, (*ct)->c_partno, (*ct)->c_file,
+                            "insert text/plain part");
+                }
+            } else {
+                free_content (tp_part);
+                free_content (mp_alt);
+                status = NOTOK;
+            }
+        } else {
+            status = NOTOK;
+        }
+    } else {
+        /* Not an error if text/plain couldn't be built. */
+    }
+
+    return status;
+}
+
 static CT
 divide_part (CT ct) {
     CT new_part;
@@ -1482,11 +1774,39 @@ set_ct_type (CT ct, int type, int subtype, int encoding) {
 
 
 static int
-decode_text_parts (CT ct, int encoding, int *message_mods) {
+decode_text_parts (CT ct, int encoding, const char *decodetypes, int *message_mods) {
     int status = OK;
+    int lf_line_endings = 0;
 
     switch (ct->c_type) {
-    case CT_TEXT:
+    case CT_MULTIPART: {
+        struct multipart *m = (struct multipart *) ct->c_ctparams;
+        struct part *part;
+
+        /* Should check to see if the body for this part is encoded?
+           For now, it gets passed along as-is by InitMultiPart(). */
+        for (part = m->mp_parts; status == OK  &&  part; part = part->mp_next) {
+            status = decode_text_parts (part->mp_part, encoding, decodetypes, message_mods);
+        }
+        break;
+    }
+
+    case CT_MESSAGE:
+        if (ct->c_subtype == MESSAGE_EXTERNAL) {
+            struct exbody *e = (struct exbody *) ct->c_ctparams;
+
+            status = decode_text_parts (e->eb_content, encoding, decodetypes, message_mods);
+        }
+        break;
+
+    default:
+        if (! should_decode(decodetypes, ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype)) {
+            break;
+        }
+
+        lf_line_endings =
+            ct->c_ctparams  &&  ((struct text *) ct->c_ctparams)->lf_line_endings;
+
         switch (ct->c_encoding) {
         case CE_BASE64:
         case CE_QUOTED: {
@@ -1538,7 +1858,9 @@ decode_text_parts (CT ct, int encoding, int *message_mods) {
                             report (NULL, ct->c_partno, ct->c_file, "decode%s",
                                     ct->c_ctline ? ct->c_ctline : "");
                         }
-                        strip_crs (ct, message_mods);
+                        if (lf_line_endings) {
+                            strip_crs (ct, message_mods);
+                        }
                     } else {
                         status = NOTOK;
                     }
@@ -1550,39 +1872,49 @@ decode_text_parts (CT ct, int encoding, int *message_mods) {
         }
         case CE_8BIT:
         case CE_7BIT:
-            strip_crs (ct, message_mods);
+            if (lf_line_endings) {
+                strip_crs (ct, message_mods);
+            }
             break;
         default:
             break;
         }
 
         break;
-
-    case CT_MULTIPART: {
-        struct multipart *m = (struct multipart *) ct->c_ctparams;
-        struct part *part;
-
-        /* Should check to see if the body for this part is encoded?
-           For now, it gets passed along as-is by InitMultiPart(). */
-        for (part = m->mp_parts; status == OK  &&  part; part = part->mp_next) {
-            status = decode_text_parts (part->mp_part, encoding, message_mods);
-        }
-        break;
     }
 
-    case CT_MESSAGE:
-        if (ct->c_subtype == MESSAGE_EXTERNAL) {
-            struct exbody *e = (struct exbody *) ct->c_ctparams;
+    return status;
+}
 
-            status = decode_text_parts (e->eb_content, encoding, message_mods);
-        }
-        break;
 
-    default:
-        break;
+/* Determine if the part with type[/subtype] should be decoded, according to
+   decodetypes (which came from the -decodetypes switch). */
+static int
+should_decode(const char *decodetypes, const char *type, const char *subtype) {
+    /* Quick search for matching type[/subtype] in decodetypes:  bracket
+       decodetypes with commas, then search for ,type, and ,type/subtype, in
+       it. */
+
+    int found_match = 0;
+    char *delimited_decodetypes = concat(",", decodetypes, ",", NULL);
+    char *delimited_type = concat(",", type, ",", NULL);
+
+    if (nmh_strcasestr(delimited_decodetypes, delimited_type)) {
+        found_match = 1;
+    } else if (subtype != NULL) {
+        char *delimited_type_subtype =
+            concat(",", type, "/", subtype, ",", NULL);
+
+        if (nmh_strcasestr(delimited_decodetypes, delimited_type_subtype)) {
+            found_match = 1;
+        }
+        free(delimited_type_subtype);
     }
 
-    return status;
+    free(delimited_type);
+    free(delimited_decodetypes);
+
+    return found_match;
 }
 
 
@@ -2007,6 +2339,44 @@ write_content (CT ct, char *input_filename, char *outfile, int modify_inplace,
 }
 
 
+/*
+ * parse_mime() does not set lf_line_endings in struct text, so use this function to do it.
+ * It touches the parts the decodetypes identifies.
+ */
+static void
+set_text_ctparams(CT ct, char *decodetypes, int lf_line_endings) {
+    switch (ct->c_type) {
+    case CT_MULTIPART: {
+        struct multipart *m = (struct multipart *) ct->c_ctparams;
+        struct part *part;
+
+        for (part = m->mp_parts; part; part = part->mp_next) {
+            set_text_ctparams(part->mp_part, decodetypes, lf_line_endings);
+        }
+        break;
+    }
+
+    case CT_MESSAGE:
+        if (ct->c_subtype == MESSAGE_EXTERNAL) {
+            struct exbody *e = (struct exbody *) ct->c_ctparams;
+
+            set_text_ctparams(e->eb_content, decodetypes, lf_line_endings);
+        }
+        break;
+
+    default:
+        if (should_decode(decodetypes, ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype)) {
+            if (ct->c_ctparams == NULL) {
+                if ((ct->c_ctparams = (struct text *) mh_xcalloc (1, sizeof (struct text))) == NULL) {
+                    adios (NULL, "out of memory");
+                }
+            }
+            ((struct text *) ct->c_ctparams)->lf_line_endings = lf_line_endings;
+        }
+    }
+}
+
+
 /*
  * If "rmmproc" is defined, call that to remove the file.  Otherwise,
  * use the standard MH backup file.
diff --git a/uip/mhical.c b/uip/mhical.c
new file mode 100644 (file)
index 0000000..324d4b6
--- /dev/null
@@ -0,0 +1,835 @@
+/*
+ * mhical.c -- operate on an iCalendar request
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh.
+ * See the COPYRIGHT file in the root directory of the nmh
+ * distribution for complete copyright information.
+ */
+
+#include "h/mh.h"
+#include "h/icalendar.h"
+#include "sbr/icalparse.h"
+#include <h/fmt_scan.h>
+#include "h/addrsbr.h"
+#include "h/mts.h"
+#include "h/utils.h"
+#include <time.h>
+
+typedef enum act {
+    ACT_NONE,
+    ACT_ACCEPT,
+    ACE_DECLINE,
+    ACT_TENTATIVE,
+    ACT_DELEGATE,
+    ACT_CANCEL
+} act;
+
+static void convert_to_reply (contentline *, act);
+static void convert_to_cancellation (contentline *);
+static void convert_common (contentline *, act);
+static void dump_unfolded (FILE *, contentline *);
+static void output (FILE *, contentline *, int);
+static void display (FILE *, contentline *, char *);
+static const char *identity (const contentline *);
+static char *format_params (char *, param_list *);
+static char *fold (char *, int);
+
+#define MHICAL_SWITCHES \
+    X("reply accept|decline|tentative", 0, REPLYSW) \
+    X("cancel", 0, CANCELSW) \
+    X("form formatfile", 0, FORMSW) \
+    X("format string", 5, FMTSW) \
+    X("infile", 0, INFILESW) \
+    X("outfile", 0, OUTFILESW) \
+    X("contenttype", 0, CONTENTTYPESW) \
+    X("nocontenttype", 0, NCONTENTTYPESW) \
+    X("unfold", 0, UNFOLDSW) \
+    X("debug", 0, DEBUGSW) \
+    X("version", 0, VERSIONSW) \
+    X("help", 0, HELPSW) \
+
+#define X(sw, minchars, id) id,
+DEFINE_SWITCH_ENUM(MHICAL);
+#undef X
+
+#define X(sw, minchars, id) { sw, minchars, id },
+DEFINE_SWITCH_ARRAY(MHICAL, switches);
+#undef X
+
+vevent vevents = { NULL, NULL, NULL};
+
+int
+main (int argc, char *argv[]) {
+    /* RFC 5322 Â§ 3.3 date-time format, including the optional
+       day-of-week and not including the optional seconds.  The
+       zone is required by the RFC but not always output by this
+       format, because RFC 5545 Â§ 3.3.5 allows date-times not
+       bound to any time zone. */
+
+    act action = ACT_NONE;
+    char *infile = NULL, *outfile = NULL;
+    FILE *inputfile = NULL, *outputfile = NULL;
+    int contenttype = 0, unfold = 0;
+    vevent *v, *nextvevent;
+    char *form = "mhical.24hour", *format = NULL;
+    char **argp, **arguments, *cp;
+
+    icaldebug = 0;  /* Global provided by bison (with name-prefix "ical"). */
+
+    if (nmh_init(argv[0], 1)) { return 1; }
+
+    arguments = getarguments (invo_name, argc, argv, 1);
+    argp = arguments;
+
+    /*
+     * Parse arguments
+     */
+    while ((cp = *argp++)) {
+        if (*cp == '-') {
+            switch (smatch (++cp, switches)) {
+            case AMBIGSW:
+                ambigsw (cp, switches);
+                done (1);
+            case UNKWNSW:
+                adios (NULL, "-%s unknown", cp);
+
+            case HELPSW: {
+                char buf[128];
+                snprintf (buf, sizeof buf, "%s [switches]", invo_name);
+                print_help (buf, switches, 1);
+                done (0);
+            }
+            case VERSIONSW:
+                print_version(invo_name);
+                done (0);
+            case DEBUGSW:
+                icaldebug = 1;
+                continue;
+
+            case REPLYSW:
+                if (! (cp = *argp++) || (*cp == '-' && cp[1]))
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                if (! strcasecmp (cp, "accept")) {
+                    action = ACT_ACCEPT;
+                } else if (! strcasecmp (cp, "decline")) {
+                    action = ACE_DECLINE;
+                } else if (! strcasecmp (cp, "tentative")) {
+                    action = ACT_TENTATIVE;
+                } else if (! strcasecmp (cp, "delegate")) {
+                    action = ACT_DELEGATE;
+                } else {
+                    adios (NULL, "Unknown action: %s", cp);
+                }
+                continue;
+
+            case CANCELSW:
+                action = ACT_CANCEL;
+                continue;
+
+            case FORMSW:
+                if (! (form = *argp++) || *form == '-')
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                format = NULL;
+                continue;
+            case FMTSW:
+                if (! (format = *argp++) || *format == '-')
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                form = NULL;
+                continue;
+
+            case INFILESW:
+                if (! (cp = *argp++) || (*cp == '-' && cp[1]))
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                infile = *cp == '-'  ?  add (cp, NULL)  :  path (cp, TFILE);
+                continue;
+            case OUTFILESW:
+                if (! (cp = *argp++) || (*cp == '-' && cp[1]))
+                    adios (NULL, "missing argument to %s", argp[-2]);
+                outfile = *cp == '-'  ?  add (cp, NULL)  :  path (cp, TFILE);
+                continue;
+
+            case CONTENTTYPESW:
+                contenttype = 1;
+                continue;
+            case NCONTENTTYPESW:
+                contenttype = 0;
+                continue;
+
+            case UNFOLDSW:
+                unfold = 1;
+                continue;
+            }
+        }
+    }
+
+    free (arguments);
+
+    if (infile) {
+        if ((inputfile = fopen (infile, "r"))) {
+            icalset_inputfile (inputfile);
+        } else {
+            adios (infile, "error opening");
+        }
+    } else {
+        inputfile = stdin;
+    }
+
+    if (outfile) {
+        if ((outputfile = fopen (outfile, "w"))) {
+            icalset_outputfile (outputfile);
+        } else {
+            adios (outfile, "error opening");
+        }
+    } else {
+        outputfile = stdout;
+    }
+
+    vevents.last = &vevents;
+    /* vevents is accessed by parser as global. */
+    icalparse ();
+
+    for (v = &vevents; v; v = nextvevent) {
+        if (! unfold  &&  v != &vevents  &&  v->contentlines  &&
+            v->contentlines->name  &&
+            strcasecmp (v->contentlines->name, "END")  &&
+            v->contentlines->value  &&
+            strcasecmp (v->contentlines->value, "VCALENDAR")) {
+            /* Output blank line between vevents.  Not before
+               first vevent and not after last. */
+            putc ('\n', outputfile);
+        }
+
+        if (action == ACT_NONE) {
+            if (unfold) {
+                dump_unfolded (outputfile, v->contentlines);
+            } else {
+                char *nfs = new_fs (form, format, NULL);
+
+                display (outputfile, v->contentlines, nfs);
+                free_fs ();
+            }
+        } else {
+            if (action == ACT_CANCEL) {
+                convert_to_cancellation (v->contentlines);
+            } else {
+                convert_to_reply (v->contentlines, action);
+            }
+            output (outputfile, v->contentlines, contenttype);
+        }
+
+        free_contentlines (v->contentlines);
+        nextvevent = v->next;
+        if (v != &vevents) {
+            free (v);
+        }
+    }
+
+    if (infile) {
+        if (fclose (inputfile) != 0) {
+            advise (infile, "error closing");
+        }
+        free (infile);
+    }
+    if (outfile) {
+        if (fclose (outputfile) != 0) {
+            advise (outfile, "error closing");
+        }
+        free (outfile);
+    }
+
+    return 0;
+}
+
+/*
+ * - Change METHOD from REQUEST to REPLY.
+ * - Change PRODID.
+ * - Remove all ATTENDEE lines for other users (based on ismymbox ()).
+ * - For the user's ATTENDEE line:
+ *   - Remove ROLE and RSVP parameters.
+ *   - Change PARTSTAT value to indicate reply action, e.g., ACCEPTED,
+ *     DECLINED, or TENTATIVE.
+ * - Insert action at beginning of SUMMARY value.
+ * - Remove all X- lines.
+ * - Update DTSTAMP with current timestamp.
+ * - Remove all DESCRIPTION lines.
+ * - Excise VALARM sections.
+ */
+static void
+convert_to_reply (contentline *clines, act action) {
+    char *partstat = NULL;
+    int found_my_attendee_line = 0;
+    contentline *node;
+
+    convert_common (clines, action);
+
+    switch (action) {
+    case ACT_ACCEPT:
+        partstat = "ACCEPTED";
+        break;
+    case ACE_DECLINE:
+        partstat = "DECLINED";
+        break;
+    case ACT_TENTATIVE:
+        partstat = "TENTATIVE";
+        break;
+    default:
+        ;
+    }
+
+    /* Call find_contentline () with node as argument to find multiple
+       matching contentlines. */
+    for (node = clines;
+         (node = find_contentline (node, "ATTENDEE", 0));
+         node = node->next) {
+        param_list *p;
+
+        ismymbox (NULL); /* need to prime ismymbox() */
+
+        /* According to RFC 5545 Â§ 3.3.3, an email address in the
+           value must be a mailto URI. */
+        if (! strncasecmp (node->value, "mailto:", 7)) {
+            char *addr = node->value + 7;
+            struct mailname *mn;
+
+            /* Skip any leading whitespace. */
+            for ( ; isspace ((unsigned char) *addr); ++addr) { continue; }
+
+            addr = getname (addr);
+            mn = getm (addr, NULL, 0, NULL, 0);
+
+            /* Need to flush getname after use. */
+            while (getname ("")) { continue; }
+
+            if (ismymbox (mn)) {
+                found_my_attendee_line = 1;
+                for (p = node->params; p && p->param_name; p = p->next) {
+                    value_list *v;
+
+                    for (v = p->values; v; v = v->next) {
+                        if (! strcasecmp (p->param_name, "ROLE")  ||
+                            ! strcasecmp (p->param_name, "RSVP")) {
+                            remove_value (v);
+                        } else if (! strcasecmp (p->param_name, "PARTSTAT")) {
+                            free (v->value);
+                            v->value = strdup (partstat);
+                        }
+                    }
+                }
+            } else {
+                remove_contentline (node);
+            }
+
+            mnfree (mn);
+        }
+    }
+
+    if (! found_my_attendee_line) {
+        /* Generate and attach an ATTENDEE line for me. */
+        contentline *node;
+
+        /* Add it after the ORGANIZER line, or if none, BEGIN:VEVENT line. */
+        if ((node = find_contentline (clines, "ORGANIZER", 0))  ||
+            (node = find_contentline (clines, "BEGIN", "VEVENT"))) {
+            contentline *new_node = add_contentline (node, "ATTENDEE");
+
+            add_param_name (new_node, strdup ("PARTSTAT"));
+            add_param_value (new_node, strdup (partstat));
+            add_param_name (new_node, strdup ("CN"));
+            add_param_value (new_node, strdup (getfullname ()));
+            new_node->value = concat ("MAILTO:", getlocalmbox (), NULL);
+        }
+    }
+
+    /* Call find_contentline () with node as argument to find multiple
+       matching contentlines. */
+    for (node = clines;
+         (node = find_contentline (node, "DESCRIPTION", 0));
+         node = node->next) {
+        /* ACCEPT, at least, replies don't seem to have DESCRIPTIONS. */
+        remove_contentline (node);
+    }
+}
+
+/*
+ * - Change METHOD from REQUEST to CANCEL.
+ * - Change PRODID.
+ * - Insert action at beginning of SUMMARY value.
+ * - Remove all X- lines.
+ * - Update DTSTAMP with current timestamp.
+ * - Change STATUS from CONFIRMED to CANCELLED.
+ * - Increment value of SEQUENCE.
+ * - Excise VALARM sections.
+ */
+static void
+convert_to_cancellation (contentline *clines) {
+    contentline *node;
+
+    convert_common (clines, ACT_CANCEL);
+
+    if ((node = find_contentline (clines, "STATUS", 0))  &&
+        ! strcasecmp (node->value, "CONFIRMED")) {
+        free (node->value);
+        node->value = strdup ("CANCELLED");
+    }
+
+    if ((node = find_contentline (clines, "SEQUENCE", 0))) {
+        int sequence = atoi (node->value);
+        char buf[32];
+
+        (void) snprintf (buf, sizeof buf, "%d", sequence + 1);
+        free (node->value);
+        node->value = strdup (buf);
+    }
+}
+
+static void
+convert_common (contentline *clines, act action) {
+    contentline *node;
+    int in_valarm;
+
+    if ((node = find_contentline (clines, "METHOD", 0))) {
+        free (node->value);
+        node->value = strdup (action == ACT_CANCEL  ?  "CANCEL"  :  "REPLY");
+    }
+
+    if ((node = find_contentline (clines, "PRODID", 0))) {
+        free (node->value);
+        node->value = strdup ("nmh mhical v0.1");
+    }
+
+    if ((node = find_contentline (clines, "VERSION", 0))) {
+        if (! node->value) {
+            admonish (NULL, "Version property is missing value, assume 2.0");
+            node->value = strdup ("2.0");
+        }
+
+        if (strcmp (node->value, "2.0")) {
+            admonish (NULL, "supports the Version 2.0 specified by RFC 5545 "
+                            "but iCalendar object has Version %s", node->value);
+            node->value = strdup ("2.0");
+        }
+    }
+
+    if ((node = find_contentline (clines, "SUMMARY", 0))) {
+        char *insert = NULL;
+
+        switch (action) {
+        case ACT_ACCEPT:
+            insert = "Accepted: ";
+            break;
+        case ACE_DECLINE:
+            insert = "Declined: ";
+            break;
+        case ACT_TENTATIVE:
+            insert = "Tentative: ";
+            break;
+        case ACT_DELEGATE:
+            adios (NULL, "Delegate replies are not supported");
+            break;
+        case ACT_CANCEL:
+            insert = "Cancelled:";
+            break;
+        default:
+            ;
+        }
+
+        if (insert) {
+            const size_t len = strlen (insert) + strlen (node->value) + 1;
+            char *tmp = mh_xmalloc (len);
+
+            (void) strncpy (tmp, insert, len);
+            (void) strncat (tmp, node->value, len - strlen (insert) - 1);
+            free (node->value);
+            node->value = tmp;
+        } else {
+            /* Should never get here. */
+            adios (NULL, "Unknown action: %d", action);
+        }
+    }
+
+    if ((node = find_contentline (clines, "DTSTAMP", 0))) {
+        const time_t now = time (NULL);
+        struct tm now_tm;
+
+        if (gmtime_r (&now, &now_tm)) {
+            /* 17 would be sufficient given that RFC 5545 Â§ 3.3.4
+               supports only a 4 digit year. */
+            char buf[32];
+
+            if (strftime (buf, sizeof buf, "%Y%m%dT%H%M%SZ", &now_tm)) {
+                free (node->value);
+                node->value = strdup (buf);
+            } else {
+                admonish (NULL, "strftime unable to format current time");
+            }
+        } else {
+            admonish (NULL, "gmtime_r failed on current time");
+        }
+    }
+
+    /* Excise X- lines and VALARM section(s). */
+    in_valarm = 0;
+    for (node = clines; node; node = node->next) {
+        /* node->name will be NULL if the line was deleted. */
+        if (! node->name) { continue; }
+
+        if (in_valarm) {
+            if (! strcasecmp ("END", node->name)  &&
+                ! strcasecmp ("VALARM", node->value)) {
+                in_valarm = 0;
+            }
+            remove_contentline (node);
+        } else {
+            if (! strcasecmp ("BEGIN", node->name)  &&
+                ! strcasecmp ("VALARM", node->value)) {
+                in_valarm = 1;
+                remove_contentline (node);
+            } else if (! strncasecmp ("X-", node->name, 2)) {
+                remove_contentline (node);
+            }
+        }
+    }
+}
+
+/* Echo the input, but with unfolded lines. */
+static void
+dump_unfolded (FILE *file, contentline *clines) {
+    contentline *node;
+
+    for (node = clines; node; node = node->next) {
+        fputs (node->input_line, file);
+    }
+}
+
+static void
+output (FILE *file, contentline *clines, int contenttype) {
+    contentline *node;
+
+    if (contenttype) {
+        /* Generate a Content-Type header to pass the method parameter
+           to mhbuild.  Per RFC 5545 Secs. 6 and 8.1, it must be
+           UTF-8.  But we don't attempt to do any conversion of the
+           input. */
+        if ((node = find_contentline (clines, "METHOD", 0))) {
+            fprintf (file,
+                     "Content-Type: text/calendar; method=\"%s\"; "
+                     "charset=\"UTF-8\"\n\n",
+                     node->value);
+        }
+    }
+
+    for (node = clines; node; node = node->next) {
+        if (node->name) {
+            char *line = NULL;
+            size_t len;
+
+            line = strdup (node->name);
+            line = format_params (line, node->params);
+
+            len = strlen (line);
+            line = mh_xrealloc (line, len + 2);
+            line[len] = ':';
+            line[len + 1] = '\0';
+
+            line = fold (add (node->value, line),
+                         clines->cr_before_lf == CR_BEFORE_LF);
+
+            if (clines->cr_before_lf == LF_ONLY) {
+                fprintf (file, "%s\n", line);
+            } else {
+                fprintf (file, "%s\r\n", line);
+            }
+            free (line);
+        }
+    }
+}
+
+/*
+ * Display these fields of the iCalendar event:
+ *   - method
+ *   - organizer
+ *   - summary
+ *   - description, except for "\n\n" and in VALARM
+ *   - location
+ *   - dtstart in local timezone
+ *   - dtend in local timezone
+ *   - attendees (limited to number specified in initialization)
+ */
+static void
+display (FILE *file, contentline *clines, char *nfs) {
+    tzdesc_t timezones = load_timezones (clines);
+    int in_vtimezone;
+    int in_valarm;
+    contentline *node;
+    struct format *fmt;
+    int dat[5] = { 0, 0, 0, INT_MAX, 0 };
+    struct comp *c;
+    charstring_t buffer = charstring_create (BUFSIZ);
+    charstring_t attendees = charstring_create (BUFSIZ);
+    const unsigned int max_attendees = 20;
+    unsigned int num_attendees;
+
+    /* Don't call on the END:VCALENDAR line. */
+    if (clines  &&  clines->next) {
+      (void) fmt_compile (nfs, &fmt, 1);
+    }
+
+    if ((c = fmt_findcomp ("method"))) {
+        if ((node = find_contentline (clines, "METHOD", 0))  &&  node->value) {
+            c->c_text = strdup (node->value);
+        }
+    }
+
+    if ((c = fmt_findcomp ("organizer"))) {
+        if ((node = find_contentline (clines, "ORGANIZER", 0))  &&
+            node->value) {
+            c->c_text = strdup (identity (node));
+        }
+    }
+
+    if ((c = fmt_findcomp ("summary"))) {
+        if ((node = find_contentline (clines, "SUMMARY", 0))  &&  node->value) {
+            c->c_text = strdup (node->value);
+        }
+    }
+
+    /* Only display DESCRIPTION lines that are outside VALARM section(s). */
+    in_valarm = 0;
+    if ((c = fmt_findcomp ("description"))) {
+        for (node = clines; node; node = node->next) {
+            /* node->name will be NULL if the line was deleted. */
+            if (node->name  &&  node->value  &&  ! in_valarm  &&
+                ! strcasecmp ("DESCRIPTION", node->name)  &&
+                strcasecmp (node->value, "\\n\\n")) {
+                c->c_text = strdup (node->value);
+            } else if (in_valarm) {
+                if (! strcasecmp ("END", node->name)  &&
+                    ! strcasecmp ("VALARM", node->value)) {
+                    in_valarm = 0;
+                }
+            } else {
+                if (! strcasecmp ("BEGIN", node->name)  &&
+                    ! strcasecmp ("VALARM", node->value)) {
+                    in_valarm = 1;
+                }
+            }
+        }
+    }
+
+    if ((c = fmt_findcomp ("location"))) {
+        if ((node = find_contentline (clines, "LOCATION", 0))  &&
+            node->value) {
+            c->c_text = strdup (node->value);
+        }
+    }
+
+    if ((c = fmt_findcomp ("dtstart"))) {
+        /* Find DTSTART outsize of a VTIMEZONE section. */
+        in_vtimezone = 0;
+        for (node = clines; node; node = node->next) {
+            /* node->name will be NULL if the line was deleted. */
+            if (! node->name) { continue; }
+
+            if (in_vtimezone) {
+                if (! strcasecmp ("END", node->name)  &&
+                    ! strcasecmp ("VTIMEZONE", node->value)) {
+                    in_vtimezone = 0;
+                }
+            } else {
+                if (! strcasecmp ("BEGIN", node->name)  &&
+                    ! strcasecmp ("VTIMEZONE", node->value)) {
+                    in_vtimezone = 1;
+                } else if (! strcasecmp ("DTSTART", node->name)) {
+                    /* Got it:  DTSTART outside of a VTIMEZONE section. */
+                    char *datetime = format_datetime (timezones, node);
+                    c->c_text = datetime ? datetime : strdup(node->value);
+                }
+            }
+        }
+    }
+
+    if ((c = fmt_findcomp ("dtend"))) {
+        if ((node = find_contentline (clines, "DTEND", 0))  &&  node->value) {
+            char *datetime = format_datetime (timezones, node);
+            c->c_text = datetime ? datetime : strdup(node->value);
+        } else if ((node = find_contentline (clines, "DTSTART", 0))  &&
+                   node->value) {
+            /* There is no DTEND.  If there's a DTSTART, use it.  If it
+               doesn't have a time, assume that the event is for the
+               entire day and append 23:59:59 to it so that it signifies
+               the end of the day.  And assume local timezone. */
+            if (strchr(node->value, 'T')) {
+                char * datetime = format_datetime (timezones, node);
+                c->c_text = datetime ? datetime : strdup(node->value);
+            } else {
+                char *datetime;
+                contentline node_copy;
+
+                memcpy(&node_copy, node, sizeof node_copy);
+                node_copy.value = concat(node_copy.value, "T235959", NULL);
+                datetime = format_datetime (timezones, &node_copy);
+                c->c_text = datetime ? datetime : strdup(node_copy.value);
+                free(node_copy.value);
+            }
+        }
+    }
+
+    if ((c = fmt_findcomp ("attendees"))) {
+        /* Call find_contentline () with node as argument to find multiple
+           matching contentlines. */
+        charstring_append_cstring (attendees, "Attendees: ");
+        for (node = clines, num_attendees = 0;
+             (node = find_contentline (node, "ATTENDEE", 0))  &&
+                 num_attendees++ < max_attendees;
+             node = node->next) {
+            const char *id = identity (node);
+
+            if (num_attendees > 1) {
+                charstring_append_cstring (attendees, ", ");
+            }
+            charstring_append_cstring (attendees, id);
+        }
+
+        if (num_attendees >= max_attendees) {
+            unsigned int not_shown = 0;
+
+            for ( ;
+                  (node = find_contentline (node, "ATTENDEE", 0));
+                  node = node->next) {
+                ++not_shown;
+            }
+
+            if (not_shown > 0) {
+                char buf[32];
+
+                (void) snprintf (buf, sizeof buf, ", and %d more", not_shown);
+                charstring_append_cstring (attendees, buf);
+            }
+        }
+
+        if (num_attendees > 0) {
+            c->c_text = charstring_buffer_copy (attendees);
+        }
+    }
+
+    /* Don't call on the END:VCALENDAR line. */
+    if (clines->next) {
+      (void) fmt_scan (fmt, buffer, INT_MAX, dat, NULL);
+      fputs (charstring_buffer (buffer), file);
+      fmt_free (fmt, 1);
+    }
+
+    charstring_free (attendees);
+    charstring_free (buffer);
+    free_timezones (timezones);
+}
+
+static const char *
+identity (const contentline *node) {
+    /* According to RFC 5545 Â§ 3.3.3, an email address in the value
+       must be a mailto URI. */
+    if (! strncasecmp (node->value, "mailto:", 7)) {
+        char *addr;
+        param_list *p;
+
+        for (p = node->params; p && p->param_name; p = p->next) {
+            value_list *v;
+
+            for (v = p->values; v; v = v->next) {
+                if (! strcasecmp (p->param_name, "CN")) {
+                    return v->value;
+                }
+            }
+        }
+
+        /* Did not find a CN parameter, so output the address. */
+        addr = node->value + 7;
+
+        /* Skip any leading whitespace. */
+        for ( ; isspace ((unsigned char) *addr); ++addr) { continue; }
+
+        return addr;
+    }
+
+    return "unknown";
+}
+
+static char *
+format_params (char *line, param_list *p) {
+    for ( ; p && p->param_name; p = p->next) {
+        value_list *v;
+        size_t num_values = 0;
+
+        for (v = p->values; v; v = v->next) {
+            if (v->value) { ++num_values; }
+        }
+
+        if (num_values) {
+            size_t len = strlen (line);
+
+            line = mh_xrealloc (line, len + 2);
+            line[len] = ';';
+            line[len + 1] = '\0';
+
+            line = add (p->param_name, line);
+
+            for (v = p->values; v; v = v->next) {
+                len = strlen (line);
+                line = mh_xrealloc (line, len + 2);
+                line[len] = v == p->values ? '=' : ',';
+                line[len + 1] = '\0';
+
+                line = add (v->value, line);
+            }
+        }
+    }
+
+    return line;
+}
+
+static char *
+fold (char *line, int uses_cr) {
+    size_t remaining = strlen (line);
+    size_t current_line_len = 0;
+    charstring_t folded_line = charstring_create (2 * remaining);
+    const char *cp = line;
+
+#ifdef MULTIBYTE_SUPPORT
+    if (mbtowc (NULL, NULL, 0)) {} /* reset shift state */
+#endif
+
+    while (*cp  &&  remaining > 0) {
+#ifdef MULTIBYTE_SUPPORT
+        int char_len = mbtowc (NULL, cp, (size_t) MB_CUR_MAX < remaining
+                                         ? (size_t) MB_CUR_MAX
+                                         : remaining);
+        if (char_len == -1) { char_len = 1; }
+#else
+        const int char_len = 1;
+#endif
+
+        charstring_push_back_chars (folded_line, cp, char_len, 1);
+        remaining -= char_len > 0 ? char_len : 1;
+
+        /* remaining must be > 0 to pass the loop condition above, so
+           if it's not > 1, it is == 1. */
+        if (++current_line_len >= 75) {
+            if (remaining > 1  ||  (*(cp+1) != '\0'  &&  *(cp+1) != '\r'  &&
+                                    *(cp+1) != '\n')) {
+                /* fold */
+                if (uses_cr) { charstring_push_back (folded_line, '\r'); }
+                charstring_push_back (folded_line, '\n');
+                charstring_push_back (folded_line, ' ');
+                current_line_len = 0;
+            }
+        }
+
+        cp += char_len > 0 ? char_len : 1;
+    }
+
+    free (line);
+    line = charstring_buffer_copy (folded_line);
+    charstring_free (folded_line);
+
+    return line;
+}
index 91b0984..3465523 100644 (file)
@@ -32,6 +32,7 @@
     X("file file", 0, FILESW) \
     X("part number", 0, PARTSW) \
     X("type content", 0, TYPESW) \
+    X("prefer content", 0, PREFERSW) \
     X("rcache policy", 0, RCACHESW) \
     X("wcache policy", 0, WCACHESW) \
     X("changecur", 0, CHGSW) \
@@ -62,6 +63,11 @@ extern char *parts[NPARTS + 1];
 extern char *types[NTYPES + 1];
 extern int userrs;
 
+/* mhparse.c */
+extern char *preferred_types[];
+extern  char *preferred_subtypes[];
+extern int npreferred;
+
 /*
  * This is currently needed to keep mhparse happy.
  * This needs to be changed.
@@ -74,7 +80,7 @@ int debugsw = 0;
 CT parse_mime (char *);
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void flush_errors (void);
 
@@ -186,6 +192,18 @@ do_cache:
                types[ntype++] = cp;
                continue;
 
+           case PREFERSW:
+               if (!(cp = *argp++) || *cp == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               if (npreferred >= NPREFS)
+                   adios (NULL, "too many preferred types (starting with %s), %d max",
+                          cp, NPREFS);
+               preferred_types[npreferred] = cp;
+               cp = strchr(cp, '/');
+               if (cp) *cp++ = '\0';
+               preferred_subtypes[npreferred++] = cp;
+               continue;
+
            case FILESW:
                if (!(cp = *argp++) || (*cp == '-' && cp[1]))
                    adios (NULL, "missing argument to %s", argp[-2]);
index 309357b..5a45d03 100644 (file)
@@ -10,7 +10,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/mts.h>
 #include <h/tws.h>
 #include <h/mime.h>
@@ -18,7 +17,7 @@
 #include <h/utils.h>
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void flush_errors (void);
 
@@ -343,7 +342,7 @@ list_multi (CT ct, int toplevel, int realsize, int verbose, int debug,
     for (part = m->mp_parts; part; part = part->mp_next) {
        CT p = part->mp_part;
 
-       if (part_ok (p, 1) && type_ok (p, 1))
+       if (part_ok (p) && type_ok (p, 1))
            list_switch (p, 0, realsize, verbose, debug, dispo);
     }
 
index 28d4894..3bc0808 100644 (file)
@@ -875,14 +875,18 @@ parse (void)
 static void
 process (char *folder, char *fname, int ofilen, int ofilec)
 {
-    char *cp = NULL;
-    FILE *fp = NULL;
+    /* static to prevent "might be clobbered" warning from gcc 4.9.2: */
+    static char *cp;
+    static FILE *fp;
     struct mcomp *c1;
     struct stat st;
     struct arglist *ap;
     /* volatile to prevent "might be clobbered" warning from gcc: */
     char *volatile fname2 = fname ? fname : "(stdin)";
 
+    cp = NULL;
+    fp = NULL;
+
     switch (setjmp (env)) {
        case OK: 
            if (fname) {
@@ -913,12 +917,12 @@ process (char *folder, char *fname, int ofilen, int ofilec)
            if (arglist_head)
                fmt_free(NULL, 1);
 
-       default: 
+       default:
            if (ontty != PITTY)
                SIGNAL (SIGINT, SIG_IGN);
-           if (mhl_action == NULL && fp != stdin)
+           if (mhl_action == NULL && fp != stdin && fp != NULL)
                fclose (fp);
-           free (cp);
+               free (cp);
            if (holder.c_text) {
                free (holder.c_text);
                holder.c_text = NULL;
index b5a3c35..c43f9c3 100755 (executable)
@@ -292,7 +292,7 @@ else
     post_or_send="$nmhbindir/send"
   fi
 
-  if "$post_or_send" "$tmpfile" $postsendargs; then
+  if "$post_or_send" $postsendargs "$tmpfile"; then
     exit
   else
     status=$?
index f4b8916..8b1f0d0 100644 (file)
@@ -30,26 +30,54 @@ static char *errs = NULL;
 /*
  * prototypes
  */
-int part_ok (CT, int);
+int part_ok (CT);
+int part_exact(CT ct);
 int type_ok (CT, int);
 void content_error (char *, CT, char *, ...);
 void flush_errors (void);
 
 
 int
-part_ok (CT ct, int sP)
+part_ok (CT ct)
 {
     char **ap;
     int len;
 
-    if (npart == 0 || (ct->c_type == CT_MULTIPART && (sP || ct->c_subtype)))
+    /* a part is "ok", i.e., should be processed, if:
+       - there were no -part arguments
+       - this part is a multipart
+     */
+    if (npart == 0 || ct->c_type == CT_MULTIPART) {
        return 1;
+    }
 
+    /* or if:
+       - this part is a an exact match for any -part option
+       - this part is a sub-part of any -part option
+     */
     for (ap = parts; *ap; ap++) {
         len = strlen(*ap);
         if (!strncmp (*ap, ct->c_partno, len) &&
-                (!ct->c_partno[len] || ct->c_partno[len] == '.' ))
+                (!ct->c_partno[len] || ct->c_partno[len] == '.' )) {
+            return 1;
+       }
+    }
+
+    return 0;
+}
+
+int
+part_exact(CT ct)
+{
+    char **ap;
+
+    if (!ct->c_partno)
+       return 0;
+
+    for (ap = parts; *ap; ap++) {
+        if (!strcmp (*ap, ct->c_partno)) {
             return 1;
+       }
     }
 
     return 0;
index 39d8693..ea56379 100644 (file)
--- a/uip/mhn.c
+++ b/uip/mhn.c
@@ -115,7 +115,7 @@ static int storesw = 0;
 CT parse_mime (char *);
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void flush_errors (void);
 
@@ -554,7 +554,7 @@ do_cache:
      * Show the message content
      */
     if (showsw)
-       show_all_messages (cts, 0, 0, 0, NULL);
+       show_all_messages (cts, 0, 0, 0);
 
     /* Now free all the structures for the content */
     for (ctp = cts; *ctp; ctp++)
index 0c0a297..456a2e2 100644 (file)
@@ -10,7 +10,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
@@ -427,7 +426,10 @@ writeBase64ct (CT ct, FILE *out)
     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
        return NOTOK;
 
-    result = writeBase64aux (ce->ce_fp, out, (ct->c_type == CT_TEXT));
+    result = writeBase64aux (ce->ce_fp, out,
+                             ct->c_type == CT_TEXT  &&  ct->c_ctparams
+                             ?  ((struct text *) ct->c_ctparams)->lf_line_endings == 0
+                             :  0);
     (*ct->c_ceclosefnx) (ct);
     return result;
 }
index a3f4d78..f6daf9a 100644 (file)
@@ -29,6 +29,7 @@ DEFINE_SWITCH_ENUM(MHPARAM);
 DEFINE_SWITCH_ARRAY(MHPARAM, switches);
 #undef X
 
+extern char *mhbindir;
 extern char *mhetcdir;
 extern char *mhlibexecdir;
 
@@ -111,6 +112,7 @@ static struct proc procs [] = {
      { "version",          &version_num },
      { "whatnowproc",      &whatnowproc },
      { "whomproc",         &whomproc },
+     { "bindir",           &mhbindir },
      { "etcdir",           &mhetcdir },
      { "libdir",           &mhlibexecdir },
      { "libexecdir",       &mhlibexecdir },
index 96af58e..6f8ebaf 100644 (file)
@@ -9,7 +9,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
@@ -42,6 +41,13 @@ int suppress_bogus_mp_content_warning;
 int bogus_mp_content;
 int suppress_extraneous_trailing_semicolon_warning;
 int extraneous_trailing_semicolon;
+int suppress_multiple_mime_version_warning = 1;
+
+/* list of preferred type/subtype pairs, for -prefer */
+char *preferred_types[NPREFS],
+     *preferred_subtypes[NPREFS];
+int npreferred;
+
 
 /*
  * Structures for TEXT messages
@@ -104,7 +110,7 @@ static struct k2v EncodingType[] = {
 int find_cache (CT, int, int *, char *, char *, int);
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void content_error (char *, CT, char *, ...);
 
@@ -121,6 +127,7 @@ static int InitGeneric (CT);
 static int InitText (CT);
 static int InitMultiPart (CT);
 static void reverse_parts (CT);
+static void prefer_parts(CT ct);
 static int InitMessage (CT);
 static int InitApplication (CT);
 static int init_encoding (CT, OpenCEFunc);
@@ -359,16 +366,12 @@ get_content (FILE *in, char *file, int toplevel)
        if (!strcasecmp (hp->name, VRSN_FIELD)) {
            int ucmp;
            char c, *cp, *dp;
+           char *vrsn;
 
-           if (ct->c_vrsn) {
-               advise (NULL, "message %s has multiple %s: fields",
-                       ct->c_file, VRSN_FIELD);
-               goto next_header;
-           }
-           ct->c_vrsn = add (hp->value, NULL);
+           vrsn = add (hp->value, NULL);
 
            /* Now, cleanup this field */
-           cp = ct->c_vrsn;
+           cp = vrsn;
 
            while (isspace ((unsigned char) *cp))
                cp++;
@@ -395,6 +398,14 @@ get_content (FILE *in, char *file, int toplevel)
                admonish (NULL, "message %s has unknown value for %s: field (%s)",
                ct->c_file, VRSN_FIELD, cp);
            }
+           if (!ct->c_vrsn) {
+               ct->c_vrsn = vrsn;
+           } else {
+               if (! suppress_multiple_mime_version_warning)
+                   advise (NULL, "message %s has multiple %s: fields",
+                           ct->c_file, VRSN_FIELD);
+               free(vrsn);
+           }
        }
        else if (!strcasecmp (hp->name, TYPE_FIELD)) {
        /* Get Content-Type field */
@@ -1008,7 +1019,6 @@ InitText (CT ct)
     char *chset = NULL;
     char *cp;
     PM pm;
-    struct k2v *kv;
     struct text *t;
     CI ci = &ct->c_ctinfo;
 
@@ -1017,10 +1027,7 @@ InitText (CT ct)
        ci->ci_subtype = add ("plain", ci->ci_subtype);
 
     /* match subtype */
-    for (kv = SubText; kv->kv_key; kv++)
-       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
-           break;
-    ct->c_subtype = kv->kv_value;
+    ct->c_subtype = ct_str_subtype (CT_TEXT, ci->ci_subtype);
 
     /* allocate text character set structure */
     if ((t = (struct text *) mh_xcalloc (1, sizeof(*t))) == NULL)
@@ -1073,7 +1080,6 @@ InitMultiPart (CT ct)
     size_t buflen;
     ssize_t gotlen;
     struct multipart *m;
-    struct k2v *kv;
     struct part *part, **next;
     CI ci = &ct->c_ctinfo;
     CT p;
@@ -1095,8 +1101,9 @@ InitMultiPart (CT ct)
 
        admonish (NULL,
                  "\"%s/%s\" type in message %s must be encoded in\n"
-                 "7bit, 8bit, or binary, per RFC 2045 (6.4).  One workaround "
-                 "is to\nmanually edit the file and change the \"%s\"\n"
+                 "7bit, 8bit, or binary, per RFC 2045 (6.4).  "
+                  "mhfixmsg -fixcte can fix it, or\n"
+                  "manually edit the file and change the \"%s\"\n"
                  "Content-Transfer-Encoding to one of those.  For now",
                  ci->ci_type, ci->ci_subtype, ct->c_file, bp);
        free (cte);
@@ -1105,10 +1112,7 @@ InitMultiPart (CT ct)
     }
 
     /* match subtype */
-    for (kv = SubMultiPart; kv->kv_key; kv++)
-       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
-           break;
-    ct->c_subtype = kv->kv_value;
+    ct->c_subtype = ct_str_subtype (CT_MULTIPART, ci->ci_subtype);
 
     /*
      * Check for "boundary" parameter, which is
@@ -1231,8 +1235,10 @@ end_part:
 
 last_part:
     /* reverse the order of the parts for multipart/alternative */
-    if (ct->c_subtype == MULTI_ALTERNATE)
+    if (ct->c_subtype == MULTI_ALTERNATE) {
        reverse_parts (ct);
+       prefer_parts (ct);
+    }
 
     /*
      * label all subparts with part number, and
@@ -1278,9 +1284,15 @@ last_part:
 
 
 /*
- * reverse the order of the parts of a multipart/alternative
+ * reverse the order of the parts of a multipart/alternative,
+ * presumably to put the "most favored" alternative first, for
+ * ease of choosing/displaying it later on.  from a mail message on
+ * nmh-workers, from kenh:
+ *  "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
+ *  see code in mhn that did the same thing...  Acccording to the RCS
+ *  logs, that code was around from the initial checkin of mhn.c by
+ *  John Romine in 1992, which is as far back as we have."
  */
-
 static void
 reverse_parts (CT ct)
 {
@@ -1297,6 +1309,60 @@ reverse_parts (CT ct)
     }
 }
 
+static void
+move_preferred_part (CT ct, char *type, char *subtype)
+{
+    struct multipart *m = (struct multipart *) ct->c_ctparams;
+    struct part *part, *prev, *head, *nhead, *ntail;
+    struct part h, n;
+    CI ci;
+
+    /* move the matching part(s) to the head of the list:  walk the
+     * list of parts, move matching parts to a new list (maintaining
+     * their order), and finally, concatenate the old list onto the
+     * new.
+     */
+
+    head = &h;
+    nhead = &n;
+
+    head->mp_next = m->mp_parts;
+    nhead->mp_next = NULL;
+    ntail = nhead;
+
+    prev = head;
+    part = head->mp_next;
+    while (part != NULL) {
+       ci = &part->mp_part->c_ctinfo;
+       if (!strcasecmp(ci->ci_type, type) &&
+               (!subtype || !strcasecmp(ci->ci_subtype, subtype))) {
+           prev->mp_next = part->mp_next;
+           part->mp_next = NULL;
+           ntail->mp_next = part;
+           ntail = part;
+           part = prev->mp_next;
+       } else {
+           prev = part;
+           part = prev->mp_next;
+       }
+    }
+    ntail->mp_next = head->mp_next;
+    m->mp_parts = nhead->mp_next;
+
+}
+
+/*
+ * move parts that match the user's preferences (-prefer) to the head
+ * of the line.  process preferences in reverse so first one given
+ * ends up first in line
+ */
+static void
+prefer_parts(CT ct)
+{
+    int i;
+    for (i = npreferred-1; i >= 0; i--)
+       move_preferred_part(ct, preferred_types[i], preferred_subtypes[i]);
+}
 
 
 
@@ -1329,7 +1395,6 @@ reverse_alternative_parts (CT ct) {
 static int
 InitMessage (CT ct)
 {
-    struct k2v *kv;
     CI ci = &ct->c_ctinfo;
 
     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
@@ -1344,10 +1409,7 @@ InitMessage (CT ct)
        ci->ci_subtype = add ("rfc822", ci->ci_subtype);
 
     /* match subtype */
-    for (kv = SubMessage; kv->kv_key; kv++)
-       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
-           break;
-    ct->c_subtype = kv->kv_value;
+    ct->c_subtype = ct_str_subtype (CT_MESSAGE, ci->ci_subtype);
 
     switch (ct->c_subtype) {
        case MESSAGE_RFC822:
@@ -1597,14 +1659,10 @@ params_external (CT ct, int composing)
 static int
 InitApplication (CT ct)
 {
-    struct k2v *kv;
     CI ci = &ct->c_ctinfo;
 
     /* match subtype */
-    for (kv = SubApplication; kv->kv_key; kv++)
-       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
-           break;
-    ct->c_subtype = kv->kv_value;
+    ct->c_subtype = ct_str_subtype (CT_APPLICATION, ci->ci_subtype);
 
     return OK;
 }
@@ -3176,6 +3234,62 @@ ct_subtype_str (int type, int subtype) {
 }
 
 
+int
+ct_str_type (const char *type) {
+    struct str2init *s2i;
+
+    for (s2i = str2cts; s2i->si_key; ++s2i) {
+        if (! strcasecmp (type, s2i->si_key)) {
+            break;
+        }
+    }
+    if (! s2i->si_key  &&  ! uprf (type, "X-")) {
+        ++s2i;
+    }
+
+    return s2i->si_val;
+}
+
+
+int
+ct_str_subtype (int type, const char *subtype) {
+    struct k2v *kv;
+
+    switch (type) {
+    case CT_APPLICATION:
+        for (kv = SubApplication; kv->kv_key; ++kv) {
+            if (! strcasecmp (subtype, kv->kv_key)) {
+                break;
+            }
+        }
+        return kv->kv_value;
+    case CT_MESSAGE:
+        for (kv = SubMessage; kv->kv_key; ++kv) {
+            if (! strcasecmp (subtype, kv->kv_key)) {
+                break;
+            }
+        }
+        return kv->kv_value;
+    case CT_MULTIPART:
+        for (kv = SubMultiPart; kv->kv_key; ++kv) {
+            if (! strcasecmp (subtype, kv->kv_key)) {
+                break;
+            }
+        }
+        return kv->kv_value;
+    case CT_TEXT:
+        for (kv = SubText; kv->kv_key; ++kv) {
+            if (! strcasecmp (subtype, kv->kv_key)) {
+                break;
+            }
+        }
+        return kv->kv_value;
+    default:
+        return 0;
+    }
+}
+
+
 /* Find the content type and InitFunc for the CT. */
 const struct str2init *
 get_ct_init (int type) {
index 0227d0c..b2a4c0d 100644 (file)
     X("noinlineonly", 0, NINLINESW) \
     X("file file", 0, FILESW) \
     X("form formfile", 0, FORMSW) \
+    X("header", 0, HEADSW) \
+    X("noheader", 0, NHEADSW) \
+    X("headerform formfile", 0, HEADFORMSW) \
     X("markform formfile", 0, MARKFORMSW) \
     X("part number", 0, PARTSW) \
     X("type content", 0, TYPESW) \
+    X("prefer content", 0, PREFERSW) \
     X("rcache policy", 0, RCACHESW) \
     X("wcache policy", 0, WCACHESW) \
     X("version", 0, VERSIONSW) \
@@ -69,6 +73,10 @@ extern char *cache_private;
 extern char *progsw;
 extern int nomore;     /* flags for moreproc/header display */
 extern char *formsw;
+extern char *folder;
+extern char *headerform;
+extern char *markerform;
+extern int headersw;
 
 /* mhmisc.c */
 extern int npart;
@@ -77,6 +85,11 @@ extern char *parts[NPARTS + 1];
 extern char *types[NTYPES + 1];
 extern int userrs;
 
+/* mhparse.c */
+extern char *preferred_types[];
+extern  char *preferred_subtypes[];
+extern int npreferred;
+
 int debugsw = 0;
 int verbosw = 0;
 
@@ -86,7 +99,7 @@ int verbosw = 0;
 CT parse_mime (char *);
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void flush_errors (void);
 
@@ -104,7 +117,7 @@ int
 main (int argc, char **argv)
 {
     int msgnum, *icachesw, concatsw = -1, textonly = -1, inlineonly = -1;
-    char *cp, *file = NULL, *folder = NULL, *markform = NULL;
+    char *cp, *file = NULL;
     char *maildir, buf[100], **argp;
     char **arguments;
     struct msgs_array msgs = { 0, 0, NULL };
@@ -125,13 +138,13 @@ main (int argc, char **argv)
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, switches)) {
-           case AMBIGSW: 
+           case AMBIGSW:
                ambigsw (cp, switches);
                done (1);
-           case UNKWNSW: 
+           case UNKWNSW:
                adios (NULL, "-%s unknown", cp);
 
-           case HELPSW: 
+           case HELPSW:
                snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
                        invo_name);
                print_help (buf, switches, 1);
@@ -203,6 +216,18 @@ do_cache:
                types[ntype++] = cp;
                continue;
 
+           case PREFERSW:
+               if (!(cp = *argp++) || *cp == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               if (npreferred >= NPREFS)
+                   adios (NULL, "too many preferred types (starting with %s), %d max",
+                          cp, NPREFS);
+               preferred_types[npreferred] = cp;
+               cp = strchr(cp, '/');
+               if (cp) *cp++ = '\0';
+               preferred_subtypes[npreferred++] = cp;
+               continue;
+
            case FILESW:
                if (!(cp = *argp++) || (*cp == '-' && cp[1]))
                    adios (NULL, "missing argument to %s", argp[-2]);
@@ -217,8 +242,20 @@ do_cache:
                formsw = getcpy (etcpath (cp));
                continue;
 
+           case HEADSW:
+               headersw = 1;
+               continue;
+           case NHEADSW:
+               headersw = 0;
+               continue;
+
+           case HEADFORMSW:
+               if (!(headerform = *argp++) || *headerform == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               continue;
+
            case MARKFORMSW:
-               if (!(markform = *argp++) || *markform == '-')
+               if (!(markerform = *argp++) || *markerform == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                continue;
 
@@ -239,10 +276,10 @@ do_cache:
                    adios (NULL, "missing argument to %s", argp[-2]);
                continue;
 
-           case VERBSW: 
+           case VERBSW:
                verbosw = 1;
                continue;
-           case NVERBSW: 
+           case NVERBSW:
                verbosw = 0;
                continue;
            case DEBUGSW:
@@ -320,6 +357,8 @@ do_cache:
 
        if ((ct = parse_mime (file)))
            *ctp++ = ct;
+
+       headersw = 0;
     } else {
        /*
         * message(s) are coming from a folder
@@ -412,7 +451,7 @@ do_cache:
     /*
      * Show the message content
      */
-    show_all_messages (cts, concatsw, textonly, inlineonly, markform);
+    show_all_messages (cts, concatsw, textonly, inlineonly);
 
     /* Now free all the structures for the content */
     for (ctp = cts; *ctp; ctp++)
index 986ffb9..056c2f4 100644 (file)
@@ -11,7 +11,6 @@
 #include <fcntl.h>
 #include <h/signals.h>
 #include <h/md5.h>
-#include <setjmp.h>
 #include <h/mts.h>
 #include <h/tws.h>
 #include <h/mime.h>
@@ -23,6 +22,8 @@
 #endif /* ! HAVE_ICONV */
 
 extern int debugsw;
+extern int npart;
+extern int ntype;
 
 int nolist   = 0;
 
@@ -32,9 +33,16 @@ char *progsw = NULL;
 int nomore   = 0;
 char *formsw = NULL;
 
+/* for output markerss and headers */
+char *folder = NULL;
+char *markerform;
+char *headerform;
+int headersw = -1;
+
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
+int part_exact (CT);
 int type_ok (CT, int);
 void content_error (char *, CT, char *, ...);
 void flush_errors (void);
@@ -57,7 +65,9 @@ static int show_external (CT, int, int, int, int, struct format *);
 static int parse_display_string (CT, char *, int *, int *, char *, char *,
                                 size_t, int multipart);
 static int convert_content_charset (CT, char **);
+static struct format *compile_header(char *);
 static struct format *compile_marker(char *);
+static void output_header (CT, struct format *);
 static void output_marker (CT, struct format *, int);
 static void free_markercomps (void);
 static int pidcheck(int);
@@ -67,6 +77,7 @@ static int pidcheck(int);
  * content marker display.
  */
 
+static struct comp *folder_comp = NULL;
 static struct comp *part_comp = NULL;
 static struct comp *ctype_comp = NULL;
 static struct comp *description_comp = NULL;
@@ -87,11 +98,10 @@ static struct param_comp_list *dispo_pc_list = NULL;
  */
 
 void
-show_all_messages (CT *cts, int concatsw, int textonly, int inlineonly,
-                  char *markerform)
+show_all_messages (CT *cts, int concatsw, int textonly, int inlineonly)
 {
     CT ct, *ctp;
-    struct format *fmt;
+    struct format *hfmt, *mfmt;
 
     /*
      * If form is not specified, then get default form
@@ -101,9 +111,10 @@ show_all_messages (CT *cts, int concatsw, int textonly, int inlineonly,
        formsw = getcpy (etcpath ("mhl.headers"));
 
     /*
-     * Compile the content marker format line
+     * Compile the content marker and header format lines
      */
-    fmt = compile_marker(markerform);
+    mfmt = compile_marker(markerform);
+    hfmt = compile_header(headerform);
 
     /*
      * If form is "mhl.null", suppress display of header.
@@ -116,12 +127,15 @@ show_all_messages (CT *cts, int concatsw, int textonly, int inlineonly,
 
        /* if top-level type is ok, then display message */
        if (type_ok (ct, 1))
+           if (headersw) output_header(ct, hfmt);
+
            show_single_message (ct, formsw, concatsw, textonly, inlineonly,
-                                fmt);
+                                mfmt);
     }
 
     free_markercomps();
-    fmt_free(fmt, 1);
+    fmt_free(hfmt, 1);
+    fmt_free(mfmt, 1);
 }
 
 
@@ -341,7 +355,7 @@ show_content_aux (CT ct, int alternate, char *cp, char *cracked, struct format *
     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
        return NOTOK;
     if (ct->c_showproc && !strcmp (ct->c_showproc, "true"))
-       return (alternate ? DONE : OK);
+       return OK;
 
     if (! strcmp(invo_name, "mhshow")  &&
         ct->c_type == CT_TEXT  &&  ct->c_subtype == TEXT_PLAIN) {
@@ -482,14 +496,17 @@ show_content_aux2 (CT ct, int alternate, char *cracked, char *buffer,
            _exit (-1);
            /* NOTREACHED */
 
-       default:
+       default: {
+           int status;
+
            arglist_free(file, vec);
 
-           pidcheck (pidXwait (child_id, NULL));
+           pidcheck ((status = pidXwait (child_id, NULL)));
 
            if (fd != NOTOK)
                (*ct->c_ceclosefnx) (ct);
-           return (alternate ? DONE : OK);
+           return (alternate ? OK : status);
+        }
     }
 }
 
@@ -544,8 +561,9 @@ show_multi (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
     if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
        return show_multi_aux (ct, alternate, cp, fmt);
 
-    if ((cp = ct->c_showproc))
+    if ((cp = ct->c_showproc)) {
        return show_multi_aux (ct, alternate, cp, fmt);
+    }
 
     /*
      * Use default method to display this multipart content.  Even
@@ -570,6 +588,9 @@ show_multi_internal (CT ct, int alternate, int concatsw, int textonly,
     struct multipart *m = (struct multipart *) ct->c_ctparams;
     struct part *part;
     int request_matched;
+    int display_success;
+    int mult_alt_done;
+    int ret;
     CT p;
 
     alternating = 0;
@@ -581,41 +602,57 @@ show_multi_internal (CT ct, int alternate, int concatsw, int textonly,
     }
 
     /*
-     * alternate   -> we are a part inside an multipart/alternative
+     * alternate   -> we are a part inside a multipart/alternative
      * alternating -> we are a multipart/alternative
      */
 
-    result = alternate ? NOTOK : OK;
+    result = NOTOK;
     request_matched = 0;
+    display_success = 0;
+    mult_alt_done = 0;
 
     for (part = m->mp_parts; part; part = part->mp_next) {
        p = part->mp_part;
 
-       if (part_ok (p, 1) && type_ok (p, 1)) {
-           int inneresult;
+       /* while looking for the right displayable alternative, we
+        * use a looser search criterion than we do after finding it.
+        * specifically, while still looking, part_ok() will match
+        * "parent" parts (e.g.  "-part 2" where 2 is a high-level
+        * multipart).  after finding it, we use part_exact() to only
+        * choose a part that was requested explicitly.
+        */
+       if ((part_exact(p) && type_ok(p, 1)) ||
+               (!mult_alt_done && part_ok (p) && type_ok (p, 1))) {
 
-           request_matched = 1;
+           int inneresult;
 
            inneresult = show_switch (p, nowalternate, concatsw, textonly,
                                      inlineonly, fmt);
            switch (inneresult) {
-               case NOTOK:
+               case NOTOK:  /* hard display error */
+                   request_matched = 1;
                    if (alternate && !alternating) {
                        result = NOTOK;
                        goto out;
                    }
                    continue;
 
-               case OK:
-               case DONE:
+               case DONE:  /* found no match on content type */
+                   continue;
+
+               case OK:  /* display successful */
+                   request_matched = 1;
+                   display_success = 1;
+                   result = OK;
+
+                   /* if we got success on a sub-part of
+                    * multipart/alternative, we're done, unless
+                    * there's a chance an explicit part should be
+                    * matched later in the alternatives.  */
                    if (alternating) {
-                       result = DONE;
-                       break;
-                   }
-                   if (alternate) {
+                       mult_alt_done = 1;
+                   } else if (alternate) {
                        alternate = nowalternate = 0;
-                       if (result == NOTOK)
-                           result = inneresult;
                    }
                    continue;
            }
@@ -623,17 +660,23 @@ show_multi_internal (CT ct, int alternate, int concatsw, int textonly,
        }
     }
 
-    if (alternating && !part && request_matched) {
+    /* we're supposed to be displaying at least something from a
+     * multipart/alternative.  if we've had parts to consider, and
+     * we've had no success, then we should complain.  we shouldn't
+     * complain if none of the parts matched any -part or -type option.
+     */
+    if (alternating && request_matched && !display_success) {
+       /* if we're ourselves an alternate.  don't complain yet. */
        if (!alternate)
            content_error (NULL, ct, "don't know how to display any of the contents");
        result = NOTOK;
-       goto out;
     }
 
 out:
     /* if no parts matched what was requested, there can't have been
-     * any display errors, so we report OK. */
-    return request_matched ? result : OK;
+     * any display errors.  we report DONE rather than OK. */
+    ret = request_matched ? result : DONE;
+    return ret;
 }
 
 
@@ -669,7 +712,7 @@ show_multi_aux (CT ct, int alternate, char *cp, struct format *fmt)
            p->c_storage = add (file, NULL);
 
            if (p->c_showproc && !strcmp (p->c_showproc, "true"))
-               return (alternate ? DONE : OK);
+               return OK;
            (*p->c_ceclosefnx) (p);
        }
     }
@@ -1221,6 +1264,7 @@ convert_content_charset (CT ct, char **file) {
  * Compile our format string and save any parameters we care about.
  */
 
+#define DEFAULT_HEADER "[ Message %{folder}%<{folder}:%>%(msg) ]"
 #define DEFAULT_MARKER "[ part %{part} - %{content-type} - " \
                       "%<{description}%{description}" \
                         "%?{cdispo-filename}%{cdispo-filename}" \
@@ -1228,7 +1272,29 @@ convert_content_charset (CT ct, char **file) {
                       "%(kilo(size))B %<(unseen)\\(suppressed\\)%> ]"
 
 static struct format *
-compile_marker(char *markerform)
+compile_header(char *form)
+{
+    struct format *fmt;
+    char *fmtstring;
+    struct comp *comp = NULL;
+    unsigned int bucket;
+
+    fmtstring = new_fs(form, NULL, DEFAULT_HEADER);
+
+    (void) fmt_compile(fmtstring, &fmt, 1);
+    free_fs();
+
+    while ((comp = fmt_nextcomp(comp, &bucket)) != NULL) {
+       if (strcasecmp(comp->c_name, "folder") == 0) {
+           folder_comp = comp;
+       }
+    }
+
+    return fmt;
+}
+
+static struct format *
+compile_marker(char *form)
 {
     struct format *fmt;
     char *fmtstring;
@@ -1236,10 +1302,10 @@ compile_marker(char *markerform)
     unsigned int bucket;
     struct param_comp_list *pc_entry;
 
-    fmtstring = new_fs(markerform, NULL, DEFAULT_MARKER);
+    fmtstring = new_fs(form, NULL, DEFAULT_MARKER);
 
     (void) fmt_compile(fmtstring, &fmt, 1);
-    free(fmtstring);
+    free_fs();
 
     /*
      * Things we care about:
@@ -1285,13 +1351,46 @@ compile_marker(char *markerform)
  * Output on stdout an appropriate marker for this content, using mh-format
  */
 
+static void
+output_header(CT ct, struct format *fmt)
+{
+    charstring_t outbuf = charstring_create (BUFSIZ);
+    int dat[5] = { 0 };
+    char *endp;
+    int message = 0;
+
+    if (folder_comp)
+       folder_comp->c_text = getcpy(folder);
+
+    if (ct->c_file && *ct->c_file) {
+       message = strtol(ct->c_file, &endp, 10);
+       if (*endp) message = 0;
+       dat[0] = message;
+    }
+
+    /* it would be nice to populate dat[2], for %(size) here,
+     * but it's not available.  it might also be nice to know
+     * if the message originally had any mime parts or not -- but
+     * there's also no record of that.  (except for MIME-version:)
+     */
+
+    fmt_scan(fmt, outbuf, BUFSIZ, dat, NULL);
+
+    fputs(charstring_buffer (outbuf), stdout);
+    charstring_free (outbuf);
+
+    fmt_freecomptext();
+}
+
 static void
 output_marker(CT ct, struct format *fmt, int hidden)
 {
     charstring_t outbuf = charstring_create (BUFSIZ);
     struct param_comp_list *pcentry;
     int partsize;
-    int dat[5];
+    int message = 0;
+    char *endp;
+    int dat[5] = { 0 };
 
     /*
      * Grab any items we care about.
@@ -1329,13 +1428,18 @@ output_marker(CT ct, struct format *fmt, int hidden)
     else
        partsize = ct->c_end - ct->c_begin;
 
+    if (ct->c_file && *ct->c_file) {
+       message = strtol(ct->c_file, &endp, 10);
+       if (*endp) message = 0;
+       dat[0] = message;
+    }
+    dat[2] = partsize;
+
     /* make the part's hidden aspect available by overloading the
      * %(unseen) function.  make the part's size available via %(size).
      * see comments in h/fmt_scan.h.
      */
-    dat[2] = partsize;
     dat[4] = hidden;
-    dat[0] = dat[1] = dat[3] = 0;
 
     fmt_scan(fmt, outbuf, BUFSIZ, dat, NULL);
 
@@ -1354,6 +1458,7 @@ free_markercomps(void)
 {
     struct param_comp_list *pc_entry, *pc2;
 
+    folder_comp = NULL;
     part_comp = NULL;
     ctype_comp = NULL;
     description_comp = NULL;
index 17dcc01..a348103 100644 (file)
@@ -29,6 +29,7 @@
     X("outfile outfile", 0, OUTFILESW) \
     X("part number", 0, PARTSW) \
     X("type content", 0, TYPESW) \
+    X("prefer content", 0, PREFERSW) \
     X("rcache policy", 0, RCACHESW) \
     X("wcache policy", 0, WCACHESW) \
     X("version", 0, VERSIONSW) \
@@ -58,6 +59,11 @@ extern char *parts[NPARTS + 1];
 extern char *types[NTYPES + 1];
 extern int userrs;
 
+/* mhparse.c */
+extern char *preferred_types[];
+extern  char *preferred_subtypes[];
+extern int npreferred;
+
 #define        quitser pipeser
 
 /* mhparse.c */
@@ -65,7 +71,7 @@ int debugsw = 0;
 CT parse_mime (char *);
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void flush_errors (void);
 
@@ -175,6 +181,18 @@ do_cache:
                types[ntype++] = cp;
                continue;
 
+           case PREFERSW:
+               if (!(cp = *argp++) || *cp == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               if (npreferred >= NPREFS)
+                   adios (NULL, "too many preferred types (starting with %s), %d max",
+                          cp, NPREFS);
+               preferred_types[npreferred] = cp;
+               cp = strchr(cp, '/');
+               if (cp) *cp++ = '\0';
+               preferred_subtypes[npreferred++] = cp;
+               continue;
+
            case FILESW:
                if (!(cp = *argp++) || (*cp == '-' && cp[1]))
                    adios (NULL, "missing argument to %s", argp[-2]);
index ae835fb..62b2f4a 100644 (file)
@@ -9,7 +9,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
@@ -77,7 +76,7 @@ typedef int (*qsort_comp) (const void *, const void *);
 
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
 void flush_errors (void);
 
@@ -287,7 +286,7 @@ store_multi (CT ct, mhstoreinfo_t info)
     for (part = m->mp_parts; part; part = part->mp_next) {
        CT  p = part->mp_part;
 
-       if (part_ok (p, 1) && type_ok (p, 1)) {
+       if (part_ok (p) && type_ok (p, 1)) {
            if (ct->c_storage) {
                /* Support mhstore -outfile.  The MIME parser doesn't
                   load c_storage, so we know that p->c_storage is
index d29799b..fba190a 100644 (file)
@@ -13,6 +13,7 @@
 #include <h/utils.h>
 
 #define PICK_SWITCHES \
+    X("reverse", 0, REVSW) \
     X("and", 0, ANDSW) \
     X("or", 0, ORSW) \
     X("not", 0, NOTSW) \
@@ -67,6 +68,7 @@ main (int argc, char **argv)
     struct msgs *mp, *mp2;
     register FILE *fp;
     int debug = 0;
+    int reverse = 0;
 
     if (nmh_init(argv[0], 1)) { return 1; }
 
@@ -104,6 +106,10 @@ main (int argc, char **argv)
                listsw = 0;     /* HACK */
                done (0);
 
+            case REVSW:
+                reverse = 1;
+                continue;
+
            case CCSW: 
            case DATESW: 
            case FROMSW: 
@@ -231,7 +237,9 @@ main (int argc, char **argv)
      * match.  If there is NOT a match, then add it to a list to
      * remove from the final sequence (it will make sense later)
      */
-    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
+    for (msgnum = reverse ? mp->hghsel : mp->lowsel;
+         reverse ? msgnum >= mp->lowsel : msgnum <= mp->hghsel;
+         msgnum += reverse ? -1 : 1) {
        if (is_selected (mp, msgnum)) {
            if ((fp = fopen (cp = m_name (msgnum), "r")) == NULL)
                admonish (cp, "unable to read message");
index 0beab55..4391608 100644 (file)
@@ -88,6 +88,7 @@
     X("notls", TLSminc(-5), NTLSSW) \
     X("fileproc", -4, FILEPROCSW) \
     X("mhlproc", -3, MHLPROCSW) \
+    X("sendmail program", 0, MTSSM) \
     X("mts smtp|sendmail/smtp|sendmail/pipe", 2, MTSSW) \
     X("credentials legacy|file:filename", 0, CREDENTIALSSW) \
     X("messageid localname|random", 2, MESSAGEIDSW) \
@@ -481,6 +482,12 @@ main (int argc, char **argv)
                    mhlproc = cp;
                    continue;
 
+               case MTSSM:
+                   if (!(cp = *argp++) || *cp == '-')
+                       adios (NULL, "missing argument to %s", argp[-2]);
+                    sendmail = cp;
+                   continue;
+
                case MTSSW:
                    if (!(cp = *argp++) || *cp == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
index 304c9a5..58347d4 100644 (file)
@@ -331,8 +331,7 @@ getln (char *buffer, int n)
     char *cp;
     static int quoting = 0;
 
-    cp = buffer;
-    *cp = 0;
+    *buffer = 0;
 
     switch (setjmp (sigenv)) {
        case OK: 
@@ -348,6 +347,9 @@ getln (char *buffer, int n)
            return NOTOK;
     }
 
+    cp = buffer;
+    *cp = 0;
+
     for (;;) {
        switch (c = getchar ()) {
            case EOF: 
index 6d144be..ae55f61 100644 (file)
@@ -118,11 +118,11 @@ main (int argc, char **argv)
        done (1);
 
     vec[vecp++] = "-dist";
-    vec[vecp++] = drft;
     if ((cp = context_find ("mhlproc"))) {
       vec[vecp++] = "-mhlproc";
       vec[vecp++] = cp;
     }
+    vec[vecp++] = drft;
     vec[vecp] = NULL;
 
     for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
index a9f03b3..ebe72ff 100644 (file)
@@ -8,9 +8,9 @@
  */
 
 #include <h/mh.h>
+#include <h/mime.h>
 #include <h/utils.h>
 
-
 #define REPL_SWITCHES \
     X("group", 0, GROUPSW) \
     X("nogroup", 0, NGROUPSW) \
@@ -23,6 +23,7 @@
     X("nodraftfolder", 0, NDFLDSW) \
     X("editor editor", 0, EDITRSW) \
     X("noedit", 0, NEDITSW) \
+    X("convertargs type argstring", 0, CONVERTARGSW) \
     X("fcc folder", 0, FCCSW) \
     X("filter filterfile", 0, FILTSW) \
     X("form formfile", 0, FORMSW) \
@@ -109,6 +110,7 @@ static char *fcc    = NULL;         /* folders to add to Fcc: header */
  * prototypes
  */
 static void docc (char *, int);
+static void add_convert_header (const char *, char *, char *, char *);
 
 
 int
@@ -123,6 +125,9 @@ main (int argc, char **argv)
     char *folder = NULL, *msg = NULL, *dfolder = NULL;
     char *dmsg = NULL, *ed = NULL, drft[BUFSIZ], buf[BUFSIZ];
     char **argp, **arguments;
+    svector_t convert_types = svector_create (10);
+    svector_t convert_args = svector_create (10);
+    size_t n;
     struct msgs *mp = NULL;
     struct stat st;
     FILE *in;
@@ -186,6 +191,34 @@ main (int argc, char **argv)
                    nedit++;
                    continue;
                    
+               case CONVERTARGSW: {
+                    char *type;
+                    size_t i;
+
+                   if (!(type = *argp++)) {
+                       adios (NULL, "missing type argument to %s", argp[-2]);
+                    }
+                   if (!(cp = *argp++)) {
+                       adios (NULL, "missing argstring argument to %s",
+                              argp[-3]);
+                    }
+
+                    for (i = 0; i < svector_size (convert_types); ++i) {
+                        if (! strcmp (svector_at (convert_types, i), type)) {
+                            /* Already saw this type, so just update
+                               its args. */
+                            svector_strs (convert_args)[i] = cp;
+                            break;
+                        }
+                    }
+
+                    if (i == svector_size (convert_types)) {
+                        svector_push_back (convert_types, type);
+                        svector_push_back (convert_args, cp);
+                    }
+                   continue;
+                }
+
                case WHATSW: 
                    if (!(whatnowproc = *argp++) || *whatnowproc == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
@@ -418,10 +451,25 @@ try_it_again:
             fcc, fmtproc);
     fclose (in);
 
+    {
+       char *filename = concat (mp->foldpath, "/", msg, NULL);
+
+        for (n = 0; n < svector_size (convert_types); ++n) {
+            add_convert_header (svector_at (convert_types, n),
+                                svector_at (convert_args, n),
+                                filename, drft);
+        }
+       free (filename);
+    }
+
     if (nwhat)
        done (0);
     what_now (ed, nedit, NOUSE, drft, msg, 0, mp, anot ? "Replied" : NULL,
            inplace, cwd, atfile);
+
+    svector_free (convert_args);
+    svector_free (convert_types);
+
     done (1);
     return 1;
 }
@@ -453,3 +501,25 @@ docc (char *cp, int ccflag)
            break;
     }
 }
+
+/*
+ * Add pseudoheaders that will pass the convert arguments to
+ * mhbuild.  They have the form:
+ *   MHBUILD_FILE_PSEUDOHEADER-text/calendar: /home/user/Mail/inbox/7
+ *   MHBUILD_ARGS_PSEUDOHEADER-text/calendar: reply -accept
+ * The ARGS pseudoheader is optional, but we always add it when
+ * -convertargs is used.
+ */
+void
+add_convert_header (const char *convert_type, char *convert_arg,
+                    char *filename, char *drft) {
+    char *field_name;
+
+    field_name = concat (MHBUILD_FILE_PSEUDOHEADER, convert_type, NULL);
+    annotate (drft, field_name, filename, 1, 0, -2, 1);
+    free (field_name);
+
+    field_name = concat (MHBUILD_ARGS_PSEUDOHEADER, convert_type, NULL);
+    annotate (drft, field_name, convert_arg, 1, 0, -2, 1);
+    free (field_name);
+}
index 4c022b4..22cdb10 100644 (file)
@@ -72,6 +72,7 @@
     X("tls", TLSminc(-3), TLSSW) \
     X("initialtls", TLSminc(-10), INITTLSSW) \
     X("notls", TLSminc(-5), NTLSSW) \
+    X("sendmail program", 0, MTSSM) \
     X("mts smtp|sendmail/smtp|sendmail/pipe", 2, MTSSW) \
     X("messageid localname|random", 2, MESSAGEIDSW) \
 
@@ -283,6 +284,7 @@ main (int argc, char **argv)
                case SASLMECHSW:
                case SASLMXSSFSW:
                case PORTSW:
+               case MTSSM:
                case MTSSW:
                case MESSAGEIDSW:
                    vec[vecp++] = --cp;
index af9412d..07c7f97 100644 (file)
@@ -389,7 +389,6 @@ sendaux (char **vec, int vecp, char *program, char *drft, struct stat *st)
     fd = pushsw ? tmp_fd () : NOTOK;
     fd2 = NOTOK;
 
-    vec[vecp++] = drft;
     if (annotext) {
        if ((fd2 = tmp_fd ()) != NOTOK) {
            vec[vecp++] = "-idanno";
@@ -400,6 +399,7 @@ sendaux (char **vec, int vecp, char *program, char *drft, struct stat *st)
                       "for annotation list", get_temp_dir());
        }
     }
+    vec[vecp++] = drft;
     if (distfile && distout (drft, distfile, backup) == NOTOK)
        done (1);
     vec[vecp] = NULL;
index e5b71ef..5b815f4 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * show.c -- show/list messages
  *
      */                                \
     X("concat", -6, CONCATSW) \
     X("noconcat", -8, NCONCATSW) \
+    /*                         \
+     * switches for mhshow     \
+     */                                \
+    X("part number", 0, PARTSW) \
+    X("type content", 0, TYPESW) \
+    X("prefer content", 0, PREFERSW) \
+    X("markform file", 0, MARKFORMSW) \
+    X("rcache policy", 0, RCACHESW) \
+    X("wcache policy", 0, WCACHESW) \
 
 #define X(sw, minchars, id) id,
 DEFINE_SWITCH_ENUM(SHOW);
@@ -79,23 +87,29 @@ main (int argc, char **argv)
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, switches)) {
-               case AMBIGSW: 
+               case AMBIGSW:
                    ambigsw (cp, switches);
                    done (1);
 
+               case HEADSW:
+                   headersw = 1;
+                   goto non_mhl_switches;
+               case NHEADSW:
+                   headersw = 0;
                case CONCATSW:
                case NCONCATSW:
+non_mhl_switches:
                    /* mhl can't handle these, so keep them separate. */
                    app_msgarg(&non_mhl_vec, --cp);
                    continue;
 
-               case UNKWNSW: 
+               case UNKWNSW:
                case NPROGSW:
                case NFMTPROCSW:
                    app_msgarg(&vec, --cp);
                    continue;
 
-               case HELPSW: 
+               case HELPSW:
                    snprintf (buf, sizeof(buf),
                        "%s [+folder] %s[switches] [switches for showproc]",
                        invo_name, mode == SHOW ? "[msgs] ": "");
@@ -105,7 +119,7 @@ main (int argc, char **argv)
                    print_version(invo_name);
                    done (0);
 
-               case DRFTSW: 
+               case DRFTSW:
                    if (file)
                        adios (NULL, "only one file at a time!");
                    draftsw++;
@@ -115,7 +129,7 @@ usage:
                    adios (NULL,
                            "usage: %s [+folder] [switches] [switches for showproc]",
                            invo_name);
-               case FILESW: 
+               case FILESW:
                    if (mode != SHOW)
                        goto usage;
                    if (draftsw || file)
@@ -125,13 +139,6 @@ usage:
                    file = path (cp, TFILE);
                    continue;
 
-               case HEADSW: 
-                   headersw++;
-                   continue;
-               case NHEADSW: 
-                   headersw = 0;
-                   continue;
-
                case FORMSW:
                    app_msgarg(&vec, --cp);
                    if (!(cp = *argp++) || *cp == '-')
@@ -143,18 +150,24 @@ usage:
                case LENSW:
                case WIDTHSW:
                case FMTPROCSW:
+               case PARTSW:
+               case TYPESW:
+               case PREFERSW:
+               case MARKFORMSW:
+               case RCACHESW:
+               case WCACHESW:
                    app_msgarg(&vec, --cp);
                    if (!(cp = *argp++) || *cp == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
                    app_msgarg(&vec, cp);
                    continue;
 
-               case SHOWSW: 
+               case SHOWSW:
                    if (!(showproc = *argp++) || *showproc == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
                    nshow = 0;
                    continue;
-               case NSHOWSW: 
+               case NSHOWSW:
                    nshow++;
                    continue;
 
@@ -194,6 +207,7 @@ usage:
            app_msgarg(&vec, getcpy (m_draft (folder, NULL, 1, &isdf)));
        else
            app_msgarg(&vec, file);
+       headersw = 0;
        goto go_to_it;
     }
 
@@ -252,11 +266,7 @@ usage:
     context_replace (pfolder, folder); /* update current folder   */
     context_save ();                   /* save the context file   */
 
-    if (headersw && vec.size == 1)
-       printf ("(Message %s:%s)\n", folder, vec.msgs[0]);
-
 go_to_it: ;
-    fflush (stdout);
 
     /*
      * Decide which "proc" to use
@@ -290,12 +300,22 @@ go_to_it: ;
     if (folder && !draftsw && !file)
        m_putenv ("mhfolder", folder);
 
-    if (strcmp (r1bindex (proc, '/'), "mhl") == 0) {
+    if (strcmp (r1bindex (proc, '/'), "cat") == 0) {
+
+       if (headersw && vec.size == 1)
+           printf ("(Message %s:%s)\n", folder, vec.msgs[0]);
+
+    } else if (strcmp (r1bindex (proc, '/'), "mhl") == 0) {
+
+       if (headersw && vec.size == 1)
+           printf ("(Message %s:%s)\n", folder, vec.msgs[0]);
+
        /* If "mhl", then run it internally */
        argsplit_insert(&vec, "mhl", &program);
        app_msgarg(&vec, NULL);
        mhl (vec.size, vec.msgs);
        done (0);
+
     } else {
        int i;
        char **mp;
@@ -324,9 +344,14 @@ go_to_it: ;
                app_msgarg(&vec, vec.msgs[vec.size - 1]);
                vec.msgs[vec.size - 2] = "-file";
            }
+       } else {
+           if (headersw && vec.size == 1)
+               printf ("(Message %s:%s)\n", folder, vec.msgs[0]);
        }
     }
 
+    fflush (stdout);
+
     argsplit_insert(&vec, proc, &program);
     app_msgarg(&vec, NULL);
     execvp (program, vec.msgs);
index 0ff765c..050ca0f 100644 (file)
@@ -9,7 +9,6 @@
 
 #include <h/mh.h>
 #include <fcntl.h>
-#include <h/signals.h>
 #include <h/md5.h>
 #include <h/mts.h>
 #include <h/tws.h>
index 2c2d204..d8862dc 100644 (file)
@@ -446,9 +446,6 @@ WhatNow (int argc, char **argv)
                    annotate(drft, ATTACH_FIELD, file, 1, 0, -2, 1);
                    if (verbose) {
                        ctype = mime_type(file);
-                   }
-
-                   if (verbose) {
                        printf ("Attaching %s as a %s\n", file, ctype);
                        free (ctype);
                    }
@@ -956,6 +953,7 @@ buildfile (char **argp, char *file)
     X("tls", TLSminc(-3), TLSSW) \
     X("initialtls", TLSminc(-10), INITTLSSW) \
     X("notls", TLSminc(-5), NTLSSW) \
+    X("sendmail program", 0, MTSSM) \
     X("mts smtp|sendmail/smtp|sendmail/pipe", 2, MTSSW) \
     X("messageid localname|random", 2, MESSAGEIDSW) \
 
@@ -1145,6 +1143,7 @@ sendit (char *sp, char **arg, char *file, int pushed)
                case SASLMECHSW:
                case USERSW:
                case PORTSW:
+               case MTSSM:
                case MTSSW:
                case MESSAGEIDSW:
                    vec[vecp++] = --cp;
@@ -1256,10 +1255,10 @@ whomfile (char **arg, char *file)
 
        case OK:
            vec = argsplit(whomproc, &program, &vecp);
-           vec[vecp++] = file;
            if (arg)
                while (*arg)
                    vec[vecp++] = *arg++;
+           vec[vecp++] = file;
            vec[vecp] = NULL;
 
            execvp (program, vec);