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/manpages/test-manpages \
test/mhbuild/test-attach \
test/mhbuild/test-cte \
+ test/mhbuild/test-ext-params \
test/mhbuild/test-forw test/mhbuild/test-header-encode \
test/mhbuild/test-utf8-body \
test/mhfixmsg/test-mhfixmsg \
- test/mhlist/test-mhlist test/mhmail/test-mhmail \
+ 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_burst_SOURCES = uip/burst.c uip/mhparse.c uip/mhmisc.c uip/mhfree.c \
uip/mhcachesbr.c uip/md5.c
- uip_burst_LDADD = $(LDADD) $(POSTLINK)
+ uip_burst_LDADD = $(LDADD) $(ICONVLIB) $(POSTLINK)
uip_comp_SOURCES = uip/comp.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \
uip/annosbr.c uip/distsbr.c
uip_mhbuild_SOURCES = uip/mhbuild.c uip/mhbuildsbr.c uip/mhcachesbr.c \
uip/mhlistsbr.c uip/mhoutsbr.c uip/mhmisc.c \
uip/mhfree.c uip/mhparse.c uip/md5.c
- uip_mhbuild_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK)
+ uip_mhbuild_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
uip_mhfixmsg_SOURCES = uip/mhfixmsg.c uip/mhparse.c uip/mhcachesbr.c \
uip/mhoutsbr.c uip/mhmisc.c uip/mhfree.c \
uip_mhlist_SOURCES = uip/mhlist.c uip/mhparse.c uip/mhcachesbr.c \
uip/mhlistsbr.c uip/mhmisc.c uip/mhfree.c uip/md5.c
- uip_mhlist_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK)
+ uip_mhlist_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
uip_mhn_SOURCES = uip/mhn.c uip/mhparse.c uip/mhcachesbr.c uip/mhshowsbr.c \
uip/mhlistsbr.c uip/mhstoresbr.c uip/mhmisc.c uip/mhfree.c \
uip_mhtest_SOURCES = uip/mhtest.c uip/mhparse.c uip/mhcachesbr.c \
uip/mhoutsbr.c uip/mhmisc.c uip/mhfree.c \
uip/md5.c
- uip_mhtest_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK)
+ 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)
uip_slocal_SOURCES = uip/slocal.c uip/aliasbr.c uip/dropsbr.c
uip_slocal_LDADD = $(LDADD) $(NDBM_LIBS) $(POSTLINK)
- uip_viamail_SOURCES = uip/viamail.c uip/mhmisc.c uip/mhoutsbr.c uip/sendsbr.c \
+ uip_viamail_SOURCES = uip/viamail.c uip/mhmisc.c uip/sendsbr.c \
uip/annosbr.c uip/distsbr.c
uip_viamail_LDADD = $(LDADD) $(POSTLINK)
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.
-----------------
OBSOLETE FEATURES
{MHTMPDIR environment variable, TMPDIR environment variable, MH Path}.
They are no longer be stored in the location specified by the TMP
environment variable.
+- Instead of printing PostScript attachments, by default, from mhshow,
+ try to find a suitable viewer.
-------------------
DEPRECATED FEATURES
$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.
/*
* Abstract types for MIME parsing/building
*/
- typedef struct cefile *CE;
- typedef struct CTinfo *CI;
- typedef struct Content *CT;
+ typedef struct cefile *CE;
+ typedef struct CTinfo *CI;
+ typedef struct Content *CT;
+ typedef struct Parameter *PM;
/*
* type for Init function (both type and transfer encoding)
HF next; /* link to next header field */
};
+ /*
+ * Structure for holding MIME parameter elements.
+ */
+ struct Parameter {
+ char *pm_name; /* Parameter name */
+ char *pm_value; /* Parameter value */
+ char *pm_charset; /* Parameter character set (optional) */
+ char *pm_lang; /* Parameter language tag (optional) */
+ PM pm_next; /* Pointer to next element */
+ };
+
/*
* Structure for storing parsed elements
* of the Content-Type component.
struct CTinfo {
char *ci_type; /* content type */
char *ci_subtype; /* content subtype */
- char *ci_attrs[NPARMS + 2]; /* attribute names */
- char *ci_values[NPARMS]; /* attribute values */
+ PM ci_first_pm; /* Pointer to first MIME parameter */
+ PM ci_last_pm; /* Pointer to last MIME parameter */
char *ci_comment; /* RFC-822 comments */
char *ci_magic;
};
char *c_id; /* Content-ID: */
char *c_descr; /* Content-Description: */
char *c_dispo; /* Content-Disposition: */
+ char *c_dispo_type; /* Type of Content-Disposition */
+ PM c_dispo_first; /* Pointer to first disposition parm */
+ PM c_dispo_last; /* Pointer to last disposition parm */
char *c_partno; /* within multipart content */
/* Content-Type info */
* CE_QUOTED.
* maxunencoded - The maximum line length before the default encoding for
* text parts is quoted-printable.
+ * verbose - If 1, output verbose information during message composition
*
* Returns a CT structure describing the resulting MIME message. If the
* -auto flag is set and a MIME-Version header is encountered, the return
* value is NULL.
*/
CT build_mime (char *infile, int autobuild, int dist, int directives,
- int encoding, size_t maxunencoded);
+ int encoding, size_t maxunencoded, int verbose);
int add_header (CT, char *, char *);
int get_ctinfo (char *, CT, int);
const struct str2init *get_ct_init (int);
const char *ce_str (int);
const struct str2init *get_ce_method (const char *);
- int parse_header_attrs (const char *, int, char **, CI, int *);
- char *update_attr (char *, const char *, const char *e);
+char *content_charset (CT);
+int convert_charset (CT, char *, int *);
+ /*
+ * Given a list of messages, display information about them on standard
+ * output.
+ *
+ * Argumens are:
+ *
+ * cts - An array of CT elements of messages that need to be
+ * displayed. Array is terminated by a NULL.
+ * headsw - If 1, display a column header.
+ * sizesw - If 1, display the size of the part.
+ * verbosw - If 1, display verbose information
+ * debugsw - If 1, turn on debugging for the output.
+ * disposw - If 1, display MIME part disposition information.
+ *
+ */
+ void list_all_messages(CT *cts, int headsw, int sizesw, int verbosw,
+ int debugsw, int disposw);
+
+ /*
+ * List the content information of a single MIME part on stdout.
+ *
+ * Arguments are:
+ *
+ * ct - MIME Content structure to display.
+ * toplevel - If set, we're at the top level of a message
+ * realsize - If set, determine the real size of the content
+ * verbose - If set, output verbose information
+ * debug - If set, turn on debugging for the output
+ * dispo - If set, display MIME part disposition information.
+ *
+ * Returns OK on success, NOTOK otherwise.
+ */
+ int list_content(CT ct, int toplevel, int realsize, int verbose, int debug,
+ int dispo);
+
+ /*
+ * Display content-appropriate information on MIME parts, decending recursively
+ * into multipart content if appropriate. Uses list_content() for displaying
+ * generic information.
+ *
+ * Arguments and return value are the same as list_content().
+ */
+ int list_switch(CT ct, int toplevel, int realsize, int verbose, int debug,
+ int dispo);
+
+ /*
+ * Given a linked list of parameters, build an output string for them. This
+ * string is designed to be concatenated on an already-built header.
+ *
+ * Arguments are:
+ *
+ * initialwidth - Current width of the header. Used to compute when to wrap
+ * parameters on the first line. The following lines will
+ * be prefixed by a tab (\t) character.
+ * params - Pointer to head of linked list of parameters.
+ * offsetout - The final line offset after all the parameters have been
+ * output. May be NULL.
+ * external - If set, outputting an external-body type and will not
+ * output a "body" parameter.
+
+ * Returns a pointer to the resulting parameter string. This string must
+ * be free()'d by the caller. Returns NULL on error.
+ */
+ char *output_params(size_t initialwidth, PM params, int *offsetout,
+ int external);
+
+ /*
+ * Add a parameter to the parameter linked list.
+ *
+ * Arguments are:
+ *
+ * first - Pointer to head of linked list
+ * last - Pointer to tail of linked list
+ * name - Name of parameter
+ * value - Value of parameter
+ * nocopy - If set, will use the pointer values directly for "name"
+ * and "value" instead of making their own copy. These
+ * pointers will be free()'d later by the MIME routines, so
+ * they should not be used after calling this function!
+ *
+ * Returns allocated parameter element
+ */
+ 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.
+ *
+ * Arguments are:
+ *
+ * first - Pointer to head of parameter linked list.
+ * name - Name of parameter.
+ * replace - If characters in the parameter list cannot be converted to
+ * the local character set, replace with this character.
+ * fetchonly - If true, return pointer to original value, no conversion
+ * performed.
+ *
+ * Returns parameter value if found, NULL otherwise. Memory must be free()'d
+ * unless fetchonly is set.
+ */
+
+ char *get_param(PM first, const char *name, char replace, int fetchonly);
+
+ /*
+ * Fetch a parameter value from a parameter structure, converting it to
+ * the local character set.
+ *
+ * Arguments are:
+ *
+ * pm - Pointer to parameter structure
+ * replace - If characters in the parameter list cannot be converted to
+ * the local character set, replace with this character.
+ *
+ * Returns a pointer to the parameter value. Memory is stored in an
+ * internal buffer, so the returned value is only valid until the next
+ * call to get_param_value() or get_param() (get_param() uses get_param_value()
+ * internally).
+ */
+ char *get_param_value(PM pm, char replace);
+
extern int checksw; /* Add Content-MD5 field */
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.
+ *
+ * Arguments are:
+ *
+ * byte1 - First character of Q-P representation
+ * byte2 - Second character of Q-P representation
+ *
+ * Returns the decoded value, -1 if the conversion failed.
+ */
+ int decode_qp(unsigned char byte1, unsigned char byte2);
+
int default_done (int);
/*
}
}
+
+/* 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.
+ *
+ * Scan up until the given endpoint (but not the actual endpoint itself).
+ * If the endpoint is NULL, scan until a '\0' is reached.
+ */
+
+ int
+ contains8bit(const char *start, const char *end)
+ {
+ if (! start)
+ return 0;
+
+ while (*start != '\0' && (!end || (start < end)))
+ if (! isascii((unsigned char) *start++))
+ return 1;
+
+ return 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>
#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 void
copy_ctinfo (CI dest, CI src) {
- char **s_ap, **d_ap, **s_vp, **d_vp;
+ PM s_pm, d_pm;
dest->ci_type = src->ci_type ? add (src->ci_type, NULL) : NULL;
dest->ci_subtype = src->ci_subtype ? add (src->ci_subtype, NULL) : NULL;
- for (s_ap = src->ci_attrs, d_ap = dest->ci_attrs,
- s_vp = src->ci_values, d_vp = dest->ci_values;
- *s_ap;
- ++s_ap, ++d_ap, ++s_vp, ++d_vp) {
- *d_ap = add (*s_ap, NULL);
- *d_vp = *s_vp;
+ for (s_pm = src->ci_first_pm; s_pm; s_pm = s_pm->pm_next) {
+ d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name,
+ s_pm->pm_value, 0);
+ if (s_pm->pm_charset)
+ d_pm->pm_charset = getcpy(s_pm->pm_charset);
+ if (s_pm->pm_lang)
+ d_pm->pm_lang = getcpy(s_pm->pm_lang);
}
- *d_ap = NULL;
dest->ci_comment = src->ci_comment ? add (src->ci_comment, NULL) : NULL;
dest->ci_magic = src->ci_magic ? add (src->ci_magic, NULL) : NULL;
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;
}
CT ct;
struct part *p;
struct multipart *m;
- char *cp;
const struct str2init *ctinit;
if ((ct = (CT) calloc (1, sizeof *ct)) == NULL)
ct->c_ctinfo.ci_subtype = add (subtypename, NULL);
}
- /*
- * The boundary indicator string must be stored in a
- * c_ctinfo.ci_values slot. We use the first slot because this is
- * a new object. ci_attrs[0] can be anything, it is never output.
- * It is chosen to be very unlikely to collide with a real
- * attribute, to contains text that ci_values[0] can point to, and
- * gives traceability back to the origin of this part, though that
- * would only be useful in a debugger.
- */
- name = concat (" ", typename, "/", subtypename, boundary_indicator,
- boundary, NULL);
- if ((cp = strstr (name, boundary_indicator))) {
- ct->c_ctinfo.ci_attrs[0] = name;
- ct->c_ctinfo.ci_attrs[1] = NULL;
- /* ci_values don't get free'd, so point into ci_attrs. */
- ct->c_ctinfo.ci_values[0] = cp + strlen (boundary_indicator);
- }
+ add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
+ "boundary", boundary, 0);
p = (struct part *) mh_xmalloc (sizeof *p);
p->mp_next = (struct part *) mh_xmalloc (sizeof *p->mp_next);
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)
{
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;
/* mhshowsbr.c */
void show_all_messages (CT *);
- /* mhlistsbr.c */
- void list_all_messages (CT *, int, int, int, int);
-
/* 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));
* List the message content
*/
if (listsw)
- list_all_messages (cts, headsw, sizesw, verbosw, debugsw);
+ list_all_messages (cts, headsw, sizesw, verbosw, debugsw, 0);
/*
* 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 */
#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 prototypes
*/
static CT get_content (FILE *, char *, int);
- static int get_comment (const char *, CI, char **, int);
+ static int get_comment (const char *, const char *, char **, char **);
static int InitGeneric (CT);
static int InitText (CT);
static int get_leftover_mp_content (CT, int);
static int InitURL (CT);
static int openURL (CT, char **);
+ static int parse_header_attrs (const char *, const char *, char **, PM *,
+ PM *, char **);
+ static size_t param_len(PM, int, size_t, int *, int *, size_t *);
+ static size_t encode_param(PM, char *, size_t, size_t, size_t, int);
+ static size_t normal_param(PM, char *, size_t, size_t, size_t);
+ static int get_dispo (char *, CT, int);
struct str2init str2cts[] = {
{ "application", CT_APPLICATION, InitApplication },
fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
if (*cp == '(' &&
- get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK)
+ get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK)
goto out;
for (dp = cp; istoken (*dp); dp++)
fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
if (*cp == '(' &&
- get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) {
+ get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) {
free (ep);
goto out;
}
}
else if (!strcasecmp (hp->name, DISPO_FIELD)) {
/* Get Content-Disposition field */
- ct->c_dispo = add (hp->value, ct->c_dispo);
+ if (get_dispo(hp->value, ct, 0) == NOTOK)
+ goto out;
}
next_header:
}
- /* Make sure that buf contains at least one appearance of name,
- followed by =. If not, insert both name and value, just after
- first semicolon, if any. Note that name should not contain a
- trailing =. And quotes will be added around the value. Typical
- usage: make sure that a Content-Disposition header contains
- filename="foo". If it doesn't and value does, use value from
- that. */
- static char *
- incl_name_value (char *buf, char *name, char *value) {
- char *newbuf = buf;
-
- /* Assume that name is non-null. */
- if (buf && value) {
- char *name_plus_equal = concat (name, "=", NULL);
-
- if (! strstr (buf, name_plus_equal)) {
- char *insertion;
- char *cp, *prefix, *suffix;
-
- /* Trim trailing space, esp. newline. */
- for (cp = &buf[strlen (buf) - 1];
- cp >= buf && isspace ((unsigned char) *cp);
- --cp) {
- *cp = '\0';
- }
-
- insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
-
- /* Insert at first semicolon, if any. If none, append to
- end. */
- prefix = add (buf, NULL);
- if ((cp = strchr (prefix, ';'))) {
- suffix = concat (cp, NULL);
- *cp = '\0';
- newbuf = concat (prefix, insertion, suffix, "\n", NULL);
- free (suffix);
- } else {
- /* Append to end. */
- newbuf = concat (buf, insertion, "\n", NULL);
- }
-
- free (prefix);
- free (insertion);
- free (buf);
- }
-
- free (name_plus_equal);
- }
-
- return newbuf;
- }
-
- /* Extract just name_suffix="foo", if any, from value. If there isn't
- one, return the entire value. Note that, for example, a name_suffix
- of name will match filename="foo", and return foo. */
- static char *
- extract_name_value (char *name_suffix, char *value) {
- char *extracted_name_value = value;
- char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
- char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
- char *cp;
-
- free (name_suffix_plus_quote);
- if (name_suffix_equals) {
- char *name_suffix_begin;
-
- /* Find first \". */
- for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
- name_suffix_begin = ++cp;
- /* Find second \". */
- for (; *cp != '"'; ++cp) /* empty */;
-
- extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
- memcpy (extracted_name_value,
- name_suffix_begin,
- cp - name_suffix_begin);
- extracted_name_value[cp - name_suffix_begin] = '\0';
- }
-
- return extracted_name_value;
- }
-
/*
* Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
* directives. Fills in the information of the CTinfo structure.
int
get_ctinfo (char *cp, CT ct, int magic)
{
- int i;
char *dp;
char c;
CI ci;
int status;
ci = &ct->c_ctinfo;
- i = strlen (invo_name) + 2;
/* store copy of Content-Type line */
cp = ct->c_ctline = add (cp, NULL);
if (debugsw)
fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
- if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+ if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+ &ci->ci_comment) == NOTOK)
return NOTOK;
for (dp = cp; istoken (*dp); dp++)
while (isspace ((unsigned char) *cp))
cp++;
- if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+ if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+ &ci->ci_comment) == NOTOK)
return NOTOK;
if (*cp != '/') {
while (isspace ((unsigned char) *cp))
cp++;
- if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+ if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+ &ci->ci_comment) == NOTOK)
return NOTOK;
for (dp = cp; istoken (*dp); dp++)
while (isspace ((unsigned char) *cp))
cp++;
- if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+ if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+ &ci->ci_comment) == NOTOK)
return NOTOK;
- if (parse_header_attrs (ct->c_file, i, &cp, ci, &status) == NOTOK) {
- return status;
+ if ((status = parse_header_attrs (ct->c_file, TYPE_FIELD, &cp,
+ &ci->ci_first_pm, &ci->ci_last_pm,
+ &ci->ci_comment)) != OK) {
+ return status == NOTOK ? NOTOK : OK;
}
/*
* Get any {Content-Disposition} given in buffer.
*/
if (magic && *cp == '{') {
- ct->c_dispo = ++cp;
+ ++cp;
for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
if (*dp == '}')
break;
c = *dp;
*dp = '\0';
- if (*ct->c_dispo)
- ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
- else
- ct->c_dispo = NULL;
+
+ if (get_dispo(cp, ct, 1) != OK)
+ return NOTOK;
+
*dp++ = c;
cp = dp;
have a *filename=, extract it from the magic contents.
The r1bindex call skips any leading directory
components. */
- if (ct->c_dispo)
- ct->c_dispo =
- incl_name_value (ct->c_dispo,
- "filename",
- r1bindex (extract_name_value ("name",
- ci->
- ci_magic),
- '/'));
+ if (ct->c_dispo_type &&
+ !get_param(ct->c_dispo_first, "filename", '_', 1)) {
+ add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename",
+ r1bindex(ci->ci_magic, '/'), 0);
+ }
}
else
advise (NULL,
- "extraneous information in message %s's %s: field\n%*.*s(%s)",
- ct->c_file, TYPE_FIELD, i, i, "", cp);
+ "extraneous information in message %s's %s: field\n%*s(%s)",
+ ct->c_file, TYPE_FIELD, strlen(invo_name) + 2, "", cp);
+ }
+
+ return OK;
+ }
+
+
+ /*
+ * Parse out a Content-Disposition header. A lot of this is cribbed from
+ * get_ctinfo().
+ */
+ static int
+ get_dispo (char *cp, CT ct, int buildflag)
+ {
+ char *dp, *dispoheader;
+ char c;
+ int status;
+
+ /*
+ * Save the whole copy of the Content-Disposition header, unless we're
+ * processing a mhbuild directive. A NULL c_dispo will be a flag to
+ * mhbuild that the disposition header needs to be generated at that
+ * time.
+ */
+
+ dispoheader = cp = add(cp, NULL);
+
+ while (isspace ((unsigned char) *cp)) /* trim leading spaces */
+ cp++;
+
+ /* change newlines to spaces */
+ for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
+ *dp++ = ' ';
+
+ /* trim trailing spaces */
+ for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
+ if (!isspace ((unsigned char) *dp))
+ break;
+ *++dp = '\0';
+
+ if (debugsw)
+ fprintf (stderr, "%s: %s\n", DISPO_FIELD, cp);
+
+ if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) ==
+ NOTOK) {
+ free(dispoheader);
+ return NOTOK;
+ }
+
+ for (dp = cp; istoken (*dp); dp++)
+ continue;
+ c = *dp, *dp = '\0';
+ ct->c_dispo_type = add (cp, NULL); /* store disposition type */
+ *dp = c, cp = dp;
+
+ if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == NOTOK)
+ return NOTOK;
+
+ if ((status = parse_header_attrs (ct->c_file, DISPO_FIELD, &cp,
+ &ct->c_dispo_first, &ct->c_dispo_last,
+ NULL)) != OK) {
+ if (status == NOTOK) {
+ free(dispoheader);
+ return NOTOK;
+ }
+ } else if (*cp) {
+ advise (NULL,
+ "extraneous information in message %s's %s: field\n%*s(%s)",
+ ct->c_file, DISPO_FIELD, strlen(invo_name) + 2, "", cp);
}
+ if (buildflag)
+ free(dispoheader);
+ else
+ ct->c_dispo = dispoheader;
+
return OK;
}
static int
- get_comment (const char *filename, CI ci, char **ap, int istype)
+ get_comment (const char *filename, const char *fieldname, char **ap,
+ char **commentp)
{
int i;
char *bp, *cp;
case '\0':
invalid:
advise (NULL, "invalid comment in message %s's %s: field",
- filename, istype ? TYPE_FIELD : VRSN_FIELD);
+ filename, fieldname);
return NOTOK;
case '\\':
}
*bp = '\0';
- if (istype) {
- if ((dp = ci->ci_comment)) {
- ci->ci_comment = concat (dp, " ", buffer, NULL);
+ if (commentp) {
+ if ((dp = *commentp)) {
+ *commentp = concat (dp, " ", buffer, NULL);
free (dp);
} else {
- ci->ci_comment = add (buffer, NULL);
+ *commentp = add (buffer, NULL);
}
}
{
char buffer[BUFSIZ];
char *chset = NULL;
- char **ap, **ep, *cp;
+ char *cp;
+ PM pm;
struct k2v *kv;
struct text *t;
CI ci = &ct->c_ctinfo;
ct->c_ctparams = (void *) t;
/* scan for charset parameter */
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
- if (!strcasecmp (*ap, "charset"))
+ for (pm = ci->ci_first_pm; pm; pm = pm->pm_next)
+ if (!strcasecmp (pm->pm_name, "charset"))
break;
/* check if content specified a character set */
- if (*ap) {
- chset = *ep;
+ if (pm) {
+ chset = pm->pm_value;
t->tx_charset = CHARSET_SPECIFIED;
} else {
t->tx_charset = CHARSET_UNSPECIFIED;
{
int inout;
long last, pos;
- char *cp, *dp, **ap, **ep;
+ char *cp, *dp;
+ PM pm;
char *bp, buffer[BUFSIZ];
struct multipart *m;
struct k2v *kv;
* required for multipart messages.
*/
bp = 0;
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "boundary")) {
- bp = *ep;
+ for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
+ if (!strcasecmp (pm->pm_name, "boundary")) {
+ bp = pm->pm_value;
break;
}
}
/* complain if boundary parameter is missing */
- if (!*ap) {
+ if (!pm) {
advise (NULL,
"a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
case MESSAGE_PARTIAL:
{
- char **ap, **ep;
+ PM pm;
struct partial *p;
if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
ct->c_ctparams = (void *) p;
/* scan for parameters "id", "number", and "total" */
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "id")) {
- p->pm_partid = add (*ep, NULL);
+ for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
+ if (!strcasecmp (pm->pm_name, "id")) {
+ p->pm_partid = add (pm->pm_value, NULL);
continue;
}
- if (!strcasecmp (*ap, "number")) {
- if (sscanf (*ep, "%d", &p->pm_partno) != 1
+ if (!strcasecmp (pm->pm_name, "number")) {
+ if (sscanf (pm->pm_value, "%d", &p->pm_partno) != 1
|| p->pm_partno < 1) {
invalid_param:
advise (NULL,
"invalid %s parameter for \"%s/%s\" type in message %s's %s field",
- *ap, ci->ci_type, ci->ci_subtype,
+ pm->pm_name, ci->ci_type, ci->ci_subtype,
ct->c_file, TYPE_FIELD);
return NOTOK;
}
continue;
}
- if (!strcasecmp (*ap, "total")) {
- if (sscanf (*ep, "%d", &p->pm_maxno) != 1
+ if (!strcasecmp (pm->pm_name, "total")) {
+ if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1
|| p->pm_maxno < 1)
goto invalid_param;
continue;
int
params_external (CT ct, int composing)
{
- char **ap, **ep;
+ PM pm;
struct exbody *e = (struct exbody *) ct->c_ctparams;
CI ci = &ct->c_ctinfo;
ct->c_ceopenfnx = NULL;
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "access-type")) {
+ for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
+ if (!strcasecmp (pm->pm_name, "access-type")) {
struct str2init *s2i;
CT p = e->eb_content;
for (s2i = str2methods; s2i->si_key; s2i++)
- if (!strcasecmp (*ep, s2i->si_key))
+ if (!strcasecmp (pm->pm_value, s2i->si_key))
break;
if (!s2i->si_key) {
- e->eb_access = *ep;
+ e->eb_access = pm->pm_value;
e->eb_flags = NOTOK;
p->c_encoding = CE_EXTERNAL;
continue;
return NOTOK;
continue;
}
- if (!strcasecmp (*ap, "name")) {
- e->eb_name = *ep;
+ if (!strcasecmp (pm->pm_name, "name")) {
+ e->eb_name = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "permission")) {
- e->eb_permission = *ep;
+ if (!strcasecmp (pm->pm_name, "permission")) {
+ e->eb_permission = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "site")) {
- e->eb_site = *ep;
+ if (!strcasecmp (pm->pm_name, "site")) {
+ e->eb_site = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "directory")) {
- e->eb_dir = *ep;
+ if (!strcasecmp (pm->pm_name, "directory")) {
+ e->eb_dir = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "mode")) {
- e->eb_mode = *ep;
+ if (!strcasecmp (pm->pm_name, "mode")) {
+ e->eb_mode = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "size")) {
- sscanf (*ep, "%lu", &e->eb_size);
+ if (!strcasecmp (pm->pm_name, "size")) {
+ sscanf (pm->pm_value, "%lu", &e->eb_size);
continue;
}
- if (!strcasecmp (*ap, "server")) {
- e->eb_server = *ep;
+ if (!strcasecmp (pm->pm_name, "server")) {
+ e->eb_server = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "subject")) {
- e->eb_subject = *ep;
+ if (!strcasecmp (pm->pm_name, "subject")) {
+ e->eb_subject = pm->pm_value;
continue;
}
- if (!strcasecmp (*ap, "url")) {
+ if (!strcasecmp (pm->pm_name, "url")) {
/*
* According to RFC 2017, we have to remove all whitespace from
* the URL
*/
- char *u, *p = *ep;
- e->eb_url = u = mh_xmalloc(strlen(*ep) + 1);
+ char *u, *p = pm->pm_value;
+ e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 1);
for (; *p != '\0'; p++) {
if (! isspace((unsigned char) *p))
*u = '\0';
continue;
}
- if (composing && !strcasecmp (*ap, "body")) {
- e->eb_body = getcpy (*ep);
+ if (composing && !strcasecmp (pm->pm_name, "body")) {
+ e->eb_body = getcpy (pm->pm_value);
continue;
}
}
}
if (ct->c_type == CT_MULTIPART) {
- char **ap, **ep;
CI ci = &ct->c_ctinfo;
+ char *buffer;
len = 0;
fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
+ 1 + strlen (ci->ci_subtype);
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- putc (';', ce->ce_fp);
- len++;
-
- snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
+ buffer = output_params(len, ci->ci_first_pm, &len, 0);
- if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
- fputs ("\n\t", ce->ce_fp);
- len = 8;
- } else {
- putc (' ', ce->ce_fp);
- len++;
- }
- fprintf (ce->ce_fp, "%s", buffer);
- len += cc;
+ if (buffer) {
+ fputs (buffer, ce->ce_fp);
+ free(buffer);
}
if (ci->ci_comment) {
return NULL;
}
- int
- parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci,
- int *status) {
- char **attr = ci->ci_attrs;
+ /*
+ * Parse a series of MIME attributes (or parameters) given a header as
+ * input.
+ *
+ * Arguments include:
+ *
+ * filename - Name of input file (for error messages)
+ * fieldname - Name of field being processed
+ * headerp - Pointer to pointer of the beginning of the MIME attributes.
+ * Updated to point to end of attributes when finished.
+ * param_head - Pointer to head of parameter list
+ * param_tail - Pointer to tail of parameter list
+ * commentp - Pointer to header comment pointer (may be NULL)
+ *
+ * Returns OK if parsing was successful, NOTOK if parsing failed, and
+ * DONE to indicate a benign error (minor parsing error, but the program
+ * should continue).
+ */
+
+ static int
+ parse_header_attrs (const char *filename, const char *fieldname,
+ char **header_attrp, PM *param_head, PM *param_tail,
+ char **commentp)
+ {
char *cp = *header_attrp;
+ PM pm;
+ struct sectlist {
+ char *value;
+ int index;
+ int len;
+ struct sectlist *next;
+ } *sp, *sp2;
+ struct parmlist {
+ char *name;
+ char *charset;
+ char *lang;
+ struct sectlist *sechead;
+ struct parmlist *next;
+ } *pp, *pp2, *phead = NULL;
while (*cp == ';') {
- char *dp, *vp, *up, c;
-
- /* Relies on knowledge of this declaration:
- * char *ci_attrs[NPARMS + 2];
- */
- if (attr >= ci->ci_attrs + sizeof ci->ci_attrs/sizeof (char *) - 2) {
- advise (NULL,
- "too many parameters in message %s's %s: field (%d max)",
- filename, TYPE_FIELD, NPARMS);
- *status = NOTOK;
- return NOTOK;
- }
+ char *dp, *vp, *up, *nameptr, *valptr, *charset = NULL, *lang = NULL;
+ int encoded = 0, partial = 0, len = 0, index = 0;
cp++;
while (isspace ((unsigned char) *cp))
cp++;
if (*cp == '(' &&
- get_comment (filename, ci, &cp, 1) == NOTOK) {
- *status = NOTOK;
+ get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
return NOTOK;
}
advise (NULL,
"extraneous trailing ';' in message %s's %s: "
"parameter list",
- filename, TYPE_FIELD);
- *status = OK;
- return NOTOK;
+ filename, fieldname);
+ return DONE;
}
/* down case the attribute name */
if (dp == cp || *dp != '=') {
advise (NULL,
"invalid parameter in message %s's %s: "
- "field\n%*.*sparameter %s (error detected at offset %d)",
- filename, TYPE_FIELD, len, len, "", cp, dp - cp);
- *status = NOTOK;
+ "field\n%*sparameter %s (error detected at offset %d)",
+ filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
return NOTOK;
}
- vp = (*attr = add (cp, NULL)) + (up - cp);
- *vp = '\0';
+ /*
+ * To handle RFC 2231, we have to deal with the following extensions:
+ *
+ * name*=encoded-value
+ * name*<N>=part-N-of-a-parameter-value
+ * name*<N>*=encoded-part-N-of-a-parameter-value
+ *
+ * So the rule is:
+ * If there's a * right before the equal sign, it's encoded.
+ * If there's a * and one or more digits, then it's section N.
+ *
+ * Remember we can have one or the other, or both. cp points to
+ * beginning of name, up points past the last character in the
+ * parameter name.
+ */
+
+ for (vp = cp; vp < up; vp++) {
+ if (*vp == '*' && vp < up - 1) {
+ partial = 1;
+ continue;
+ } else if (*vp == '*' && vp == up - 1) {
+ encoded = 1;
+ } else if (partial) {
+ if (isdigit((unsigned char) *vp))
+ index = *vp - '0' + index * 10;
+ else {
+ advise (NULL, "invalid parameter index in message %s's "
+ "%s: field\n%*s(parameter %s)", filename,
+ fieldname, strlen(invo_name) + 2, "", cp);
+ return NOTOK;
+ }
+ } else {
+ len++;
+ }
+ }
+
+ /*
+ * Break out the parameter name and value sections and allocate
+ * memory for each.
+ */
+
+ nameptr = mh_xmalloc(len + 1);
+ strncpy(nameptr, cp, len);
+ nameptr[len] = '\0';
+
for (dp++; isspace ((unsigned char) *dp);)
dp++;
- /* Now store the attribute value. */
- ci->ci_values[attr - ci->ci_attrs] = vp = *attr + (dp - cp);
+ if (encoded) {
+ /*
+ * Single quotes delimit the character set and language tag.
+ * They are required on the first section (or a complete
+ * parameter).
+ */
+ if (index == 0) {
+ vp = dp;
+ while (*vp != '\'' && !isspace((unsigned char) *vp) &&
+ *vp != '\0')
+ vp++;
+ if (*vp == '\'') {
+ if (vp != dp) {
+ len = vp - dp;
+ charset = mh_xmalloc(len + 1);
+ strncpy(charset, dp, len);
+ charset[len] = '\0';
+ } else {
+ charset = NULL;
+ }
+ vp++;
+ } else {
+ advise(NULL, "missing charset in message %s's %s: "
+ "field\n%*s(parameter %s)", filename, fieldname,
+ strlen(invo_name) + 2, "", nameptr);
+ free(nameptr);
+ return NOTOK;
+ }
+ dp = vp;
+
+ while (*vp != '\'' && !isspace((unsigned char) *vp) &&
+ *vp != '\0')
+ vp++;
+
+ if (*vp == '\'') {
+ if (vp != dp) {
+ len = vp - dp;
+ lang = mh_xmalloc(len + 1);
+ strncpy(lang, dp, len);
+ lang[len] = '\0';
+ } else {
+ lang = NULL;
+ }
+ vp++;
+ } else {
+ advise(NULL, "missing language tag in message %s's %s: "
+ "field\n%*s(parameter %s)", filename, fieldname,
+ strlen(invo_name) + 2, "", nameptr);
+ free(nameptr);
+ if (charset)
+ free(charset);
+ return NOTOK;
+ }
+
+ dp = vp;
+ }
+
+ /*
+ * At this point vp should be pointing at the beginning
+ * of the encoded value/section. Continue until we reach
+ * the end or get whitespace. But first, calculate the
+ * length so we can allocate the correct buffer size.
+ */
+
+ for (vp = dp, len = 0; istoken(*vp); vp++) {
+ if (*vp == '%') {
+ if (*(vp + 1) == '\0' ||
+ !isxdigit((unsigned char) *(vp + 1)) ||
+ *(vp + 2) == '\0' ||
+ !isxdigit((unsigned char) *(vp + 2))) {
+ advise(NULL, "invalid encoded sequence in message "
+ "%s's %s: field\n%*s(parameter %s)",
+ filename, fieldname, strlen(invo_name) + 2,
+ "", nameptr);
+ free(nameptr);
+ if (charset)
+ free(charset);
+ if (lang)
+ free(lang);
+ return NOTOK;
+ }
+ vp += 2;
+ }
+ len++;
+ }
+
+ up = valptr = mh_xmalloc(len + 1);
- if (*dp == '"') {
- for (cp = ++dp, dp = vp;;) {
- switch (c = *cp++) {
+ for (vp = dp; istoken(*vp); vp++) {
+ if (*vp == '%') {
+ *up++ = decode_qp(*(vp + 1), *(vp + 2));
+ vp += 2;
+ } else {
+ *up++ = *vp;
+ }
+ }
+
+ *up = '\0';
+ cp = vp;
+ } else {
+ /*
+ * A "normal" string. If it's got a leading quote, then we
+ * strip the quotes out. Otherwise go until we reach the end
+ * or get whitespace. Note we scan it twice; once to get the
+ * length, then the second time copies it into the destination
+ * buffer.
+ */
+
+ len = 0;
+
+ if (*dp == '"') {
+ for (cp = dp + 1;;) {
+ switch (*cp++) {
case '\0':
bad_quote:
advise (NULL,
"invalid quoted-string in message %s's %s: "
- "field\n%*.*s(parameter %s)",
- filename, TYPE_FIELD, len, len, "", *attr);
- *status = NOTOK;
+ "field\n%*s(parameter %s)",
+ filename, fieldname, strlen(invo_name) + 2, "",
+ nameptr);
+ free(nameptr);
+ if (charset)
+ free(charset);
+ if (lang)
+ free(lang);
return NOTOK;
+ case '"':
+ break;
case '\\':
- *dp++ = c;
- if ((c = *cp++) == '\0')
+ if (*++cp == '\0')
goto bad_quote;
- /* else fall... */
-
+ /* FALL THROUGH */
default:
- *dp++ = c;
+ len++;
continue;
+ }
+ break;
+ }
- case '"':
- *dp = '\0';
+ } else {
+ for (cp = dp; istoken (*cp); cp++) {
+ len++;
+ }
+ }
+
+ valptr = mh_xmalloc(len + 1);
+
+ if (*dp == '"') {
+ int i;
+ for (cp = dp + 1, vp = valptr, i = 0; i < len; i++) {
+ if (*cp == '\\') {
+ cp++;
+ }
+ *vp++ = *cp++;
+ }
+ cp++;
+ } else {
+ strncpy(valptr, cp = dp, len);
+ cp += len;
+ }
+
+ valptr[len] = '\0';
+ }
+
+ /*
+ * If 'partial' is set, we don't allocate a parameter now. We
+ * put it on the parameter linked list to be reassembled later.
+ *
+ * "phead" points to a list of all parameters we need to reassemble.
+ * Each parameter has a list of sections. We insert the sections in
+ * order.
+ */
+
+ if (partial) {
+ for (pp = phead; pp != NULL; pp = pp->next) {
+ if (strcasecmp(nameptr, pp->name) == 0)
+ break;
+ }
+
+ if (pp == NULL) {
+ pp = mh_xmalloc(sizeof(*pp));
+ memset(pp, 0, sizeof(*pp));
+ pp->name = nameptr;
+ pp->next = phead;
+ phead = pp;
+ }
+
+ /*
+ * Insert this into the section linked list
+ */
+
+ sp = mh_xmalloc(sizeof(*sp));
+ memset(sp, 0, sizeof(*sp));
+ sp->value = valptr;
+ sp->index = index;
+ sp->len = len;
+
+ if (pp->sechead == NULL || pp->sechead->index > index) {
+ sp->next = pp->sechead;
+ pp->sechead = sp;
+ } else {
+ for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) {
+ if (sp2->index == sp->index) {
+ advise (NULL, "duplicate index (%d) in message "
+ "%s's %s: field\n%*s(parameter %s)", sp->index,
+ filename, fieldname, strlen(invo_name) + 2, "",
+ nameptr);
+ return NOTOK;
+ }
+ if (sp2->index < sp->index &&
+ (sp2->next == NULL || sp2->next->index > sp->index)) {
+ sp->next = sp2->next;
+ sp2->next = sp;
break;
+ }
+ }
+
+ if (sp2 == NULL) {
+ advise(NULL, "Internal error: cannot insert partial "
+ "param in message %s's %s: field\n%*s(parameter %s)",
+ filename, fieldname, strlen(invo_name) + 2, "",
+ nameptr);
+ return NOTOK;
}
- break;
+ }
+
+ /*
+ * Save our charset and lang tags.
+ */
+
+ if (index == 0 && encoded) {
+ if (pp->charset)
+ free(pp->charset);
+ pp->charset = charset;
+ if (pp->lang)
+ free(pp->lang);
+ pp->lang = lang;
}
} else {
- for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
- continue;
- *dp = '\0';
- }
- if (!*vp) {
- advise (NULL,
- "invalid parameter in message %s's %s: "
- "field\n%*.*s(parameter %s)",
- filename, TYPE_FIELD, len, len, "", *attr);
- *status = NOTOK;
- return NOTOK;
+ pm = add_param(param_head, param_tail, nameptr, valptr, 1);
+ pm->pm_charset = charset;
+ pm->pm_lang = lang;
}
while (isspace ((unsigned char) *cp))
cp++;
if (*cp == '(' &&
- get_comment (filename, ci, &cp, 1) == NOTOK) {
- *status = NOTOK;
+ get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
return NOTOK;
}
+ }
+
+ /*
+ * Now that we're done, reassemble all of the partial parameters.
+ */
- ++attr;
+ for (pp = phead; pp != NULL; ) {
+ char *p, *q;
+ size_t tlen = 0;
+ int pindex = 0;
+ for (sp = pp->sechead; sp != NULL; sp = sp->next) {
+ if (sp->index != pindex++) {
+ advise(NULL, "missing section %d for parameter in "
+ "message %s's %s: field\n%*s(parameter %s)", pindex - 1,
+ filename, fieldname, strlen(invo_name) + 2, "",
+ pp->name);
+ return NOTOK;
+ }
+ tlen += sp->len;
+ }
+
+ p = q = mh_xmalloc(tlen + 1);
+ for (sp = pp->sechead; sp != NULL; ) {
+ memcpy(q, sp->value, sp->len);
+ q += sp->len;
+ free(sp->value);
+ sp2 = sp->next;
+ free(sp);
+ sp = sp2;
+ }
+
+ p[tlen] = '\0';
+
+ pm = add_param(param_head, param_tail, pp->name, p, 1);
+ pm->pm_charset = pp->charset;
+ pm->pm_lang = pp->lang;
+ pp2 = pp->next;
+ free(pp);
+ pp = pp2;
}
*header_attrp = cp;
return OK;
}
- const char *const charset = "charset";
- char *default_charset = NULL;
- CI ctinfo = &ct->c_ctinfo;
- char **ap, **vp;
- char **src_charset = NULL;
-
- for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) {
- if (! strcasecmp (*ap, charset)) {
- src_charset = vp;
- break;
- }
++/*
++ * 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) {
- /* RFC 2045, Sec. 5.2: default to us-ascii. */
- if (src_charset == NULL) src_charset = &default_charset;
- if (*src_charset == NULL) *src_charset = "US-ASCII";
++ static char *ret_charset = NULL;
++
++ if (ret_charset != NULL) {
++ free(ret_charset);
+ }
+
- return *src_charset;
++ ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
+
- /* 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. */
++ 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
+ * with the separator (;). Perform RFC 2231 encoding when necessary.
+ */
+
char *
- update_attr (char *body, const char *name, const char *value) {
- char *bp = nmh_strcasestr (body, name);
- char *new_body;
+ output_params(size_t initialwidth, PM params, int *offsetout, int external)
+ {
+ char *paramout = NULL;
+ char line[CPERLIN * 2], *q;
+ int curlen, index, cont, encode, i;
+ size_t valoff, numchars;
+
+ while (params != NULL) {
+ encode = 0;
+ index = 0;
+ valoff = 0;
+ q = line;
+
+ if (external && strcasecmp(params->pm_name, "body") == 0)
+ continue;
- if (bp) {
- char *other_attrs = strchr (bp, ';');
+ if (strlen(params->pm_name) > CPERLIN) {
+ advise(NULL, "Parameter name \"%s\" is too long", params->pm_name);
+ if (paramout)
+ free(paramout);
+ return NULL;
+ }
- *(bp + strlen (name)) = '\0';
- new_body = concat (body, "\"", value, "\"", NULL);
+ curlen = param_len(params, index, valoff, &encode, &cont, &numchars);
- if (other_attrs) {
- char *cp;
+ /*
+ * Loop until we get a parameter that fits within a line. We
+ * assume new lines start with a tab, so check our overflow based
+ * on that.
+ */
- /* 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);
- }
+ while (cont) {
+ *q++ = ';';
+ *q++ = '\n';
+ *q++ = '\t';
+
+ /*
+ * At this point we're definitely continuing the line, so
+ * be sure to include the parameter name and section index.
+ */
+
+ q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
+ params->pm_name, index);
+
+ /*
+ * Both of these functions do a NUL termination
+ */
+
+ if (encode)
+ i = encode_param(params, q, sizeof(line) - (q - line),
+ numchars, valoff, index);
+ else
+ i = normal_param(params, q, sizeof(line) - (q - line),
+ numchars, valoff);
+
+ if (i == 0) {
+ if (paramout)
+ free(paramout);
+ return NULL;
+ }
+
+ valoff += numchars;
+ index++;
+ curlen = param_len(params, index, valoff, &encode, &cont,
+ &numchars);
+ q = line;
+
+ /*
+ * "line" starts with a ;\n\t, so that doesn't count against
+ * the length. But add 8 since it starts with a tab; that's
+ * how we end up with 5.
+ */
+
+ initialwidth = strlen(line) + 5;
+
+ /*
+ * At this point the line should be built, so add it to our
+ * current output buffer.
+ */
+
+ paramout = add(line, paramout);
+ }
+
+ /*
+ * If this won't fit on the line, start a new one. Save room in
+ * case we need a semicolon on the end
+ */
+
+ if (initialwidth + curlen > CPERLIN - 1) {
+ *q++ = ';';
+ *q++ = '\n';
+ *q++ = '\t';
+ initialwidth = 8;
+ } else {
+ *q++ = ';';
+ *q++ = ' ';
+ initialwidth += 2;
+ }
+
+ /*
+ * At this point, we're either finishing a contined parameter, or
+ * we're working on a new one.
+ */
+
+ if (index > 0) {
+ q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
+ params->pm_name, index);
+ } else {
+ strncpy(q, params->pm_name, sizeof(line) - (q - line));
+ q += strlen(q);
+ }
+
+ if (encode)
+ i = encode_param(params, q, sizeof(line) - (q - line),
+ strlen(params->pm_value + valoff), valoff, index);
+ else
+ i = normal_param(params, q, sizeof(line) - (q - line),
+ strlen(params->pm_value + valoff), valoff);
+
+ if (i == 0) {
+ if (paramout)
+ free(paramout);
+ return NULL;
+ }
+
+ paramout = add(line, paramout);
+ initialwidth += strlen(line);
+
+ params = params->pm_next;
+ }
+
+ if (offsetout)
+ *offsetout = initialwidth;
+
+ return paramout;
+ }
+
+ /*
+ * Calculate the size of a parameter.
+ *
+ * Arguments include
+ *
+ * pm - The parameter being output
+ * index - If continuing the parameter, the index of the section
+ * we're on.
+ * valueoff - The current offset into the parameter value that we're
+ * working on (previous sections have consumed valueoff bytes).
+ * encode - Set if we should perform encoding on this parameter section
+ * (given that we're consuming bytesfit bytes).
+ * cont - Set if the remaining data in value will not fit on a single
+ * line and will need to be continued.
+ * bytesfit - The number of bytes that we can consume from the parameter
+ * value and still fit on a completely new line. The
+ * calculation assumes the new line starts with a tab,
+ * includes the parameter name and any encoding, and fits
+ * within CPERLIN bytes. Will always be at least 1.
+ */
+
+ static size_t
+ param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
+ size_t *bytesfit)
+ {
+ char *start = pm->pm_value + valueoff, *p, indexchar[32];
+ size_t len = 0, fit = 0;
+ int fitlimit = 0, eightbit, maxfit;
+
+ *encode = 0;
+
+ /*
+ * Add up the length. First, start with the parameter name.
+ */
+
+ len = strlen(pm->pm_name);
+
+ /*
+ * Scan the parameter value and see if we need to do encoding for this
+ * section.
+ */
+
+ eightbit = contains8bit(start, NULL);
+
+ /*
+ * Determine if we need to encode this section. Encoding is necessary if:
+ *
+ * - There are any 8-bit characters at all and we're on the first
+ * section.
+ * - There are 8-bit characters within N bytes of our section start.
+ * N is calculated based on the number of bytes it would take to
+ * reach CPERLIN. Specifically:
+ * 8 (starting tab) +
+ * strlen(param name) +
+ * 4 ('* for section marker, '=', opening/closing '"')
+ * strlen (index)
+ * is the number of bytes used by everything that isn't part of the
+ * value. So that gets subtracted from CPERLIN.
+ */
+
+ snprintf(indexchar, sizeof(indexchar), "%d", index);
+ maxfit = CPERLIN - (12 + len + strlen(indexchar));
+ if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
+ *encode = 1;
+ }
+
+ len++; /* Add in equal sign */
+
+ if (*encode) {
+ /*
+ * We're using maxfit as a marker for how many characters we can
+ * fit into the line. Bump it by two because we're not using quotes
+ * when encoding.
+ */
+
+ maxfit += 2;
+
+ /*
+ * If we don't have a charset or language tag in this parameter,
+ * add them now.
+ */
+
+ if (! pm->pm_charset)
+ pm->pm_charset = getcpy(write_charset_8bit());
+ if (! pm->pm_lang)
+ pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */
+
+ len++; /* For the encoding marker */
+ maxfit--;
+ if (index == 0) {
+ int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
+ len += enclen;
+ maxfit-= enclen;
+ } else {
+ /*
+ * We know we definitely need to include an index. maxfit already
+ * includes the section marker.
+ */
+ len += strlen(indexchar);
+ }
+ for (p = start; *p != '\0'; p++) {
+ if (isparamencode(*p)) {
+ len += 3;
+ maxfit -= 3;
+ } else {
+ len++;
+ maxfit--;
+ }
+ /*
+ * Just so there's no confusion: maxfit is counting OUTPUT
+ * characters (post-encoding). fit is counting INPUT characters.
+ */
+ if (! fitlimit && maxfit >= 0)
+ fit++;
+ else if (! fitlimit)
+ fitlimit++;
+ }
} else {
- char *cp;
+ /*
+ * Calculate the string length, but add room for quoting \
+ * and " if necessary. Also account for quotes at beginning
+ * and end.
+ */
+ for (p = start; *p != '\0'; p++) {
+ switch (*p) {
+ case '"':
+ case '\\':
+ len++;
+ maxfit--;
+ /* FALL THROUGH */
+ default:
+ len++;
+ maxfit--;
+ }
+ if (! fitlimit && maxfit >= 0)
+ fit++;
+ else if (! fitlimit)
+ fitlimit++;
+ }
+
+ len += 2;
+ }
+
+ if (fit < 1)
+ fit = 1;
+
+ *cont = fitlimit;
+ *bytesfit = fit;
+
+ return len;
+ }
+
+ /*
+ * Output an encoded parameter string.
+ */
+
+ static size_t
+ encode_param(PM pm, char *output, size_t len, size_t valuelen,
+ size_t valueoff, int index)
+ {
+ size_t outlen = 0, n;
+ char *endptr = output + len, *p;
+
+ /*
+ * First, output the marker for an encoded string.
+ */
+
+ *output++ = '*';
+ *output++ = '=';
+ outlen += 2;
- /* 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);
+ /*
+ * If the index is 0, output the character set and language tag.
+ * If theses were NULL, they should have already been filled in
+ * by param_len().
+ */
+
+ if (index == 0) {
+ n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
+ pm->pm_lang);
+ output += n;
+ outlen += n;
+ if (output > endptr) {
+ advise(NULL, "Internal error: parameter buffer overflow");
+ return 0;
+ }
+ }
+
+ /*
+ * Copy over the value, encoding if necessary
+ */
+
+ p = pm->pm_value + valueoff;
+ while (valuelen-- > 0) {
+ if (isparamencode(*p)) {
+ n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
+ output += n;
+ outlen += n;
+ } else {
+ *output++ = *p++;
+ outlen++;
+ }
+ if (output > endptr) {
+ advise(NULL, "Internal error: parameter buffer overflow");
+ return 0;
+ }
+ }
+
+ *output = '\0';
+
+ return outlen;
+ }
+
+ /*
+ * Output a "normal" parameter, without encoding. Be sure to escape
+ * quotes and backslashes if necessary.
+ */
+
+ static size_t
+ normal_param(PM pm, char *output, size_t len, size_t valuelen,
+ size_t valueoff)
+ {
+ size_t outlen = 0;
+ char *endptr = output + len, *p;
+
+ *output++ = '=';
+ *output++ = '"';
+ outlen += 2;
+
+ p = pm->pm_value + valueoff;
+
+ while (valuelen-- > 0) {
+ switch (*p) {
+ case '\\':
+ case '"':
+ *output++ = '\\';
+ outlen++;
+ default:
+ *output++ = *p++;
+ outlen++;
+ }
+ if (output > endptr) {
+ advise(NULL, "Internal error: parameter buffer overflow");
+ return 0;
+ }
+ }
+
+ if (output - 2 > endptr) {
+ advise(NULL, "Internal error: parameter buffer overflow");
+ return 0;
+ }
+
+ *output++ = '"';
+ *output++ = '\0';
+
+ return outlen + 1;
+ }
+
+ /*
+ * Add a parameter to the parameter linked list
+ */
+
+ PM
+ add_param(PM *first, PM *last, char *name, char *value, int nocopy)
+ {
+ PM pm = mh_xmalloc(sizeof(*pm));
+
+ memset(pm, 0, sizeof(*pm));
+
+ pm->pm_name = nocopy ? name : getcpy(name);
+ pm->pm_value = nocopy ? value : getcpy(value);
+
+ if (*first) {
+ (*last)->pm_next = pm;
+ *last = pm;
+ } else {
+ *first = pm;
+ *last = pm;
+ }
+
+ 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 new_body;
++ 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.
+ */
+
+ char *
+ get_param(PM first, const char *name, char replace, int fetchonly)
+ {
+ while (first != NULL) {
+ if (strcasecmp(name, first->pm_name) == 0) {
+ if (fetchonly)
+ return first->pm_value;
+ else
+ return getcpy(get_param_value(first, replace));
+ }
+ first = first->pm_next;
+ }
+
+ return NULL;
+ }
+
+ /*
+ * Return a parameter value, converting to the local character set if
+ * necessary
+ */
+
+ char *get_param_value(PM pm, char replace)
+ {
+ static char buffer[4096]; /* I hope no parameters are larger */
+ size_t bufsize = sizeof(buffer);
+ #ifdef HAVE_ICONV
+ size_t inbytes;
+ int utf8;
+ iconv_t cd;
+ ICONV_CONST char *p;
+ #endif /* HAVE_ICONV */
+ char *q;
+
+ /*
+ * If we don't have a character set indicated, it's assumed to be
+ * US-ASCII. If it matches our character set, we don't need to convert
+ * anything.
+ */
+
+ if (!pm->pm_charset || check_charset(pm->pm_charset,
+ strlen(pm->pm_charset))) {
+ return pm->pm_value;
+ }
+
+ /*
+ * In this case, we need to convert. If we have iconv support, use
+ * that. Otherwise, go through and simply replace every non-ASCII
+ * character with the substitution character.
+ */
+
+ #ifdef HAVE_ICONV
+ q = buffer;
+ bufsize = sizeof(buffer);
+ utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0;
+
+ cd = iconv_open(get_charset(), pm->pm_charset);
+ if (cd == (iconv_t) -1) {
+ goto noiconv;
+ }
+
+ inbytes = strlen(pm->pm_value);
+ p = pm->pm_value;
+
+ while (inbytes) {
+ if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) {
+ if (errno != EILSEQ) {
+ iconv_close(cd);
+ goto noiconv;
+ }
+ /*
+ * Reset shift state, substitute our character,
+ * try to restart conversion.
+ */
+
+ iconv(cd, NULL, NULL, &q, &bufsize);
+
+ if (bufsize == 0) {
+ iconv_close(cd);
+ goto noiconv;
+ }
+ *q++ = replace;
+ bufsize--;
+ if (bufsize == 0) {
+ iconv_close(cd);
+ goto noiconv;
+ }
+ if (utf8) {
+ for (++p, --inbytes;
+ inbytes > 0 && (((unsigned char) *q) & 0xc0) == 0x80;
+ ++p, --inbytes)
+ continue;
+ } else {
+ p++;
+ inbytes--;
+ }
+ }
+ }
+
+ iconv_close(cd);
+
+ if (bufsize == 0)
+ q--;
+ *q = '\0';
+
+ return buffer;
+ #endif /* HAVE_ICONV */
+
+ noiconv:
+ /*
+ * Take everything non-ASCII and substituite the replacement character
+ */
+
+ q = buffer;
+ bufsize = sizeof(buffer);
+ for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) {
+ if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p))
+ *q = *p;
+ else
+ *q = replace;
+ }
+
+ *q = '\0';
+
+ return buffer;
}
#include <h/mime.h>
#include <h/mhparse.h>
#include <h/utils.h>
+#ifdef HAVE_ICONV
+# include <iconv.h>
+#endif /* ! HAVE_ICONV */
extern int debugsw;
void content_error (char *, CT, char *, ...);
void flush_errors (void);
- /* mhlistsbr.c */
- int list_switch (CT, int, int, int, int);
- int list_content (CT, int, int, int, int);
-
/*
* prototypes
*/
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 const char *parameter_value (CI, const char *);
+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);
char prompt[BUFSIZ];
if (ct->c_type == CT_MULTIPART)
- list_content (ct, -1, 1, 0, 0);
+ list_content (ct, -1, 1, 0, 0, 0);
else
- list_switch (ct, -1, 1, 0, 0);
+ list_switch (ct, -1, 1, 0, 0, 0);
if (xpause && isatty (fileno (stdout))) {
int intr;
}
}
- 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 */
{
- char **ap, **ep;
+ PM pm;
char *s = "";
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
+ 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;
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;
- const char *value;
+ 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);
- value = parameter_value(ci, param);
++ 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 =
- update_attr (ct->c_ctline, "charset=", dest_charset);
++ 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_less_newline =
- update_attr (hf->value, "charset=", dest_charset);
- char *ctline = concat (ctline_less_newline, "\n", NULL);
- free (ctline_less_newline);
++ 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;
}
- /*
- * If a Content-Type parameter named param exists, returns its value.
- * Otherwise, returns NULL.
- */
- static const char *
- parameter_value (CI ci, const char *param) {
- const char *value = NULL;
- char **ap, **vp;
-
- for (ap = ci->ci_attrs, vp = ci->ci_values; *ap; ++ap, ++vp) {
- if (! strcasecmp (*ap, param)) {
- value = *vp;
- break;
- }
- }
-
- return value;
- }
-
-
static void
intrser (int i)
{
#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)
{
- char **ap, **ep;
CI ci = &ct->c_ctinfo;
/* Check if the content specifies a filename */
- if (autosw)
+ if (info->autosw)
get_storeproc (ct);
/*
*/
if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
int tarP = 0, zP = 0, gzP = 0;
+ char *cp;
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- /* check for "type=tar" attribute */
- if (!strcasecmp (*ap, "type")) {
- if (strcasecmp (*ep, "tar"))
- break;
-
+ if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) {
+ if (strcasecmp (cp, "tar") == 0)
tarP = 1;
- continue;
- }
+ }
- /* check for "conversions=compress" attribute */
- if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
- && (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) {
+ /* check for "conversions=compress" attribute */
+ if ((cp = get_param(ci->ci_first_pm, "conversions", ' ', 1)) ||
+ (cp = get_param(ci->ci_first_pm, "x-conversions", ' ', 1))) {
+ if (strcasecmp (cp, "compress") == 0 ||
+ strcasecmp (cp, "x-compress") == 0) {
zP = 1;
- continue;
}
- /* check for "conversions=gzip" attribute */
- if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
- && (!strcasecmp (*ep, "gzip") || !strcasecmp (*ep, "x-gzip"))) {
+ if (strcasecmp (cp, "gzip") == 0 ||
+ strcasecmp (cp, "x-gzip") == 0) {
gzP = 1;
- continue;
}
}
: (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;
buflen--;
continue;
} else {
- char **ap, **ep;
+ PM pm;
char *s = "";
- for (ap = ci->ci_attrs, ep = ci->ci_values;
- *ap; ap++, ep++) {
- snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
+ 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;
static void
get_storeproc (CT ct)
{
- char **ap, **ep, *cp;
+ char *cp;
CI ci;
/*
* use that (RFC-2183).
*/
if (ct->c_dispo) {
- char *cp = strchr (ct->c_dispo, ';');
- CI ci = calloc (1, sizeof *ci);
- int status;
- int found_filename = 0;
-
- if (cp && parse_header_attrs (ct->c_file, strlen (invo_name) + 2, &cp,
- ci, &status) == OK) {
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (! strcasecmp (*ap, "filename")
- && *(cp = *ep) != '/'
- && *cp != '.'
- && *cp != '|'
- && *cp != '!'
- && !strchr (cp, '%')) {
- ct->c_storeproc = add (cp, NULL);
- found_filename = 1;
- }
- free (*ap);
- }
+ if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0))
+ && *cp != '/'
+ && *cp != '.'
+ && *cp != '|'
+ && *cp != '!'
+ && !strchr (cp, '%')) {
+ ct->c_storeproc = add (cp, NULL);
+ free(cp);
+ return;
}
-
- free (ci);
- if (found_filename) return;
+ if (cp)
+ free(cp);
}
/*
* the storeproc.
*/
ci = &ct->c_ctinfo;
- for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (! strcasecmp (*ap, "name")
- && *(cp = *ep) != '/'
- && *cp != '.'
- && *cp != '|'
- && *cp != '!'
- && !strchr (cp, '%')) {
+ if ((cp = get_param(ci->ci_first_pm, "name", '_', 0))
+ && *cp != '/'
+ && *cp != '.'
+ && *cp != '|'
+ && *cp != '!'
+ && !strchr (cp, '%')) {
ct->c_storeproc = add (cp, NULL);
- return;
- }
+
}
+ if (cp)
+ free(cp);
}
/******************************************************************************/
/* -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;
}