char *tmp;
/*
- * Structure for mapping types to their internal flags
- */
-struct k2v {
- char *kv_key;
- int kv_value;
-};
-
-/*
* Structures for TEXT messages
*/
-static struct k2v SubText[] = {
+struct k2v SubText[] = {
{ "plain", TEXT_PLAIN },
{ "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */
{ "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */
{ NULL, TEXT_UNKNOWN } /* this one must be last! */
};
-static struct k2v Charset[] = {
+struct k2v Charset[] = {
{ "us-ascii", CHARSET_USASCII },
{ "iso-8859-1", CHARSET_LATIN },
{ NULL, CHARSET_UNKNOWN } /* this one must be last! */
/*
* Structures for MULTIPART messages
*/
-static struct k2v SubMultiPart[] = {
+struct k2v SubMultiPart[] = {
{ "mixed", MULTI_MIXED },
{ "alternative", MULTI_ALTERNATE },
{ "digest", MULTI_DIGEST },
/*
* Structures for MESSAGE messages
*/
-static struct k2v SubMessage[] = {
+struct k2v SubMessage[] = {
{ "rfc822", MESSAGE_RFC822 },
{ "partial", MESSAGE_PARTIAL },
{ "external-body", MESSAGE_EXTERNAL },
/*
* Structure for APPLICATION messages
*/
-static struct k2v SubApplication[] = {
+struct k2v SubApplication[] = {
{ "octet-stream", APPLICATION_OCTETS },
{ "postscript", APPLICATION_POSTSCRIPT },
{ NULL, APPLICATION_UNKNOWN } /* this one must be last! */
void free_encoding (CT, int);
/*
- * prototypes
- */
-int pidcheck (int);
-CT parse_mime (char *);
-
-/*
* static prototypes
*/
static CT get_content (FILE *, char *, int);
-static int add_header (CT, char *, char *);
-static int get_ctinfo (unsigned char *, CT);
static int get_comment (CT, unsigned char **, int);
+
static int InitGeneric (CT);
static int InitText (CT);
static int InitMultiPart (CT);
static void reverse_parts (CT);
static int InitMessage (CT);
-static int params_external (CT, int);
static int InitApplication (CT);
static int init_encoding (CT, OpenCEFunc);
-static void close_encoding (CT);
static unsigned long size_encoding (CT);
static int InitBase64 (CT);
static int openBase64 (CT, char **);
static int InitQuoted (CT);
static int openQuoted (CT, char **);
static int Init7Bit (CT);
-static int open7Bit (CT, char **);
static int openExternal (CT, CT, CE, char **, int *);
static int InitFile (CT);
static int openFile (CT, char **);
static int openMail (CT, char **);
static int readDigest (CT, char *);
-/*
- * Structures for mapping (content) types to
- * the functions to handle them.
- */
-struct str2init {
- char *si_key;
- int si_val;
- InitFunc si_init;
-};
-
-static struct str2init str2cts[] = {
+struct str2init str2cts[] = {
{ "application", CT_APPLICATION, InitApplication },
{ "audio", CT_AUDIO, InitGeneric },
{ "image", CT_IMAGE, InitGeneric },
{ NULL, CT_UNKNOWN, NULL },
};
-static struct str2init str2ces[] = {
+struct str2init str2ces[] = {
{ "base64", CE_BASE64, InitBase64 },
{ "quoted-printable", CE_QUOTED, InitQuoted },
{ "8bit", CE_8BIT, Init7Bit },
*
* si_key is 1 if access method is anonymous.
*/
-static struct str2init str2methods[] = {
+struct str2init str2methods[] = {
{ "afs", 1, InitFile },
{ "anon-ftp", 1, InitFTP },
{ "ftp", 0, InitFTP },
* Check if file is actually standard input
*/
if ((is_stdin = !(strcmp (file, "-")))) {
- file = add (m_tmpfil (invo_name), NULL);
- if ((fp = fopen (file, "w+")) == NULL) {
- advise (file, "unable to fopen for writing and reading");
- return NULL;
- }
+ char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
+ if (tfile == NULL) {
+ advise("mhparse", "unable to create temporary file");
+ return NULL;
+ }
+ file = add (tfile, NULL);
chmod (file, 0600);
+
while (fgets (buffer, sizeof(buffer), stdin))
fputs (buffer, fp);
fflush (fp);
}
/* Parse the Content-Type field */
- if (get_ctinfo (hp->value, ct) == NOTOK)
+ if (get_ctinfo (hp->value, ct, 0) == NOTOK)
goto out;
/*
/* Get Content-Description field */
ct->c_descr = add (hp->value, ct->c_descr);
}
+ else if (!mh_strcasecmp (hp->name, DISPO_FIELD)) {
+ /* Get Content-Disposition field */
+ ct->c_dispo = add (hp->value, ct->c_dispo);
+ }
next_header:
hp = hp->next; /* next header field */
* so default type is message/rfc822
*/
if (toplevel < 0) {
- if (get_ctinfo ("message/rfc822", ct) == NOTOK)
+ if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
goto out;
ct->c_type = CT_MESSAGE;
ct->c_ctinitfnx = InitMessage;
/*
* Else default type is text/plain
*/
- if (get_ctinfo ("text/plain", ct) == NOTOK)
+ if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
goto out;
ct->c_type = CT_TEXT;
ct->c_ctinitfnx = InitText;
* small routine to add header field to list
*/
-static int
+int
add_header (CT ct, char *name, char *value)
{
HF hp;
}
+/* 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 (unsigned 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;
+ unsigned char *cp;
+ char *prefix, *suffix;
+
+ /* Trim trailing space, esp. newline. */
+ for (cp = &buf[strlen (buf) - 1];
+ cp >= buf && isspace (*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 fill in the
- * information of the CTinfo structure.
+ * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
+ * directives. Fills in the information of the CTinfo structure.
*/
-
-static int
-get_ctinfo (unsigned char *cp, CT ct)
+int
+get_ctinfo (unsigned char *cp, CT ct, int magic)
{
int i;
unsigned char *dp;
return NOTOK;
if (*cp != '/') {
- ci->ci_subtype = add ("", NULL);
+ if (!magic)
+ ci->ci_subtype = add ("", NULL);
goto magic_skip;
}
}
/*
+ * Get any <Content-Id> given in buffer
+ */
+ if (magic && *cp == '<') {
+ if (ct->c_id) {
+ free (ct->c_id);
+ ct->c_id = NULL;
+ }
+ if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
+ advise (NULL, "invalid ID in message %s", ct->c_file);
+ return NOTOK;
+ }
+ c = *dp;
+ *dp = '\0';
+ if (*ct->c_id)
+ ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
+ else
+ ct->c_id = NULL;
+ *dp++ = c;
+ cp = dp;
+
+ while (isspace (*cp))
+ cp++;
+ }
+
+ /*
+ * Get any [Content-Description] given in buffer.
+ */
+ if (magic && *cp == '[') {
+ ct->c_descr = ++cp;
+ for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
+ if (*dp == ']')
+ break;
+ if (dp < cp) {
+ advise (NULL, "invalid description in message %s", ct->c_file);
+ ct->c_descr = NULL;
+ return NOTOK;
+ }
+
+ c = *dp;
+ *dp = '\0';
+ if (*ct->c_descr)
+ ct->c_descr = concat (ct->c_descr, "\n", NULL);
+ else
+ ct->c_descr = NULL;
+ *dp++ = c;
+ cp = dp;
+
+ while (isspace (*cp))
+ cp++;
+ }
+
+ /*
+ * Get any {Content-Disposition} given in buffer.
+ */
+ if (magic && *cp == '{') {
+ ct->c_dispo = ++cp;
+ for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
+ if (*dp == '}')
+ break;
+ if (dp < cp) {
+ advise (NULL, "invalid disposition in message %s", ct->c_file);
+ ct->c_dispo = NULL;
+ return NOTOK;
+ }
+
+ c = *dp;
+ *dp = '\0';
+ if (*ct->c_dispo)
+ ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
+ else
+ ct->c_dispo = NULL;
+ *dp++ = c;
+ cp = dp;
+
+ while (isspace (*cp))
+ cp++;
+ }
+
+ /*
* Check if anything is left over
*/
if (*cp) {
- advise (NULL, "extraneous information in message %s's %s: field\n%*.*s(%s)",
- ct->c_file, TYPE_FIELD, i, i, "", cp);
+ if (magic) {
+ ci->ci_magic = add (cp, NULL);
+
+ /* If there is a Content-Disposition header and it doesn't
+ 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),
+ '/'));
+ }
+ else
+ advise (NULL,
+ "extraneous information in message %s's %s: field\n%*.*s(%s)",
+ ct->c_file, TYPE_FIELD, i, i, "", cp);
}
return OK;
InitText (CT ct)
{
char buffer[BUFSIZ];
- char *chset;
+ char *chset = NULL;
char **ap, **ep, *cp;
struct k2v *kv;
struct text *t;
break;
ct->c_subtype = kv->kv_value;
- /* allocate text structure */
+ /* allocate text character set structure */
if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
adios (NULL, "out of memory");
ct->c_ctparams = (void *) t;
if (!mh_strcasecmp (*ap, "charset"))
break;
- if (*ap)
- chset = *ep;
- else
- chset = "US-ASCII"; /* default for text */
-
- /* match character set, or set to unknown */
- for (kv = Charset; kv->kv_key; kv++)
- if (!mh_strcasecmp (chset, kv->kv_key))
- break;
- t->tx_charset = kv->kv_value;
+ /* check if content specified a character set */
+ if (*ap) {
+ /* match character set or set to CHARSET_UNKNOWN */
+ for (kv = Charset; kv->kv_key; kv++) {
+ if (!mh_strcasecmp (*ep, kv->kv_key)) {
+ chset = *ep;
+ break;
+ }
+ }
+ t->tx_charset = kv->kv_value;
+ } else {
+ t->tx_charset = CHARSET_UNSPECIFIED;
+ }
/*
* If we can not handle character set natively,
* then check profile for string to modify the
* terminal or display method.
+ *
+ * termproc is for mhshow, though mhlist -debug prints it, too.
*/
- if (!check_charset (chset, strlen (chset))) {
+ if (chset != NULL && !check_charset (chset, strlen (chset))) {
snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
if ((cp = context_find (buffer)))
ct->c_termproc = getcpy (cp);
}
-static int
+int
params_external (CT ct, int composing)
{
char **ap, **ep;
}
-static void
+void
close_encoding (CT ct)
{
CE ce;
unsigned char value, *b, *b1, *b2, *b3;
unsigned char *cp, *ep;
char buffer[BUFSIZ];
- /* sbeck -- handle prefixes */
+ /* sbeck -- handle suffixes */
CI ci;
CE ce;
MD5_CTX mdContext;
}
if (*file == NULL) {
- ce->ce_file = add (m_scratch ("", tmp), NULL);
+ ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
ce->ce_unlink = 1;
} else {
ce->ce_file = add (*file, NULL);
ci->ci_type);
cp = context_find (buffer);
}
- if (cp != NULL && *cp != '\0')
- ce->ce_file = add (cp, ce->ce_file);
+ if (cp != NULL && *cp != '\0') {
+ if (ce->ce_unlink) {
+ // Temporary file already exists, so we rename to
+ // version with extension.
+ char *file_org = strdup(ce->ce_file);
+ ce->ce_file = add (cp, ce->ce_file);
+ if (rename(file_org, ce->ce_file)) {
+ adios (ce->ce_file, "unable to rename %s to ", file_org);
+ }
+ free(file_org);
+
+ } else {
+ ce->ce_file = add (cp, ce->ce_file);
+ }
+ }
if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
char buffer[BUFSIZ];
unsigned char mask;
CE ce;
- /* sbeck -- handle prefixes */
+ /* sbeck -- handle suffixes */
CI ci;
MD5_CTX mdContext;
}
if (*file == NULL) {
- ce->ce_file = add (m_scratch ("", tmp), NULL);
+ ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
ce->ce_unlink = 1;
} else {
ce->ce_file = add (*file, NULL);
ci->ci_type);
cp = context_find (buffer);
}
- if (cp != NULL && *cp != '\0')
- ce->ce_file = add (cp, ce->ce_file);
+ if (cp != NULL && *cp != '\0') {
+ if (ce->ce_unlink) {
+ // Temporary file already exists, so we rename to
+ // version with extension.
+ char *file_org = strdup(ce->ce_file);
+ ce->ce_file = add (cp, ce->ce_file);
+ if (rename(file_org, ce->ce_file)) {
+ adios (ce->ce_file, "unable to rename %s to ", file_org);
+ }
+ free(file_org);
+
+ } else {
+ ce->ce_file = add (cp, ce->ce_file);
+ }
+ }
if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
fseek (ct->c_fp, ct->c_begin, SEEK_SET);
while (len > 0) {
- char *dp;
-
if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
content_error (NULL, ct, "premature eof");
goto clean_up;
*++ep = '\n', ep++;
for (; cp < ep; cp++) {
- if (quoted) {
- if (quoted > 1) {
- if (!isxdigit (*cp)) {
-invalid_hex:
- dp = "expecting hexidecimal-digit";
- goto invalid_encoding;
- }
+ if (quoted > 0) {
+ /* in an escape sequence */
+ if (quoted == 1) {
+ /* at byte 1 of an escape sequence */
+ mask = hex2nib[*cp & 0x7f];
+ /* next is byte 2 */
+ quoted = 2;
+ } else {
+ /* at byte 2 of an escape sequence */
mask <<= 4;
mask |= hex2nib[*cp & 0x7f];
putc (mask, ce->ce_fp);
if (digested)
MD5Update (&mdContext, &mask, 1);
- } else {
- switch (*cp) {
- case ':':
- putc (*cp, ce->ce_fp);
- if (digested)
- MD5Update (&mdContext, (unsigned char *) ":", 1);
- break;
-
- default:
- if (!isxdigit (*cp))
- goto invalid_hex;
- mask = hex2nib[*cp & 0x7f];
- quoted = 2;
- continue;
+ if (ferror (ce->ce_fp)) {
+ content_error (ce->ce_file, ct, "error writing to");
+ goto clean_up;
}
+ /* finished escape sequence; next may be literal or a new
+ * escape sequence */
+ quoted = 0;
}
-
- if (ferror (ce->ce_fp)) {
- content_error (ce->ce_file, ct, "error writing to");
- goto clean_up;
- }
- quoted = 0;
+ /* on to next byte */
continue;
}
- switch (*cp) {
- default:
- if (*cp < '!' || *cp > '~') {
- int i;
- dp = "expecting character in range [!..~]";
-
-invalid_encoding:
- i = strlen (invo_name) + 2;
- content_error (NULL, ct,
- "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
- dp, i, i, "", *cp);
- goto clean_up;
- }
- /* and fall...*/
- case ' ':
- case '\t':
- case '\n':
- putc (*cp, ce->ce_fp);
- if (digested) {
- if (*cp == '\n')
- MD5Update (&mdContext, (unsigned char *) "\r\n",2);
- else
- MD5Update (&mdContext, (unsigned char *) cp, 1);
+ /* not in an escape sequence */
+ if (*cp == '=') {
+ /* starting an escape sequence, or invalid '='? */
+ if (cp + 1 < ep && cp[1] == '\n') {
+ /* "=\n" soft line break, eat the \n */
+ cp++;
+ continue;
}
- if (ferror (ce->ce_fp)) {
- content_error (ce->ce_file, ct, "error writing to");
- goto clean_up;
+ if (cp + 1 >= ep || cp + 2 >= ep) {
+ /* We don't have 2 bytes left, so this is an invalid
+ * escape sequence; just show the raw bytes (below). */
+ } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
+ /* Next 2 bytes are hex digits, making this a valid escape
+ * sequence; let's decode it (above). */
+ quoted = 1;
+ continue;
+ } else {
+ /* One or both of the next 2 is out of range, making this
+ * an invalid escape sequence; just show the raw bytes
+ * (below). */
}
- break;
+ }
- case '=':
- if (*++cp != '\n') {
- quoted = 1;
- cp--;
+ /* Just show the raw byte. */
+ putc (*cp, ce->ce_fp);
+ if (digested) {
+ if (*cp == '\n') {
+ MD5Update (&mdContext, (unsigned char *) "\r\n",2);
+ } else {
+ MD5Update (&mdContext, (unsigned char *) cp, 1);
}
- break;
+ }
+ if (ferror (ce->ce_fp)) {
+ content_error (ce->ce_file, ct, "error writing to");
+ goto clean_up;
}
}
}
}
-static int
+int
open7Bit (CT ct, char **file)
{
int cc, fd, len;
char buffer[BUFSIZ];
- /* sbeck -- handle prefixes */
+ /* sbeck -- handle suffixes */
char *cp;
CI ci;
CE ce;
}
if (*file == NULL) {
- ce->ce_file = add (m_scratch ("", tmp), NULL);
+ ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
ce->ce_unlink = 1;
} else {
ce->ce_file = add (*file, NULL);
ci->ci_type);
cp = context_find (buffer);
}
- if (cp != NULL && *cp != '\0')
- ce->ce_file = add (cp, ce->ce_file);
+ if (cp != NULL && *cp != '\0') {
+ if (ce->ce_unlink) {
+ // Temporary file already exists, so we rename to
+ // version with extension.
+ char *file_org = strdup(ce->ce_file);
+ ce->ce_file = add (cp, ce->ce_file);
+ if (rename(file_org, ce->ce_file)) {
+ adios (ce->ce_file, "unable to rename %s to ", file_org);
+ }
+ free(file_org);
+
+ } else {
+ ce->ce_file = add (cp, ce->ce_file);
+ }
+ }
if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
if (ct->c_descr)
fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
+ if (ct->c_dispo)
+ fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
fprintf (ce->ce_fp, "\n");
}
else if (caching)
ce->ce_file = add (cachefile, NULL);
else
- ce->ce_file = add (m_scratch ("", tmp), NULL);
+ ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
}
if (*file == NULL) {
- ce->ce_file = add (m_scratch ("", tmp), NULL);
+ ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
ce->ce_unlink = 1;
} else {
ce->ce_file = add (*file, NULL);
return NOTOK;
}
+ /* showproc is for mhshow and mhstore, though mhlist -debug
+ * prints it, too. */
if (ct->c_showproc)
free (ct->c_showproc);
ct->c_showproc = add ("true", NULL);
while (*cp)
cp++;
fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
- cp - bp);
+ (int)(cp - bp));
}
return NOTOK;