]> diplodocus.org Git - nmh/commitdiff
Merge branch 'extended-params'
authorKen Hornstein <kenh@pobox.com>
Thu, 6 Mar 2014 04:17:17 +0000 (23:17 -0500)
committerKen Hornstein <kenh@pobox.com>
Thu, 6 Mar 2014 04:17:17 +0000 (23:17 -0500)
44 files changed:
.gitignore
MACHINES
Makefile.am
SPECS/nmh.spec
docs/contrib/replyfilter
docs/pending-release-notes
etc/mhn.defaults.sh
h/mh.h
h/mhparse.h
h/prototypes.h
man/mh-folders.man
man/mh-profile.man
man/mhfixmsg.man
man/mhparam.man
man/mhshow.man
man/mhstore.man
man/mkstemp.man [new file with mode: 0644]
man/nmh.man
man/pick.man
sbr/credentials.c
sbr/ext_hook.c
sbr/m_getfld.c
sbr/ruserpass.c
sbr/utils.c
test/common.sh.in
test/install-mh/test-install-mh
test/mhbuild/test-attach
test/mhbuild/test-cte
test/mhfixmsg/test-mhfixmsg
test/mhparam/test-mhparam
test/mhshow/test-charset
test/mhshow/test-textcharset [new file with mode: 0755]
test/mhstore/test-mhstore
test/mkstemp/test-mkstemp [new file with mode: 0755]
test/sequences/test-flist
uip/mhfixmsg.c
uip/mhmail
uip/mhn.c
uip/mhparam.c
uip/mhparse.c
uip/mhshowsbr.c
uip/mhstore.c
uip/mhstoresbr.c
uip/mkstemp.c [new file with mode: 0644]

index 743853b..b8e0d46 100644 (file)
@@ -79,6 +79,7 @@ a.out.dSYM/
 /uip/mhshow
 /uip/mhstore
 /uip/mhtest
+/uip/mkstemp
 /uip/msgchk
 /uip/msh
 /uip/new
index d184287..1bc5da1 100644 (file)
--- a/MACHINES
+++ b/MACHINES
@@ -28,7 +28,7 @@ Platform-specific notes follow.
 
 Run-time package requirements:
     ncurses-libs
-    libdb (only needed for slocal(1))
+    gdbm, db4, or libdb (only needed for slocal(1))
     readline (if you want readline support)
     cyrus-sasl-lib / libsasl2 (if configured with --with-cyrus-sasl)
     openssl-libs / libssl (if configured with --with-tls)
@@ -36,7 +36,7 @@ Run-time package requirements:
 Additional build-time package requirements:
     flex
     ncurses-devel / libncurses5-devel
-    libdb-devel / libdb-dev (only needed for slocal(1))
+    gdbm-devel, db4-devel or libdb-devel/libdb-dev (only needed for slocal(1))
     readline-devel (if you want readline support)
     cyrus-sasl-devel / libsasl2-dev (if configuring with --with-cyrus-sasl)
     openssl-devel / libssl-dev (if configuring with --with-tls)
index d9b3229..a73a421 100644 (file)
@@ -38,6 +38,7 @@ TESTS_ENVIRONMENT = MH_OBJ_DIR="@abs_builddir@" \
                    auxexecdir="$(auxexecdir)" bindir="$(bindir)" \
                    mandir="$(mandir)" sysconfdir="$(sysconfdir)" \
                    supported_locks="$(supported_locks)" \
+                   default_locking="${default_locking}" \
                    MULTIBYTE_ENABLED=$(MULTIBYTE_ENABLED) \
                    ICONV_ENABLED=$(ICONV_ENABLED) \
                    $(TESTS_SHELL) ## Keep at end of TESTS_ENVIRONMENT.
@@ -74,11 +75,11 @@ TESTS = test/ali/test-ali test/anno/test-anno \
        test/mhlist/test-mhlist test/mhlist/test-ext-params \
        test/mhmail/test-mhmail \
        test/mhparam/test-mhparam test/mhpath/test-mhpath \
-       test/mhshow/test-charset \
+       test/mhshow/test-charset test/mhshow/test-textcharset \
        test/mhshow/test-cte-binary test/mhshow/test-qp \
        test/mhshow/test-subpart test/mhshow/test-msg-buffer-boundaries \
-       test/mhstore/test-mhstore test/new/test-basic \
-       test/pick/test-pick test/pick/test-stderr \
+       test/mhstore/test-mhstore test/mkstemp/test-mkstemp \
+       test/new/test-basic test/pick/test-pick test/pick/test-stderr \
        test/post/test-post-aliases test/post/test-post-basic \
        test/post/test-post-multiple test/post/test-post-bcc \
        test/post/test-post-dcc test/post/test-post-fcc \
@@ -151,9 +152,9 @@ bin_SCRIPTS = uip/mhmail etc/sendfiles
 ##
 ## This is all programs that get installed in the "lib" directory
 ##
-auxexec_PROGRAMS = uip/ap uip/conflict uip/dp uip/fmtdump uip/mhl uip/post \
-                  uip/rcvdist uip/rcvpack uip/rcvstore uip/rcvtty uip/slocal \
-                  uip/viamail uip/mhtest
+auxexec_PROGRAMS = uip/ap uip/conflict uip/dp uip/fmtdump uip/mhl uip/mkstemp \
+                  uip/post uip/rcvdist uip/rcvpack uip/rcvstore uip/rcvtty \
+                  uip/slocal uip/viamail uip/mhtest
 
 auxexec_SCRIPTS = uip/spost
 
@@ -229,8 +230,8 @@ man_MANS = man/ali.1 man/anno.1 man/ap.8 man/burst.1 man/comp.1 \
           man/mh-format.5 man/mh-mail.5 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/msgchk.1 man/msh.1 \
-          man/mts.conf.5 man/new.1 man/next.1 man/nmh.7 man/packf.1 \
+          man/mhpath.1 man/mhshow.1 man/mhstore.1 man/mkstemp.1 man/msgchk.1 \
+          man/msh.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/rcvtty.1 man/refile.1 \
           man/repl.1 man/rmf.1 man/rmm.1 man/scan.1 man/send.1 \
@@ -250,8 +251,8 @@ man_SRCS = man/ali.man man/anno.man man/ap.man man/burst.man man/comp.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/msgchk.man man/msh.man \
-          man/mts.conf.man man/new.man man/next.man man/nmh.man \
+          man/mhshow.man man/mhstore.man man/mkstemp.man man/msgchk.man \
+          man/msh.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 \
@@ -433,6 +434,9 @@ uip_mhtest_SOURCES = uip/mhtest.c uip/mhparse.c uip/mhcachesbr.c \
                     uip/md5.c
 uip_mhtest_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
+uip_mkstemp_SOURCES = uip/mkstemp.c
+uip_mkstemp_LDADD = $(LDADD) $(POSTLINK)
+
 uip_post_SOURCES = uip/post.c uip/aliasbr.c
 uip_post_LDADD = mts/libmts.a $(LDADD) $(SASLLIB) $(TLSLIB) $(POSTLINK)
 
index 39e87ab..9999026 100644 (file)
@@ -43,7 +43,7 @@ Summary:       A capable mail handling system with a command line interface
 Group:         Applications/Internet
 License:       BSD
 URL:           http://savannah.nongnu.org/projects/nmh
-BuildRequires: gdbm-devel ncurses-devel
+BuildRequires: flex ncurses-devel
 %define        tarfile nmh-%rawversion.tar.gz
 Source0:       %tarfile
 Source1:       VERSION
index cbb8fff..7f5b447 100755 (executable)
@@ -68,11 +68,11 @@ $outcharset = 'utf-8';
 $maxcolwidth = 78;
 
 #
-# Out HTML converter program & arguments
+# Out HTML converter program & arguments. charset will be appended
 #
 
 @htmlconv = ('w3m', '-dump', '-cols', $maxcolwidth - 2, '-T', 'text/html',
-            '-O', $outcharset);
+            '-O', $outcharset, '-I');
 
 
 die "Usage: $0 Content-type content-transfer-encoding quote-prefix\n"
@@ -400,7 +400,8 @@ sub process_html (*$$;$)
                # Exec our converter
                #
 
-               exec (@htmlconv) ||
+               my @conv = (@htmlconv, $charset);
+               exec (@conv) ||
                                die "Unable to exec $filterprogram: $!\n";
        } else {
                die "Fork for $htmlconv[0] failed: $!\n";
index 1b90141..780b2ab 100644 (file)
@@ -74,6 +74,10 @@ NEW FEATURES
   Nmh-Attachment).
 - The default Content-Transfer-Encoding for text parts is now 8bit.
 - mhbuild(1) now supports a selectable Content-Transfer-Encoding
+- If nmh was configured with iconv(3) support, mhshow will convert, if
+  necessary, the charset of text/plain content to match the user's
+  locale setting.
+- Added support for %{charset} display string escape to mhshow(1).
 - The MIME parsing and generating routines now support RFC 2231 extended
   parameter information.
 
@@ -125,6 +129,8 @@ OBSOLETE FEATURES
   {MHTMPDIR environment variable, TMPDIR environment variable, MH Path}.
   They are no longer be stored in the location specified by the TMP
   environment variable.
+- Instead of printing PostScript attachments, by default, from mhshow,
+  try to find a suitable viewer.
 
 -------------------
 DEPRECATED FEATURES
@@ -166,6 +172,7 @@ BUG FIXES
   $HOME/.netrc.
 - Added quoting of local part of invalid address in message being
   replied to [Bug #26780].
-- Fix segmentation fauls for %(putlit) and %(zputlit) format escapes when
+- Fix segmentation faults for %(putlit) and %(zputlit) format escapes when
   the "str" register was NULL.
 - Encode and decode text MIME types with canonical line breaks properly.
+- mhstore(1) now obeys its -noverbose switch.
index 2f91a96..7e9ca56 100755 (executable)
@@ -27,14 +27,16 @@ trap "rm -f $TMP" 0 1 2 3 13 15
 
 
 if [ ! -z "`$SEARCHPROG $SEARCHPATH w3m`" ]; then
-  echo "mhfixmsg-format-text/html: w3m -dump -T text/html -O utf-8 '%F'" >> $TMP
+  echo 'mhfixmsg-format-text/html: charset=%{charset}; '"\
+"'w3m -dump -T text/html "${charset:+-I $charset}" -O utf-8 %F' >> $TMP
 elif [ ! -z "`$SEARCHPROG $SEARCHPATH lynx`" ]; then
   #### lynx indents with 3 spaces, remove them and any trailing spaces.
-  echo "mhfixmsg-format-text/html: lynx -child -dump -force_html '%F' | \
+  echo 'mhfixmsg-format-text/html: charset=%{charset}; '"\
+"'lynx -child -dump -force_html "${charset:+--assume_charset $charset}" %F | '"\
 expand | sed -e 's/^   //' -e 's/  *$//'" >> $TMP
 elif [ ! -z "`$SEARCHPROG $SEARCHPATH elinks`" ]; then
   echo "mhfixmsg-format-text/html: elinks -dump -force-html -no-numbering \
--eval 'set document.browse.margin_width = 0' '%F'" >> $TMP
+-eval 'set document.browse.margin_width = 0' %F" >> $TMP
 fi
 
 
@@ -59,7 +61,7 @@ fi
 
 PGM="`$SEARCHPROG $SEARCHPATH xv`"
 if [ ! -z "$PGM" ]; then
-    echo "mhshow-show-image: %p$PGM -geometry =-0+0 '%f'" >> $TMP
+    echo "mhshow-show-image: %p$PGM -geometry =-0+0 %f" >> $TMP
 elif [ ! -z $"NETPBM" -a ! -z "$XWUD" ]; then
     echo "mhshow-show-image/gif: %p${NETPBMDIR}giftopnm | ${NETPBMDIR}ppmtopgm | ${NETPBMDIR}pgmtopbm | ${NETPBMDIR}pnmtoxwd | $XWUD -geometry =-0+0" >> $TMP
     echo "mhshow-show-image/x-pnm: %p${NETPBMDIR}pnmtoxwd | $XWUD -geometry =-0+0" >> $TMP
@@ -77,7 +79,7 @@ if [ -f "/dev/audioIU" ]; then
     PGM="`$SEARCHPROG $SEARCHPATH recorder`"
     if [ ! -z "$PGM" ]; then
        echo "mhstore-store-audio/basic: %m%P.au" >> $TMP
-        echo "mhbuild-compose-audio/basic: ${AUDIODIR}recorder '%f' -au -pause > /dev/tty" >> $TMP
+        echo "mhbuild-compose-audio/basic: ${AUDIODIR}recorder %f -au -pause > /dev/tty" >> $TMP
         echo "mhshow-show-audio/basic: %p${AUDIODIR}splayer -au" >> $TMP
     fi
 elif [ -f "/dev/audio" ]; then
@@ -88,7 +90,7 @@ elif [ -f "/dev/audio" ]; then
         echo "mhstore-store-audio/x-next: %m%P.au" >> $TMP
        AUDIOTOOL="`$SEARCHPROG $SEARCHPATH audiotool`"
        if [ ! -z "$AUDIOTOOL" ]; then
-           echo "mhbuild-compose-audio/basic: $AUDIOTOOL '%f' && ${AUDIODIR}raw2audio -F < '%f'" >> $TMP
+           echo "mhbuild-compose-audio/basic: $AUDIOTOOL %f && ${AUDIODIR}raw2audio -F < %f" >> $TMP
        else
            echo "mhbuild-compose-audio/basic: trap \"exit 0\" 2 && ${AUDIODIR}record | ${AUDIODIR}raw2audio -F" >> $TMP
        fi
@@ -98,14 +100,14 @@ elif [ -f "/dev/audio" ]; then
        if [ ! -z "$PGM" ]; then
            DIR="`echo $PGM | awk -F/ '{ for(i=2;i<NF;i++)printf "/%s", $i;}'`"/
            if [ ! -z "$AUDIOTOOL" ]; then
-               echo "mhbuild-compose-audio/x-next: $AUDIOTOOL '%f' && ${DIR}adpcm_enc < '%f'" >> $TMP
+               echo "mhbuild-compose-audio/x-next: $AUDIOTOOL %f && ${DIR}adpcm_enc < %f" >> $TMP
            else
                echo "mhbuild-compose-audio/x-next: ${AUDIODIR}record | ${DIR}adpcm_enc" >> $TMP
            fi
            echo "mhshow-show-audio/x-next: %p${DIR}adpcm_dec | ${AUDIODIR}play" >> $TMP
        else
            if [ ! -z "$AUDIOTOOL" ]; then
-               echo "mhbuild-compose-audio/x-next: $AUDIOTOOL '%f'" >> $TMP
+               echo "mhbuild-compose-audio/x-next: $AUDIOTOOL %f" >> $TMP
            else
                echo "mhbuild-compose-audio/x-next: ${AUDIODIR}record" >> $TMP
            fi
@@ -119,27 +121,35 @@ fi
 
 PGM="`$SEARCHPROG $SEARCHPATH mpeg_play`"
 if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-video/mpeg: %p$PGM '%f'" >> $TMP
+       echo "mhshow-show-video/mpeg: %p$PGM %f" >> $TMP
 fi
 
-PGM="`$SEARCHPROG $SEARCHPATH lpr`"
+PGM="`$SEARCHPROG $SEARCHPATH okular`"
 if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-application/PostScript: %plpr -Pps" >> $TMP
+       echo "mhshow-show-application/PostScript: %pokular %F" >> $TMP
 else
-    PGM="`$SEARCHPROG $SEARCHPATH lp`"
+    PGM="`$SEARCHPROG $SEARCHPATH evince`"
     if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-application/PostScript: %plp -dps" >> $TMP
+       echo "mhshow-show-application/PostScript: %pevince %F" >> $TMP
+    else
+       PGM="`$SEARCHPROG $SEARCHPATH gv`"
+       if [ ! -z "$PGM" ]; then
+           echo "mhshow-show-application/PostScript: %pgv %F" >> $TMP
+       fi
     fi
 fi
 
 PGM="`$SEARCHPROG $SEARCHPATH ivs_replay`"
 if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-application/x-ivs: %p$PGM -o '%F'" >> $TMP
+       echo "mhshow-show-application/x-ivs: %p$PGM -o %F" >> $TMP
 fi
 
+# The application/vnd.openxmlformats-officedocument.wordprocessingml.document
+# through application/onenote associations are from
+# http://technet.microsoft.com/en-us/library/cc179224.aspx
+
 cat <<EOF >> ${TMP}
 mhshow-suffix-application/msword: .doc
-mhshow-suffix-application/msword: .docx
 mhshow-suffix-application/ogg: .ogg
 mhshow-suffix-application/pdf: .pdf
 mhshow-suffix-application/postscript: .ps
@@ -150,13 +160,35 @@ mhshow-suffix-application/vnd.ms-excel: .xld
 mhshow-suffix-application/vnd.ms-excel: .xll
 mhshow-suffix-application/vnd.ms-excel: .xlm
 mhshow-suffix-application/vnd.ms-excel: .xls
-mhshow-suffix-application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: .xlsx
 mhshow-suffix-application/vnd.ms-excel: .xlt
 mhshow-suffix-application/vnd.ms-excel: .xlw
 mhshow-suffix-application/vnd.ms-powerpoint: .pot
 mhshow-suffix-application/vnd.ms-powerpoint: .pps
 mhshow-suffix-application/vnd.ms-powerpoint: .ppt
 mhshow-suffix-application/vnd.ms-powerpoint: .ppz
+mhshow-suffix-application/vnd.openxmlformats-officedocument.wordprocessingml.document: .docx
+mhshow-suffix-application/vnd.ms-word.document.macroEnabled.12: .docm
+mhshow-suffix-application/vnd.openxmlformats-officedocument.wordprocessingml.template: .dotx
+mhshow-suffix-application/vnd.ms-word.template.macroEnabled.12: .dotm
+mhshow-suffix-application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: .xlsx
+mhshow-suffix-application/vnd.ms-excel.sheet.macroEnabled.12: .xlsm
+mhshow-suffix-application/vnd.openxmlformats-officedocument.spreadsheetml.template: .xltx
+mhshow-suffix-application/vnd.ms-excel.template.macroEnabled.12: .xltm
+mhshow-suffix-application/vnd.ms-excel.sheet.binary.macroEnabled.12: .xlsb
+mhshow-suffix-application/vnd.ms-excel.addin.macroEnabled.12: .xlam
+mhshow-suffix-application/vnd.openxmlformats-officedocument.presentationml.presentation: .pptx
+mhshow-suffix-application/vnd.ms-powerpoint.presentation.macroEnabled.12: .pptm
+mhshow-suffix-application/vnd.openxmlformats-officedocument.presentationml.slideshow: .ppsx
+mhshow-suffix-application/vnd.ms-powerpoint.slideshow.macroEnabled.12: .ppsm
+mhshow-suffix-application/vnd.openxmlformats-officedocument.presentationml.template: .potx
+mhshow-suffix-application/vnd.ms-powerpoint.template.macroEnabled.12: .potm
+mhshow-suffix-application/vnd.ms-powerpoint.addin.macroEnabled.12: .ppam
+mhshow-suffix-application/vnd.openxmlformats-officedocument.presentationml.slide: .sldx
+mhshow-suffix-application/vnd.ms-powerpoint.slide.macroEnabled.12: .sldm
+mhshow-suffix-application/onenote: .onetoc
+mhshow-suffix-application/onenote: .onetoc2
+mhshow-suffix-application/onenote: .onetmp
+mhshow-suffix-application/onenote: .onepkg
 mhshow-suffix-application/x-bzip2: .bz2
 mhshow-suffix-application/x-cpio: .cpio
 mhshow-suffix-application/x-dvi: .dvi
@@ -207,28 +239,30 @@ EOF
 # that another netscape is already running and certain things can't be done).
 PGM="`$SEARCHPROG $SEARCHPATH lynx`"
 if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-text/html: %p$PGM -force-html '%F'" >> $TMP
+       echo 'mhshow-show-text/html: charset=%{charset}; '"\
+%p$PGM"' -force-html "${charset:+--assume_charset $charset}" %F' >> $TMP
 else
   PGM="`$SEARCHPROG $SEARCHPATH w3m`"
   if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-text/html: %p$PGM -T text/html '%F'" >> $TMP
+       echo 'mhshow-show-text/html: charset=%{charset}; '"\
+%p$PGM"' "${charset:+-I $charset}" -T text/html %F' >> $TMP
   fi
 fi
 
 PGM="`$SEARCHPROG $SEARCHPATH richtext`"
 if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-text/richtext: %p$PGM -p '%F'" >> $TMP
+       echo "mhshow-show-text/richtext: %p$PGM -p %F" >> $TMP
 else
     PGM="`$SEARCHPROG $SEARCHPATH rt2raw`"
     if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-text/richtext: %p$PGM < '%f' | fmt -78 | more" >> $TMP
+       echo "mhshow-show-text/richtext: %p$PGM < %f | fmt -78 | more" >> $TMP
     fi
 fi
 
 # staroffice to read .doc files
 PGM="`$SEARCHPROG $SEARCHPATH soffice`"
 if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-application/msword: %psoffice '%F'" >> $TMP
+       echo "mhshow-show-application/msword: %psoffice %F" >> $TMP
        echo "mhshow-suffix-application/msword: .doc" >> $TMP
 fi
 
@@ -245,7 +279,7 @@ if [ "$DISPLAY" = "unix:0.0" -a ! -z "$PGM" ]; then
 else
     PGM="`$SEARCHPROG $SEARCHPATH gs`"
     if [ ! -z "$PGM" ]; then
-       echo "mhshow-show-application/PostScript: %p$PGM -- '%F'" >> $TMP
+       echo "mhshow-show-application/PostScript: %p$PGM -- %F" >> $TMP
        echo "mhshow-suffix-application/PostScript: .ps" >> $TMP
     fi
 fi
@@ -254,5 +288,5 @@ fi
 
 PGM="`$SEARCHPROG $SEARCHPATH ivs_record`"
 if [ ! -z "$PGM" ]; then
-       echo "mhbuild-compose-application/x-ivs: $PGM -u localhost '%F'" >> $TMP
+       echo "mhbuild-compose-application/x-ivs: $PGM -u localhost %F" >> $TMP
 fi
diff --git a/h/mh.h b/h/mh.h
index 1ecb488..4e8be7b 100644 (file)
--- a/h/mh.h
+++ b/h/mh.h
@@ -165,7 +165,6 @@ unsigned int bvector_at (bvector_t, size_t);
 const unsigned long *bvector_bits (bvector_t);
 size_t bvector_maxsize (bvector_t);
 
-struct svector;
 typedef struct svector *svector_t;
 
 svector_t svector_create (size_t);
@@ -176,7 +175,6 @@ char **svector_find(svector_t, const char *);
 char **svector_strs (svector_t);
 size_t svector_size (svector_t);
 
-struct ivector;
 typedef struct ivector *ivector_t;
 
 ivector_t ivector_create (size_t);
index 9b058e6..7ade8a3 100644 (file)
@@ -342,6 +342,8 @@ char *ct_subtype_str (int, int);
 const struct str2init *get_ct_init (int);
 const char *ce_str (int);
 const struct str2init *get_ce_method (const char *);
+char *content_charset (CT);
+int convert_charset (CT, char *, int *);
 
 /*
  * Given a list of messages, display information about them on standard
@@ -427,6 +429,15 @@ char *output_params(size_t initialwidth, PM params, int *offsetout,
  */
 PM add_param(PM *first, PM *last, char *name, char *value, int nocopy);
 
+/*
+ * Replace (or add) a parameter to the parameter linked list.
+ *
+ * If the named parameter already exists on the parameter linked list,
+ * replace the value with the new one.  Otherwise add it to the linked
+ * list.  All parameters are identical to add_param().
+ */
+PM replace_param(PM *first, PM *last, char *name, char *value, int nocopy);
+
 /*
  * Retrieve a parameter value from a parameter linked list.  Convert to the
  * local character set if required.
index 03c783a..f8adbac 100644 (file)
@@ -46,6 +46,7 @@ void cpydgst (int, int, char *, char *);
 char *cpytrim (const char *);
 int decode_rfc2047 (char *, char *, size_t);
 void discard (FILE *);
+char *upcase (const char *);
 
 /*
  * Decode two characters into their quoted-printable representation.
index 4b560e4..52cde27 100644 (file)
@@ -1,4 +1,4 @@
-.TH MH-MAIL %manext5% "March 7, 2013" "%nmhversion%"
+.TH MH-FOLDERS %manext5% "February 26, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -128,10 +128,24 @@ outside of
 that accesses these files must be sure to lock them using the same
 locking method as
 .BR nmh .
-The locking method is selected when
+The default data locking method is selected when
 .B nmh
 is configured and can be accessed as a string using
-.BR "mhparam lockmethod" .
+.BR "mhparam datalocking" .
+By default, fcntl locking is used, but this may be overridden with
+the
+.B datalocking
+profile entry.
+.PP
+A second, possibly different, locking method is used by
+.BR inc (1)
+when accessing the user's mail spool file or by
+.B nmh
+programs that open any mbox file.  This locking method is selected
+when
+.B nmh
+is configured and can be accessed as a string using
+.BR "mhparam spoollocking" .
 By default, kernel-level locking is used if appropriate for the
 platform, and it is for popular platforms.  That default should also
 be the same as used by the
index 14d1c5a..f991860 100644 (file)
@@ -1,4 +1,4 @@
-.TH MH-PROFILE %manext5% "April 14, 2013" "%nmhversion%"
+.TH MH-PROFILE %manext5% "February 16, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -459,7 +459,7 @@ can be used to select alternate programs if the
 user wishes.  The default values are given in the examples.
 .PP
 If the profile element contains spaces, the element is split at spaces
-into tokens and each token is given as a seperate argument to the
+into tokens and each token is given as a separate argument to the
 .IR execvp (2)
 system call.  If the element contains shell metacharacters then the entire
 element is executed using
@@ -859,7 +859,6 @@ default.  See mh-tailor(5).
 .PP
 .B $MHTMPDIR
 .B $TMPDIR
-.B $TMP
 .RS 5
 These variables are searched, in order, for the directory in which to
 create some temporary files.
index f45447b..ea34233 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHFIXMSG %manext1% "October 27, 2013" "%nmhversion%"
+.TH MHFIXMSG %manext1% "February 8, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -16,9 +16,9 @@ mhfixmsg \- rewrite MIME messages with various transformations
 .RB [ \-decodetext
 8bit/7bit |
 .BR \-nodecodetext ]
-.RB [ \-textcodeset
-.I codeset
-.RB "| " \-notextcodeset ]
+.RB [ \-textcharset
+.I charset
+.RB "| " \-notextcharset ]
 .RB [ \-reformat " | " \-noreformat ]
 .RB [ \-replacetextplain " | " \-noreplacetextplain ]
 .RB [ \-fixboundary " | " \-nofixboundary ]
@@ -69,11 +69,11 @@ linefeed character is removed from text parts encoded in ASCII,
 ISO-8859-x, UTF-8, or Windows-12xx.
 .PP
 The
-.B \-textcodeset
+.B \-textcharset
 switch specifies that all text/plain parts of the message(s)
 should be converted to
-.IR codeset .
-Codeset conversions require that
+.IR charset .
+Charset conversions require that
 .B nmh
 be built with
 .IR iconv (3).
@@ -214,7 +214,7 @@ content type and/or encoding as follows:
 .nf
 .ta \w'\-fixboundary 'u
 \-decodetext   base64 and quoted-printable encoded text parts
-\-textcodeset  text/plain parts
+\-textcharset  text/plain parts
 \-reformat     text parts that are not text/plain
 \-fixboundary  outermost multipart part
 \-fixcte       multipart part
@@ -321,6 +321,7 @@ is checked.
 .fi
 .SH "SEE ALSO"
 .IR inc (1),
+.IR iconv (3),
 .IR mh-profile (5),
 .IR mhbuild (1),
 .IR mhlist (1),
@@ -335,7 +336,7 @@ is checked.
 .RB ` +folder "' defaults to the current folder"
 .RB ` msgs "' defaults to cur"
 .RB ` "\-decodetext 8bit"'
-.RB ` \-notextcodeset '
+.RB ` \-notextcharset '
 .RB ` \-reformat '
 .RB ` \-noreplacetextplain '
 .RB ` \-fixboundary '
index 6f51ffd..bf7c40a 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHPARAM %manext1% "November 6, 2012" "%nmhversion%"
+.TH MHPARAM %manext1% "February 26, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -52,7 +52,7 @@ and
 .I libdir
 install directories, all
 .I proc
-settings, and the locking method with which the
+settings, and the default locking method with which the
 .B nmh
 installation was configured.  The
 .B \-debug
@@ -91,8 +91,11 @@ context: context
 % mhparam libdir
 %libdir%
 
-% mhparam lockmethod
+% mhparam datalocking
 fcntl
+
+% mhparam spoollocking
+flock
 .fi
 .RE
 .PP
index 9cc44c1..03ca5f8 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHSHOW %manext1% "May 4, 2013" "%nmhversion%"
+.TH MHSHOW %manext1% "February 18, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -224,16 +224,17 @@ The display string may contain the following escapes:
 .PP
 .RS 5
 .nf
-.ta \w'%F  'u
-%a     Insert parameters from Content-Type field
-%e     exclusive execution
-%f     Insert filename containing content
-%F     %e, %f, and stdin is terminal not content
-%l     display listing prior to displaying content
-%p     %l, and ask for confirmation
-%s     Insert content subtype
-%d     Insert content description
-%%     Insert the character %
+.ta \w'%F        'u
+%a        Insert parameters from Content-Type field
+%{parameter} Insert the parameter value from the Content-Type field
+%e        exclusive execution
+%f        Insert filename containing content
+%F        %e, %f, and stdin is terminal not content
+%l        display listing prior to displaying content
+%p        %l, and ask for confirmation
+%s        Insert content subtype
+%d        Insert content description
+%%        Insert the character %
 .fi
 .RE
 .PP
@@ -257,6 +258,22 @@ control-\\) will tell
 .B mhshow
 to wrap things up immediately.
 .PP
+The {parameter} escape is typically used in a command line argument
+that should only be present if it has a non-null value.  Its value
+will be wrapped with single quotes if the escape is not so wrapped.
+Shell parameter expansion can construct the argument only when it is
+non-null, e.g.,
+.PP
+.RS 5
+.nf
+mhshow-show-text/html: charset=%{charset};
+  w3m ${charset:+-I $charset} -T text/html %F
+.fi
+.RE
+.PP
+That example also shows the use of indentation to signify continuation:
+the two text lines combine to form a single entry.
+.PP
 Note that if the content being displayed is multipart, but not one of
 the subtypes listed above, then the f- and F-escapes expand to multiple
 filenames, one for each subordinate content.  Further, stdin is not
@@ -268,8 +285,8 @@ behaves as if these profile entries were supplied and supported:
 .PP
 .RS 5
 .nf
-mhshow-show-text/plain: %pmoreproc '%F'
-mhshow-show-message/rfc822: %pshow -file '%F'
+mhshow-show-text/plain: %pmoreproc %F
+mhshow-show-message/rfc822: %pshow -file %F
 .fi
 .RE
 .PP
@@ -299,14 +316,13 @@ Example entries might be:
 .RS 5
 .nf
 mhshow-show-audio/basic: raw2audio 2>/dev/null | play
-mhshow-show-image: xv '%f'
+mhshow-show-image: xv %f
 mhshow-show-application/PostScript: lpr -Pps
 .fi
 .RE
 .PP
-Note that when using the f- or F-escape, it's a good idea to use
-single-quotes around the escape.  This prevents misinterpretation by
-the shell of any funny characters that might be present in the filename.
+If an f- or F-escape is not quoted with single quotes, its expansion
+will be wrapped with single quotes.
 .PP
 Finally,
 .B mhshow
@@ -321,6 +337,18 @@ switch can be given to tell
 .B mhshow
 to never display parts in parallel.
 .SS "Showing Alternate Character Sets"
+If
+.B mhshow
+was built with
+.IR iconv (3),
+then all text/plain parts of the message(s) will be displayed using
+the character set of the current locale.  To convert text parts other
+than text/plain, or if
+.B mhshow
+was not built with
+.IR iconv ,
+an external program can be used, as described next.
+.PP
 Because a content of type text might be in a non-ASCII character
 set, when
 .B mhshow
@@ -379,29 +407,24 @@ needed beforehand.
 Note that many pagers strip off the high-order bit or have problems
 displaying text with the high-order bit set.  However, the pager
 .B less
-has support for single-octet character sets.  The source
-to
-.B less
-is available on many ftp sites carrying free software.
-In order to view messages sent in the ISO-8859-1 character set using
+has support for single-octet character sets.  For example, messages
+encoded in the ISO-8859-1 character set can be view using
 .BR less ,
-.PP
-put these lines in your
-.I \&.login
-file:
+with these environment variable settings:
 .PP
 .RS 5
 .nf
-setenv LESSCHARSET latin1
-setenv LESS "-f"
+.ta \w'%F  'u
+LESSCHARSET latin1
+LESS        -f
 .fi
 .RE
 .PP
-The first line tells
+The first setting tells
 .B less
 to use the ISO-8859-1 definition for
 determining whether a character is \*(lqnormal\*(rq, \*(lqcontrol\*(lq,
-or \*(lqbinary\*(rq.  The second line tells
+or \*(lqbinary\*(rq.  The second setting tells
 .B less
 not to warn you
 if it encounters a file that has non-ASCII characters.  Then, simply
@@ -599,6 +622,7 @@ is checked.
 ^moreproc:~^Default program to display text/plain content
 .fi
 .SH "SEE ALSO"
+.IR iconv (3),
 .IR mhbuild (1),
 .IR mhl (1),
 .IR mhlist (1),
index 7e6f850..6fb6008 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHSTORE %manext1% "March 21, 2013" "%nmhversion%"
+.TH MHSTORE %manext1% "March 2, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -28,6 +28,7 @@ mhstore \- store contents of MIME messages into files
 .RB [ \-wcache
 .IR policy ]
 .RB [ \-check " | " \-nocheck ]
+.RB [ \-verbose " | " \-noverbose ]
 .RB [ \-version ]
 .RB [ \-help ]
 .ad
@@ -169,12 +170,11 @@ specified as the \*(lqfilename\*(rq attribute in the
 attribute in the \*(lqContent-Type\*(rq header for the content you are
 storing.  For security reasons, this filename will be ignored if it
 begins with the character '/', '.', '|', or '!', or if it contains the
-character '%'.  For the sake of security, this switch is not the
-default, and it is recommended that you do NOT put the
-.B \-auto
-switch in your
-.I \&.mh\(ruprofile
-file.
+character '%'.  We also recommend using a \*(lqnmh-storage\*(rq profile
+entry or a
+.B \-clobber
+switch setting other than the default of \*(lqalways\*(rq to avoid
+overwriting existing files.
 .PP
 If the
 .B \-auto
@@ -210,15 +210,17 @@ folder.
 If the formatting string consists solely of a \*(lq-\*(rq character,
 then the content is sent to the standard output.
 .PP
-If the formatting string starts with a '|', then the display string
-will represent a command for
+If the formatting string starts with a '|', then it represents
+a command for
 .B mhstore
 to execute which should
 ultimately store the content.  The content will be passed to the
 standard input of the command.  Before the command is executed,
 .B mhstore
 will change to the appropriate directory, and any
-escapes (given below) in the display string will be expanded.
+escapes (given below) in the formatting string will be expanded.
+The use of the \*(lq%a\*(rq sequence is not recommended because
+the user has no control over the Content-Type parameter data.
 .PP
 Otherwise the formatting string will represent a pathname in which
 to store the content.  If the formatting string starts with a '/',
@@ -236,7 +238,7 @@ listed above) content, the p-escapes are ignored.
 .RS 5
 .nf
 .ta \w'%P  'u
-%a     Parameters from Content-type  (only valid with command)
+%a     Parameters from Content-Type  (only valid with command)
 %m     Insert message number
 %P     Insert part number with leading dot
 %p     Insert part number without leading dot
@@ -275,6 +277,15 @@ mhstore-store-application/PostScript: %m%P.ps
 .fi
 .RE
 .PP
+The
+.B \-verbose
+switch directs
+.B mhstore
+to print out the names of files that it stores.  For backward
+compatibility, it is the default.  The
+.B \-noverbose
+switch suppresses these printouts.
+.PP
 .SS "Overwriting Existing Files"
 The
 .B \-clobber
@@ -495,7 +506,7 @@ nmh-private-cache: .cache
 .PP
 (which is the default value).
 .SS "User Environment"
-Because the display environment in which
+Because the environment in which
 .B mhstore
 operates may vary for
 different machines,
@@ -563,6 +574,7 @@ is checked.
 .RB ` \-nocheck '
 .RB ` \-rcache\ ask '
 .RB ` \-wcache\ ask '
+.RB ` \-verbose '
 .SH CONTEXT
 If a folder is given, it will become the current folder.  The last
 message selected will become the current message.
diff --git a/man/mkstemp.man b/man/mkstemp.man
new file mode 100644 (file)
index 0000000..1df5fd6
--- /dev/null
@@ -0,0 +1,81 @@
+.TH MKSTEMP %manext1% "March 2, 2014" "%nmhversion%"
+.\"
+.\" %nmhwarning%
+.\"
+.SH NAME
+mkstemp \- create a temporary file
+.SH SYNOPSIS
+.HP 5
+.na
+.B %libdir%/mkstemp
+.RB [ \-directory
+.IR directory ]
+.RB [ \-prefix
+.IR prefix ]
+.RB [ \-suffix
+.IR suffix ]
+.RB [ \-version ]
+.RB [ \-help ]
+.ad
+.SH DESCRIPTION
+.B mkstemp
+creates a temporary file using
+.IR mkstemp (3),
+or if the
+.B \-suffix
+switch is available and used,
+.IR mkstemps (3).
+The file name identifies a unique, newly created file, and always
+contains 6 characters that appear to be random.
+.PP
+The
+.B \-directory
+switch specifies a
+.I directory
+in which to create the file.
+.PP
+The
+.B \-prefix
+switch specifies an initial part, before the 6 characters, of the file
+name.
+.PP
+The
+.B \-suffix
+switch is only supported on platforms that provide the
+.IR mkstemps (3)
+library function.  It specifies a
+.I suffix
+to appear after the 6 characters; if it should begin with a special
+character such as a period, that must be explicit in
+.IR suffix .
+.PP
+Unlike arguments to most switches of other
+.B nmh
+commands, the arguments to
+.B mkstemp
+switches can begin with a dash.
+.SH "EXIT STATUS"
+If
+.B mkstemp
+successfully creates the temporary file, it prints the file name on
+standard output and exits with status 0.  On failure, it prints a
+diagnostic message on standard error and exits with status -1.
+.SH "PROFILE COMPONENTS"
+None
+.SH "SEE ALSO"
+.IR mkstemp (3),
+.IR mkstemps (3)
+.SH DEFAULTS
+.PD 0
+.TP 14
+\-directory
+current directory
+.TP
+\-prefix
+none
+.TP
+\-suffix
+none
+.PD
+.SH CONTEXT
+None
index f173258..304a15c 100644 (file)
@@ -1,4 +1,4 @@
-.TH NMH %manext7% "November 30, 2012" "%nmhversion%"
+.TH NMH %manext7% "March 1, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -122,16 +122,28 @@ its
 .B attach
 and related responses, as described in its man page.
 .PP
+.B nmh
+command arguments are usually called
+.IR switches .
+Some switches have a corresponding \*(lq\-no\*(rq switch, which
+negates all previous occurrences of that switch on the command line.
+This allows a user to conveniently override, on the command line, a
+switch in their profile.  Switches may be abbreviated as long as there
+is no ambiguity with another switch of the same command.  To avoid
+ambiguity with any switches that may be added in the future, it is
+recommended that full switch names be used in durable code such as
+shell scripts, functions, and aliases.
+.PP
 All the
 .B nmh
-commands may be run with the single argument
+commands may be run with the single switch
 .BR \-help ,
-which causes them to print a list of the arguments they may be invoked
+which causes them to print a list of the switches they may be invoked
 with and then exit.
 .PP
 All the
 .B nmh
-commands may be run with the single argument
+commands may be run with the single switch
 .BR \-version ,
 which causes them to print the version number of the
 .B nmh
@@ -194,7 +206,7 @@ messages, if they exist.
 Commands that take a folder name
 .RB ( inc ,
 .BR refile ,
-.BR scan, 
+.BR scan,
 \&...) accept the folder name in two formats:  \*(lq+folder\*(rq or
 \*(lq@folder\*(rq.  In both cases, \*(lqfolder\*(rq can be a
 \*(lq/\*(rq-separated path, e.g. \*(lqfoo/bar\*(rq.  \*(lq+folder\*(rq
@@ -487,23 +499,31 @@ Files Used by nmh Commands
 .IR mh\-alias (5)
 alias file for nmh message system
 .TP
+.IR mh\-format (5)
+format file for nmh message system
+.TP
+.IR mh\-profile (5)
+user customization for nmh message system
+.TP
+.IR mh\-tailor (5)
+mail transport customization for nmh message system
+.PD
+.ne 4
+.SS
+Formats
+.TP \n(ttu
+.PD 0
 .IR mh\-draft (5)
 draft folder facility
 .TP
-.IR mh\-format (5)
-format file for nmh message system
+.IR mh\-folders (5)
+nmh message storage format specification
 .TP
 .IR mh\-mail (5)
 message format for nmh message system
 .TP
-.IR mh\-profile (5)
-user customization for nmh message system
-.TP
 .IR mh\-sequence (5)
 sequence specification for nmh message system
-.TP
-.IR mh\-tailor (5)
-mail transport customization for nmh message system
 .PD
 .ne 4
 .SH FILES
index 202db09..3816645 100644 (file)
@@ -1,4 +1,4 @@
-.TH PICK %manext1% "November 6, 2012" "%nmhversion%"
+.TH PICK %manext1% "March 1, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -287,6 +287,39 @@ in the
 same way
 .B mark
 uses them.
+.SS "Output when no messages are matched"
+If
+.B pick
+is used in a backquoted operation, such as
+.PP
+.RS 5
+scan\0`pick\0\-from\0jones`
+.RE
+.PP
+and
+.B pick
+selects no messages (e.g., no messages are from
+\*(lqjones\*(rq), then the shell will still run the outer command (e.g.,
+.BR scan ).
+Since no messages were matched,
+.B pick
+produced
+no output, and the argument given to the outer command as a result of
+backquoting
+.B pick
+is empty.  In the case of
+.B nmh
+programs,
+the outer command now acts as if the default `msg' or `msgs' should be
+used (e.g., \*(lqall\*(rq in the case of
+.BR scan ).
+To prevent this
+unexpected behavior, if
+.B \-list
+was given, and if its standard output is not a tty, then
+.B pick
+outputs the illegal message number \*(lq0\*(rq
+when it fails.  This lets the outer command fail gracefully as well.
 .SH FILES
 .fc ^ ~
 .nf
@@ -371,38 +404,5 @@ or
 .B \-before
 switch it applies to.
 .PP
-If
-.B pick
-is used in a backquoted operation, such as
-.PP
-.RS 5
-scan\0`pick\0\-from\0jones`
-.RE
-.PP
-and
-.B pick
-selects no messages (e.g., no messages are from
-\*(lqjones\*(rq), then the shell will still run the outer command (e.g.,
-.BR scan ).
-Since no messages were matched,
-.B pick
-produced
-no output, and the argument given to the outer command as a result of
-backquoting
-.B pick
-is empty.  In the case of
-.B nmh
-programs,
-the outer command now acts as if the default `msg' or `msgs' should be
-used (e.g., \*(lqall\*(rq in the case of
-.BR scan ).
-To prevent this
-unexpected behavior, if
-.B \-list
-was given, and if its standard output is not a tty, then
-.B pick
-outputs the illegal message number \*(lq0\*(rq
-when it fails.  This lets the outer command fail gracefully as well.
-.PP
 The pattern syntax \*(lq[l-r]\*(rq is not supported; each letter to be
 matched must be included within the square brackets.
index 2b9e843..9f3faa9 100644 (file)
@@ -21,7 +21,7 @@ init_credentials_file () {
             struct stat st;
             char *filename = cred_style + 5;
 
-            while (*filename  &&  isspace ((int) *filename)) ++filename;
+            while (*filename && isspace ((unsigned char) *filename)) ++filename;
 
             if (*filename == '/') {
                 credentials_file = filename;
index 26f2c1c..7b94e57 100644 (file)
@@ -37,6 +37,7 @@ ext_hook(char *hook_name, char *message_file_name_1, char *message_file_name_2)
        vec[vecp++] = message_file_name_2;
        vec[vecp++] = NULL;
        execvp(program, vec);
+       advise(program, "Unable to execute");
        _exit(-1);
        /* NOTREACHED */
 
@@ -47,11 +48,14 @@ ext_hook(char *hook_name, char *message_file_name_1, char *message_file_name_2)
 
     if (status != OK) {
        if (did_message == 0) {
-           if ((hook = context_find("msg-hook")) != (char *)0)
-               advise(NULL, hook);
-           else
-               advise(NULL, "external hook (%s) did not work properly.", hook);
-       
+           char *msghook;
+           if ((msghook = context_find("msg-hook")) != (char *)0)
+               advise(NULL, msghook);
+           else {
+               char errbuf[BUFSIZ];
+               snprintf(errbuf, sizeof(errbuf), "external hook \"%s\"", hook);
+               pidstatus(status, stderr, errbuf);
+           }
            did_message = 1;
        }
 
index 9884ba4..d278e52 100644 (file)
@@ -630,7 +630,7 @@ m_getfld (m_getfld_state_t *gstate, char name[NAMESZ], char *buf, int *bufsz,
            n = 0;
            for (finished = 0; ! finished; ) {
                while (c != '\n'  &&  c != EOF  &&  n++ < max) {
-                   *cp++ = c = Getc (s);
+                   if ((c = Getc (s)) != EOF) { *cp++ = c; }
                }
 
                if (c != EOF) c = Peek (s);
index 3c8565f..7669f8b 100644 (file)
@@ -34,7 +34,7 @@ static FILE *cfile;
 #define        ID      10
 #define        MACH    11
 
-static char tokval[100];
+#define MAX_TOKVAL_SIZE 1024
 
 struct toktab {
     char *tokstr;
@@ -55,7 +55,7 @@ static struct toktab toktabs[] = {
 /*
  * prototypes
  */
-static int token(void);
+static int token(char *);
 
 
 void
@@ -71,7 +71,10 @@ ruserpass(char *host, char **aname, char **apass)
        if (errno != ENOENT)
            perror (credentials_file);
     } else {
-       while ((t = token())) {
+        char tokval[MAX_TOKVAL_SIZE];
+        tokval[0] = '\0';
+
+       while ((t = token(tokval))) {
            switch(t) {
            case DEFAULT:
                usedefault = 1;
@@ -79,7 +82,7 @@ ruserpass(char *host, char **aname, char **apass)
 
            case MACH:
                if (!usedefault) {
-                   if (token() != ID)
+                   if (token(tokval) != ID)
                        continue;
                    /*
                     * Allow match either for user's host name.
@@ -89,10 +92,10 @@ ruserpass(char *host, char **aname, char **apass)
                    continue;
                }
            match:
-               while ((t = token()) && t != MACH && t != DEFAULT) {
+               while ((t = token(tokval)) && t != MACH && t != DEFAULT) {
                    switch(t) {
                    case LOGIN:
-                       if (token() && *aname == 0) {
+                       if (token(tokval) && *aname == 0) {
                            *aname = mh_xmalloc((size_t) strlen(tokval) + 1);
                            strcpy(*aname, tokval);
                        }
@@ -108,7 +111,7 @@ ruserpass(char *host, char **aname, char **apass)
                            adios(NULL, "Remove password or correct file "
                                  "permissions.");
                        }
-                       if (token() && *apass == 0) {
+                       if (token(tokval) && *apass == 0) {
                            *apass = mh_xmalloc((size_t) strlen(tokval) + 1);
                            strcpy(*apass, tokval);
                        }
@@ -161,7 +164,7 @@ ruserpass(char *host, char **aname, char **apass)
 
        snprintf(prompt, sizeof(prompt), "Password (%s:%s): ", host, *aname);
        mypass = nmh_getpass(prompt);
-       
+
        if (*mypass == '\0') {
            mypass = *aname;
        }
@@ -173,7 +176,7 @@ ruserpass(char *host, char **aname, char **apass)
 }
 
 static int
-token(void)
+token(char *tokval)
 {
     char *cp;
     int c;
@@ -192,6 +195,10 @@ token(void)
            if (c == '\\')
                c = getc(cfile);
            *cp++ = c;
+            if (cp - tokval > MAX_TOKVAL_SIZE-1) {
+                adios(NULL, "credential tokens restricted to length %d",
+                      MAX_TOKVAL_SIZE - 1);
+            }
        }
     } else {
        *cp++ = c;
@@ -200,6 +207,10 @@ token(void)
            if (c == '\\')
                c = getc(cfile);
            *cp++ = c;
+            if (cp - tokval > MAX_TOKVAL_SIZE-1) {
+                adios(NULL, "credential tokens restricted to length %d",
+                      MAX_TOKVAL_SIZE - 1);
+            }
        }
     }
     *cp = 0;
index 989a9c8..4405f4c 100644 (file)
@@ -353,6 +353,20 @@ nmh_init(const char *argv0, int read_context) {
     }
 }
 
+
+/* Returns copy of argument str with all characters converted to upper
+   case, and trimmed whitespace (see cpytrim()) . */
+char *
+upcase (const char *str) {
+    char *up = cpytrim (str);
+    char *cp;
+
+    for (cp = up; *cp; ++cp) { *cp = toupper ((unsigned char) *cp); }
+
+    return up;
+}
+
+
 /*
  * Scan for any 8-bit characters.  Return 1 if they exist.
  *
index 85ec208..67b18a8 100644 (file)
@@ -14,6 +14,7 @@ test -z "$bindir"  &&  bindir="@bindir@"
 test -z "$mandir"  &&  mandir="@mandir@"
 test -z "$sysconfdir"  &&  sysconfdir="@sysconfdir@"
 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 auxexecdir bindir mandir sysconfdir
@@ -265,7 +266,7 @@ setup_test ()
   export PATH
 
   # clean old test data
-  trap "rm -rf '$MH_TEST_DIR/Mail'" 0
+  trap "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
index 9798e5e..46d01cd 100755 (executable)
@@ -49,7 +49,7 @@ n
 $installpath
 y" | run_prog install-mh >/dev/null
 
-if [ -f $MH_TEST_DIR/Mail/context  -a  -f $MH_TEST_DIR/Mail/.mh_profile ]; then
+if [ -f $MH_TEST_DIR/Mail/context  -a  -f "$MH" ]; then
   run_prog install-mh -check
 else
   failed=`expr ${failed:-0} + 1`
@@ -61,7 +61,7 @@ fi
 rm -rf $MH_TEST_DIR/Mail
 HOME=$MH_TEST_DIR install-mh -auto >/dev/null
 
-if [ -f $MH_TEST_DIR/Mail/context  -a  -f $MH_TEST_DIR/Mail/.mh_profile ]; then
+if [ -f $MH_TEST_DIR/Mail/context  -a  -f "$MH" ]; then
   run_prog install-mh -check
 else
   failed=`expr ${failed:-0} + 1`
index 12d3da3..6d5a594 100755 (executable)
@@ -17,6 +17,7 @@ setup_test
 LC_ALL=en_US.UTF-8; export LC_ALL
 
 draft="$MH_TEST_DIR/$$.draft"
+backup="${MH_TEST_DIR}/`mhparam sbackup`$$.draft.orig"
 expected="$MH_TEST_DIR/$$.expected"
 
 #
@@ -270,4 +271,6 @@ EOF
 
 check "$draft" "$expected"
 
+[ ${failed:=0} -eq 0 ]  &&  rm -f ${backup}
+
 exit ${failed:-0}
index 1ccfe19..f996e73 100755 (executable)
@@ -19,6 +19,7 @@ set -e
 LC_ALL=en_US.UTF-8; export LC_ALL
 
 draft="$MH_TEST_DIR/$$.draft"
+backup="${MH_TEST_DIR}/`mhparam sbackup`$$.draft.orig"
 expected="$MH_TEST_DIR/$$.expected"
 
 #
@@ -356,7 +357,7 @@ To: Mr Test <mrtest@example.com>
 cc:
 Fcc: +outbox
 ------
-This is the test of a lønger line than the ususal limit.  Assuming we set the maxunencded parameter properly, this should end up as 8bit?
+This is the test of a lønger line than the usual limit.  Assuming we set the maxunencded parameter properly, this should end up as 8bit?
 EOF
 
 run_prog mhbuild -maxunencoded 200 "$draft"
@@ -369,9 +370,11 @@ MIME-Version: 1.0
 Content-Type: text/plain; charset="UTF-8"
 Content-Transfer-Encoding: 8bit
 
-This is the test of a lønger line than the ususal limit.  Assuming we set the maxunencded parameter properly, this should end up as 8bit?
+This is the test of a lønger line than the usual limit.  Assuming we set the maxunencded parameter properly, this should end up as 8bit?
 EOF
 
 check "$draft" "$expected"
 
+[ ${failed:=0} -eq 0 ]  &&  rm -f ${backup}
+
 exit ${failed:-0}
index aa8c951..d60b9d7 100755 (executable)
@@ -47,7 +47,7 @@ Usage: mhfixmsg [+folder] [msgs] [switches]
   switches are:
   -decodetext 8bit|7bit
   -nodecodetext
-  -[no]textcodeset
+  -[no]textcharset
   -[no]reformat
   -[no]replacetextplain
   -[no]fixboundary
@@ -567,7 +567,7 @@ MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
 
 ------- =_aaaaaaaaaa0
-Content-Type: text/plain; charset="iso-8859-1"; name="square.txt"
+Content-Type: text/plain; charset="iso-8859-1"; name="nul+square.txt"
 Content-Transfer-Encoding: base64
 
 AL2yPbwK
@@ -588,7 +588,7 @@ MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary=\"----- =_aaaaaaaaaa0\"
 
 ------- =_aaaaaaaaaa0
-Content-Type: text/plain; charset=\"iso-8859-1\"; name=\"square.txt\"
+Content-Type: text/plain; charset=\"iso-8859-1\"; name=\"nul+square.txt\"
 Content-Transfer-Encoding: binary
 
 " "
@@ -674,13 +674,13 @@ else
 fi
 
 
-# check -textcodeset
+# check -textcharset
 # Also checks preservation of attributes after one (charset) that is
 # modified.
 cat >"$expected" <<EOF
 To: recipient@example.com
 From: sender@example.com
-Subject: mhfixmsg textcodeset test
+Subject: mhfixmsg textcharset test
 MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
 
@@ -699,7 +699,7 @@ EOF
 cat >`mhpath new` <<EOF
 To: recipient@example.com
 From: sender@example.com
-Subject: mhfixmsg textcodeset test
+Subject: mhfixmsg textcharset test
 MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
 
@@ -713,17 +713,13 @@ vbI9vAo=
 ------- =_aaaaaaaaaa0--
 EOF
 
-set +e
-run_prog mhfixmsg last -textcodeset utf-8 -outfile "$actual" 2>"$actual.err"
-if grep "mhfixmsg: Can't convert .* to .* without iconv" "$actual.err" \
-  >/dev/null; then
-  echo "$0: skipping -textcodeset check because nmh was built without iconv"
-  rm -f "$expected" "$actual" "$actual.err"
-else
+if test "$ICONV_ENABLED" -eq 1; then
+  run_prog mhfixmsg last -textcharset utf-8 -outfile "$actual" 2>&1
   check "$expected" "$actual"
-  rm "$actual.err"
+else
+  echo "$0: skipping -textcharset check because nmh was built without iconv"
+  rm -f "$expected"
 fi
-set -e
 
 
 # check -nofixboundary
@@ -774,7 +770,7 @@ fi
 cat >"$expected" <<EOF
 Date: Fri, 13 May 2011 08:21:12 -0500
 Content-Type: multipart/alternative;
-        boundary="----=_NextPart_000_1781A1A_01CC1147.81EBA8D4"
+       boundary="----=_NextPart_000_1781A1A_01CC1147.81EBA8D4"
 Content-Transfer-Encoding: 8bit
 MIME-Version: 1.0
 From: <sender@example.com>
index 0dd8e2b..89bcb5d 100755 (executable)
@@ -47,7 +47,7 @@ esac
 run_test 'mhparam -nonexistent' 'mhparam: -nonexistent unknown'
 
 # check -all
-cp $MH_TEST_DIR/Mail/.mh_profile $expected
+cp "$MH" $expected
 # -all adds current folder
 echo 'Current-Folder: '`folder -f` >>$expected
 run_prog mhparam -all >$actual
@@ -69,7 +69,7 @@ check $expected_err $actual_err
 run_test 'mhparam path' "$MH_TEST_DIR/Mail"
 
 # check more than one component, which enables -component
-echo 'AliasFile: aliases' >>"$MH_TEST_DIR/Mail/.mh_profile"
+echo 'AliasFile: aliases' >>"$MH"
 run_test 'mhparam path AliasFile' "path: $MH_TEST_DIR/Mail
 AliasFile: aliases"
 
@@ -100,6 +100,8 @@ $bindir/whatnow
 $bindir/whom
 $sysconfdir
 ${MH_LIB_DIR}
+fcntl
+${default_locking}
 EOF
 
 run_prog mhparam -nocomponent \
@@ -125,7 +127,9 @@ vmhproc \
 whatnowproc \
 whomproc \
 etcdir \
-libdir >$actual 2>&1
+libdir \
+datalocking \
+spoollocking >$actual 2>&1
 
 check $expected $actual
 
@@ -165,4 +169,8 @@ set -e
 # check for correctness here.
 mhparam -debug >/dev/null
 
+# check with text file that does not end with newline
+printf 'Editor: emacs' >>"$MH"
+run_test 'mhparam -nocomponent editor' 'emacs'
+
 exit $failed
index d3e7354..d0d8028 100755 (executable)
@@ -16,10 +16,12 @@ fi
 
 setup_test
 
+LC_ALL=en_US.UTF-8; export LC_ALL
+
 expected=$MH_TEST_DIR/$$.expected
 actual=$MH_TEST_DIR/$$.actual
 
-echo 'mhshow-charset-windows-1252: echo "This is a test"' >> $MH_TEST_DIR/Mail/.mh_profile
+echo 'mhshow-charset-windows-1252: echo "This is a test"' >> "$MH"
 
 # Write message with bogus quoted-printable data.
 msgfile=`mhpath new`
diff --git a/test/mhshow/test-textcharset b/test/mhshow/test-textcharset
new file mode 100755 (executable)
index 0000000..4e8cc37
--- /dev/null
@@ -0,0 +1,290 @@
+#!/bin/sh
+##########################################################
+#
+# Test display of text/plain parts with charset conversion
+#
+##########################################################
+
+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
+
+if test "$ICONV_ENABLED" -eq 0; then
+  test_skip 'test-textcharset requires that nmh have been built with iconv'
+fi
+
+LC_ALL=en_US.UTF-8; export LC_ALL
+
+expected="$MH_TEST_DIR"/$$.expected
+actual="$MH_TEST_DIR"/$$.actual
+
+# check charset conversion
+msgfile=`mhpath new`
+cat >"$msgfile" <<'EOF'
+From: foo@example.edu
+To: bar@example.edu
+Subject: test display with charset conversion
+MIME-Version: 1.0
+Content-Type: text/plain; charset=windows-1252
+Content-Transfer-Encoding: quoted-printable
+Date: Sun, 18 Dec 2005 00:52:39 +0100
+
+4 =F7 2 =3D 2
+EOF
+
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with charset conversion
+
+MIME-Version: 1.0
+
+part       text/plain                  10
+4 Ã· 2 = 2
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+cat >>"$MH" <<'EOF'
+mhshow-show-text/plain: echo %{charset}
+EOF
+
+
+# check expansion of %{charset} by itself
+msgfile=`mhpath new`
+cat >"$msgfile" <<'EOF'
+From: foo@example.edu
+To: bar@example.edu
+Subject: test display with %{charset} expansion
+MIME-Version: 1.0
+Content-Type: text/plain; charset=windows-1252
+Content-Transfer-Encoding: quoted-printable
+Date: Sun, 18 Dec 2005 00:52:39 +0100
+
+4 =F7 2 =3D 2
+EOF
+
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with %{charset} expansion
+
+MIME-Version: 1.0
+
+windows-1252
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+# check expansion of empty %{charset} by itself
+msgfile=`mhpath new`
+cat >"$msgfile" <<'EOF'
+From: foo@example.edu
+To: bar@example.edu
+Subject: test display with empty %{charset} expansion
+MIME-Version: 1.0
+Content-Type: text/plain
+Content-Transfer-Encoding: quoted-printable
+Date: Sun, 18 Dec 2005 00:52:39 +0100
+
+4 =F7 2 =3D 2
+EOF
+
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with empty %{charset} expansion
+
+MIME-Version: 1.0
+
+
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+grep -v 'mhshow-show-text/plain:' "$MH" >"$MH.new"
+mv -f "$MH.new" "$MH"
+cat >>"$MH" <<'EOF'
+mhshow-show-text/plain: charset=%{charset}; echo ${charset:+-I $charset}
+EOF
+
+# check expansion of embedded %{charset} with no text following
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with %{charset} expansion
+
+MIME-Version: 1.0
+
+-I windows-1252
+EOF
+
+run_prog mhshow -nopause prev >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+# check expansion of empty embedded %{charset} with no text following
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with empty %{charset} expansion
+
+MIME-Version: 1.0
+
+
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+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'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with %{charset} expansion
+
+MIME-Version: 1.0
+
+-I windows-1252 file
+EOF
+
+run_prog mhshow -nopause prev >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+# check expansion of empty embedded %{charset} with text following
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with empty %{charset} expansion
+
+MIME-Version: 1.0
+
+file
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+sed -e 's/charset/method/g' "$MH" >"$MH.new"
+mv -f "$MH.new" "$MH"
+
+# check parameter value quoting
+msgfile=`mhpath new`
+cat >"$msgfile" <<'EOF'
+From: foo@example.edu
+To: bar@example.edu
+Subject: test C-T parameter expansion quoting
+MIME-Version: 1.0
+Content-Type: text/plain; method=$QUOTEME'
+Content-Transfer-Encoding: quoted-printable
+Date: Sun, 18 Dec 2005 00:52:39 +0100
+
+4 =F7 2 =3D 2
+EOF
+
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test C-T parameter expansion quoting
+
+MIME-Version: 1.0
+
+-I $QUOTEME' file
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+sed -e 's/method/unknown/g' "$MH" >"$MH.new"
+mv -f "$MH.new" "$MH"
+
+# check that unknown parameter is not expanded
+msgfile=`mhpath new`
+cat >"$msgfile" <<'EOF'
+From: foo@example.edu
+To: bar@example.edu
+Subject: test display with unknown C-T parameter expansion
+MIME-Version: 1.0
+Content-Type: text/plain; charset=windows-1252
+Content-Transfer-Encoding: quoted-printable
+Date: Sun, 18 Dec 2005 00:52:39 +0100
+
+4 =F7 2 =3D 2
+EOF
+
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: test display with unknown C-T parameter expansion
+
+MIME-Version: 1.0
+
+file
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+grep -v '^mhshow-show-text/plain: ' "$MH" >>"$MH.new"
+mv -f "$MH.new" "$MH"
+cat >>"$MH" <<EOF
+mhshow-show-text/plain: true '%F' %F
+EOF
+
+# check parameter value quoting with text following
+msgfile=`mhpath new`
+cat >"$msgfile" <<'EOF'
+From: foo@example.edu
+To: bar@example.edu
+Subject: check parameter value quoting with text following
+MIME-Version: 1.0
+Content-Type: text/plain
+Date: Sun, 18 Dec 2005 00:52:39 +0100
+
+This is a test.
+EOF
+
+cat >"$expected" <<'EOF'
+Date:    Sun, 18 Dec 2005 00:52:39 +0100
+To:      bar@example.edu
+From:    foo@example.edu
+Subject: check parameter value quoting with text following
+
+MIME-Version: 1.0
+
+EOF
+
+run_prog mhshow -nopause last >"$actual" 2>&1
+check "$expected" "$actual"
+
+
+exit $failed
index 2a42039..06dc474 100755 (executable)
@@ -17,48 +17,51 @@ fi
 setup_test
 
 expected="$MH_TEST_DIR/test-mhstore$$.expected"
+expected_err="$MH_TEST_DIR/test-mhmail$$.expected_err"
 actual="$MH_TEST_DIR/test-mhstore$$.actual"
+actual_err="$MH_TEST_DIR/test-mhmail$$.actual_err"
 
-cd $MH_TEST_DIR
+cd "$MH_TEST_DIR"
 
 # check with no options and no current message
 run_test 'mhstore' 'mhstore: no cur message'
 
 # check with no options and current message
-folder +inbox 5 > /dev/null
-cat > $expected <<EOF
+folder +inbox 5 >/dev/null
+cat >"$expected" <<'EOF'
 This is message number 5
 EOF
 run_test 'mhstore' 'storing message 5 as file 5.txt'
-check $expected 5.txt 'keep first'
+check "$expected" 5.txt 'keep first'
 
 # check with folder and current message
 run_test 'mhstore +inbox' 'storing message 5 as file 5.txt'
-check $expected 5.txt 'keep first'
+check "$expected" 5.txt 'keep first'
 
 # check with specified message
 run_test 'mhstore 5' 'storing message 5 as file 5.txt'
-check $expected 5.txt 'keep first'
+check "$expected" 5.txt 'keep first'
 
 # check -file
 run_test "mhstore -file $MH_TEST_DIR/Mail/inbox/5" \
   "storing message $MH_TEST_DIR/Mail/inbox/5 as file 5.txt"
-check $expected 5.txt 'keep first'
+check "$expected" 5.txt 'keep first'
 
 # check -file -
 stored_contents=`run_prog mhstore -file - < $MH_TEST_DIR/Mail/inbox/5 2>&1 | \
   sed 's/.*as file //'`
-check $expected $stored_contents 'keep first'
+check "$expected" "$stored_contents" 'keep first'
 
-# check -outfile
-run_prog mhstore 5 -outfile "$actual" 2>&1 | grep -v '^storing' && \
-  echo unexpected output from test-mhstore check -outfile >&2
-check $expected "$actual" 'keep first'
+# check -outfile and -noverbose
+run_prog mhstore 5 -noverbose -outfile "$actual" >"$actual_err" 2>&1
+printf '' >"$expected_err"
+check "$expected" "$actual" 'keep first'
+check "$expected_err" "$actual_err"
 
 # check -outfile -
-echo 'storing message 5 to stdout' >>"$expected"
+printf '%s\n' 'storing message 5 to stdout' >>"$expected"
 run_prog mhstore 5 -outfile - >"$actual" 2>&1
-check $expected "$actual"
+check "$expected" "$actual"
 
 # check message number greater than highest
 run_test 'mhstore 11' "mhstore: message 11 doesn't exist"
@@ -66,18 +69,18 @@ run_test 'mhstore 11' "mhstore: message 11 doesn't exist"
 # check multiple messages
 run_test 'mhstore 4 6' 'storing message 4 as file 4.txt
 storing message 6 as file 6.txt'
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is message number 4
 EOF
-check $expected 4.txt
-cat > $expected <<EOF
+check "$expected" 4.txt
+cat >"$expected" <<'EOF'
 This is message number 6
 EOF
-check $expected 6.txt
+check "$expected" 6.txt
 
 # Write message with a text/plain subpart.
 msgfile=`mhpath new`
-cat > $msgfile <<EOF
+cat >"$msgfile" <<'EOF'
 To: recipient@example.com
 From: sender@example.com
 Subject: mhlist test
@@ -123,19 +126,19 @@ bGUgbGluZXMuDQo=
 EOF
 
 # check -part
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is the third text/plain part.
 EOF
 run_test 'mhstore last -part 3' 'storing message 11 part 3 as file 11.3.txt'
-check $expected 11.3.txt
+check "$expected" 11.3.txt
 
 # check -part of a subpart
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is the first text/plain part, in a subpart.
 EOF
 run_test 'mhstore last -part 1.1' \
          'storing message 11 part 1.1 as file 11.1.1.txt'
-check $expected 11.1.1.txt 'keep first'
+check "$expected" 11.1.1.txt 'keep first'
 
 # check -type
 run_test 'mhstore last -type text/plain' \
@@ -143,78 +146,77 @@ run_test 'mhstore last -type text/plain' \
 storing message 11 part 2 as file 11.2.txt
 storing message 11 part 3 as file 11.3.txt
 storing message 11 part 4 as file 11.4.txt'
-check $expected 11.1.1.txt
-cat > $expected <<EOF
+check "$expected" 11.1.1.txt
+cat >"$expected" <<'EOF'
 This is the second text/plain part.
 EOF
-check $expected 11.2.txt
-cat > $expected <<EOF
+check "$expected" 11.2.txt
+cat >"$expected" <<'EOF'
 This is the third text/plain part.
 EOF
-check $expected 11.3.txt
-cat > $expected <<EOF
+check "$expected" 11.3.txt
+cat >"$expected" <<'EOF'
 This is the fourth text/plain part.
 This test has multiple lines.
 EOF
-check $expected 11.4.txt
+check "$expected" 11.4.txt
 
 # check -auto
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is the fourth text/plain part.
 This test has multiple lines.
 EOF
 run_test 'mhstore last -part 4 -auto' \
   'storing message 11 part 4 as file test4.txt'
-check $expected test4.txt 'keep first'
+check "$expected" test4.txt 'keep first'
 
 # check -noauto, the default
 run_test 'mhstore last -part 4 -auto -noauto' \
   'storing message 11 part 4 as file 11.4.txt'
-check $expected 11.4.txt 'keep first'
+check "$expected" 11.4.txt 'keep first'
 
-# check -verbose, which doesn't do anything
-run_test 'mhstore last -part 4 -verbose' \
+# check -verbose, the default
+run_test 'mhstore last -part 4 -noverbose -verbose' \
   'storing message 11 part 4 as file 11.4.txt'
-check $expected 11.4.txt 'keep first'
+check "$expected" 11.4.txt 'keep first'
 
-# check -noverbose, the default
-run_test 'mhstore last -part 4 -verbose -noverbose' \
-  'storing message 11 part 4 as file 11.4.txt'
-check $expected 11.4.txt
+# check -noverbose
+run_test 'mhstore last -part 4 -noverbose' ''
+check "$expected" 11.4.txt
 
 # check that -auto obeys Content-Disposition header
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is the first text/plain part, in a subpart.
 EOF
 run_test 'mhstore last -part 1.1 -auto' \
   'storing message 11 part 1.1 as file test1.txt'
-check $expected test1.txt
+check "$expected" test1.txt
 
 # check -check
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is the second text/plain part.
 EOF
 run_test 'mhstore last -part 2 -check' \
          'storing message 11 part 2 as file 11.2.txt'
-check $expected 11.2.txt 'keep first'
+check "$expected" 11.2.txt 'keep first'
 
 # check -check with bad MD5 checksum
-sed 's/\(Content-MD5: \)kq+Hnc/\1BADBAD/' $msgfile > $MH_TEST_DIR/$$.tmp
-mv -f $MH_TEST_DIR/$$.tmp $msgfile
+sed 's/\(Content-MD5: \)kq+Hnc/\1BADBAD/' "$msgfile" >"$MH_TEST_DIR/$$.tmp"
+mv -f "$MH_TEST_DIR/$$.tmp" "$msgfile"
 run_test 'mhstore last -part 2 -check' \
 'storing message 11 part 2 as file 11.2.txt
 mhstore: content integrity suspect (digest mismatch) -- continuing
          (content text/plain in message 11, part 2)'
-check $expected 11.2.txt 'keep first'
+check "$expected" 11.2.txt 'keep first'
 
 # check -nocheck, the default
 run_test 'mhstore last -part 2 -check -nocheck' \
          'storing message 11 part 2 as file 11.2.txt'
-check $expected 11.2.txt
+check "$expected" 11.2.txt
 
 # check reassembly of message/partial messages (RFC 1521, 7.3.2)
 msgfile=`mhpath new`
-cat > $msgfile <<EOF
+cat >"$msgfile" <<'EOF'
 To: recipient@example.com
 From: sender@example.com
 Subject: mhlist test
@@ -227,7 +229,7 @@ This is part one of three.
 EOF
 
 msgfile=`mhpath new`
-cat > $msgfile <<EOF
+cat >"$msgfile" <<'EOF'
 To: recipient@example.com
 From: sender@example.com
 Subject: mhlist test
@@ -238,7 +240,7 @@ This is part two of three.
 EOF
 
 msgfile=`mhpath new`
-cat > $msgfile <<EOF
+cat >"$msgfile" <<'EOF'
 To: recipient@example.com
 From: sender@example.com
 Subject: mhlist test
@@ -252,43 +254,43 @@ EOF
 run_test 'mhstore last:3' \
          'reassembling partials 12,13,14 to folder inbox as message 15'
 
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is part one of three.
 This is part two of three.
 This is part three of three.
 EOF
 run_test 'mhstore last' 'storing message 15 as file 15.txt'
-check $expected 15.txt
+check "$expected" 15.txt
 
 # cd into Mail directory because it gets cleaned up by the exit trap.
 cd Mail
 
 # check -clobber always
-folder +inbox 7 > /dev/null
+folder +inbox 7 >/dev/null
 touch 7.txt
-cat > $expected <<EOF
+cat >"$expected" <<'EOF'
 This is message number 7
 EOF
 run_test 'mhstore' 'storing message 7 as file 7.txt'
-check $expected 7.txt 'keep first'
+check "$expected" 7.txt 'keep first'
 run_test 'mhstore -clobber ask -clobber always' \
          'storing message 7 as file 7.txt'
-check $expected 7.txt 'keep first'
+check "$expected" 7.txt 'keep first'
 
 # check -clobber auto
 touch 7.txt
 run_test 'mhstore -clobber auto' 'storing message 7 as file 7-1.txt'
-check $expected 7-1.txt 'keep first'
+check "$expected" 7-1.txt 'keep first'
 touch 7-1.txt
 run_test 'mhstore -clobber auto' 'storing message 7 as file 7-2.txt'
-check $expected 7-2.txt 'keep first'
+check "$expected" 7-2.txt 'keep first'
 
 # check -clobber suffix
 run_test 'mhstore -clobber suffix' 'storing message 7 as file 7.txt.1'
-check $expected 7.txt.1 'keep first'
+check "$expected" 7.txt.1 'keep first'
 touch 7.txt.1
 run_test 'mhstore -clobber suffix' 'storing message 7 as file 7.txt.2'
-check $expected 7.txt.2 'keep first'
+check "$expected" 7.txt.2 'keep first'
 
 # Don't check -clobber ask because it requires connection to a
 # terminal, and this test won't always be run with one.
@@ -307,33 +309,33 @@ cd ..
 storagedir=storagedir
 dir="$MH_TEST_DIR/Mail/inbox/$storagedir"
 mkdir "$dir"
-echo "nmh-storage: $storagedir" >> $MH
-cat > $expected <<EOF
+printf '%s\n' "nmh-storage: $storagedir" >>"$MH"
+cat >"$expected" <<'EOF'
 This is the fourth text/plain part.
 This test has multiple lines.
 EOF
 run_test 'mhstore 11 -part 4' \
 "storing message 11 part 4 as file $storagedir/11.4.txt"
-check $expected "$dir/11.4.txt" 'keep first'
+check "$expected" "$dir/11.4.txt" 'keep first'
 
 # check with relative nmh-storage profile component and -auto
 run_test 'mhstore 11 -part 4 -auto' \
 "storing message 11 part 4 as file $storagedir/test4.txt"
-check $expected "$dir/test4.txt" 'keep first'
+check "$expected" "$dir/test4.txt" 'keep first'
 
 # check with absolute nmh-storage profile component
 absstoragedir="$MH_TEST_DIR/$storagedir"
 mkdir "$absstoragedir"
-sed "s%\(nmh-storage: \)storagedir%\1$absstoragedir%" $MH > $MH.new
-mv -f $MH.new $MH
+sed "s%\(nmh-storage: \)storagedir%\1$absstoragedir%" "$MH" >"$MH.new"
+mv -f "$MH.new" "$MH"
 run_test 'mhstore 11 -part 4' \
 "storing message 11 part 4 as file $storagedir/11.4.txt"
-check $expected "$absstoragedir/11.4.txt" 'keep first'
+check "$expected" "$absstoragedir/11.4.txt" 'keep first'
 
 # check with absolute nmh-storage profile component and -auto
 run_test 'mhstore 11 -part 4 -auto' \
 "storing message 11 part 4 as file $storagedir/test4.txt"
-check $expected "$absstoragedir/test4.txt"
+check "$expected" "$absstoragedir/test4.txt"
 rmdir "$absstoragedir"
 
 
diff --git a/test/mkstemp/test-mkstemp b/test/mkstemp/test-mkstemp
new file mode 100755 (executable)
index 0000000..3894936
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+##################################
+#
+# Test creation of temporary file.
+#
+##################################
+
+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
+
+expected="$MH_TEST_DIR"/$$.expected
+actual="$MH_TEST_DIR"/$$.actual
+
+mkstemp="${MH_LIB_DIR}/mkstemp"
+
+cd "$MHTMPDIR"
+
+
+# check -help
+cat >$expected <<'EOF'
+Usage: mkstemp [switches]
+  switches are:
+  -directory
+  -prefix
+  -suffix
+  -version
+  -help
+EOF
+
+$mkstemp -h >$actual 2>&1
+check $expected $actual
+
+
+# check -version
+# Verified same behavior as compiled mhmail.
+case `$mkstemp -v` in
+  mkstemp\ --*) ;;
+  *          ) printf '%s: mkstemp -v generated unexpected output\n' "$0" >&2
+               failed=`expr ${failed:-0} + 1`;;
+esac
+
+
+# check with no switches
+tmpfile=`$mkstemp`
+if [ -f "$tmpfile" ]; then
+    rm "$tmpfile"
+else
+    failed=1
+fi
+
+
+# check -directory
+tmpfile=`$mkstemp -directory "$MHTMPDIR"`
+[ -f "$tmpfile" ]  &&  rm "$tmpfile"  ||  failed=`expr ${failed:-0} + 1`
+check_tmpfile=`echo $tmpfile | grep "^$MHTMPDIR/......$" >/dev/null`
+run_test `echo $check_tmpfile` ''
+
+
+# check -prefix
+tmpfile=`$mkstemp -prefix mkstemptest`
+[ -f "$tmpfile" ]  &&  rm "$tmpfile"  ||  failed=`expr ${failed:-0} + 1`
+check_tmpfile=`echo $tmpfile | grep '^mkstemptest......$' >/dev/null`
+run_test `echo $check_tmpfile` ''
+
+
+# check -suffix
+tmpfile=`$mkstemp -suffix .txt`
+[ -f "$tmpfile" ]  &&  rm "$tmpfile"  ||  failed=`expr ${failed:-0} + 1`
+check_tmpfile=`echo $tmpfile | grep '^......\.txt$' >/dev/null`
+run_test `echo $check_tmpfile` ''
+
+
+exit $failed
index b897037..ca56eb1 100755 (executable)
@@ -53,7 +53,7 @@ other+ has  4 in sequence unseen; out of  4
 other+ has  0 in sequence odd   ; out of  4'
 
 # Test Flist-Order with -noalpha.
-echo 'Flist-Order: o* i*' >> $MH_TEST_DIR/Mail/.mh_profile
+echo 'Flist-Order: o* i*' >> "$MH"
 run_test 'flists -seq unseen -seq odd -noalpha' \
          'other+ has  4 in sequence unseen; out of  4
 other+ has  0 in sequence odd   ; out of  4
index 0743337..f00fc97 100644 (file)
 #include <h/utils.h>
 #include <h/signals.h>
 #include <fcntl.h>
-#ifdef HAVE_ICONV
-#   include <iconv.h>
-#endif
 
 #define MHFIXMSG_SWITCHES \
     X("decodetext 8bit|7bit", 0, DECODETEXTSW) \
     X("nodecodetext", 0, NDECODETEXTSW) \
-    X("textcodeset", 0, TEXTCODESETSW) \
-    X("notextcodeset", 0, NTEXTCODESETSW) \
+    X("textcharset", 0, TEXTCHARSETSW) \
+    X("notextcharset", 0, NTEXTCHARSETSW) \
     X("reformat", 0, REFORMATSW) \
     X("noreformat", 0, NREFORMATSW) \
     X("replacetextplain", 0, REPLACETEXTPLAINSW) \
@@ -81,15 +78,14 @@ typedef struct fix_transformations {
     int reformat;
     int replacetextplain;
     int decodetext;
-    char *textcodeset;
+    char *textcharset;
 } fix_transformations;
 
 int mhfixmsgsbr (CT *, const fix_transformations *, char *);
 static void reverse_alternative_parts (CT);
 static int fix_boundary (CT *, int *);
 static int get_multipart_boundary (CT, char **);
-static int replace_boundary (CT, char *, const char *);
-static char *update_attr (char *, const char *, const char *e);
+static int replace_boundary (CT, char *, char *);
 static int fix_multipart_cte (CT, int *);
 static int set_ce (CT, int);
 static int ensure_text_plain (CT *, CT, int *, int);
@@ -104,15 +100,12 @@ 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 content_encoding (CT);
+static int content_encoding (CT, const char **);
 static int strip_crs (CT, int *);
-static int convert_codesets (CT, char *, int *);
-static int convert_codeset (CT, char *, int *);
-static char *content_codeset (CT);
+static int convert_charsets (CT, char *, int *);
 static int write_content (CT, char *, char *, int, int);
 static int remove_file (char *);
 static void report (char *, char *, char *, ...);
-static char *upcase (char *);
 static void pipeser (int);
 
 
@@ -132,7 +125,7 @@ main (int argc, char **argv) {
     fx.reformat = fx.fixcte = fx.fixboundary = 1;
     fx.replacetextplain = 0;
     fx.decodetext = CE_8BIT;
-    fx.textcodeset = NULL;
+    fx.textcharset = NULL;
 
     if (nmh_init(argv[0], 1)) { return 1; }
 
@@ -176,13 +169,13 @@ main (int argc, char **argv) {
             case NDECODETEXTSW:
                 fx.decodetext = 0;
                 continue;
-            case TEXTCODESETSW:
+            case TEXTCHARSETSW:
                 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
                     adios (NULL, "missing argument to %s", argp[-2]);
-                fx.textcodeset = cp;
+                fx.textcharset = cp;
                 continue;
-            case NTEXTCODESETSW:
-                fx.textcodeset = 0;
+            case NTEXTCHARSETSW:
+                fx.textcharset = 0;
                 continue;
             case FIXBOUNDARYSW:
                 fx.fixboundary = 1;
@@ -415,8 +408,8 @@ mhfixmsgsbr (CT *ctp, const fix_transformations *fx, char *outfile) {
     if (status == OK  &&  fx->decodetext) {
         status = decode_text_parts (*ctp, fx->decodetext, &message_mods);
     }
-    if (status == OK  &&  fx->textcodeset != NULL) {
-        status = convert_codesets (*ctp, fx->textcodeset, &message_mods);
+    if (status == OK  &&  fx->textcharset != NULL) {
+        status = convert_charsets (*ctp, fx->textcharset, &message_mods);
     }
 
     if (! (*ctp)->c_umask) {
@@ -629,7 +622,7 @@ get_multipart_boundary (CT ct, char **part_boundary) {
 
 /* Open and copy ct->c_file to file, replacing the multipart boundary. */
 static int
-replace_boundary (CT ct, char *file, const char *boundary) {
+replace_boundary (CT ct, char *file, char *boundary) {
     FILE *fpin, *fpout;
     int compnum, state;
     char buf[BUFSIZ], name[NAMESZ];
@@ -675,10 +668,22 @@ replace_boundary (CT ct, char *file, const char *boundary) {
             if (strcasecmp (TYPE_FIELD, np)) {
                 fprintf (fpout, "%s:%s", np, vp);
             } else {
-                char *new_boundary = update_attr (vp, "boundary=", boundary);
-
-                fprintf (fpout, "%s:%s\n", np, new_boundary);
-                free (new_boundary);
+               char *new_ctline, *new_params;
+
+               replace_param(&ct->c_ctinfo.ci_first_pm,
+                             &ct->c_ctinfo.ci_last_pm, "boundary",
+                             boundary, 0);
+
+               new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/",
+                                   ct->c_ctinfo.ci_subtype, NULL);
+               new_params = output_params(strlen(TYPE_FIELD) +
+                                          strlen(new_ctline) + 1,
+                                          ct->c_ctinfo.ci_first_pm, NULL, 0);
+                fprintf (fpout, "%s:%s%s\n", np, new_ctline,
+                        new_params ? new_params : "");
+               free(new_ctline);
+               if (new_params)
+                   free(new_params);
             }
 
             free (vp);
@@ -718,44 +723,6 @@ replace_boundary (CT ct, char *file, const char *boundary) {
 }
 
 
-/* Change the value of a name=value pair in a header field body.
-   If the name isn't there, append them.  In any case, a new
-   string will be allocated and must be free'd by the caller.
-   Trims any trailing newlines. */
-static char *
-update_attr (char *body, const char *name, const char *value) {
-    char *bp = nmh_strcasestr (body, name);
-    char *new_body;
-
-    if (bp) {
-        char *other_attrs = strchr (bp, ';');
-
-        *(bp + strlen (name)) = '\0';
-        new_body = concat (body, "\"", value, "\"", NULL);
-
-        if (other_attrs) {
-            char *cp;
-
-            /* Trim any trailing newlines. */
-            for (cp = &other_attrs[strlen (other_attrs) - 1];
-                 cp > other_attrs  &&  *cp == '\n';
-                 *cp-- = '\0') continue;
-            new_body = add (other_attrs, new_body);
-        }
-    } else {
-        char *cp;
-
-        /* Append name/value pair, after first removing a final newline
-           and (extraneous) semicolon. */
-        if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0';
-        if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0';
-        new_body = concat (body, "; ", name, "\"", value, "\"", NULL);
-    }
-
-    return new_body;
-}
-
-
 static int
 fix_multipart_cte (CT ct, int *message_mods) {
     int status = OK;
@@ -1196,11 +1163,11 @@ reformat_part (CT ct, char *file, char *type, char *subtype, int c_type) {
 static int
 charset_encoding (CT ct) {
     /* norm_charmap() is case sensitive. */
-    char *codeset = upcase (content_codeset (ct));
+    char *charset = upcase (content_charset (ct));
     int encoding =
-        strcmp (norm_charmap (codeset), "US-ASCII")  ?  CE_8BIT  :  CE_7BIT;
+        strcmp (norm_charmap (charset), "US-ASCII")  ?  CE_8BIT  :  CE_7BIT;
 
-    free (codeset);
+    free (charset);
     return encoding;
 }
 
@@ -1468,16 +1435,19 @@ decode_text_parts (CT ct, int encoding, int *message_mods) {
             int ct_encoding;
 
             if (decode_part (ct) == OK  &&  ct->c_cefile.ce_file) {
-                if ((ct_encoding = content_encoding (ct)) == CE_BINARY  &&
-                    encoding != CE_BINARY) {
+                const char *reason = NULL;
+
+                if ((ct_encoding = content_encoding (ct, &reason)) == CE_BINARY
+                    &&  encoding != CE_BINARY) {
                     /* The decoding isn't acceptable so discard it.
                        Leave status as OK to allow other transformations. */
                     if (verbosw) {
                         report (ct->c_partno, ct->c_file,
-                                "will not decode%s because it is binary",
+                                "will not decode%s because it is binary (%s)",
                                 ct->c_partno  ?  ""
                                               :  ct->c_ctline  ?  ct->c_ctline
-                                                               :  "");
+                                                               :  "",
+                                reason);
                     }
                     (void) m_unlink (ct->c_cefile.ce_file);
                     free (ct->c_cefile.ce_file);
@@ -1561,9 +1531,10 @@ decode_text_parts (CT ct, int encoding, int *message_mods) {
 
 /* See if the decoded content is 7bit, 8bit, or binary.  It's binary
    if it has any NUL characters, a CR not followed by a LF, or lines
-   greater than 998 characters in length. */
+   greater than 998 characters in length.  If binary, reason is set
+   to a string explaining why. */
 static int
-content_encoding (CT ct) {
+content_encoding (CT ct, const char **reason) {
     CE ce = &ct->c_cefile;
     int encoding = CE_7BIT;
 
@@ -1588,6 +1559,16 @@ content_encoding (CT ct) {
                 if (*cp == '\0'  ||  ++line_len > 998  ||
                     (*cp != '\n'  &&  last_char_was_cr)) {
                     encoding = CE_BINARY;
+                    if (*cp == '\0') {
+                        *reason = "null character";
+                    } else if (line_len > 998) {
+                        *reason = "line length > 998";
+                    } else if (*cp != '\n'  &&  last_char_was_cr) {
+                        *reason = "CR not followed by LF";
+                    } else {
+                        /* Should not reach this. */
+                        *reason = "";
+                    }
                     break;
                 } else if (*cp == '\n') {
                     line_len = 0;
@@ -1610,18 +1591,18 @@ content_encoding (CT ct) {
 static int
 strip_crs (CT ct, int *message_mods) {
     /* norm_charmap() is case sensitive. */
-    char *codeset = upcase (content_codeset (ct));
+    char *charset = upcase (content_charset (ct));
     int status = OK;
 
     /* Only strip carriage returns if content is ASCII or another
-       codeset that has the same readily recognizable CR followed by a
+       charset that has the same readily recognizable CR followed by a
        LF.  We can include UTF-8 here because if the high-order bit of
        a UTF-8 byte is 0, then it must be a single-byte ASCII
        character. */
-    if (! strcmp (norm_charmap (codeset), "US-ASCII")  ||
-        ! strncmp (norm_charmap (codeset), "ISO-8859-", 9)  ||
-        ! strncmp (norm_charmap (codeset), "UTF-8", 5)  ||
-        ! strncmp (norm_charmap (codeset), "WINDOWS-12", 10)) {
+    if (! strcmp (norm_charmap (charset), "US-ASCII")  ||
+        ! strncmp (norm_charmap (charset), "ISO-8859-", 9)  ||
+        ! strncmp (norm_charmap (charset), "UTF-8", 5)  ||
+        ! strncmp (norm_charmap (charset), "WINDOWS-12", 10)) {
         char **file = NULL;
         FILE **fp = NULL;
         size_t begin;
@@ -1683,7 +1664,7 @@ strip_crs (CT ct, int *message_mods) {
             if (has_crs) {
                 int fd;
                 char *stripped_content_file;
-                char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL); 
+                char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL);
 
                 if (tempfile == NULL) {
                     adios (NULL, "unable to create temporary file in %s",
@@ -1745,45 +1726,23 @@ strip_crs (CT ct, int *message_mods) {
         }
     }
 
-    free (codeset);
+    free (charset);
     return status;
 }
 
 
-char *
-content_codeset (CT ct) {
-    const char *const charset = "charset";
-    char *default_codeset = NULL;
-    CI ctinfo = &ct->c_ctinfo;
-    char **src_codeset = NULL;
-    PM pm;
-
-    for (pm = ctinfo->ci_first_pm; pm; pm = pm->pm_next) {
-        if (! strcasecmp (pm->pm_name, charset)) {
-            src_codeset = &pm->pm_value;
-            break;
-        }
-    }
-
-    /* RFC 2045, Sec. 5.2:  default to us-ascii. */
-    if (src_codeset == NULL) {
-       src_codeset = &default_codeset;
-       if (*src_codeset == NULL)
-           *src_codeset = "US-ASCII";
-    }
-
-    return *src_codeset;
-}
-
-
 static int
-convert_codesets (CT ct, char *dest_codeset, int *message_mods) {
+convert_charsets (CT ct, char *dest_charset, int *message_mods) {
     int status = OK;
 
     switch (ct->c_type) {
     case CT_TEXT:
         if (ct->c_subtype == TEXT_PLAIN) {
-            status = convert_codeset (ct, dest_codeset, message_mods);
+            status = convert_charset (ct, dest_charset, message_mods);
+            if (verbosw  &&  status == OK) {
+               report (ct->c_partno, ct->c_file, "convert %s to %s",
+                       content_charset(ct), dest_charset);
+            }
         }
         break;
 
@@ -1795,7 +1754,7 @@ convert_codesets (CT ct, char *dest_codeset, int *message_mods) {
            For now, it gets passed along as-is by InitMultiPart(). */
         for (part = m->mp_parts; status == OK  &&  part; part = part->mp_next) {
             status =
-                convert_codesets (part->mp_part, dest_codeset, message_mods);
+                convert_charsets (part->mp_part, dest_charset, message_mods);
         }
         break;
     }
@@ -1806,7 +1765,7 @@ convert_codesets (CT ct, char *dest_codeset, int *message_mods) {
 
             e = (struct exbody *) ct->c_ctparams;
             status =
-                convert_codesets (e->eb_content, dest_codeset, message_mods);
+                convert_charsets (e->eb_content, dest_charset, message_mods);
         }
         break;
 
@@ -1818,161 +1777,6 @@ convert_codesets (CT ct, char *dest_codeset, int *message_mods) {
 }
 
 
-static int
-convert_codeset (CT ct, char *dest_codeset, int *message_mods) {
-    char *src_codeset = content_codeset (ct);
-    int status = OK;
-
-    /* norm_charmap() is case sensitive. */
-    char *src_codeset_u = upcase (src_codeset);
-    char *dest_codeset_u = upcase (dest_codeset);
-    int different_codesets =
-        strcmp (norm_charmap (src_codeset), norm_charmap (dest_codeset));
-
-    free (dest_codeset_u);
-    free (src_codeset_u);
-
-    if (different_codesets) {
-#ifdef HAVE_ICONV
-        iconv_t conv_desc = NULL;
-        char *dest;
-        int fd = -1;
-        char **file = NULL;
-        FILE **fp = NULL;
-        size_t begin;
-        size_t end;
-        int opened_input_file = 0;
-        char src_buffer[BUFSIZ];
-        HF hf;
-        char *tempfile;
-
-        if ((conv_desc = iconv_open (dest_codeset, src_codeset)) ==
-            (iconv_t) -1) {
-            advise (NULL, "Can't convert %s to %s", src_codeset, dest_codeset);
-            return -1;
-        }
-
-        if ((tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) {
-            adios (NULL, "unable to create temporary file in %s",
-                   get_temp_dir());
-        }
-        dest = add (tempfile, NULL);
-
-        if (ct->c_cefile.ce_file) {
-            file = &ct->c_cefile.ce_file;
-            fp = &ct->c_cefile.ce_fp;
-            begin = end = 0;
-        } else if (ct->c_file) {
-            file = &ct->c_file;
-            fp = &ct->c_fp;
-            begin = (size_t) ct->c_begin;
-            end = (size_t) ct->c_end;
-        } /* else no input file: shouldn't happen */
-
-        if (file  &&  *file  &&  fp) {
-            if (! *fp) {
-                if ((*fp = fopen (*file, "r")) == NULL) {
-                    advise (*file, "unable to open for reading");
-                    status = NOTOK;
-                } else {
-                    opened_input_file = 1;
-                }
-            }
-        }
-
-        if (fp  &&  *fp) {
-            size_t inbytes;
-            size_t bytes_to_read =
-                end > 0 && end > begin  ?  end - begin  :  sizeof src_buffer;
-
-            fseeko (*fp, begin, SEEK_SET);
-            while ((inbytes = fread (src_buffer, 1,
-                                     min (bytes_to_read, sizeof src_buffer),
-                                     *fp)) > 0) {
-                char dest_buffer[BUFSIZ];
-                ICONV_CONST char *ib = src_buffer;
-                char *ob = dest_buffer;
-                size_t outbytes = sizeof dest_buffer;
-                size_t outbytes_before = outbytes;
-
-                if (end > 0) bytes_to_read -= inbytes;
-
-                if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) ==
-                    (size_t) -1) {
-                    status = NOTOK;
-                    break;
-                } else {
-                    write (fd, dest_buffer, outbytes_before - outbytes);
-                }
-            }
-
-            if (opened_input_file) {
-                fclose (*fp);
-                *fp = NULL;
-            }
-        }
-
-        iconv_close (conv_desc);
-        close (fd);
-
-        if (status == OK) {
-            /* Replace the decoded file with the converted one. */
-            if (ct->c_cefile.ce_file) {
-                if (ct->c_cefile.ce_unlink) {
-                    (void) m_unlink (ct->c_cefile.ce_file);
-                }
-                free (ct->c_cefile.ce_file);
-            }
-            ct->c_cefile.ce_file = dest;
-            ct->c_cefile.ce_unlink = 1;
-
-            ++*message_mods;
-            if (verbosw) {
-                report (ct->c_partno, ct->c_file, "convert %s to %s",
-                        src_codeset, dest_codeset);
-            }
-
-            /* Update ci_attrs. */
-            src_codeset = dest_codeset;
-
-            /* Update ct->c_ctline. */
-            if (ct->c_ctline) {
-                char *ctline =
-                    update_attr (ct->c_ctline, "charset=", dest_codeset);
-
-                free (ct->c_ctline);
-                ct->c_ctline = ctline;
-            } /* else no CT line, which is odd */
-
-            /* Update Content-Type header field. */
-            for (hf = ct->c_first_hf; hf; hf = hf->next) {
-                if (! strcasecmp (TYPE_FIELD, hf->name)) {
-                    char *ctline_less_newline =
-                        update_attr (hf->value, "charset=", dest_codeset);
-                    char *ctline = concat (ctline_less_newline, "\n", NULL);
-                    free (ctline_less_newline);
-
-                    free (hf->value);
-                    hf->value = ctline;
-                    break;
-                }
-            }
-        } else {
-            (void) m_unlink (dest);
-        }
-#else  /* ! HAVE_ICONV */
-        NMH_UNUSED (message_mods);
-
-        advise (NULL, "Can't convert %s to %s without iconv", src_codeset,
-                dest_codeset);
-        status = NOTOK;
-#endif /* ! HAVE_ICONV */
-    }
-
-    return status;
-}
-
-
 static int
 write_content (CT ct, char *input_filename, char *outfile, int modify_inplace,
                int message_mods) {
@@ -2086,17 +1890,6 @@ report (char *partno, char *filename, char *message, ...) {
 }
 
 
-static char *
-upcase (char *str) {
-    char *up = cpytrim (str);
-    char *cp;
-
-    for (cp = up; *cp; ++cp) *cp = toupper ((unsigned char) *cp);
-
-    return up;
-}
-
-
 static void
 pipeser (int i)
 {
index a0baf2e..b9dc0dd 100755 (executable)
@@ -48,8 +48,16 @@ finddir() {
   esac
 }
 
+#### Convenience function to allow concise code below.
+die() {
+    [ $# -gt 0 ]  &&  printf '%s\n' "$1" >&2
+    exit 1
+}
+
+
 bindir=`finddir $0`
 nmhbindir=`cd "$bindir" && pwd`
+nmhlibdir=`$nmhbindir/mhparam libdir`
 case `printf 'OK\n' | tail -n 1 2>&1` in
   OK) tail='tail -n ' ;;
   *)  tail='tail -' ;;
@@ -59,19 +67,19 @@ esac
 #### Checks for missing mandatory arguments.
 checkforargs() {
   if [ $attacharg -eq 1 ]; then
-    printf 'mhmail: missing argument to -attach\n' >&2; exit 1
+    die 'mhmail: missing argument to -attach'
   elif [ $bodyarg -eq 1 ]; then
-    printf 'mhmail: missing argument to -body\n' >&2; exit 1
+    die 'mhmail: missing argument to -body'
   elif [ $ccarg -eq 1  -a  "$cclist"x = x ]; then
-    printf 'mhmail: missing argument to -cc\n' >&2; exit 1
+    die 'mhmail: missing argument to -cc'
   elif [ $fromarg -eq 1 ]; then
-    printf 'mhmail: missing argument to -from\n' >&2; exit 1
+    die 'mhmail: missing argument to -from'
   elif [ $headerfieldarg -eq 1 ]; then
-    printf 'mhmail: missing argument to -headerfield\n' >&2; exit 1
+    die 'mhmail: missing argument to -headerfield'
   elif [ $subjectarg -eq 1 ]; then
-    printf 'mhmail: missing argument to -subject\n' >&2; exit 1
+    die 'mhmail: missing argument to -subject'
   elif [ $toarg -eq 1 ]; then
-    printf 'mhmail: missing argument to -to\n' >&2; exit 1
+    die 'mhmail: missing argument to -to'
   fi
 }
 
@@ -122,8 +130,7 @@ for arg in "$@"; do
     -hel|-help) printf '%s\n' "$usage"; exit ;;
     -nose|-nosen|-nosend) mhmailswitch=1; sendsw=0 ;;
     -p|-pr|-pro|-prof|-profi|-profil|-profile) mhmailswitch=1; use_send=1 ;;
-    -resend) printf 'mhmail: did you mean -resent instead of -resend?\n' >&2
-       exit 1 ;;
+    -resend) die 'mhmail: did you mean -resent instead of -resend?' ;;
     -r|-re|-res|-rese|-resen|-resent) mhmailswitch=1; resent=1 ;;
     -se|-sen|-send) mhmailswitch=1; sendsw=1 ;;
     -su|-sub|-subj|-subje|-subjec|-subject) mhmailswitch=1; subjectarg=1 ;;
@@ -179,11 +186,10 @@ done
 
 #### Check for at least one address and -from.
 if [ "$tolist"x = x ]; then
-  printf 'Usage: mhmail [-t(o)] addrs ... [switches]\n' >&2; exit 1
+  die 'Usage: mhmail [-t(o)] addrs ... [switches]'
 fi
 if [ "$from"x = x ]; then
-  nmhlibdir=`$nmhbindir/mhparam libdir`/
-  from=`${nmhlibdir}ap -format '%(localmbox)' 0`
+  from=`${nmhlibdir}/ap -format '%(localmbox)' 0`
 fi
 
 #### Check for missing mandatory arguments.
@@ -204,13 +210,16 @@ if [ "$headerfieldlist" ]; then
   header="$header$headerfieldlist"
 fi
 
-#### Set up a file to supply as a draft to post/send.  And set a
-#### trap to remove it.  send moves the file to a backup, so it will
-#### remove that, too.
+#### Set up a file to supply as a draft to post/send.  And set a trap
+#### to remove it.  send moves the file to a backup and can create a
+#### .orig file, so it will remove them, too.
 umask 077
-tmpdir="${MHTMPDIR:-${TMPDIR:-${TMP:-`$nmhbindir/mhpath +`}}}"
-tmpfile="$tmpdir/mhmail$$"
-tmpfilebackup="'$tmpdir'/,mhmail$$ '$tmpdir'/#mhmail$$"
+tmpdir="${MHTMPDIR:-${TMPDIR:-`$nmhbindir/mhpath +`}}"
+tmpfilename=`cd "$tmpdir"  &&  "${nmhlibdir}/mkstemp" -p mhmail`
+[ $? -ne 0 ]  &&  die "mhmail: failed to create temporary file in $tmpdir"
+tmpfile="$tmpdir/$tmpfilename"
+backup_char=`"$nmhbindir"/mhparam sbackup`
+tmpfilebackup="$tmpdir/${backup_char}${tmpfilename}*"
 tmpfileresent=
 
 message_file=
@@ -227,7 +236,8 @@ else
     #### When resending with send, tmpfile will just contain the
     #### Resent- header fields.  "$tmpfileresent" will contain
     #### the message that is being resent.
-    tmpfileresent="$tmpdir/mhmail-resent$$"
+    tmpfileresent=`"${nmhlibdir}/mkstemp" -d "$tmpdir" -p mhmail-resent`
+    [ $? -ne 0 ]  &&  die "mhmail: failed to create temporary file in $tmpdir"
     mhdist=1; export mhdist
     mhaltmsg=$tmpfileresent; export mhaltmsg
     message_file="$tmpfileresent"
@@ -250,8 +260,7 @@ if [ "$body"x = x ]; then
   #### Don't allow an empty body (from stdin).  Use string
   #### comparison so we don't have to strip the filename, etc.
   if [ "$tmpfile_size_before" = "$tmpfile_size_after" ]; then
-    printf 'mhmail: empty message not sent, use -body '"''"' to force.\n' >&2
-    exit 1
+    die 'mhmail: empty message not sent, use -body '"''"' to force.'
   fi
 
   #### Add trailing newline to body if it doesn't have one.
index 220a73d..c0b7e78 100644 (file)
--- a/uip/mhn.c
+++ b/uip/mhn.c
@@ -91,10 +91,6 @@ extern int nolist;
 extern int nomore;     /* flags for moreproc/header display */
 extern char *formsw;
 
-/* mhstoresbr.c */
-extern int autosw;
-extern char *cwd;      /* cache current working directory */
-
 /* mhmisc.c */
 extern int npart;
 extern int ntype;
@@ -133,7 +129,10 @@ void flush_errors (void);
 void show_all_messages (CT *);
 
 /* mhstoresbr.c */
-void store_all_messages (CT *);
+typedef struct mhstoreinfo *mhstoreinfo_t;
+mhstoreinfo_t mhstoreinfo_create(CT *, char *, const char *, int, int);
+void mhstoreinfo_free(mhstoreinfo_t);
+void store_all_messages (mhstoreinfo_t);
 
 /* mhcachesbr.c */
 void cache_all_messages (CT *);
@@ -151,15 +150,17 @@ static void pipeser (int);
 int
 main (int argc, char **argv)
 {
-    int sizesw = 1, headsw = 1;
+    int sizesw = 1, headsw = 1, autosw = 0;
     int msgnum, *icachesw;
     char *cp, *file = NULL, *folder = NULL;
     char *maildir, buf[100], **argp;
     char **arguments;
+    char *cwd;
     struct msgs_array msgs = { 0, 0, NULL };
     struct msgs *mp = NULL;
     CT ct, *ctp;
     FILE *fp;
+    mhstoreinfo_t info;
 
     if (nmh_init(argv[0], 1)) { return 1; }
 
@@ -399,7 +400,7 @@ do_cache:
     /*
      * Cache the current directory before we do any chdirs()'s.
      */
-    cwd = getcpy (pwd());
+    cwd = add(pwd(), NULL);
 
     if (!context_find ("path"))
        free (path ("./", TFOLDER));
@@ -558,8 +559,11 @@ do_cache:
     /*
      * Store the message content
      */
-    if (storesw)
-       store_all_messages (cts);
+    if (storesw) {
+       info = mhstoreinfo_create (cts, cwd, "always", autosw, verbosw);;
+       store_all_messages (info);
+       mhstoreinfo_free (info);
+    }
 
     /*
      * Cache the message content
@@ -577,7 +581,7 @@ do_cache:
     for (ctp = cts; *ctp; ctp++)
        free_content (*ctp);
 
-    free ((char *) cts);
+    free (cts);
     cts = NULL;
 
     /* If reading from a folder, do some updating */
index 8a276f7..325a1a3 100644 (file)
@@ -33,19 +33,9 @@ extern char *mhetcdir;
 
 char *sbackup = BACKUP_PREFIX;
 
-char *lockmethod =
-#if defined FCNTL_LOCKING
-    "fcntl"
-#elif defined FLOCK_LOCKING
-    "flock"
-#elif defined LOCKF_LOCKING
-    "lockf"
-#elif defined DOT_LOCKING
-    "dot"
-#else
-    "none"
-#endif
-    ;
+char *datalocking = "fcntl";
+
+extern char *spoollocking;
 
 char *sasl =
 #ifdef CYRUS_SASL
@@ -109,7 +99,8 @@ static struct proc procs [] = {
      { "etcdir",           &mhetcdir },
      { "libdir",           &mhlibdir },
      { "sbackup",          &sbackup },
-     { "lockmethod",       &lockmethod },
+     { "datalocking",      &datalocking },
+     { "spoollocking",     &spoollocking },
      { "sasl",             &sasl },
      { "tls",              &tls },
      { NULL,               NULL },
@@ -191,6 +182,9 @@ main(int argc, char **argv)
     } else if (debug) {
        struct proc *ps;
 
+       /* Need to see if datalocking was set in profile. */
+       if ((cp = context_find("datalocking"))) { datalocking = cp; }
+
        /*
         * Print the current value of everything in
         * procs array.  This will show their current
@@ -218,7 +212,7 @@ main(int argc, char **argv)
                missed++;
        }
     }
-    
+
     done (missed);
     return 1;
 }
index 943e423..ec7db7a 100644 (file)
@@ -3647,6 +3647,25 @@ bad_quote:
     return OK;
 }
 
+/*
+ * Return the charset for a particular content type.  Return pointer is
+ * only valid until the next call to content_charset().
+ */
+
+char *
+content_charset (CT ct) {
+    static char *ret_charset = NULL;
+
+    if (ret_charset != NULL) {
+       free(ret_charset);
+    }
+
+    ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
+
+    return ret_charset ? ret_charset : "US-ASCII";
+}
+
+
 /*
  * Create a string based on a list of output parameters.  Assume that this
  * parameter string will be appended to an existing header, so start out
@@ -4074,6 +4093,33 @@ add_param(PM *first, PM *last, char *name, char *value, int nocopy)
     return pm;
 }
 
+/*
+ * Either replace a current parameter with a new value, or add the parameter
+ * to the parameter linked list.
+ */
+
+PM
+replace_param(PM *first, PM *last, char *name, char *value, int nocopy)
+{
+    PM pm;
+
+    for (pm = *first; pm != NULL; pm = pm->pm_next) {
+       if (strcasecmp(name, pm->pm_name) == 0) {
+           /*
+            * If nocopy is set, it's assumed that we own both name
+            * and value.  We don't need name, so we discard it now.
+            */
+           if (nocopy)
+               free(name);
+           free(pm->pm_value);
+           pm->pm_value = nocopy ? value : getcpy(value);
+           return pm;
+       }
+    }
+
+    return add_param(first, last, name, value, nocopy);
+}
+
 /*
  * Retrieve a parameter value from a parameter linked list.  If the parameter
  * value needs converted to the local character set, do that now.
index bb0a646..295f43e 100644 (file)
@@ -17,6 +17,9 @@
 #include <h/mime.h>
 #include <h/mhparse.h>
 #include <h/utils.h>
+#ifdef HAVE_ICONV
+#   include <iconv.h>
+#endif /* ! HAVE_ICONV */
 
 extern int debugsw;
 
@@ -54,7 +57,8 @@ static void show_single_message (CT, char *);
 static void DisplayMsgHeader (CT, char *);
 static int show_switch (CT, int, int);
 static int show_content (CT, int, int);
-static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int, int);
+static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int,
+                              int);
 static int show_text (CT, int, int);
 static int show_multi (CT, int, int);
 static int show_multi_internal (CT, int, int);
@@ -62,6 +66,9 @@ static int show_multi_aux (CT, int, int, char *);
 static int show_message_rfc822 (CT, int, int);
 static int show_partial (CT, int, int);
 static int show_external (CT, int, int);
+static int parse_display_string (CT, char *, int *, int *, int *, int *, char *,
+                                 char *, size_t, int multipart);
+static int convert_content_charset (CT, char **);
 static void intrser (int);
 
 
@@ -260,13 +267,13 @@ show_content (CT ct, int serial, int alternate)
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
 
-    /* Check for mhn-show-type/subtype */
+    /* Check for invo_name-show-type/subtype */
     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
                invo_name, ci->ci_type, ci->ci_subtype);
     if ((cp = context_find (buffer)) && *cp != '\0')
        return show_content_aux (ct, serial, alternate, cp, NULL);
 
-    /* Check for mhn-show-type */
+    /* Check for invo_name-show-type */
     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
     if ((cp = context_find (buffer)) && *cp != '\0')
        return show_content_aux (ct, serial, alternate, cp, NULL);
@@ -289,10 +296,9 @@ show_content (CT ct, int serial, int alternate)
 int
 show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked)
 {
-    int fd, len, buflen, quoted;
-    int        xstdin, xlist, xpause, xtty;
-    char *bp, *pp, *file, buffer[BUFSIZ];
-    CI ci = &ct->c_ctinfo;
+    int fd;
+    int xstdin = 0, xlist = 0, xpause = 0, xtty = 0;
+    char *file, buffer[BUFSIZ];
 
     if (!ct->c_ceopenfnx) {
        if (!alternate)
@@ -306,169 +312,32 @@ show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked)
        return NOTOK;
     if (ct->c_showproc && !strcmp (ct->c_showproc, "true"))
        return (alternate ? DONE : OK);
-    
-    xlist  = 0;
-    xpause = 0;
-    xstdin = 0;
-    xtty   = 0;
+
+    if (! strcmp(invo_name, "mhshow")  &&
+        ct->c_type == CT_TEXT  &&  ct->c_subtype == TEXT_PLAIN) {
+        /* This has to be done after calling c_ceopenfnx, so
+           unfortunately the type checks are necessary without
+           some code rearrangement.  And to make this really ugly,
+           only do it in mhshow, not mhfixmsg, mhn, or mhstore. */
+        if (convert_content_charset (ct, &file) != OK) {
+            admonish (NULL, "unable to convert character set%s to %s",
+                      ct->c_partno  ?  "of part "  :  "",
+                      ct->c_partno  ?  ct->c_partno  :  "",
+                      content_charset (ct));
+        }
+    }
 
     if (cracked) {
        strncpy (buffer, cp, sizeof(buffer));
        goto got_command;
     }
 
-    /* get buffer ready to go */
-    bp = buffer;
-    buflen = sizeof(buffer) - 1;
-    bp[0] = bp[buflen] = '\0';
-    quoted = 0;
-
-    /* Now parse display string */
-    for ( ; *cp && buflen > 0; cp++) {
-       if (*cp == '%') {
-           pp = bp;
-
-           switch (*++cp) {
-           case 'a':
-               /* insert parameters from Content-Type field */
-           {
-               PM pm;
-               char *s = "";
-
-               for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
-                   snprintf (bp, buflen, "%s%s=\"%s\"", s, pm->pm_name,
-                             get_param_value(pm, '?'));
-                   len = strlen (bp);
-                   bp += len;
-                   buflen -= len;
-                   s = " ";
-               }
-           }
-           break;
-
-           case 'd':
-               /* insert content description */
-               if (ct->c_descr) {
-                   char *s;
-
-                   s = trimcpy (ct->c_descr);
-                   strncpy (bp, s, buflen);
-                   free (s);
-               }
-               break;
-
-           case 'e':
-               /* exclusive execution */
-               xtty = 1;
-               break;
-
-           case 'F':
-               /* %e, %f, and stdin is terminal not content */
-               xstdin = 1;
-               xtty = 1;
-               /* and fall... */
-
-           case 'f':
-               /* insert filename containing content */
-               snprintf (bp, buflen, "'%s'", file);
-               /* since we've quoted the file argument, set things up
-                * to look past it, to avoid problems with the quoting
-                * logic below.  (I know, I should figure out what's
-                * broken with the quoting logic, but..)
-                */
-               len = strlen(bp);
-               buflen -= len;
-               bp += len;
-               pp = bp;
-               break;
-
-           case 'p':
-               /* %l, and pause prior to displaying content */
-               xpause = pausesw;
-               /* and fall... */
-
-           case 'l':
-               /* display listing prior to displaying content */
-               xlist = !nolist;
-               break;
-
-           case 's':
-               /* insert subtype of content */
-               strncpy (bp, ci->ci_subtype, buflen);
-               break;
-
-           case '%':
-               /* insert character % */
-               goto raw;
-
-           default:
-               *bp++ = *--cp;
-               *bp = '\0';
-               buflen--;
-               continue;
-           }
-           len = strlen (bp);
-           bp += len;
-           buflen -= len;
-
-           /* Did we actually insert something? */
-           if (bp != pp) {
-               /* Insert single quote if not inside quotes already */
-               if (!quoted && buflen) {
-                   len = strlen (pp);
-                   memmove (pp + 1, pp, len);
-                   *pp++ = '\'';
-                   buflen--;
-                   bp++;
-               }
-               /* Escape existing quotes */
-               while ((pp = strchr (pp, '\'')) && buflen > 3) {
-                   len = strlen (pp++);
-                   memmove (pp + 3, pp, len);
-                   *pp++ = '\\';
-                   *pp++ = '\'';
-                   *pp++ = '\'';
-                   buflen -= 3;
-                   bp += 3;
-               }
-               /* If pp is still set, that means we ran out of space. */
-               if (pp)
-                   buflen = 0;
-               if (!quoted && buflen) {
-                   *bp++ = '\'';
-                   *bp = '\0';
-                   buflen--;
-               }
-           }
-       } else {
-raw:
-           *bp++ = *cp;
-           *bp = '\0';
-           buflen--;
-
-           if (*cp == '\'')
-               quoted = !quoted;
-       }
-    }
-
-    if (buflen <= 0 ||
-        (ct->c_termproc && (size_t) buflen <= strlen(ct->c_termproc))) {
-       /* content_error would provide a more useful error message
-        * here, except that if we got overrun, it probably would
-        * too.
-        */
-       fprintf(stderr, "Buffer overflow constructing show command!\n");
+    if (parse_display_string (ct, cp, &xstdin, &xlist, &xpause, &xtty, file,
+                             buffer, sizeof(buffer) - 1, 0)) {
+       admonish (NULL, "Buffer overflow constructing show command!\n");
        return NOTOK;
     }
 
-    /* use charset string to modify display method */
-    if (ct->c_termproc) {
-       char term[BUFSIZ];
-
-       strncpy (term, buffer, sizeof(term));
-       snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
-    }
-
 got_command:
     return show_content_aux2 (ct, serial, alternate, cracked, buffer,
                              fd, xlist, xpause, xstdin, xtty);
@@ -484,9 +353,9 @@ show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer
                    int fd, int xlist, int xpause, int xstdin, int xtty)
 {
     pid_t child_id;
-    int i;
-    char *vec[4], exec[BUFSIZ + sizeof "exec "];
-    
+    int i, vecp;
+    char **vec, *file;
+
     if (debugsw || cracked) {
        fflush (stdout);
 
@@ -537,12 +406,8 @@ show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer
        }
     }
 
-    snprintf (exec, sizeof(exec), "exec %s", buffer);
-
-    vec[0] = "/bin/sh";
-    vec[1] = "-c";
-    vec[2] = exec;
-    vec[3] = NULL;
+    vec = argsplit(buffer, &file, &vecp);
+    vec[vecp++] = NULL;
 
     fflush (stdout);
 
@@ -560,13 +425,15 @@ show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer
            if (!xstdin)
                dup2 (fd, 0);
            close (fd);
-           execvp ("/bin/sh", vec);
+           execvp (file, vec);
            fprintf (stderr, "unable to exec ");
            perror ("/bin/sh");
            _exit (-1);
            /* NOTREACHED */
 
        default:
+           arglist_free(file, vec);
+
            if (!serial) {
                ct->c_pid = child_id;
                if (xtty)
@@ -592,13 +459,13 @@ show_text (CT ct, int serial, int alternate)
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
 
-    /* Check for mhn-show-type/subtype */
+    /* Check for invo_name-show-type/subtype */
     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
                invo_name, ci->ci_type, ci->ci_subtype);
     if ((cp = context_find (buffer)) && *cp != '\0')
        return show_content_aux (ct, serial, alternate, cp, NULL);
 
-    /* Check for mhn-show-type */
+    /* Check for invo_name-show-type */
     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
     if ((cp = context_find (buffer)) && *cp != '\0')
        return show_content_aux (ct, serial, alternate, cp, NULL);
@@ -608,7 +475,7 @@ show_text (CT ct, int serial, int alternate)
      * if it is not a text part of a multipart/alternative
      */
     if (!alternate || ct->c_subtype == TEXT_PLAIN) {
-       snprintf (buffer, sizeof(buffer), "%%p%s '%%F'", progsw ? progsw :
+       snprintf (buffer, sizeof(buffer), "%%p%s %%F", progsw ? progsw :
                moreproc && *moreproc ? moreproc : DEFAULT_PAGER);
        cp = (ct->c_showproc = add (buffer, NULL));
        return show_content_aux (ct, serial, alternate, cp, NULL);
@@ -628,13 +495,13 @@ show_multi (CT ct, int serial, int alternate)
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
 
-    /* Check for mhn-show-type/subtype */
+    /* Check for invo_name-show-type/subtype */
     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
                invo_name, ci->ci_type, ci->ci_subtype);
     if ((cp = context_find (buffer)) && *cp != '\0')
        return show_multi_aux (ct, serial, alternate, cp);
 
-    /* Check for mhn-show-type */
+    /* Check for invo_name-show-type */
     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
     if ((cp = context_find (buffer)) && *cp != '\0')
        return show_multi_aux (ct, serial, alternate, cp);
@@ -695,7 +562,7 @@ show_multi_internal (CT ct, int serial, int alternate)
 
 /*
  * alternate   -> we are a part inside an multipart/alternative
- * alternating -> we are a multipart/alternative 
+ * alternating -> we are a multipart/alternative
  */
 
     result = alternate ? NOTOK : OK;
@@ -791,12 +658,12 @@ out:
 static int
 show_multi_aux (CT ct, int serial, int alternate, char *cp)
 {
-    int len, buflen, quoted;
-    int xlist, xpause, xtty;
-    char *bp, *pp, *file, buffer[BUFSIZ];
+    /* xstdin is only used in the call to parse_display_string():
+       its value is ignored in the function. */
+    int xstdin = 0, xlist = 0, xpause = 0, xtty = 0;
+    char *file, buffer[BUFSIZ];
     struct multipart *m = (struct multipart *) ct->c_ctparams;
     struct part *part;
-    CI ci = &ct->c_ctinfo;
     CT p;
 
     for (part = m->mp_parts; part; part = part->mp_next) {
@@ -813,7 +680,6 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
            if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
                return NOTOK;
 
-           /* I'm not sure if this is necessary? */
            p->c_storage = add (file, NULL);
 
            if (p->c_showproc && !strcmp (p->c_showproc, "true"))
@@ -822,20 +688,104 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
        }
     }
 
-    xlist     = 0;
-    xpause    = 0;
-    xtty      = 0;
+    if (parse_display_string (ct, cp, &xstdin, &xlist, &xpause, &xtty, file,
+                             buffer, sizeof(buffer) - 1, 1)) {
+       admonish (NULL, "Buffer overflow constructing show command!\n");
+       return NOTOK;
+    }
+
+    return show_content_aux2 (ct, serial, alternate, NULL, buffer,
+                             NOTOK, xlist, xpause, 0, xtty);
+}
+
+
+/*
+ * show content of type "message/rfc822"
+ */
+
+static int
+show_message_rfc822 (CT ct, int serial, int alternate)
+{
+    char *cp, buffer[BUFSIZ];
+    CI ci = &ct->c_ctinfo;
+
+    /* Check for invo_name-show-type/subtype */
+    snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
+               invo_name, ci->ci_type, ci->ci_subtype);
+    if ((cp = context_find (buffer)) && *cp != '\0')
+       return show_content_aux (ct, serial, alternate, cp, NULL);
+
+    /* Check for invo_name-show-type */
+    snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
+    if ((cp = context_find (buffer)) && *cp != '\0')
+       return show_content_aux (ct, serial, alternate, cp, NULL);
+
+    if ((cp = ct->c_showproc))
+       return show_content_aux (ct, serial, alternate, cp, NULL);
+
+    /* default method for message/rfc822 */
+    if (ct->c_subtype == MESSAGE_RFC822) {
+       cp = (ct->c_showproc = add ("%pecho -file %F", NULL));
+       return show_content_aux (ct, serial, alternate, cp, NULL);
+    }
+
+    /* complain if we are not a part of a multipart/alternative */
+    if (!alternate)
+       content_error (NULL, ct, "don't know how to display content");
+
+    return NOTOK;
+}
+
+
+/*
+ * Show content of type "message/partial".
+ */
+
+static int
+show_partial (CT ct, int serial, int alternate)
+{
+    NMH_UNUSED (serial);
+    NMH_UNUSED (alternate);
+
+    content_error (NULL, ct,
+       "in order to display this message, you must reassemble it");
+    return NOTOK;
+}
+
+
+/*
+ * Show content of type "message/external".
+ *
+ * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
+ */
+
+static int
+show_external (CT ct, int serial, int alternate)
+{
+    struct exbody *e = (struct exbody *) ct->c_ctparams;
+    CT p = e->eb_content;
+
+    if (!type_ok (p, 0))
+       return OK;
+
+    return show_switch (p, serial, alternate);
+}
+
+
+static int
+parse_display_string (CT ct, char *cp, int *xstdin, int *xlist, int *xpause,
+                      int *xtty, char *file, char *buffer, size_t buflen,
+                      int multipart) {
+    int len, quoted = 0;
+    char *bp = buffer, *pp;
+    CI ci = &ct->c_ctinfo;
 
-    /* get buffer ready to go */
-    bp = buffer;
-    buflen = sizeof(buffer) - 1;
     bp[0] = bp[buflen] = '\0';
-    quoted = 0;
 
-    /* Now parse display string */
     for ( ; *cp && buflen > 0; cp++) {
        if (*cp == '%') {
            pp = bp;
+
            switch (*++cp) {
            case 'a':
                /* insert parameters from Content-Type field */
@@ -867,43 +817,73 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
 
            case 'e':
                /* exclusive execution */
-               xtty = 1;
+               *xtty = 1;
                break;
 
            case 'F':
-               /* %e and %f */
-               xtty = 1;
+               /* %e, %f, and stdin is terminal not content */
+               *xstdin = 1;
+               *xtty = 1;
                /* and fall... */
 
            case 'f':
-               /* insert filename(s) containing content */
-           {
-               char *s = "";
-                       
-               for (part = m->mp_parts; part; part = part->mp_next) {
-                   p = part->mp_part;
-
-                   snprintf (bp, buflen, "%s'%s'", s, p->c_storage);
-                   len = strlen (bp);
-                   bp += len;
-                   buflen -= len;
-                   s = " ";
+               if (multipart) {
+                   /* insert filename(s) containing content */
+                   struct multipart *m = (struct multipart *) ct->c_ctparams;
+                   struct part *part;
+                   char *s = "";
+                   CT p;
+
+                   for (part = m->mp_parts; part; part = part->mp_next) {
+                       p = part->mp_part;
+
+                       snprintf (bp, buflen, "%s%s", s, p->c_storage);
+                       len = strlen (bp);
+                       bp += len;
+                       buflen -= len;
+                       s = " ";
+                   }
+               } else {
+                   /* insert filename containing content */
+                   snprintf (bp, buflen, "%s", file);
+
+                   /*
+                    * Old comments below are left here for posterity.
+                    * This was/is tricky.
+                    */
+                   /* since we've quoted the file argument, set things up
+                    * to look past it, to avoid problems with the quoting
+                    * logic below.  (I know, I should figure out what's
+                    * broken with the quoting logic, but..)
+                    */
+                   /*
+                    * Here's the email that submitted the patch with
+                    * the comment above:
+                    *   https://www.mail-archive.com/nmh-workers@mhost.com/
+                    *     msg00288.html
+                    * I can't tell from that exactly what was broken,
+                    * beyond misquoting of the filename.  The profile
+                    * had appearances of %F both with and without quotes.
+                    * The unquoted ones should have been quoted by the
+                    * code below.
+                    * The fix was to always quote the filename.  But
+                    * that broke '%F' because it expanded to ''filename''.
+                    */
+                   /*
+                    * Old comments above are left here for posterity.
+                    * The quoting below should work properly now.
+                    */
                }
-               /* set our starting pointer back to bp, to avoid
-                * requoting the filenames we just added
-                */
-               pp = bp;
-           }
-           break;
+               break;
 
            case 'p':
                /* %l, and pause prior to displaying content */
-               xpause = pausesw;
+               *xpause = pausesw;
                /* and fall... */
 
            case 'l':
                /* display listing prior to displaying content */
-               xlist = !nolist;
+               *xlist = !nolist;
                break;
 
            case 's':
@@ -915,6 +895,42 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
                /* insert character % */
                goto raw;
 
+           case '{' : {
+               const char *closing_brace = strchr(cp, '}');
+
+               if (closing_brace) {
+                   const size_t param_len = closing_brace - cp - 1;
+                   char *param = mh_xmalloc(param_len + 1);
+                   char *value;
+
+                   (void) strncpy(param, cp + 1, param_len);
+                   param[param_len] = '\0';
+                   value = get_param(ci->ci_first_pm, param, '?', 0);
+                   free(param);
+
+                   cp += param_len + 1; /* Skip both braces, too. */
+
+                   if (value) {
+                       /* %{param} is set in the Content-Type header.
+                          After the break below, quote it if necessary. */
+                       (void) strncpy(bp, value, buflen);
+                       free(value);
+                   } else {
+                       /* %{param} not found, so skip it completely.  cp
+                          was advanced above. */
+                       continue;
+                   }
+               } else {
+                   /* This will get confused if there are multiple %{}'s,
+                      but its real purpose is to avoid doing bad things
+                      above if a closing brace wasn't found. */
+                   admonish(NULL,
+                            "no closing brace for display string escape %s",
+                            cp);
+               }
+               break;
+           }
+
            default:
                *bp++ = *--cp;
                *bp = '\0';
@@ -924,54 +940,93 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
            len = strlen (bp);
            bp += len;
            buflen -= len;
+           *bp = '\0';
 
            /* Did we actually insert something? */
            if (bp != pp) {
                /* Insert single quote if not inside quotes already */
                if (!quoted && buflen) {
                    len = strlen (pp);
-                   memmove (pp + 1, pp, len);
+                   memmove (pp + 1, pp, len+1);
                    *pp++ = '\'';
                    buflen--;
                    bp++;
+                   quoted = 1;
                }
                /* Escape existing quotes */
                while ((pp = strchr (pp, '\'')) && buflen > 3) {
                    len = strlen (pp++);
-                   memmove (pp + 3, pp, len);
-                   *pp++ = '\\';
-                   *pp++ = '\'';
-                   *pp++ = '\'';
-                   buflen -= 3;
-                   bp += 3;
+                   if (quoted) {
+                       /* Quoted.  Let this quote close that quoting.
+                          Insert an escaped quote to replace it and
+                          another quote to reopen quoting, which will be
+                          closed below. */
+                       memmove (pp + 2, pp, len);
+                       *pp++ = '\\';
+                       *pp++ = '\'';
+                       buflen -= 2;
+                       bp += 2;
+                       quoted = 0;
+                   } else {
+                       /* Not quoted.  This should not be reached with
+                          the current code, but handle the condition
+                          in case the code changes.  Just escape the
+                          quote. */
+                       memmove (pp, pp-1, len+1);
+                       *(pp++-1) = '\\';
+                       buflen -= 1;
+                       bp += 1;
+                   }
                }
                /* If pp is still set, that means we ran out of space. */
                if (pp)
                    buflen = 0;
-               if (!quoted && buflen) {
-                   *bp++ = '\'';
-                   *bp = '\0';
-                   buflen--;
+               /* Close quoting. */
+               if (quoted && buflen) {
+                   /* See if we need to close the quote by looking
+                      for an odd number of unescaped close quotes in
+                      the remainder of the display string. */
+                   int found_quote = 0, escaped = 0;
+                   char *c;
+
+                   for (c = cp+1; *c; ++c) {
+                       if (*c == '\\') {
+                           escaped = ! escaped;
+                       } else {
+                           if (escaped) {
+                               escaped = 0;
+                           } else {
+                               if (*c == '\'') {
+                                   found_quote = ! found_quote;
+                               }
+                           }
+                       }
+                   }
+                   if (! found_quote) {
+                       *bp++ = '\'';
+                       buflen--;
+                       quoted = 0;
+                   }
                }
            }
        } else {
 raw:
            *bp++ = *cp;
-           *bp = '\0';
            buflen--;
 
            if (*cp == '\'')
                quoted = !quoted;
        }
+
+       *bp = '\0';
     }
 
     if (buflen <= 0 ||
-        (ct->c_termproc && buflen <= (ssize_t) strlen(ct->c_termproc))) {
+        (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
        /* content_error would provide a more useful error message
         * here, except that if we got overrun, it probably would
         * too.
         */
-       fprintf(stderr, "Buffer overflow constructing show command!\n");
        return NOTOK;
     }
 
@@ -980,84 +1035,195 @@ raw:
        char term[BUFSIZ];
 
        strncpy (term, buffer, sizeof(term));
-       snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
+       snprintf (buffer, buflen, ct->c_termproc, term);
     }
 
-    return show_content_aux2 (ct, serial, alternate, NULL, buffer,
-                             NOTOK, xlist, xpause, 0, xtty);
+    return OK;
 }
 
 
-/*
- * show content of type "message/rfc822"
- */
-
-static int
-show_message_rfc822 (CT ct, int serial, int alternate)
-{
-    char *cp, buffer[BUFSIZ];
-    CI ci = &ct->c_ctinfo;
-
-    /* Check for mhn-show-type/subtype */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
-               invo_name, ci->ci_type, ci->ci_subtype);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
-
-    /* Check for mhn-show-type */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
-
-    if ((cp = ct->c_showproc))
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+int
+convert_charset (CT ct, char *dest_charset, int *message_mods) {
+    char *src_charset = content_charset (ct);
+    int status = OK;
+
+    /* norm_charmap() is case sensitive. */
+    char *src_charset_u = upcase (src_charset);
+    char *dest_charset_u = upcase (dest_charset);
+    int different_charsets =
+        strcmp (norm_charmap (src_charset), norm_charmap (dest_charset));
+
+    free (dest_charset_u);
+    free (src_charset_u);
+
+    if (different_charsets) {
+#ifdef HAVE_ICONV
+        iconv_t conv_desc = NULL;
+        char *dest;
+        int fd = -1;
+        char **file = NULL;
+        FILE **fp = NULL;
+        size_t begin;
+        size_t end;
+        int opened_input_file = 0;
+        char src_buffer[BUFSIZ];
+        HF hf;
+        char *tempfile;
+
+        if ((conv_desc = iconv_open (dest_charset, src_charset)) ==
+            (iconv_t) -1) {
+            advise (NULL, "Can't convert %s to %s", src_charset, dest_charset);
+            return NOTOK;
+        }
+
+        if ((tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) {
+            adios (NULL, "unable to create temporary file in %s",
+                   get_temp_dir());
+        }
+        dest = add (tempfile, NULL);
+
+        if (ct->c_cefile.ce_file) {
+            file = &ct->c_cefile.ce_file;
+            fp = &ct->c_cefile.ce_fp;
+            begin = end = 0;
+        } else if (ct->c_file) {
+            file = &ct->c_file;
+            fp = &ct->c_fp;
+            begin = (size_t) ct->c_begin;
+            end = (size_t) ct->c_end;
+        } /* else no input file: shouldn't happen */
+
+        if (file  &&  *file  &&  fp) {
+            if (! *fp) {
+                if ((*fp = fopen (*file, "r")) == NULL) {
+                    advise (*file, "unable to open for reading");
+                    status = NOTOK;
+                } else {
+                    opened_input_file = 1;
+                }
+            }
+        }
+
+        if (fp  &&  *fp) {
+            size_t inbytes;
+            size_t bytes_to_read =
+                end > 0 && end > begin  ?  end - begin  :  sizeof src_buffer;
+
+            fseeko (*fp, begin, SEEK_SET);
+            while ((inbytes = fread (src_buffer, 1,
+                                     min (bytes_to_read, sizeof src_buffer),
+                                     *fp)) > 0) {
+                char dest_buffer[BUFSIZ];
+                ICONV_CONST char *ib = src_buffer;
+                char *ob = dest_buffer;
+                size_t outbytes = sizeof dest_buffer;
+                size_t outbytes_before = outbytes;
+
+                if (end > 0) bytes_to_read -= inbytes;
+
+                if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) ==
+                    (size_t) -1) {
+                    status = NOTOK;
+                    break;
+                } else {
+                    write (fd, dest_buffer, outbytes_before - outbytes);
+                }
+            }
+
+            if (opened_input_file) {
+                fclose (*fp);
+                *fp = NULL;
+            }
+        }
+
+        iconv_close (conv_desc);
+        close (fd);
+
+        if (status == OK) {
+            /* Replace the decoded file with the converted one. */
+            if (ct->c_cefile.ce_file) {
+                if (ct->c_cefile.ce_unlink) {
+                    (void) m_unlink (ct->c_cefile.ce_file);
+                }
+                free (ct->c_cefile.ce_file);
+            }
+            ct->c_cefile.ce_file = dest;
+            ct->c_cefile.ce_unlink = 1;
+
+            ++*message_mods;
+
+            /* Update ci_attrs. */
+            src_charset = dest_charset;
+
+            /* Update ct->c_ctline. */
+            if (ct->c_ctline) {
+                char *ctline = concat(" ", ct->c_ctinfo.ci_type, "/",
+                                     ct->c_ctinfo.ci_subtype, NULL);
+               replace_param(&ct->c_ctinfo.ci_first_pm,
+                             &ct->c_ctinfo.ci_last_pm, "charset",
+                             dest_charset, 0);
+               char *outline = output_params(strlen(TYPE_FIELD) + 1 +
+                                             strlen(ctline),
+                                             ct->c_ctinfo.ci_first_pm,
+                                             NULL, 0);
+               if (outline) {
+                   ctline = add(outline, ctline);
+                   free(outline);
+               }
 
-    /* default method for message/rfc822 */
-    if (ct->c_subtype == MESSAGE_RFC822) {
-       cp = (ct->c_showproc = add ("%pshow -file '%F'", NULL));
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+                free (ct->c_ctline);
+                ct->c_ctline = ctline;
+            } /* else no CT line, which is odd */
+
+            /* Update Content-Type header field. */
+            for (hf = ct->c_first_hf; hf; hf = hf->next) {
+                if (! strcasecmp (TYPE_FIELD, hf->name)) {
+                    char *ctline = concat (ct->c_ctline, "\n", NULL);
+
+                    free (hf->value);
+                    hf->value = ctline;
+                    break;
+                }
+            }
+        } else {
+            (void) m_unlink (dest);
+        }
+#else  /* ! HAVE_ICONV */
+        NMH_UNUSED (message_mods);
+
+        advise (NULL, "Can't convert %s to %s without iconv", src_charset,
+                dest_charset);
+        status = NOTOK;
+#endif /* ! HAVE_ICONV */
     }
 
-    /* complain if we are not a part of a multipart/alternative */
-    if (!alternate)
-       content_error (NULL, ct, "don't know how to display content");
-
-    return NOTOK;
+    return status;
 }
 
 
-/*
- * Show content of type "message/partial".
- */
-
 static int
-show_partial (CT ct, int serial, int alternate)
-{
-    NMH_UNUSED (serial);
-    NMH_UNUSED (alternate);
-
-    content_error (NULL, ct,
-       "in order to display this message, you must reassemble it");
-    return NOTOK;
-}
+convert_content_charset (CT ct, char **file) {
+#ifdef HAVE_ICONV
+    /* Using current locale, see if the content needs to be converted. */
 
+    /* content_charset() cannot return NULL. */
+    char *charset = content_charset (ct);
 
-/*
- * Show content of type "message/external".
- *
- * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
- */
-
-static int
-show_external (CT ct, int serial, int alternate)
-{
-    struct exbody *e = (struct exbody *) ct->c_ctparams;
-    CT p = e->eb_content;
+    if (! check_charset (charset, strlen (charset))) {
+        int unused = 0;
 
-    if (!type_ok (p, 0))
-       return OK;
+        if (convert_charset (ct, get_charset (), &unused) == 0) {
+            *file = ct->c_cefile.ce_file;
+        } else {
+            return NOTOK;
+        }
+    }
+#else  /* ! HAVE_ICONV */
+    NMH_UNUSED (ct);
+    NMH_UNUSED (file);
+#endif /* ! HAVE_ICONV */
 
-    return show_switch (p, serial, alternate);
+    return OK;
 }
 
 
index fb5349f..72079a8 100644 (file)
@@ -45,9 +45,6 @@ DEFINE_SWITCH_ARRAY(MHSTORE, switches);
 #undef X
 
 
-int save_clobber_policy (const char *);
-extern int files_not_clobbered;
-
 /* mhcachesbr.c */
 extern int rcachesw;
 extern int wcachesw;
@@ -55,8 +52,10 @@ extern char *cache_public;
 extern char *cache_private;
 
 /* mhstoresbr.c */
-extern int autosw;
-extern char *cwd;      /* cache current working directory */
+typedef struct mhstoreinfo *mhstoreinfo_t;
+mhstoreinfo_t mhstoreinfo_create(CT *, char *, const char *, int, int);
+int mhstoreinfo_files_not_clobbered(const mhstoreinfo_t);
+void mhstoreinfo_free(mhstoreinfo_t);
 
 /* mhmisc.c */
 extern int npart;
@@ -65,12 +64,10 @@ extern char *parts[NPARTS + 1];
 extern char *types[NTYPES + 1];
 extern int userrs;
 
-int debugsw = 0;
-int verbosw = 0;
-
 #define        quitser pipeser
 
 /* mhparse.c */
+int debugsw = 0;
 CT parse_mime (char *);
 
 /* mhmisc.c */
@@ -79,7 +76,7 @@ int type_ok (CT, int);
 void flush_errors (void);
 
 /* mhstoresbr.c */
-void store_all_messages (CT *);
+void store_all_messages (mhstoreinfo_t);
 
 /* mhfree.c */
 extern CT *cts;
@@ -94,14 +91,19 @@ static void pipeser (int);
 int
 main (int argc, char **argv)
 {
-    int msgnum, *icachesw;
+    /* verbosw defaults to 1 for backward compatibility. */
+    int msgnum, *icachesw, autosw = 0, verbosw = 1;
+    const char *clobbersw = "always";
     char *cp, *file = NULL, *outfile = NULL, *folder = NULL;
     char *maildir, buf[100], **argp;
     char **arguments;
+    char *cwd;
     struct msgs_array msgs = { 0, 0, NULL };
     struct msgs *mp = NULL;
     CT ct, *ctp;
     FILE *fp;
+    int files_not_clobbered;
+    mhstoreinfo_t info;
 
     if (nmh_init(argv[0], 1)) { return 1; }
 
@@ -116,13 +118,13 @@ main (int argc, char **argv)
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, switches)) {
-           case AMBIGSW: 
+           case AMBIGSW:
                ambigsw (cp, switches);
                done (1);
-           case UNKWNSW: 
+           case UNKWNSW:
                adios (NULL, "-%s unknown", cp);
 
-           case HELPSW: 
+           case HELPSW:
                snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
                        invo_name);
                print_help (buf, switches, 1);
@@ -194,19 +196,16 @@ do_cache:
                outfile = *cp == '-' ? cp : path (cp, TFILE);
                continue;
 
-           case VERBSW: 
+           case VERBSW:
                verbosw = 1;
                continue;
-           case NVERBSW: 
+           case NVERBSW:
                verbosw = 0;
                continue;
             case CLOBBERSW:
                if (!(cp = *argp++) || *cp == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
-                if (save_clobber_policy (cp)) {
-                  adios (NULL, "invalid argument, %s, to %s", argp[-1],
-                         argp[-2]);
-                }
+               clobbersw = cp;
                continue;
            case DEBUGSW:
                debugsw = 1;
@@ -258,7 +257,7 @@ do_cache:
     /*
      * Cache the current directory before we do any chdirs()'s.
      */
-    cwd = getcpy (pwd());
+    cwd = add(pwd(), NULL);
 
     if (!context_find ("path"))
        free (path ("./", TFOLDER));
@@ -351,13 +350,16 @@ do_cache:
     /*
      * Store the message content
      */
-    store_all_messages (cts);
+    info = mhstoreinfo_create (cts, cwd, clobbersw, autosw, verbosw);
+    store_all_messages (info);
+    files_not_clobbered = mhstoreinfo_files_not_clobbered(info);
+    mhstoreinfo_free(info);
 
     /* Now free all the structures for the content */
     for (ctp = cts; *ctp; ctp++)
        free_content (*ctp);
 
-    free ((char *) cts);
+    free (cts);
     cts = NULL;
 
     /* If reading from a folder, do some updating */
index 156411b..fc26554 100644 (file)
 #include <h/mhparse.h>
 #include <h/utils.h>
 
+enum clobber_policy_t {
+  NMH_CLOBBER_ALWAYS = 0,
+  NMH_CLOBBER_AUTO,
+  NMH_CLOBBER_SUFFIX,
+  NMH_CLOBBER_ASK,
+  NMH_CLOBBER_NEVER
+};
 
-/*
- * The list of top-level contents to display
- */
-extern CT *cts;
+static enum clobber_policy_t clobber_policy (const char *);
 
-int autosw = 0;
+struct mhstoreinfo {
+    CT *cts;                 /* Top-level list of contents to store. */
+    char *cwd;               /* cached current directory */
+    int autosw;              /* -auto enabled */
+    int verbosw;             /* -verbose enabled */
+    int files_not_clobbered; /* output flag indicating that store failed
+                                in order to not clobber an existing file */
 
-/*
- * Cache of current directory.  This must be
- * set before these routines are called.
- */
-char *cwd;
+    /* The following must never be touched by a caller:  they are for
+       internal use by the mhstoresbr functions. */
+    char *dir;               /* directory in which to store contents */
+    enum clobber_policy_t clobber_policy;  /* -clobber selection */
+};
+
+typedef struct mhstoreinfo *mhstoreinfo_t;
+
+mhstoreinfo_t
+mhstoreinfo_create (CT *ct, char *pwd, const char *csw, int asw, int vsw) {
+    mhstoreinfo_t info = mh_xmalloc (sizeof *info);
+
+    info->cts = ct;
+    info->cwd = pwd;
+    info->autosw = asw;
+    info->verbosw = vsw;
+    info->files_not_clobbered = 0;
+    info->dir = NULL;
+    info->clobber_policy = clobber_policy (csw);
+
+    return info;
+}
+
+void
+mhstoreinfo_free (mhstoreinfo_t info) {
+    free (info->cwd);
+    free (info);
+}
+
+int
+mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info) {
+    return info->files_not_clobbered;
+}
 
-/*
- * The directory in which to store the contents.
- */
-static char *dir;
 
 /*
  * Type for a compare function for qsort.  This keeps
@@ -54,26 +88,26 @@ int show_content_aux (CT, int, int, char *, char *);
 /*
  * prototypes
  */
-void store_all_messages (CT *);
+void store_all_messages (mhstoreinfo_t);
 
 /*
  * static prototypes
  */
-static void store_single_message (CT);
-static int store_switch (CT);
-static int store_generic (CT);
-static int store_application (CT);
-static int store_multi (CT);
-static int store_partial (CT);
-static int store_external (CT);
+static void store_single_message (CT, mhstoreinfo_t);
+static int store_switch (CT, mhstoreinfo_t);
+static int store_generic (CT, mhstoreinfo_t);
+static int store_application (CT, mhstoreinfo_t);
+static int store_multi (CT, mhstoreinfo_t);
+static int store_partial (CT, mhstoreinfo_t);
+static int store_external (CT, mhstoreinfo_t);
 static int ct_compar (CT *, CT *);
-static int store_content (CT, CT);
+static int store_content (CT, CT, mhstoreinfo_t);
 static int output_content_file (CT, int);
 static int output_content_folder (char *, char *);
 static int parse_format_string (CT, char *, char *, int, char *);
 static void get_storeproc (CT);
 static int copy_some_headers (FILE *, CT);
-static char *clobber_check (char *);
+static char *clobber_check (char *, mhstoreinfo_t);
 
 /*
  * Main entry point to store content
@@ -81,7 +115,7 @@ static char *clobber_check (char *);
  */
 
 void
-store_all_messages (CT *cts)
+store_all_messages (mhstoreinfo_t info)
 {
     CT ct, *ctp;
     char *cp;
@@ -91,13 +125,13 @@ store_all_messages (CT *cts)
      * store any contents.
      */
     if ((cp = context_find (nmhstorage)) && *cp)
-       dir = getcpy (cp);
+       info->dir = getcpy (cp);
     else
-       dir = getcpy (cwd);
+       info->dir = getcpy (info->cwd);
 
-    for (ctp = cts; *ctp; ctp++) {
+    for (ctp = info->cts; *ctp; ctp++) {
        ct = *ctp;
-       store_single_message (ct);
+       store_single_message (ct, info);
     }
 
     flush_errors ();
@@ -110,11 +144,11 @@ store_all_messages (CT *cts)
  */
 
 static void
-store_single_message (CT ct)
+store_single_message (CT ct, mhstoreinfo_t info)
 {
     if (type_ok (ct, 1)) {
        umask (ct->c_umask);
-       store_switch (ct);
+       store_switch (ct, info);
        if (ct->c_fp) {
            fclose (ct->c_fp);
            ct->c_fp = NULL;
@@ -130,33 +164,33 @@ store_single_message (CT ct)
  */
 
 static int
-store_switch (CT ct)
+store_switch (CT ct, mhstoreinfo_t info)
 {
     switch (ct->c_type) {
        case CT_MULTIPART:
-           return store_multi (ct);
+           return store_multi (ct, info);
 
        case CT_MESSAGE:
            switch (ct->c_subtype) {
                case MESSAGE_PARTIAL:
-                   return store_partial (ct);
+                   return store_partial (ct, info);
 
                case MESSAGE_EXTERNAL:
-                   return store_external (ct);
+                   return store_external (ct, info);
 
                case MESSAGE_RFC822:
                default:
-                   return store_generic (ct);
+                   return store_generic (ct, info);
            }
 
        case CT_APPLICATION:
-           return store_application (ct);
+           return store_application (ct, info);
 
        case CT_TEXT:
        case CT_AUDIO:
        case CT_IMAGE:
        case CT_VIDEO:
-           return store_generic (ct);
+           return store_generic (ct, info);
 
        default:
            adios (NULL, "unknown content type %d", ct->c_type);
@@ -172,17 +206,17 @@ store_switch (CT ct)
  */
 
 static int
-store_generic (CT ct)
+store_generic (CT ct, mhstoreinfo_t info)
 {
     /*
      * Check if the content specifies a filename.
      * Don't bother with this for type "message"
      * (only "message/rfc822" will use store_generic).
      */
-    if (autosw && ct->c_type != CT_MESSAGE)
+    if (info->autosw && ct->c_type != CT_MESSAGE)
        get_storeproc (ct);
 
-    return store_content (ct, NULL);
+    return store_content (ct, NULL, info);
 }
 
 
@@ -191,12 +225,12 @@ store_generic (CT ct)
  */
 
 static int
-store_application (CT ct)
+store_application (CT ct, mhstoreinfo_t info)
 {
     CI ci = &ct->c_ctinfo;
 
     /* Check if the content specifies a filename */
-    if (autosw)
+    if (info->autosw)
        get_storeproc (ct);
 
     /*
@@ -231,7 +265,7 @@ store_application (CT ct)
                                  : (gzP ? "%egzip -dc | tar tvf -"
                                     : "%etar tvf -"), NULL);
            if (!ct->c_storeproc) {
-               if (autosw) {
+               if (info->autosw) {
                    ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -"
                                           : (gzP ? "| gzip -dc | tar xvpf -"
                                              : "| tar xvpf -"), NULL);
@@ -245,7 +279,7 @@ store_application (CT ct)
        }
     }
 
-    return store_content (ct, NULL);
+    return store_content (ct, NULL, info);
 }
 
 
@@ -254,7 +288,7 @@ store_application (CT ct)
  */
 
 static int
-store_multi (CT ct)
+store_multi (CT ct, mhstoreinfo_t info)
 {
     int        result;
     struct multipart *m = (struct multipart *) ct->c_ctparams;
@@ -271,7 +305,7 @@ store_multi (CT ct)
                   NULL here. */
                p->c_storage = ct->c_storage;
            }
-           result = store_switch (p);
+           result = store_switch (p, info);
            p->c_storage = NULL;
 
            if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
@@ -289,7 +323,7 @@ store_multi (CT ct)
  */
 
 static int
-store_partial (CT ct)
+store_partial (CT ct, mhstoreinfo_t info)
 {
     int        cur, hi, i;
     CT p, *ctp, *ctq;
@@ -301,7 +335,7 @@ store_partial (CT ct)
        return OK;
 
     hi = i = 0;
-    for (ctp = cts; *ctp; ctp++) {
+    for (ctp = info->cts; *ctp; ctp++) {
        p = *ctp;
        if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
            pm = (struct partial *) p->c_ctparams;
@@ -327,7 +361,7 @@ store_partial (CT ct)
        adios (NULL, "out of memory");
 
     ctq = base;
-    for (ctp = cts; *ctp; ctp++) {
+    for (ctp = info->cts; *ctp; ctp++) {
        p = *ctp;
        if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
            pm = (struct partial *) p->c_ctparams;
@@ -373,7 +407,7 @@ missing_part:
 
     ctq = base;
     ct = *ctq++;
-    if (store_content (ct, NULL) == NOTOK) {
+    if (store_content (ct, NULL, info) == NOTOK) {
 losing:
        free ((char *) base);
        return NOTOK;
@@ -381,7 +415,7 @@ losing:
 
     for (; *ctq; ctq++) {
        p = *ctq;
-       if (store_content (p, ct) == NOTOK)
+       if (store_content (p, ct, info) == NOTOK)
            goto losing;
     }
 
@@ -395,7 +429,7 @@ losing:
  */
 
 static int
-store_external (CT ct)
+store_external (CT ct, mhstoreinfo_t info)
 {
     int        result = NOTOK;
     struct exbody *e = (struct exbody *) ct->c_ctparams;
@@ -408,7 +442,7 @@ store_external (CT ct)
      * Check if the parameters for the external body
      * specified a filename.
      */
-    if (autosw) {
+    if (info->autosw) {
        char *cp;
 
        if ((cp = e->eb_name)
@@ -438,7 +472,7 @@ store_external (CT ct)
           c_storage, so we know that p->c_storage is NULL here. */
        p->c_storage = ct->c_storage;
     }
-    result = store_switch (p);
+    result = store_switch (p, info);
     p->c_storage = NULL;
 
     p->c_partno = NULL;
@@ -473,7 +507,7 @@ ct_compar (CT *a, CT *b)
  */
 
 static int
-store_content (CT ct, CT p)
+store_content (CT ct, CT p, mhstoreinfo_t info)
 {
     int appending = 0, msgnum = 0;
     int is_partial = 0, first_partial = 0;
@@ -584,23 +618,24 @@ store_content (CT ct, CT p)
         * Parse and expand the storage formatting string
         * in `cp' into `buffer'.
         */
-       parse_format_string (ct, cp, buffer, sizeof(buffer), dir);
+       parse_format_string (ct, cp, buffer, sizeof(buffer), info->dir);
 
        /*
         * If formatting begins with '|' or '!', then pass
         * content to standard input of a command and return.
         */
        if (buffer[0] == '|' || buffer[0] == '!')
-           return show_content_aux (ct, 1, 0, buffer + 1, dir);
+           return show_content_aux (ct, 1, 0, buffer + 1, info->dir);
 
         /* record the filename */
-       if ((ct->c_storage = clobber_check (add (buffer, NULL))) == NULL) {
+       if ((ct->c_storage = clobber_check (add (buffer, NULL), info)) ==
+           NULL) {
            return NOTOK;
        }
     } else {
-        /* The output filename was explicitly specified, so use it. */
-       if ((ct->c_storage = clobber_check (add (ct->c_storage, NULL))) ==
-            NULL) {
+       /* The output filename was explicitly specified, so use it. */
+       if ((ct->c_storage = clobber_check (add (ct->c_storage, NULL), info)) ==
+           NULL) {
            return NOTOK;
        }
     }
@@ -625,42 +660,44 @@ got_filename:
            return NOTOK;
     }
 
-    /*
-     * Now print out the name/number of the message
-     * that we are storing.
-     */
-    if (is_partial) {
-       if (first_partial)
-           fprintf (stderr, "reassembling partials ");
-       if (last_partial)
-           fprintf (stderr, "%s", ct->c_file);
-       else
-           fprintf (stderr, "%s,", ct->c_file);
-    } else {
-       fprintf (stderr, "storing message %s", ct->c_file);
-       if (ct->c_partno)
-           fprintf (stderr, " part %s", ct->c_partno);
-    }
-
-    /*
-     * Unless we are in the "middle" of group of message/partials,
-     * we now print the name of the file, folder, and/or message
-     * to which we are storing the content.
-     */
-    if (!is_partial || last_partial) {
-       if (ct->c_folder) {
-           fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, msgnum);
-       } else if (!strcmp(ct->c_storage, "-")) {
-           fprintf (stderr, " to stdout\n");
-       } else {
-           int cwdlen;
+    if (info->verbosw) {
+        /*
+         * Now print out the name/number of the message
+         * that we are storing.
+         */
+        if (is_partial) {
+            if (first_partial)
+                fprintf (stderr, "reassembling partials ");
+            if (last_partial)
+                fprintf (stderr, "%s", ct->c_file);
+            else
+                fprintf (stderr, "%s,", ct->c_file);
+        } else {
+            fprintf (stderr, "storing message %s", ct->c_file);
+            if (ct->c_partno)
+                fprintf (stderr, " part %s", ct->c_partno);
+        }
 
-           cwdlen = strlen (cwd);
-           fprintf (stderr, " as file %s\n",
-               strncmp (ct->c_storage, cwd, cwdlen)
-               || ct->c_storage[cwdlen] != '/'
-               ? ct->c_storage : ct->c_storage + cwdlen + 1);
-       }
+        /*
+         * Unless we are in the "middle" of group of message/partials,
+         * we now print the name of the file, folder, and/or message
+         * to which we are storing the content.
+         */
+        if (!is_partial || last_partial) {
+            if (ct->c_folder) {
+                fprintf (stderr, " to folder %s as message %d\n", ct->c_folder,
+                         msgnum);
+            } else if (!strcmp(ct->c_storage, "-")) {
+                fprintf (stderr, " to stdout\n");
+            } else {
+                int cwdlen = strlen (info->cwd);
+
+                fprintf (stderr, " as file %s\n",
+                         strncmp (ct->c_storage, info->cwd, cwdlen)
+                         || ct->c_storage[cwdlen] != '/'
+                         ? ct->c_storage : ct->c_storage + cwdlen + 1);
+            }
+        }
     }
 
     return OK;
@@ -1114,35 +1151,23 @@ copy_some_headers (FILE *out, CT ct)
 /******************************************************************************/
 /* -clobber support */
 
-enum clobber_policy_t {
-  NMH_CLOBBER_ALWAYS,
-  NMH_CLOBBER_AUTO,
-  NMH_CLOBBER_SUFFIX,
-  NMH_CLOBBER_ASK,
-  NMH_CLOBBER_NEVER
-};
-
-static enum clobber_policy_t clobber_policy = NMH_CLOBBER_ALWAYS;
-
-int files_not_clobbered = 0;
-
-int
-save_clobber_policy (const char *value) {
-  if (! strcasecmp (value, "always")) {
-    clobber_policy = NMH_CLOBBER_ALWAYS;
+static
+enum clobber_policy_t
+clobber_policy (const char *value) {
+  if (value == NULL  ||  ! strcasecmp (value, "always")) {
   } else if (! strcasecmp (value, "auto")) {
-    clobber_policy = NMH_CLOBBER_AUTO;
+    return NMH_CLOBBER_AUTO;
   } else if (! strcasecmp (value, "suffix")) {
-    clobber_policy = NMH_CLOBBER_SUFFIX;
+    return NMH_CLOBBER_SUFFIX;
   } else if (! strcasecmp (value, "ask")) {
-    clobber_policy = NMH_CLOBBER_ASK;
+    return NMH_CLOBBER_ASK;
   } else if (! strcasecmp (value, "never")) {
-    clobber_policy = NMH_CLOBBER_NEVER;
+    return NMH_CLOBBER_NEVER;
   } else {
-    return 1;
+    adios (NULL, "invalid argument, %s, to clobber", value);
   }
 
-  return 0;
+  return NMH_CLOBBER_ALWAYS;
 }
 
 
@@ -1179,7 +1204,6 @@ next_version (char *file, enum clobber_policy_t clobber_policy) {
         /* Should never get here. */
         advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
         free (buffer);
-        ++files_not_clobbered;
         return NULL;
     }
 
@@ -1202,7 +1226,6 @@ next_version (char *file, enum clobber_policy_t clobber_policy) {
     advise (NULL, "will not overwrite %s, too many versions", buffer);
     free (buffer);
     buffer = NULL;
-    ++files_not_clobbered;
   }
 
   return buffer;
@@ -1210,7 +1233,7 @@ next_version (char *file, enum clobber_policy_t clobber_policy) {
 
 
 static char *
-clobber_check (char *original_file) {
+clobber_check (char *original_file, mhstoreinfo_t info) {
   /* clobber policy        return value
    * --------------        ------------
    *   -always             original_file
@@ -1224,7 +1247,7 @@ clobber_check (char *original_file) {
   char *cwd = NULL;
   int check_again;
 
-  if (clobber_policy == NMH_CLOBBER_ASK) {
+  if (info->clobber_policy == NMH_CLOBBER_ASK) {
     /* Save cwd for possible use in loop below. */
     char *slash;
 
@@ -1245,14 +1268,17 @@ clobber_check (char *original_file) {
     file = original_file;
     check_again = 0;
 
-    switch (clobber_policy) {
+    switch (info->clobber_policy) {
       case NMH_CLOBBER_ALWAYS:
         break;
 
       case NMH_CLOBBER_SUFFIX:
       case NMH_CLOBBER_AUTO:
         if (stat (file, &st) == OK) {
-          file = next_version (original_file, clobber_policy);
+          if ((file = next_version (original_file, info->clobber_policy)) ==
+              NULL) {
+              ++info->files_not_clobbered;
+          }
         }
         break;
 
@@ -1260,7 +1286,10 @@ clobber_check (char *original_file) {
         if (stat (file, &st) == OK) {
           enum answers { NMH_YES, NMH_NO, NMH_RENAME };
           static struct swit answer[4] = {
-            { "yes", 0, NMH_YES }, { "no", 0, NMH_NO }, { "rename", 0, NMH_RENAME }, { NULL, 0, 0 } };
+            { "yes", 0, NMH_YES },
+            { "no", 0, NMH_NO },
+            { "rename", 0, NMH_RENAME },
+            { NULL, 0, 0 } };
           char **ans;
 
           if (isatty (fileno (stdin))) {
@@ -1280,7 +1309,7 @@ clobber_check (char *original_file) {
             case NMH_NO:
               free (file);
               file = NULL;
-              ++files_not_clobbered;
+              ++info->files_not_clobbered;
               break;
             case NMH_RENAME: {
               char buf[PATH_MAX];
@@ -1288,7 +1317,7 @@ clobber_check (char *original_file) {
               if (fgets (buf, sizeof buf, stdin) == NULL  ||
                   buf[0] == '\0') {
                 file = NULL;
-                ++files_not_clobbered;
+                ++info->files_not_clobbered;
               } else {
                 char *newline = strchr (buf, '\n');
                 if (newline) {
@@ -1320,7 +1349,7 @@ clobber_check (char *original_file) {
           advise (NULL, "will not overwrite %s with -clobber never", file);
           free (file);
           file = NULL;
-          ++files_not_clobbered;
+          ++info->files_not_clobbered;
         }
         break;
     }
diff --git a/uip/mkstemp.c b/uip/mkstemp.c
new file mode 100644 (file)
index 0000000..16ddf64
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * mkstemp.c -- create a temporary file
+ *
+ * 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.
+ */
+
+/* define NMH to 0 to remove dependencies on nmh. */
+#ifndef NMH
+#   define NMH 1
+#endif /* ! NMH */
+
+#ifdef HAVE_CONFIG_H
+#   include <config.h>
+#endif /* HAVE_CONFIG_H */
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#if ! defined HAVE_MKSTEMPS
+#   define HAVE_MKSTEMPS 0
+#endif /* ! HAVE_MKSTEMPS */
+
+char *build_template(const char *, const char *, const char *);
+void process_args(int, char **, const char **, const char **, const char **);
+
+/*
+ * Use a template of the form:
+ *     [directory/][prefix]XXXXXX[suffix]
+ * where some of those named components might be null.  suffix is only
+ * supported if HAVE_MKSTEMPS.
+ */
+
+int
+main(int argc, char *argv[]) {
+    const char *directory = "", *prefix = "", *suffix = "";
+    size_t suffix_len;
+    int fd;
+    char *template;
+
+    process_args(argc, argv, &directory, &prefix, &suffix);
+    if ((template = build_template(directory, prefix, suffix)) == NULL) {
+        return -1;
+    }
+
+    if ((suffix_len = strlen(suffix)) > 0) {
+#       if HAVE_MKSTEMPS
+        if ((fd = mkstemps(template, suffix_len)) < 0) { perror("mkstemps"); }
+#       endif /* HAVE_MKSTEMPS */
+    } else {
+        if ((fd = mkstemp(template)) < 0) { perror("mkstemp"); }
+    }
+
+    if (fd >= 0) {
+        (void) puts(template);
+        (void) close(fd);
+    }
+
+    free(template);
+
+    return fd >= 0  ?  0  :  -1;
+}
+
+
+char *
+build_template(const char *directory, const char *prefix, const char *suffix) {
+    const char pattern[] = "XXXXXX";
+    size_t len, directory_len, pathsep_len, prefix_len, suffix_len;
+    char *template;
+
+    directory_len = strlen(directory);
+    if (directory_len > 0) {
+        pathsep_len = 1;
+        if (directory[directory_len - 1] == '/') {
+            /* Will insert a '/' separately, so truncate the one provided
+               in the directory name. */
+            --directory_len;
+        }
+    } else {
+        pathsep_len = 0;
+    }
+    prefix_len = strlen(prefix);
+    suffix_len = strlen(suffix);
+    /* sizeof pattern includes its final NULL, so don't add another. */
+    len = directory_len  +  pathsep_len  +  prefix_len  +   sizeof pattern  +
+        suffix_len;
+
+    if ((template = malloc(len))) {
+        char *tp = template;
+
+        (void) strncpy(tp, directory, directory_len);
+        tp += directory_len;
+
+        if (pathsep_len == 1) { *tp++ = '/'; }
+
+        (void) strncpy(tp, prefix, prefix_len);
+        tp += prefix_len;
+
+        (void) strncpy(tp, pattern, sizeof pattern - 1);
+        tp += sizeof pattern - 1;
+
+        (void) strncpy(tp, suffix, suffix_len);
+        tp += suffix_len;
+
+        template[len-1] = '\0';
+
+        return template;
+    } else {
+        perror("malloc");
+        return NULL;
+    }
+}
+
+
+#if NMH
+#include <h/mh.h>
+
+#if HAVE_MKSTEMPS
+#   define MHFIXMSG_SWITCHES \
+    X("directory", 0, DIRECTORYSW) \
+    X("prefix", 0, PREFIXSW) \
+    X("suffix", 0, SUFFIXSW) \
+    X("version", 0, VERSIONSW) \
+    X("help", 0, HELPSW)
+#else  /* ! HAVE_MKSTEMPS */
+#   define MHFIXMSG_SWITCHES \
+    X("directory", 0, DIRECTORYSW) \
+    X("prefix", 0, PREFIXSW) \
+    X("version", 0, VERSIONSW) \
+    X("help", 0, HELPSW)
+#endif /* ! HAVE_MKSTEMPS */
+
+#define X(sw, minchars, id) id,
+DEFINE_SWITCH_ENUM(MHFIXMSG);
+#undef X
+
+#define X(sw, minchars, id) { sw, minchars, id },
+DEFINE_SWITCH_ARRAY(MHFIXMSG, switches);
+#undef X
+
+void
+process_args(int argc, char **argv, const char **directory,
+             const char **prefix, const char **suffix) {
+    char **argp, **arguments, *cp, buf[100];
+#   if ! HAVE_MKSTEMPS
+    NMH_UNUSED(suffix);
+#   endif /* ! HAVE_MKSTEMPS */
+
+    if (nmh_init(argv[0], 1)) { done(NOTOK); }
+    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(NOTOK);
+            case UNKWNSW:
+                advise (NULL, "-%s unknown", cp);
+                (void) snprintf(buf, sizeof buf, "%s [switches]", invo_name);
+                print_help(buf, switches, 1);
+                done(NOTOK);
+            case HELPSW:
+                (void) snprintf(buf, sizeof buf, "%s [switches]", invo_name);
+                print_help(buf, switches, 1);
+                done(OK);
+            case VERSIONSW:
+                print_version(invo_name);
+                done(OK);
+
+            case DIRECTORYSW:
+                /* Allow the directory to start with '-'. */
+                if ((cp = *argp++) == NULL) {
+                    adios(NULL, "missing argument to %s", argp[-2]);
+                }
+                *directory = cp;
+                continue;
+
+            case PREFIXSW:
+                /* Allow the prefix to start with '-'. */
+                if ((cp = *argp++) == NULL) {
+                    adios(NULL, "missing argument to %s", argp[-2]);
+                }
+                *prefix = cp;
+                continue;
+
+#           if HAVE_MKSTEMPS
+            case SUFFIXSW:
+                /* Allow the suffix to start with '-'. */
+                if ((cp = *argp++) == NULL) {
+                    adios(NULL, "missing argument to %s", argp[-2]);
+                }
+                *suffix = cp;
+                continue;
+#           endif /* HAVE_MKSTEMPS */
+            }
+        }
+    }
+}
+#else  /* ! NMH */
+void
+process_args(int argc, char **argv, const char **directory,
+             const char **prefix, const char **suffix) {
+#   if HAVE_MKSTEMPS
+    const char usage[] =
+        "usage: %s [-h] [-d directory] [-p prefix] [-s suffix]\n";
+    const char optstring[] = "d:hp:s:";
+#   else  /* ! HAVE_MKSTEMPS */
+    const char usage[] = "usage: %s [-h] [-d directory] [-p prefix]\n";
+    const char optstring[] = "d:hp:";
+#   endif /* ! HAVE_MKSTEMPS */
+    int opt;
+
+    while ((opt = getopt(argc, argv, optstring)) != -1) {
+        switch (opt) {
+        case 'd':
+            *directory = optarg;
+            break;
+        case 'p':
+            *prefix = optarg;
+            break;
+        case 's':
+            *suffix = optarg;
+            break;
+        case 'h':
+            (void) printf(usage, argv[0]);
+            exit(0);
+        default:
+            (void) fprintf(stderr, usage, argv[0]);
+            exit(-1);
+        }
+    }
+}
+#endif /* ! NMH */