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 \
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)
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
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;
+ }
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>
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);
/* 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);
/* 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 *);
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;
}
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);
/* 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';
}
-/*
- * 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;
}
*/
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 */