/ylwrap
/nmh-*.tar.gz
/nmh-*.tar.gz.sig
+/sbr/icalendar.c
+/sbr/icalparse.[hc]
# Removed by maintainer-clean:
/autom4te.cache/
/uip/mark
/uip/mhbuild
/uip/mhfixmsg
+/uip/mhical
/uip/mhl
/uip/mhlist
/uip/mhlogin
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.
## 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
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) \
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 \
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 \
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:
##
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 \
## 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 \
## 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
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/
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
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 \
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 \
## 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 \
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 \
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)
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 \
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)"'
/* 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
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.
*/
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
*/
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
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
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
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
'; 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 ----------------
+++ /dev/null
-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.
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
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
####
[ $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
--- /dev/null
+#### 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:
# 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;
# Filter HTML through a converter program
#
-sub process_html (*$$;$)
+sub process_html
{
my ($input, $encoding, $charset, $boundary) = @_;
my $filterpid, $prefixpid, $finread, $finwrite;
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";
}
# 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), '');
# the content of this part
#
-sub process_multipart ($$$)
+sub process_multipart
{
my ($input, $subtype, $boundary) = @_;
my $altout;
# "Eat" a MIME part; consume content until we hit the boundary or EOF
#
-sub eat_part ($$)
+sub eat_part
{
my ($input, $boundary) = @_;
my $ret;
# Return the decoder subroutine to use
#
-sub find_decoder ($)
+sub find_decoder
{
my ($encoding) = @_;
}
}
-sub null_decoder ($)
+sub null_decoder
{
my ($input) = @_;
# Match a line against the boundary string
#
-sub match_boundary($$)
+sub match_boundary
{
my ($line, $boundary) = @_;
- 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
--- /dev/null
+%; 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%>\
--- /dev/null
+%; 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%>\
Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%>"
To:
cc:
-From:decode
+From:formatfield="%(unquote(decode{text}))"
Subject:decode
:
extras:nocomponent
Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%>"
To:
cc:
-From:decode
+From:formatfield="%(unquote(decode{text}))"
Subject:decode
:
extras:nocomponent
;
; 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
--- /dev/null
+; mhl.replywithoutbody
+;
+; message filter for `repl' (repl -format) that excludes the message body
+;
+from:nocomponent,formatfield="%(unquote(decode(friendly{text}))) writes:"
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
#### 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
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
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
--- /dev/null
+%;
+%; 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) ]
%;
%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}>>%>
%;
%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}>>%>
%;
%; 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
%;
%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}>>%>
%<(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))
%<{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})
%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}%>
%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}%>
%?(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}%>
%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}>>%>
*/
/* 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 */
--- /dev/null
+/*
+ * 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 *);
#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 */
#define NPARTS 50
#define NTYPES 20
-#define NPARMS 10
+#define NPREFS 20
/*
* Abstract type for header fields
/* 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. */
};
/*
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 *);
* 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
#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) \
#define DEFAULT_PAGER "more"
#define DEFAULT_EDITOR "vi"
-
-#include <signal.h>
*
*/
void unquote_string(const char *input, char *output);
-int uprf (char *, char *);
+int uprf (const char *, const char *);
int vfgets (FILE *, char **);
/*
-.TH MH-FOLDERS %manext5% "July 22, 2014" "%nmhversion%"
+.TH MH-FOLDERS %manext5% "February 25, 2016" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
-.TH MH-FORMAT %manext5% "October 27, 2014" "%nmhversion%"
+.TH MH-FORMAT %manext5% "January 10, 2015" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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.
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.
-.TH MH\-MIME %manext7% "March 16, 2014" "%nmhversion%"
+.TH MH\-MIME %manext7% "December 14, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
-.TH MH-PROFILE %manext5% "April 18, 2014" "%nmhversion%"
+.TH MH-PROFILE %manext5% "December 21, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
.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
-.TH MHBUILD %manext1% "March 13, 2014" "%nmhversion%"
+.TH MHBUILD %manext1% "December 14, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.RB [ \-verbose " | " \-noverbose ]
.RB [ \-disposition " | " \-nodisposition ]
.RB [ \-check " | " \-nocheck ]
-.RB [ \-headerencoding
+.RB [ \-headerencoding
.IR encoding\-algorithm
.RB " | " \-autoheaderencoding ]
.RB [ \-maxunencoded
.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
.nf
#off
#include <stdio.h>
-printf("Hello, World!);
+printf("Hello, World!");
#pop
.fi
.RE
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
.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
.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),
-.TH MHFIXMSG %manext1% "November 30, 2014" "%nmhversion%"
+.TH MHFIXMSG %manext1% "March 12, 2016" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.RB [ \-decodetext
8bit/7bit |
.BR \-nodecodetext ]
+.RB [ \-decodetypes
+.IR "type/[subtype][,...]" ]
+.RB [ \-crlflinebreaks " | " \-nocrlflinebreaks ]
.RB [ \-textcharset
.I charset
.RB "| " \-notextcharset ]
.RB [ \-replacetextplain " | " \-noreplacetextplain ]
.RB [ \-fixboundary " | " \-nofixboundary ]
.RB [ \-fixcte " | " \-nofixcte ]
+.RB [ \-fixtype
+.IR mimetype ]
.RB [ \-outfile
.IR outfile ]
.RB [ \-rmmproc
.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
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
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
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.
.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
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.
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
| 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
.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 '
.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.
--- /dev/null
+.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.
-.TH MHLIST %manext1% "August 20, 2014" "%nmhversion%"
+.TH MHLIST %manext1% "February 6, 2015" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.RB [ \-type
.IR content ]
\&...
+.RB [ \-prefer
+.IR content ]
+\&...
.RB [ \-headers " | " \-noheaders ]
.RB [ \-realsize " | " \-norealsize ]
.RB [ \-rcache
.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.
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
can provide other information, such as the
.B nmh
version identifier, the locations of the nmh
+.IR bindir ,
.I etcdir
and
.I libexecdir
-.TH MHSHOW %manext1% "April 9, 2014" "%nmhversion%"
+.TH MHSHOW %manext1% "February 8, 2015" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.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
.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
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
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.
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
.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
.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
^%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 ^ ~
-.TH MHSTORE %manext1% "March 2, 2014" "%nmhversion%"
+.TH MHSTORE %manext1% "February 6, 2015" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.RB [ \-type
.IR content ]
\&...
+.RB [ \-prefer
+.IR content ]
+\&...
.RB [ \-auto " | " \-noauto ]
.RB [ \-clobber
.IR always " | " auto " | " suffix " | " ask " | " never ]
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
.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
.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
.B pick
.RI [ +folder ]
.RI [ msgs ]
+.RB [ \-reverse
+\&...]
.RB [ \-and
\&...]
.RB [ \-or
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
.IR columns ]
.RB [ \-mts
.IR smtp " | " sendmail/smtp " | " sendmail/pipe ]
+.RB [ \-sendmail
+.IR program ]
.RB [ \-server
.IR servername ]
.RB [ \-port
.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
-.TH REPL %manext1% "November 4, 2014" "%nmhversion%"
+.TH REPL %manext1% "December 14, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.RB [ \-editor
.IR editor ]
.RB [ \-noedit ]
+.RB [ \-convertargs
+.IR "type argstring" ]
.RB [ \-whatnowproc
.IR program ]
.RB [ \-nowhatnowproc ]
.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
.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
.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"
.RB [ \-watch " | " \-nowatch ]
.RB [ \-mts
.IR smtp " | " sendmail/smtp " | " sendmail/pipe ]
+.RB [ \-sendmail
+.IR program ]
.RB [ \-server
.IR servername ]
.RB [ \-port
.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
-.TH SHOW %manext1% "June 6, 2012" "%nmhversion%"
+.TH SHOW %manext1% "February 8, 2015" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
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);
* 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;
return;
ctxflags &= ~CTXMOD;
- if ((action = m_chkids ()) > 0)
- return; /* child did it for us */
-
/* block a few signals */
sigemptyset (&set);
sigaddset (&set, SIGHUP);
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;
}
--- /dev/null
+/*
+ * 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));
+}
{ "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 },
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. */
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))
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;
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 == '(')
/* 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);
*/
#include <h/mh.h>
-#include <h/signals.h>
-#include <setjmp.h>
#ifdef READLINE_SUPPORT
#include <readline/readline.h>
--- /dev/null
+/*
+ * 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.
+ */
--- /dev/null
+/*
+ * 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 */
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;
}
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 */
int
-uprf (char *c1, char *c2)
+uprf (const char *c1, const char *c2)
{
int c, mask;
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
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
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"
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"
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() {
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
-# 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
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
#### 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
#### 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 ]
{
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
#
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
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
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
. "$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
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"
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
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
# 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
# check -version
-# Verified same behavior as compiled mhfixmsg.
case `mhfixmsg -version` in
mhfixmsg\ --*) ;;
*) printf '%s: mhfixmsg -version generated unexpected output\n' "$0" >&2
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"
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"
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
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
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
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>
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
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'
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
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"
--- /dev/null
+#!/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
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,
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
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}
EOF
cat > $expected <<EOF
+[ Message inbox:11 ]
Date: Sun, 18 Dec 2005 00:52:39 +0100
To: bar@example.edu
From: foo@example.edu
EOF
run_prog mhshow $msgnum > $actual 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : bogus quoted-printable data
exit $failed
# 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
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
# 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
# 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
# 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
# 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
=\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
# 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
# check charset conversion
msgfile=`mhpath new`
+msgnum=`basename $msgfile`
cat >"$msgfile" <<'EOF'
From: foo@example.edu
To: bar@example.edu
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
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}
# check expansion of %{charset} by itself
msgfile=`mhpath new`
+msgnum=`basename $msgfile`
cat >"$msgfile" <<'EOF'
From: foo@example.edu
To: bar@example.edu
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
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
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
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"
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
-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
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
-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
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"
# check parameter value quoting
msgfile=`mhpath new`
+msgnum=`basename $msgfile`
cat >"$msgfile" <<'EOF'
From: foo@example.edu
To: bar@example.edu
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
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"
# check that unknown parameter is not expanded
msgfile=`mhpath new`
+msgnum=`basename $msgfile`
cat >"$msgfile" <<'EOF'
From: foo@example.edu
To: bar@example.edu
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
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"
# check parameter value quoting with text following
msgfile=`mhpath new`
+msgnum=`basename $msgfile`
cat >"$msgfile" <<'EOF'
From: foo@example.edu
To: bar@example.edu
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
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
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
=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
EOF
run_prog mhshow last >"$actual" 2>&1
-check "$expected" "$actual"
+check "$expected" "$actual" : large file conversion to UTF-8
exit $failed
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' \
--- /dev/null
+#!/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
-nodraftfolder
-editor editor
-noedit
+ -convertargs type argstring
-fcc folder
-filter filterfile
-form formfile
--- /dev/null
+#!/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
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
+}
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
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);
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");
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);
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");
*/
#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
* 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 */
static void
inc_done (int status)
{
+ done = exit;
if (packfile && pd != NOTOK)
mbx_close (packfile, pd);
if (locked)
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
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
*/
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);
*/
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);
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];
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);
} 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);
}
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.
* 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.
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);
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,
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;
}
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
/* 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);
#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) \
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) \
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;
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);
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);
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; }
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]);
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;
}
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
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;
+ }
}
}
status = 1;
}
+ if (fx.fixtypes != NULL) { svector_free (fx.fixtypes); }
free (outfile);
free (file);
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);
}
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);
}
+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;
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;
}
}
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;
+ }
}
}
}
+/* 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;
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: {
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;
}
}
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;
}
}
+/*
+ * 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.
--- /dev/null
+/*
+ * 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;
+}
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) \
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.
CT parse_mime (char *);
/* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
int type_ok (CT, int);
void flush_errors (void);
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]);
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/mts.h>
#include <h/tws.h>
#include <h/mime.h>
#include <h/utils.h>
/* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
int type_ok (CT, int);
void flush_errors (void);
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);
}
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) {
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;
post_or_send="$nmhbindir/send"
fi
- if "$post_or_send" "$tmpfile" $postsendargs; then
+ if "$post_or_send" $postsendargs "$tmpfile"; then
exit
else
status=$?
/*
* 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;
CT parse_mime (char *);
/* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
int type_ok (CT, int);
void flush_errors (void);
* 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++)
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
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;
}
DEFINE_SWITCH_ARRAY(MHPARAM, switches);
#undef X
+extern char *mhbindir;
extern char *mhetcdir;
extern char *mhlibexecdir;
{ "version", &version_num },
{ "whatnowproc", &whatnowproc },
{ "whomproc", &whomproc },
+ { "bindir", &mhbindir },
{ "etcdir", &mhetcdir },
{ "libdir", &mhlibexecdir },
{ "libexecdir", &mhlibexecdir },
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
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
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 *, ...);
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);
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++;
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 */
char *chset = NULL;
char *cp;
PM pm;
- struct k2v *kv;
struct text *t;
CI ci = &ct->c_ctinfo;
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)
size_t buflen;
ssize_t gotlen;
struct multipart *m;
- struct k2v *kv;
struct part *part, **next;
CI ci = &ct->c_ctinfo;
CT p;
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);
}
/* 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
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
/*
- * 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)
{
}
}
+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]);
+}
static int
InitMessage (CT ct)
{
- struct k2v *kv;
CI ci = &ct->c_ctinfo;
if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
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:
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;
}
}
+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) {
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) \
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;
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;
CT parse_mime (char *);
/* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
int type_ok (CT, int);
void flush_errors (void);
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 };
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);
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]);
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;
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:
if ((ct = parse_mime (file)))
*ctp++ = ct;
+
+ headersw = 0;
} else {
/*
* message(s) are coming from a folder
/*
* 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++)
#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>
#endif /* ! HAVE_ICONV */
extern int debugsw;
+extern int npart;
+extern int ntype;
int nolist = 0;
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);
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);
* 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;
*/
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
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.
/* 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);
}
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) {
_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);
+ }
}
}
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
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;
}
/*
- * 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;
}
}
}
- 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;
}
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);
}
}
* 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}" \
"%(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;
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:
* 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.
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);
{
struct param_comp_list *pc_entry, *pc2;
+ folder_comp = NULL;
part_comp = NULL;
ctype_comp = NULL;
description_comp = NULL;
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) \
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 */
CT parse_mime (char *);
/* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
int type_ok (CT, int);
void flush_errors (void);
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]);
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
/* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
int type_ok (CT, int);
void flush_errors (void);
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
#include <h/utils.h>
#define PICK_SWITCHES \
+ X("reverse", 0, REVSW) \
X("and", 0, ANDSW) \
X("or", 0, ORSW) \
X("not", 0, NOTSW) \
struct msgs *mp, *mp2;
register FILE *fp;
int debug = 0;
+ int reverse = 0;
if (nmh_init(argv[0], 1)) { return 1; }
listsw = 0; /* HACK */
done (0);
+ case REVSW:
+ reverse = 1;
+ continue;
+
case CCSW:
case DATESW:
case FROMSW:
* 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");
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) \
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]);
char *cp;
static int quoting = 0;
- cp = buffer;
- *cp = 0;
+ *buffer = 0;
switch (setjmp (sigenv)) {
case OK:
return NOTOK;
}
+ cp = buffer;
+ *cp = 0;
+
for (;;) {
switch (c = getchar ()) {
case EOF:
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++)
*/
#include <h/mh.h>
+#include <h/mime.h>
#include <h/utils.h>
-
#define REPL_SWITCHES \
X("group", 0, GROUPSW) \
X("nogroup", 0, NGROUPSW) \
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) \
* prototypes
*/
static void docc (char *, int);
+static void add_convert_header (const char *, char *, char *, char *);
int
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;
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]);
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;
}
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);
+}
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) \
case SASLMECHSW:
case SASLMXSSFSW:
case PORTSW:
+ case MTSSM:
case MTSSW:
case MESSAGEIDSW:
vec[vecp++] = --cp;
fd = pushsw ? tmp_fd () : NOTOK;
fd2 = NOTOK;
- vec[vecp++] = drft;
if (annotext) {
if ((fd2 = tmp_fd ()) != NOTOK) {
vec[vecp++] = "-idanno";
"for annotation list", get_temp_dir());
}
}
+ vec[vecp++] = drft;
if (distfile && distout (drft, distfile, backup) == NOTOK)
done (1);
vec[vecp] = NULL;
-
/*
* 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);
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] ": "");
print_version(invo_name);
done (0);
- case DRFTSW:
+ case DRFTSW:
if (file)
adios (NULL, "only one file at a time!");
draftsw++;
adios (NULL,
"usage: %s [+folder] [switches] [switches for showproc]",
invo_name);
- case FILESW:
+ case FILESW:
if (mode != SHOW)
goto usage;
if (draftsw || file)
file = path (cp, TFILE);
continue;
- case HEADSW:
- headersw++;
- continue;
- case NHEADSW:
- headersw = 0;
- continue;
-
case FORMSW:
app_msgarg(&vec, --cp);
if (!(cp = *argp++) || *cp == '-')
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;
app_msgarg(&vec, getcpy (m_draft (folder, NULL, 1, &isdf)));
else
app_msgarg(&vec, file);
+ headersw = 0;
goto go_to_it;
}
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
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;
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);
#include <h/mh.h>
#include <fcntl.h>
-#include <h/signals.h>
#include <h/md5.h>
#include <h/mts.h>
#include <h/tws.h>
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);
}
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) \
case SASLMECHSW:
case USERSW:
case PORTSW:
+ case MTSSM:
case MTSSW:
case MESSAGEIDSW:
vec[vecp++] = --cp;
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);