/uip/mhshow
/uip/mhstore
/uip/mhtest
+/uip/mkstemp
/uip/msgchk
/uip/msh
/uip/new
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)
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)
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.
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 \
##
## 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
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 \
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 \
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)
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
$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"
# 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";
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.
{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
$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.
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
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
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
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
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
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
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
# 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
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
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
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);
char **svector_strs (svector_t);
size_t svector_size (svector_t);
-struct ivector;
typedef struct ivector *ivector_t;
ivector_t ivector_create (size_t);
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
*/
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.
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.
-.TH MH-MAIL %manext5% "March 7, 2013" "%nmhversion%"
+.TH MH-FOLDERS %manext5% "February 26, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
-.TH MH-PROFILE %manext5% "April 14, 2013" "%nmhversion%"
+.TH MH-PROFILE %manext5% "February 16, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
.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.
-.TH MHFIXMSG %manext1% "October 27, 2013" "%nmhversion%"
+.TH MHFIXMSG %manext1% "February 8, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.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 ]
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).
.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
.fi
.SH "SEE ALSO"
.IR inc (1),
+.IR iconv (3),
.IR mh-profile (5),
.IR mhbuild (1),
.IR mhlist (1),
.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 '
-.TH MHPARAM %manext1% "November 6, 2012" "%nmhversion%"
+.TH MHPARAM %manext1% "February 26, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.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
% mhparam libdir
%libdir%
-% mhparam lockmethod
+% mhparam datalocking
fcntl
+
+% mhparam spoollocking
+flock
.fi
.RE
.PP
-.TH MHSHOW %manext1% "May 4, 2013" "%nmhversion%"
+.TH MHSHOW %manext1% "February 18, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.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
.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
.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
.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
.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
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
^moreproc:~^Default program to display text/plain content
.fi
.SH "SEE ALSO"
+.IR iconv (3),
.IR mhbuild (1),
.IR mhl (1),
.IR mhlist (1),
-.TH MHSTORE %manext1% "March 21, 2013" "%nmhversion%"
+.TH MHSTORE %manext1% "March 2, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.RB [ \-wcache
.IR policy ]
.RB [ \-check " | " \-nocheck ]
+.RB [ \-verbose " | " \-noverbose ]
.RB [ \-version ]
.RB [ \-help ]
.ad
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
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 '/',
.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
.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
.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,
.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.
--- /dev/null
+.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
-.TH NMH %manext7% "November 30, 2012" "%nmhversion%"
+.TH NMH %manext7% "March 1, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
.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
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
.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
-.TH PICK %manext1% "November 6, 2012" "%nmhversion%"
+.TH PICK %manext1% "March 1, 2014" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
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
.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.
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;
vec[vecp++] = message_file_name_2;
vec[vecp++] = NULL;
execvp(program, vec);
+ advise(program, "Unable to execute");
_exit(-1);
/* NOTREACHED */
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;
}
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);
#define ID 10
#define MACH 11
-static char tokval[100];
+#define MAX_TOKVAL_SIZE 1024
struct toktab {
char *tokstr;
/*
* prototypes
*/
-static int token(void);
+static int token(char *);
void
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;
case MACH:
if (!usedefault) {
- if (token() != ID)
+ if (token(tokval) != ID)
continue;
/*
* Allow match either for user's host name.
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);
}
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);
}
snprintf(prompt, sizeof(prompt), "Password (%s:%s): ", host, *aname);
mypass = nmh_getpass(prompt);
-
+
if (*mypass == '\0') {
mypass = *aname;
}
}
static int
-token(void)
+token(char *tokval)
{
char *cp;
int c;
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;
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;
}
}
+
+/* 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.
*
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
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
$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`
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`
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"
#
check "$draft" "$expected"
+[ ${failed:=0} -eq 0 ] && rm -f ${backup}
+
exit ${failed:-0}
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"
#
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"
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}
switches are:
-decodetext 8bit|7bit
-nodecodetext
- -[no]textcodeset
+ -[no]textcharset
-[no]reformat
-[no]replacetextplain
-[no]fixboundary
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
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
" "
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"
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"
------- =_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
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>
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
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"
$bindir/whom
$sysconfdir
${MH_LIB_DIR}
+fcntl
+${default_locking}
EOF
run_prog mhparam -nocomponent \
whatnowproc \
whomproc \
etcdir \
-libdir >$actual 2>&1
+libdir \
+datalocking \
+spoollocking >$actual 2>&1
check $expected $actual
# 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
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`
--- /dev/null
+#!/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
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"
# 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
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' \
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
EOF
msgfile=`mhpath new`
-cat > $msgfile <<EOF
+cat >"$msgfile" <<'EOF'
To: recipient@example.com
From: sender@example.com
Subject: mhlist test
EOF
msgfile=`mhpath new`
-cat > $msgfile <<EOF
+cat >"$msgfile" <<'EOF'
To: recipient@example.com
From: sender@example.com
Subject: mhlist test
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.
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"
--- /dev/null
+#!/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
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
#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) \
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);
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);
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; }
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;
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) {
/* 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];
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);
}
-/* 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;
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;
}
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);
/* 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;
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;
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;
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",
}
}
- 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;
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;
}
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;
}
-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) {
}
-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)
{
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 -' ;;
#### 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
}
-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 ;;
#### 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.
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=
#### 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"
#### 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.
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;
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 *);
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; }
/*
* Cache the current directory before we do any chdirs()'s.
*/
- cwd = getcpy (pwd());
+ cwd = add(pwd(), NULL);
if (!context_find ("path"))
free (path ("./", TFOLDER));
/*
* 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
for (ctp = cts; *ctp; ctp++)
free_content (*ctp);
- free ((char *) cts);
+ free (cts);
cts = NULL;
/* If reading from a folder, do some updating */
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
{ "etcdir", &mhetcdir },
{ "libdir", &mhlibdir },
{ "sbackup", &sbackup },
- { "lockmethod", &lockmethod },
+ { "datalocking", &datalocking },
+ { "spoollocking", &spoollocking },
{ "sasl", &sasl },
{ "tls", &tls },
{ NULL, NULL },
} 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
missed++;
}
}
-
+
done (missed);
return 1;
}
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
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.
#include <h/mime.h>
#include <h/mhparse.h>
#include <h/utils.h>
+#ifdef HAVE_ICONV
+# include <iconv.h>
+#endif /* ! HAVE_ICONV */
extern int debugsw;
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);
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);
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);
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)
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);
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);
}
}
- 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);
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)
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);
* 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);
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);
/*
* 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;
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) {
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"))
}
}
- 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 */
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':
/* 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';
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;
}
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;
}
#undef X
-int save_clobber_policy (const char *);
-extern int files_not_clobbered;
-
/* mhcachesbr.c */
extern int rcachesw;
extern int wcachesw;
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;
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 */
void flush_errors (void);
/* mhstoresbr.c */
-void store_all_messages (CT *);
+void store_all_messages (mhstoreinfo_t);
/* mhfree.c */
extern CT *cts;
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; }
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);
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;
/*
* Cache the current directory before we do any chdirs()'s.
*/
- cwd = getcpy (pwd());
+ cwd = add(pwd(), NULL);
if (!context_find ("path"))
free (path ("./", TFOLDER));
/*
* 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 */
#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
/*
* 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
*/
void
-store_all_messages (CT *cts)
+store_all_messages (mhstoreinfo_t info)
{
CT ct, *ctp;
char *cp;
* 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 ();
*/
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;
*/
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);
*/
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);
}
*/
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);
/*
: (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);
}
}
- return store_content (ct, NULL);
+ return store_content (ct, NULL, info);
}
*/
static int
-store_multi (CT ct)
+store_multi (CT ct, mhstoreinfo_t info)
{
int result;
struct multipart *m = (struct multipart *) ct->c_ctparams;
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)
*/
static int
-store_partial (CT ct)
+store_partial (CT ct, mhstoreinfo_t info)
{
int cur, hi, i;
CT p, *ctp, *ctq;
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;
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;
ctq = base;
ct = *ctq++;
- if (store_content (ct, NULL) == NOTOK) {
+ if (store_content (ct, NULL, info) == NOTOK) {
losing:
free ((char *) base);
return NOTOK;
for (; *ctq; ctq++) {
p = *ctq;
- if (store_content (p, ct) == NOTOK)
+ if (store_content (p, ct, info) == NOTOK)
goto 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;
* Check if the parameters for the external body
* specified a filename.
*/
- if (autosw) {
+ if (info->autosw) {
char *cp;
if ((cp = e->eb_name)
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;
*/
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;
* 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;
}
}
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;
/******************************************************************************/
/* -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;
}
/* Should never get here. */
advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
free (buffer);
- ++files_not_clobbered;
return NULL;
}
advise (NULL, "will not overwrite %s, too many versions", buffer);
free (buffer);
buffer = NULL;
- ++files_not_clobbered;
}
return buffer;
static char *
-clobber_check (char *original_file) {
+clobber_check (char *original_file, mhstoreinfo_t info) {
/* clobber policy return value
* -------------- ------------
* -always 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;
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;
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))) {
case NMH_NO:
free (file);
file = NULL;
- ++files_not_clobbered;
+ ++info->files_not_clobbered;
break;
case NMH_RENAME: {
char buf[PATH_MAX];
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) {
advise (NULL, "will not overwrite %s with -clobber never", file);
free (file);
file = NULL;
- ++files_not_clobbered;
+ ++info->files_not_clobbered;
}
break;
}
--- /dev/null
+/*
+ * 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 */