Logo Search packages:      
Sourcecode: af version File versions  Download package

mime.c

/* Mime.c - MIME header checking and translation for af.
   Copyright (C) 1994 - 2002 Malc Arnold.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <ctype.h>
#include "af.h"
#include "atom.h"
#include "mime.h"
#include "mailcap.h"
#include "keyseq.h"
#include "functions.h"
#include "variable.h"
#include STRING_HDR

/****************************************************************************/
/* RCS info */

#ifndef lint
static char *RcsId = "$Id: mime.c,v 2.5 2003/11/27 01:45:57 malc Exp $";
static char *MimeId = MIMEID;
#endif /* ! lint */

/****************************************************************************/
/* Global function declarations */

extern char *xmalloc(), *xrealloc(), *xstrdup();
extern char *vstrcat(), *get_vtext(), *atext();
extern int strcasecmp(), strncasecmp(), atoi();
extern int known_charset(), is_blank();
extern int is_fromline(), is_header();
extern unsigned count_messages();
extern void afree(), free(), free_messages();
extern void set_sys_tags();
extern ATOM *tokenise(), *ctokenise();
extern ATOM *atoken(), *asearch();
extern ATOM *acut(), *adiscard();
extern ATOM *adelete();
extern MESSAGE *read_body_part();
extern MESSAGE *copy_one_message();
extern MAILCAP *find_mailcap();

/* Local function declarations */

char *get_param();
int viewable_ctype(), viewable_encoding();
int viewable_charset();
void free_message_parts();
static char *mime_type(), *mime_charset();
static char *mime_encoding(), *mime_disp();
static char *ct_text(), *param_value();
static char *find_param();
static int message_in_parts(), id_on_stack();
static int check_message_parts();
static void free_mimetype();
static MIMETYPE *parse_contype(), *parse_condisp();
static MIMEPARAM *parse_params();
static TEXTLINE *find_delimiter(), *find_body();
static MESSAGE *add_body_part(), *add_body_text();
static MESSAGE_PART *message_part();
static MESSAGE_PART *recursive_part();
static MESSAGE_PART *new_message_part();
static MESSAGE_PART *add_message_part();
static PARTIAL_ID *push_partial_id();
static PARTIAL_ID *pop_partial_id();

/****************************************************************************/
/* Import the error flag and text for parsing and translation */

extern int a_errno;
extern char *a_errtext;

/****************************************************************************/
char *contype(ctype)
char *ctype;
{
      /*
       * Form a Content-Type header from the string passed,
       * and return it in a newly-allocated string.
       */

      return(mime_type(ctype, FALSE, TRUE, AC_TRIM));
}
/****************************************************************************/
char *c_contype(ctype)
char *ctype;
{
      /*
       * Form a fully-canonical Content-Type header from the string
       * passed, and return it in a newly-allocated string.
       */

      return(mime_type(ctype, FALSE, TRUE, AC_FULL));
}
/****************************************************************************/
char *mc_contype(ctype)
char *ctype;
{
      /*
       * Form a fully-canonical Content-Type value from a mailcap
       * file, and return it in a newly-allocated string.
       */

      return(mime_type(ctype, TRUE, FALSE, AC_FULL));
}
/****************************************************************************/
char *typeonly(ctype)
char *ctype;
{
      /*
       * Form a Content-Type header without parameters
       * and return it in a newly-allocated string.
       */

      return(mime_type(ctype, FALSE, FALSE, AC_FULL));
}
/****************************************************************************/
char *charset(cset)
char *cset;
{
      /* Canonicalise and return a charset value */

      return(mime_charset(cset, AC_TRIM));
}     
/****************************************************************************/
char *get_charset(ctype)
char *ctype;
{
      /* Extract and return any charset parameter from ctype */

      return(param_value(ctype, CHARSET_PARAM, FALSE, TRUE));
}
/****************************************************************************/
char *get_param(ctype, param)
char *ctype, *param;
{
      /* Extract and return any named parameter from ctype */

      return(param_value(ctype, param, FALSE, FALSE));
}
/****************************************************************************/
char *set_param(ctype, name, value)
char *ctype, *name, *value;
{
      /* Set the named parameter of ctype to value */

      char *newtype;
      int found = FALSE;
      MIMETYPE *mtype;
      MIMEPARAM *param;

      /* First parse and check the content type */

      if ((mtype = parse_contype(ctype, AC_FULL)) == NULL) {
            /* Error in the Content-Type; fail */

            return(NULL);
      }

      /* Now look for the parameter */

      for (param = mtype->params; param != NULL; param = param->next) {
            /* Is this the parameter? */

            if (!strcasecmp(param->name, name)) {
                  /* Set the value of the parameter */

                  free(param->value);
                  param->value = xstrdup(value);
                  found = TRUE;
                  break;
            }
      }

      /* Add a new parameter if no charset found */

      if (!found) {
            /* Allocate and fill the parameter */

            param = (MIMEPARAM *) xmalloc(sizeof(MIMEPARAM));
            param->name = xstrdup(name);
            param->value = xstrdup(value);

            /* And prepend it to the list */

            param->next = mtype->params;
            mtype->params = param;
      }

      /* Generate the new Content-Type string */

      newtype = ct_text(mtype, TRUE);

      /* Clean up and return the Content-Type */

      free_mimetype(mtype);
      return(newtype);
}
/****************************************************************************/
int match_contype(pattern, ctype)
char *pattern, *ctype;
{
      /*
       * Return whether the content-type pattern matches ctype.
       * We can assume that both pattern and ctype are fully
       * canonical, so this routine can be simplified.
       */

      char *slash, *semi;
      size_t len;

      /* Check if we're looking for a wildcard subtype */

      if ((slash = strchr(pattern, '/')) == NULL
          || !strcmp(slash + 1, MCAP_WILDCARD)) {
            /* Find the length of the type */

            len = (slash != NULL) ? slash - pattern : strlen(pattern);

            /* Find any slash in the content-type */

            slash = strchr(ctype, '/');

            /* Now compare the primary types only */

            return((slash != NULL && slash - ctype == len
                  || slash == NULL && strlen(ctype) == len)
                   && !strncasecmp(pattern, ctype, len));
      }

      /* Find the end of the content-type and subtype */

      semi = strchr(ctype, ';');
      len = (semi != NULL) ? semi - ctype : strlen(ctype);

      /* Now compare the content-types and subtypes */

      return(strlen(pattern) == len && !strncasecmp(pattern, ctype, len));
}
/****************************************************************************/
int is_wildcard(ctype)
char *ctype;
{
      /* Return TRUE if contype is a wildcard content-type */

      char *slash;

      /* Check for no subtype or * as subtype */

      return((slash = strchr(ctype, '/')) == NULL
             || !strcmp(slash + 1, MCAP_WILDCARD));
}
/****************************************************************************/
int compare_contypes(ctype1, ctype2)
char *ctype1, *ctype2;
{
      /*
       * Return the sort order for the two content-types, in the
       * same fashion as strcmp.  We can assume that the content
       * types are in canonical form, but any '*' entries must
       * be sorted last.
       */

      char *slash1, *slash2;
      int len1, len2, comparison;

      /* First find the subtypes of each content-type */

      slash1 = strchr(ctype1, '/');
      slash2 = strchr(ctype2, '/');

      /* And the lengths of the types */

      len1 = (slash1 != NULL) ? slash1 - ctype1 : strlen(ctype1);
      len2 = (slash2 != NULL) ? slash2 - ctype2 : strlen(ctype2);

      /* Now check if the types match */
      
      if (len1 != len2) {
            /* Different lengths, return the comparison */

            return(strcasecmp(ctype1, ctype2));
      } else if (comparison = strncasecmp(ctype1, ctype2, len1)) {
            /* Compared differently, return the result */

            return (comparison);
      }

      /* Now check if either subtype is a wildcard */

      if ((slash1 == NULL || !strcmp(slash1 + 1, MCAP_WILDCARD))
          && slash2 != NULL && strcmp(slash2 + 1, MCAP_WILDCARD)) {
            /* ctype1 is a wildcard and sorts last */

            return(1);
      } else if ((slash2 == NULL || !strcmp(slash2 + 1, MCAP_WILDCARD))
               && slash1 != NULL && strcmp(slash1 + 1, MCAP_WILDCARD)) {
            /* ctype2 is a wildcard and sorts last */

            return(-1);
      }

      /* No wildcards to worry about, just compare the types */

      return(strcasecmp(slash1 + 1, slash2 + 1));
}
/****************************************************************************/
char *encoding(cte)
char *cte;
{
      /*
       * Form a Content-Transfer-Encoding header from the string
       * passed, and return it in a newly-allocated string.
       */

      return(mime_encoding(cte, TRUE, AC_TRIM));
}
/****************************************************************************/
char *c_encoding(cte)
char *cte;
{
      /*
       * Form a fully-canonical Content-Transfer-Encoding header from
       * the string passed, and return it in a newly-allocated string.
       */

      return(mime_encoding(cte, FALSE, AC_FULL));
}
/****************************************************************************/
char *disposition(cdisp)
char *cdisp;
{
      /*
       * Form a Content-Disposition header from the string passed,
       * and return it in a newly-allocated string.
       */

      return(mime_disp(cdisp, TRUE, AC_TRIM));
}
/****************************************************************************/
char *disponly(cdisp)
char *cdisp;
{
      /*
       * Form a fully-canonical Content-Disposition header from the
       * string passed, and return it in a newly-allocated string.
       */

      return(mime_disp(cdisp, FALSE, AC_FULL));
}
/****************************************************************************/
char *get_disp_param(cdisp, param)
char *cdisp, *param;
{
      /* Extract and return any named parameter from cdisp */

      return(param_value(cdisp, param, TRUE, FALSE));
}
/****************************************************************************/
int set_mime_flags(message)
MESSAGE *message;
{
      /*
       * Set the MIME flags for the message.  Returns TRUE if any
       * of the values have changed.
       */

      int textual = TRUE;
      int multipart = FALSE;
      int alternative = FALSE;
      int parallel = FALSE;
      int viewable = TRUE;
      int decodable = TRUE;
      int changed;
      MIMETYPE *mtype;

      /* Parse the content type string if we have one */

      if (message->contype != NULL &&
          (mtype = parse_contype(message->contype, AC_FULL)) != NULL) {
            /* Check whether the message is textual */

            textual = (!strcasecmp(mtype->type, TEXT_TYPE));

            /* And check whether the message is multipart */

            multipart = (!strcasecmp(mtype->type, MULTIPART_TYPE));

            /* If it's multipart is is alternative or parallel? */

            alternative = (multipart && !strcasecmp(mtype->subtype,
                                          ALTERNATIVE_SUBTYPE));
            parallel = (multipart && !strcasecmp(mtype->subtype,
                                         PARALLEL_SUBTYPE));

            /* And check whether the message is viewable */

            viewable = (viewable_ctype(message->contype) &&
                      (!textual || message->charset == NULL
                       || viewable_charset(message->charset)));

            /* We don't count unknown charsets as textual */

            textual = (textual && (viewable || message->charset == NULL
                               || known_charset(message->charset)));

            /* Free the mime type */

            free_mimetype(mtype);
      }

      /* Check whether we know how to decode the message */

      decodable = (message->encoding == NULL ||
                 viewable_encoding(message->encoding));
            
      /* Now check if the values have changed */

      changed = (textual != message->textual ||
               multipart != message->multipart ||
               alternative != message->alternative ||
               parallel != message->parallel ||
               viewable != message->viewable ||
               decodable != message->decodable);

      /* Update the message's flags */

      message->textual = textual;
      message->multipart = multipart;
      message->alternative = alternative;
      message->parallel = parallel;
      message->viewable = viewable;
      message->decodable = decodable;

      /* And return whether any values changed */

      return(changed);
}
/****************************************************************************/
void remime(buf)
MAILBUF *buf;
{
      /* Update the Mime flags after viewable-charsets changes */

      MAILBUF *b = buf;
      MESSAGE *m;

      /* Loop over all available buffers */

      do {
            /* Check all messages in this buffer */

            for (m = b->messages; m != NULL &&
                 m->text != NULL; m = m->next) {
                  /* Reset the MIME flags on this message */

                  if (set_mime_flags(m)) {
                        /* This message's status has changed */

                        set_sys_tags(m);
                  }
            }

            /* Move on to the next buffer */

            b = b->next;
      } while (b != buf);

      /* That's that done */

      return;
}
/****************************************************************************/
int viewable_ctype(ctype)
char *ctype;
{
      /* Return whether af can handle the content-type internally */

      static char *viewable_types[] = VIEWABLE_TYPES;
      char *canon_type, **vtype;
      int viewable = FALSE;

      /* Canonicalise the Content-Type */

      canon_type = mime_type(ctype, FALSE, FALSE, AC_FULL);

      /* Is this Content-Type listed as viewable? */

      for (vtype = viewable_types; !viewable && *vtype != NULL; vtype++) {
            /* Is this the type we're looking for? */

            viewable = (!strcasecmp(canon_type, *vtype));
      }

      /* Clean up and return whether ctype is viewable */

      free(canon_type);
      return(viewable);
}
/****************************************************************************/
int viewable_encoding(enc)
char *enc;
{
      /* Return TRUE if the encoding is viewable */

      static char *encodings[] = VALID_ENCODINGS;
      char **e;
      
      /* Loop over all the known encodings */

      for (e = encodings; *e != NULL; e++) {
            /* Is this the encoding we're looking for? */

            if (!strcasecmp(enc, *e)) {
                  /* This encoding is viewable */

                  return(TRUE);
            }
      }

      /* This encoding is not viewable */

      return(FALSE);
}
/****************************************************************************/
int viewable_charset(cset)
char *cset;
{
      /* Return TRUE if the character set can be viewed */

      char *viewed, *canonical;
      char *cs, *end;
      unsigned len;
      ATOM *alist;

      /* First canonicalise the charset */

      if ((alist = ctokenise(cset)) == NULL) {
            /* Not a valid charset */

            return(FALSE);
      }
      canonical = atext(NULL, alist, AC_UNQUOTE);
      afree(alist);

      /* Get the list of viewable charsets */

      if ((viewed = get_vtext(V_VIEWABLE)) == NULL) {
            /* No character sets are viewable */

            free(canonical);
            return(FALSE);
      }

      /* Loop through checking charset against viewed */

      cs = viewed;
      while (cs != NULL && *cs != '\0') {
            /* Find the end of the viewable charset */

            end = strchr(cs, ':');

            /* How long is the current charset? */

            len = (end != NULL) ? end - cs : strlen(cs);

            /* Does this charset match the current one? */

            if (len == strlen(canonical) &&
                !strncasecmp(canonical, cs, len)) {
                  free(canonical);
                  return(TRUE);
            }

            /* Update the loop counter */

            cs = (end != NULL) ? end + 1 : NULL;
      }

      /* The character set is not viewable */

      free(canonical);
      return(FALSE);
}
/****************************************************************************/
MESSAGE *get_body_parts(message)
MESSAGE *message;
{
      /*
       * Return a linked list containing all the body parts of a
       * message.  Each body-part is stored as a message in its
       * own right, which makes later handling of the body part
       * extremely simple.
       */

      char *boundary, *delimiter;
      char *terminator;
      int what;
      MIMETYPE *mtype;
      TEXTLINE *delim, *next;
      MESSAGE *body_parts = NULL;

      /* First check if message could be a multipart message */

      if (!message->multipart || message->contype == NULL ||
          (mtype = parse_contype(message->contype, AC_FULL)) == NULL) {
            /* This message can't be a valid multipart */

            return(NULL);
      }

      /* Now check if it is a valid multipart message */

      if (strcasecmp(mtype->type, MULTIPART_TYPE) ||
          (boundary = find_param(mtype->params, BOUNDARY_PARAM)) == NULL) {
            /* This isn't a valid multipart message */

            free_mimetype(mtype);
            return(NULL);
      }

      /* Check how to expand the body parts */

      what = (mtype->subtype != NULL &&
            !strcasecmp(mtype->subtype, DIGEST_SUBTYPE))
            ? BE_DIGEST : BE_NORMAL;

      /* Free the mime type */

      free_mimetype(mtype);

      /* Set the delimiter and terminator from the boundary */

      delimiter = vstrcat(BOUNDARY_DELIM, boundary, NULL);
      terminator = vstrcat(BOUNDARY_DELIM, boundary,
                       BOUNDARY_DELIM, NULL);
      free(boundary);

      /* Find the first delimiter in the message */

      delim = find_delimiter(message->text, delimiter, NULL, TRUE);

      /* Now loop over the body parts */

      while (delim != NULL &&
             strncmp(delim->line, terminator, strlen(terminator))) {
            /* Find the next delimiter in the message */

            next = find_delimiter(delim->next, delimiter, NULL, TRUE);

            /* Now add a new body part to the list */

            body_parts = add_body_part(body_parts, message, delim->next,
                                 next, NULL, what);

            /* And move on to the next delimiter */

            delim = next;
      }
      
      /* Clean up and return the body-part list */

      free(delimiter);
      free(terminator);
      return(body_parts);
}
/****************************************************************************/
MESSAGE *get_alternative(message, body_parts, what)
MESSAGE *message, *body_parts;
int what;
{
      /*
       * Select a single alternative from the body parts of a
       * message/alternative for the operation defined by what.
       * Returns NULL if the message isn't a multipart/alternative,
       * or the selected body part otherwise.
       */

      MESSAGE *selected, *b;

      /* First check if message could be a multipart/alternative */

      if (!message->multipart || !message->alternative
          || body_parts == NULL) {
            /* This message can't be a valid multipart */

            return(NULL);
      }

      /* Initially select the first body part */

      selected = body_parts;

      /* Now loop over the body parts */

      for (b = body_parts; b != NULL && b->text != NULL; b = b->next) {
            /* Do we know how to display this body part? */

            if (b->viewable || b->decodable
                && find_mailcap(b->contype, b, what) != NULL) {
                  /* Select this body part instead */

                  selected = b;
            }
      }

      /* Now return a copy of the selected body part */

      return(copy_one_message(selected));
}
/****************************************************************************/
MESSAGE *get_submessage(message)
MESSAGE *message;
{
      /*
       * Return a message entry giving the message encapsulated in
       * the message.  The entry contains a full set of headers
       * for a message/rfc822 entry.
       */

      MIMETYPE *mtype;
      TEXTLINE *body;
      MESSAGE *submessage;

      /* First check if message could be an encapsulated message */

      if (message->contype == NULL || message->textual
          || !message->viewable || !message->decodable
          || (mtype = parse_contype(message->contype, AC_FULL)) == NULL) {
            /* This message can't be an encapsulated message */

            return(NULL);
      }

      /* Now check if it is an encapsulated message */

      if (strcasecmp(mtype->type, MESSAGE_TYPE) ||
          strcasecmp(mtype->subtype, RFC822_SUBTYPE) &&
          strcasecmp(mtype->subtype, NEWS_SUBTYPE)) {
            /* This isn't a valid encapsulated message */

            free_mimetype(mtype);
            return(NULL);
      }

      /* Free the mime type */

      free_mimetype(mtype);

      /* Find the text of the encapsulated message */

      body = find_body(message->text);

      /* Now extract the encapsulated message */

      submessage = add_body_part(NULL, message, body, NULL,
                           NULL, BE_MESSAGE);

      /* And return the encapsulated message */

      return(submessage);
}
/****************************************************************************/
MESSAGE_PART *get_message_parts(list, message)
MESSAGE *list, *message;
{
      /* Find all the parts of a message/partial message */

      char *id;
      MIMETYPE *mtype;
      MESSAGE_PART *msg_parts = NULL;
      MESSAGE_PART *part;
      MESSAGE *m;

      /* First check if message could be a partial message */

      if (message->contype == NULL || message->textual
          || !message->viewable || !message->decodable
          || (mtype = parse_contype(message->contype, AC_FULL)) == NULL) {
            /* This message can't be an partial message */

            return(NULL);
      }

      /* Now check if it is a partial message */

      if (strcasecmp(mtype->type, MESSAGE_TYPE) ||
          strcasecmp(mtype->subtype, PARTIAL_SUBTYPE)) {
            /* This isn't a partial message */

            free_mimetype(mtype);
            return(NULL);
      }

      /* Get the id for the parts of the message */

      if ((id = find_param(mtype->params, ID_PARAM)) == NULL) {
            /* This isn't a valid partial message */

            free_mimetype(mtype);
            return(NULL);
      }

      /* Free the mime type */

      free_mimetype(mtype);

      /* Now look for the parts of the message */

      for (m = list; m != NULL && m->text != NULL; m = m->next) {
            /* Does this message contain part of the message? */

            if ((part = message_part(m, id)) != NULL) {
                  /* Add this message part to the list */

                  msg_parts = add_message_part(msg_parts, part);
            }
      }

      /* Now try recursing over any encapsulated messages */

      for (m = list; m != NULL && m->text != NULL &&
           !check_message_parts(msg_parts); m = m->next) {
            /* Does this message encapsulate part of the message */

            if ((part = recursive_part(list, m, msg_parts, id)) != NULL) {
                  /* Add this message part to the list */

                  msg_parts = add_message_part(msg_parts, part);
            }
      }

      /* Now free the partial message id */

      free(id);

      /* Check that we got all the parts of the message */

      if (!check_message_parts(msg_parts)) {
            /* Invalid or incomplete partial message */

            free_message_parts(msg_parts);
            return(NULL);
      }

      /* Now return the parts of the message */

      return(msg_parts);
}
/****************************************************************************/
MESSAGE *rebuild_message(msg_parts)
MESSAGE_PART *msg_parts;
{
      /*
       * Return a message entry giving the message which was split
       * into the messages in message_parts.
       */

      TEXTLINE *body;
      MESSAGE_PART *p;
      MESSAGE *submessage;

      /* Find the body of the first partial message */

      body = find_body(msg_parts->message->text);

      /* Now extract the first part of the encapsulated message */

      submessage = add_body_part(NULL, msg_parts->message, body,
                           NULL, NULL, BE_MESSAGE);

      /* Now add any more text to the submessage body */

      for (p = msg_parts->next; p != NULL; p = p->next) {
            /* Find the start of the encapsulated message body */

            body = find_body(p->message->text);

            /* And add this body part to the message */

            submessage = add_body_text(submessage, body, NULL, FALSE);
      }

      /* Now return the encapsulated message */

      return(submessage);
}
/****************************************************************************/
void free_message_parts(msg_parts)
MESSAGE_PART *msg_parts;
{
      /* Recursively free a message part list */

      if (msg_parts != NULL) {
            /* Free the data in the message part */

            if (msg_parts->parts != NULL) {
                  free_messages(msg_parts->message);
            }

            /* Free the sub-parts of the part */

            free_message_parts(msg_parts->parts);
            free_message_parts(msg_parts->next);

            /* And free the message part itself */

            free(msg_parts);
      }

      /* That's all folks */

      return;
}
/****************************************************************************/
MESSAGE *get_digest_parts(message)
MESSAGE *message;
{
      /*
       * Return a linked list containing all the body parts of a
       * mail digest.  Each body-part is stored as a message in
       * its own right, which makes later handling of the body
       * part extremely simple.
       */

      char *delimiter = NULL, *escape = NULL;
      TEXTLINE *delim, *next;
      MESSAGE *body_parts = NULL;

      /* Only a text/plain message can be a digest */

      if (!message->textual || !message->viewable
          || !message->decodable) {
            /* This isn't a valid digest */

            return(NULL);
      }

      /* Find the first RFC1153 delimiter in the message */

      if ((delim = find_delimiter(message->text, RFC1153_START,
                            NULL, FALSE)) != NULL
          && find_delimiter(delim->next, RFC1153_DELIM,
                        NULL, FALSE) != NULL) {
            /* We're looking at an RFC1153 digest */

            delimiter = xstrdup(RFC1153_DELIM);
      } else if ((delim = find_delimiter(message->text, RFC934_DELIM,
                                RFC934_ESCAPE, FALSE)) != NULL) {
            /* We're looking at an RFC943 digest */

            delimiter = xstrdup(RFC934_DELIM);
            escape = xstrdup(RFC934_ESCAPE);
      }

      /* Now loop over the digest entries */

      while (delim != NULL &&
             (next = find_delimiter(delim->next, delimiter,
                              escape, FALSE)) != NULL) {
            /* Skip any white space before the entry */

            while (delim->next != NULL && is_blank(delim->next->line)) {
                  delim = delim->next;
            }

            /* Add any new body-part to the list */

            body_parts = (delim->next != NULL && delim->next != next)
                  ? add_body_part(body_parts, message, delim->next,
                              next, escape, BE_MESSAGE)
                  : body_parts;

            /* And move on to the next delimiter */

            delim = next;
      }
      
      /* Free up space */

      if (delimiter != NULL) {
            free(delimiter);
      }
      if (escape != NULL) {
            free(escape);
      }

      /* And return the body-part list */

      return(body_parts);
}
/****************************************************************************/
MESSAGE *make_body_part(message)
MESSAGE *message;
{
      /* Return the body of the message as a body-part */

      return(read_body_part(message, message->text,
                        NULL, NULL, 1, BE_BODY_PART));
}
/****************************************************************************/
static char *mime_type(text, mailcap, params, canon)
char *text;
int mailcap, params, canon;
{
      /* Parse a Content-Type and return it as a string */

      char *type;
      MIMETYPE *mtype;

      /* Parse the content type and parameters */

      if ((mtype = parse_contype(text, mailcap, canon)) == NULL) {
            /* Failed parsing content type */

            return(NULL);
      }

      /* Make a Content-Type string from the structure */

      type = ct_text(mtype, params);

      /* Free the type info and return the Content-Type string */

      free_mimetype(mtype);
      return(type);
}
/****************************************************************************/
static char *mime_charset(text, canon)
char *text;
int canon;
{
      /* Canonicalise and return a charset */

      char *cset;
      ATOM *alist, *start;

      /* Tokenise the charset string */

      if ((alist = ctokenise(text)) == NULL) {
            return(NULL);
      }

      /* Find the charset value in the first token */

      if ((start = atoken(alist)) == NULL) {
            a_errno = AERR_NULL;
            afree(alist);
            return(NULL);
      }

      /* Check the type of the parameter name */

      if (start->type != AT_ATOM && start->type != AT_QSTRING) {
            a_errno = SERR_CHARSET;
            afree(alist);
            return(NULL);
      }

      /* Check there are no trailing tokens */

      if (atoken(start->next) != NULL) {
            a_errno = SERR_CHARSET;
            afree(alist);
            return(NULL);
      }

      /* Now copy the text into a buffer */

      cset = atext(NULL, alist, canon);

      /* Free the atom list and return the charset */

      afree(alist);
      return(cset);
}
/****************************************************************************/
static char *mime_encoding(text, strict, canon)
char *text;
int strict, canon;
{
      /* Parse a Content-Transfer-Encoding header */

      static char *encodings[] = ENCODINGS;
      char **cte, *enc;
      int valid = FALSE;
      ATOM *alist, *start;

      /* Tokenise the encoding string */

      if ((alist = tokenise(text)) == NULL) {
            return(NULL);
      }

      /* Find the encoding value in the first token */

      if ((start = atoken(alist)) == NULL) {
            a_errno = EERR_NULL;
            afree(alist);
            return(NULL);
      }

      /* Check the type of the encoding */

      if (start->type != AT_ATOM && start->type != AT_QSTRING) {
            a_errno = EERR_ENCODING;
            afree(alist);
            return(NULL);
      }

      /* Check there are no trailing tokens */

      if (atoken(start->next) != NULL) {
            a_errno = EERR_ENCODING;
            afree(alist);
            return(NULL);
      }

      /* Check if this is an x- encoding */

      valid = (!strncasecmp(start->text, X_PREFIX, strlen(X_PREFIX)));

      /* Check the encoding is valid */

      for (cte = encodings; strict && !valid && *cte != NULL; cte++) {
            /* Is this one of the valid encodings? */

            valid = (!strcasecmp(*cte, start->text));
      }

      /* Check if the encoding wasn't valid */

      if (strict && !valid) {
            a_errno = EERR_ENCODING;
            afree(alist);
            return(NULL);
      }

      /* Now copy the text into a buffer */

      enc = atext(NULL, alist, canon);

      /* Free the atom list and return the encoding */

      afree(alist);
      return(enc);
}
/****************************************************************************/
static char *mime_disp(text, params, canon)
char *text;
int params, canon;
{
      /* Parse a Content-Disposition and return it as a string */

      char *type;
      MIMETYPE *mtype;

      /* Parse the content type and parameters */

      if ((mtype = parse_condisp(text, canon)) == NULL) {
            /* Failed parsing content type */

            return(NULL);
      }

      /* Make a Content-Type string from the structure */

      type = ct_text(mtype, params);

      /* Free the type info and return the Content-Disposition string */

      free_mimetype(mtype);
      return(type);
}
/****************************************************************************/
static MIMETYPE *parse_contype(ctype, mailcap, canon)
char *ctype;
int mailcap, canon;
{
      /* Parse a Content-Type: header */

      ATOM *alist, *type, *slash;
      ATOM *subtype, *semi;
      MIMETYPE *mtype;

      /* Tokenise the content-type string */

      if ((alist = ctokenise(ctype)) == NULL) {
            /* Failed to tokenise the string */

            return(NULL);
      }

      /* Find the type and subtype in the atom list */

      type = atoken(alist);
      slash = (type != NULL) ? atoken(type->next) : NULL;
      subtype = (slash != NULL) ? atoken(slash->next) : NULL;
      semi = (subtype != NULL) ? atoken(subtype->next)
            : (slash != NULL) ? atoken(slash->next)
            : (type != NULL) ? atoken(type->next) : NULL;

      /* Check we got a valid type and subtype? */

      if (type == NULL || type->type != AT_ATOM ||
          semi != NULL && semi->type != AT_SEMI || !mailcap &&
          (slash == NULL || slash->type != AT_SLASH
           || subtype == NULL || subtype->type != AT_ATOM)) {
            /* Invalid type/subtype string, set the error */

            a_errno = (type == NULL) ? CERR_NULL : CERR_TYPE;
            if (a_errtext != NULL) {
                  free(a_errtext);
            }
            a_errtext = (type != NULL) ? atext(NULL, type, AC_NONE)
                                 : xstrdup(END_ERRTEXT);
            afree(alist);
            return(NULL);
      }

      /* Split the atom list into it's components */

      type = adelete(alist, alist, type);
      subtype = (slash != NULL) ? acut(type, type, slash) : NULL;
      subtype = (slash != NULL) ? adiscard(subtype, slash) : NULL;
      alist = (subtype != NULL) ? acut(subtype, subtype, semi) : NULL;

      /* Allocate and set up the mime type structure */

      mtype = (MIMETYPE *) xmalloc(sizeof(MIMETYPE));
      mtype->type = atext(NULL, type, canon);
      mtype->subtype = (subtype != NULL) ?
            atext(NULL, subtype, canon) : xstrdup(MCAP_WILDCARD);
      mtype->params = NULL;

      /* Free the type atom lists */

      afree(type);
      afree(subtype);

      /* Now parse the parameters */

      if (semi != NULL &&
          (mtype->params = parse_params(semi, FALSE, canon)) == NULL) {
            /* Error parsing parameters */

            free_mimetype(mtype);
            return(NULL);
      }

      /* Now return the type structure */

      return(mtype);
}
/****************************************************************************/
static MIMETYPE *parse_condisp(cdisp, canon)
char *cdisp;
int canon;
{
      /* Parse a Content-Disposition: header */

      ATOM *alist, *disp, *semi;
      MIMETYPE *mtype;

      /* Tokenise the content-disposition string */

      if ((alist = ctokenise(cdisp)) == NULL) {
            /* Failed to tokenise the string */

            return(NULL);
      }

      /* Find the disposition in the atom list */

      disp = atoken(alist);
      semi = (disp != NULL) ? atoken(disp->next) : NULL;

      /* Check we got a valid disposition */

      if (disp == NULL || disp->type != AT_ATOM ||
          semi != NULL && semi->type != AT_SEMI) {
            /* Invalid disposition string, set the error */
          
            a_errno = (disp == NULL) ? DERR_NULL : DERR_DISP;
            if (a_errtext != NULL) {
                  free(a_errtext);
            }
            a_errtext = (disp != NULL) ? atext(NULL, disp, AC_NONE)
                                 : xstrdup(END_ERRTEXT);
            afree(alist);
            return(NULL);
      }

      /* Extract the disposition from the list */

      alist = adelete(alist, alist, disp);
      alist = acut(alist, disp, semi);

      /* Allocate and set up the mime type structure */

      mtype = (MIMETYPE *) xmalloc(sizeof(MIMETYPE));
      mtype->type = atext(NULL, disp, canon);
      mtype->subtype = NULL;
      mtype->params = NULL;

      /* Free the disposition atom list */

      afree(disp);

      /* Now parse the parameters */

      if (semi != NULL &&
          (mtype->params = parse_params(semi, TRUE, canon)) == NULL) {
            /* Error parsing parameters */

            free_mimetype(mtype);
            return(NULL);
      }

      /* Now return the type structure */

      return(mtype);
}
/****************************************************************************/
static MIMEPARAM *parse_params(alist, content_disp, canon)
ATOM *alist;
int content_disp, canon;
{
      /* Parse a content-type's or content-disposition's parameters */

      ATOM *semi, *name, *equals, *value, *newsemi;
      MIMEPARAM *params = NULL, *p = NULL;

      /* We start at the first semicolon */

      semi = alist;

      /* Now loop over each parameter */

      while (semi != NULL) {
            /* Extract the parameter from the atom list */

            name = atoken(semi->next);
            equals = (name != NULL) ? atoken(name->next) : NULL;
            value = (equals != NULL) ? atoken(equals->next) : NULL;
            newsemi = (value != NULL) ? atoken(value->next) : NULL;

            /* Check we got a valid parameter */

            if (semi->type != AT_SEMI || name == NULL ||
                name->type != AT_ATOM || equals == NULL ||
                equals->type != AT_EQUALS || value == NULL ||
                value->type != AT_ATOM && value->type != AT_QSTRING ||
                newsemi != NULL && newsemi->type != AT_SEMI) {

                  /* Invalid parameter string, set the error */
          
                  a_errno = (content_disp) ? DERR_PARAM : CERR_PARAM;
                  if (a_errtext != NULL) {
                        free(a_errtext);
                  }
                  a_errtext = atext(NULL, semi, AC_NONE);
                  afree(semi);
                  return(NULL);
            }

            /* Split the atom list into it's components */

            name = adelete(semi, semi, name);
            value = acut(name, name, equals);
            value = adiscard(value, equals);
            semi = acut(value, value, newsemi);

            /* Add the parameter to the list */

            if (params == NULL) {
                  params = p = (MIMEPARAM *) xmalloc(sizeof(MIMEPARAM));
            } else {
                   p->next = (MIMEPARAM *) xmalloc(sizeof(MIMEPARAM));
                   p = p->next;
            }
            p->name = atext(NULL, name, canon);
            p->value = atext(NULL, value, canon);
            p->next = NULL;

            /* And free the parameter lists */

            afree(name);
            afree(value);
      }

      /* Parsed ok, return the parameter list */

      return(params);
}
/****************************************************************************/
static char *param_value(ctype, name, content_disp, textual_only)
char *ctype, *name;
int content_disp, textual_only;
{
      /* Return the value of the named parameter specified in ctype */

      char *value;
      int textual;
      MIMETYPE *mtype;

      /* First parse and check the content type */

      if ((mtype = (content_disp) ? parse_condisp(ctype, AC_FULL)
           : parse_contype(ctype, AC_FULL)) != NULL) {
            /* Check the message is textual */

            textual = (!strcasecmp(mtype->type, TEXT_TYPE));

            /* Now find the parameter in the list */

            if ((!textual_only || textual) &&
                (value = find_param(mtype->params, name)) != NULL) {
                  /* Clean up and return the value */

                  free_mimetype(mtype);
                  return(value);
            }

            /* And free the mime type */

            free_mimetype(mtype);
      }

      /* We didn't find a value */

      return(NULL);
}
/****************************************************************************/
static char *find_param(params, name)
MIMEPARAM *params;
char *name;
{
      /* Return the (unquoted) value of the named parameter */

      char *value;
      MIMEPARAM *param;
      ATOM *alist;

      /* Loop over the parameters looking for the name */

      for (param = params; param != NULL; param = param->next) {
            /* Is this the parameter we're looking for? */

            if (!strcasecmp(param->name, name) &&
                (alist = ctokenise(param->value)) != NULL) {
                  /* Canonicalise the parameter value */

                  value = atext(NULL, alist, AC_UNQUOTE);
                  afree(alist);
                  return(value);
            }
      }

      /* We didn't find the parameter */

      return(NULL);
}
/****************************************************************************/
static char *ct_text(mtype, params)
MIMETYPE *mtype;
int params;
{
      /* Make a string from a Content-Type structure */

      char *typebuf;
      MIMEPARAM *param;

      /* Make the new content-type string */

      typebuf = (mtype->subtype == NULL) ? xstrdup(mtype->type)
            : vstrcat(mtype->type, "/", mtype->subtype, NULL);

      /* Add on any supplied parameters */

      for (param = mtype->params; params &&
           param != NULL; param = param->next) {
            /* Append the parameter to the string */

            typebuf = xrealloc(typebuf, strlen(typebuf) +
                           strlen(param->name) +
                           strlen(param->value) + 4);
            (void) strcat(typebuf, "; ");
            (void) strcat(typebuf, param->name);
            (void) strcat(typebuf, "=");
            (void) strcat(typebuf, param->value);
      }

      /* Now return the Content-Type string */

      return(typebuf);
}
/****************************************************************************/
static void free_mimetype(mtype)
MIMETYPE *mtype;
{
      /* Free a mime type structure */

      MIMEPARAM *param = mtype->params, *next;

      /* Free the type */

      free(mtype->type);

      /* Free the subtype, if any */

      if (mtype->subtype != NULL) {
            free(mtype->subtype);
      }

      /* Free the parameters */
      
      while (param != NULL) {
            /* Save next param and free this one */

            next = param->next;
            free(param->name);
            free(param->value);
            free(param);

            /* Update current parameter */

            param = next;
      }

      /* Now free the structure itself */

      free(mtype);
      return;
}
/****************************************************************************/
static TEXTLINE *find_delimiter(text, delimiter, escape, mime)
TEXTLINE *text;
char *delimiter, *escape;
int mime;
{
      /* Return the next delimiter in the message text */

      char *end;
      size_t len;
      TEXTLINE *t;

      /* Get the length of the delimiter */

      len = strlen(delimiter);

      /* Loop over the text, looking for a delimiter */

      for (t = text; t != NULL; t = t->next) {
            /* Is this line a delimiter but not an escape? */

            if (!strncmp(t->line, delimiter, len)) {
                  /* Save the end of the delimiter */

                  end = t->line + len;

                  /* Is this a final delimiter? */

                  if (!strncmp(end, BOUNDARY_DELIM,
                             strlen(BOUNDARY_DELIM))) {
                        /* Move on the end of the delimiter */

                        end += strlen(BOUNDARY_DELIM);
                  }

                  /* Check the boundary is terminated and not escaped */

                  if ((!mime || is_blank(end))
                      && (escape == NULL
                        || strncmp(t->line, escape, strlen(escape)))) {
                        /* This is the next delimiter line */

                        return(t);
                  }
            }
      }

      /* No delimiter found in the list */

      return(NULL);
}
/****************************************************************************/
static TEXTLINE *find_body(text)
TEXTLINE *text;
{
      /* Return the start of the message body in text */

      TEXTLINE *t;

      /* Loop over the text, looking for end-of-headers */

      for (t = text; t != NULL; t = t->next) {
            /* Is this the line after the headers? */

            if (is_blank(t->line)) {
                  /* We've found the body */

                  return(t->next);
            }
      }

      /* No body found in the message */

      return(NULL);
}
/****************************************************************************/
static MESSAGE_PART *message_part(message, partial_id)
MESSAGE *message;
char *partial_id;
{
      /* Return any part of a partial message contained in message */

      char *id;
      MIMETYPE *mtype;

      /* Could this message be a partial message? */

      if (message->contype != NULL && !message->textual && message->viewable
          && (mtype = parse_contype(message->contype, AC_FULL)) != NULL) {
            /* Check it is a valid partial message */

            if (!strcasecmp(mtype->type, MESSAGE_TYPE) &&
                !strcasecmp(mtype->subtype, PARTIAL_SUBTYPE) &&
                (id = find_param(mtype->params, ID_PARAM)) != NULL) {
                  /* Check it is a part of the message */
                  
                  if (!strcmp(id, partial_id)) {
                        /* This is a part of the message */

                        free(id);
                        free_mimetype(mtype);
                        return(new_message_part(message));
                  }

                  /* Free the message's id */

                  free(id);
            }

            /* Free the mime type */

            free_mimetype(mtype);
      }

      /* No match for the id found in the message */

      return(NULL);
}
/****************************************************************************/
static MESSAGE_PART *recursive_part(list, message, msg_parts, partial_id)
MESSAGE *list, *message;
MESSAGE_PART *msg_parts;
char *partial_id;
{
      /*
       * It is possible that a message part could itself have
       * been encapsulated in a message/rfc822 or message/partial
       * message by an MTA.  This routine scans for parts of the
       * message in the bodies of the messages in the list.
       */

      static PARTIAL_ID *id_stack = NULL;
      MESSAGE *submessage;
      MESSAGE_PART *sub_parts;
      MESSAGE_PART *part;

      /* Check if we've already handled this partial id */

      if (message_in_parts(msg_parts, message)
          || id_on_stack(id_stack, partial_id)) {
            /* We've already seen this id */

            return(NULL);
      }
        
      /* Push the partial id on to the stack */

      id_stack = push_partial_id(id_stack, partial_id);

      /* Now try for an encapsulated message */

      submessage = get_submessage(message);
      sub_parts = get_message_parts(list, message);

      /* Rebuild any partial message */

      submessage = (sub_parts == NULL) ? submessage
            : rebuild_message(sub_parts);

      /* Now check for the message part in the encapsulated message */

      if (submessage != NULL &&
          (part = message_part(submessage, partial_id)) != NULL) {
            /* Add the parts of the message and pop the id stack */

            part->parts = sub_parts;
            id_stack = pop_partial_id(id_stack);

            /* And return the message part */

            return(part);
      }

      /* Pop the partial id from the stack */

      id_stack = pop_partial_id(id_stack);

      /* Clean up the submessage and body parts */

      free_messages(submessage);
      free_message_parts(sub_parts);

      /* No encapsulated message part found */

      return(NULL);
}
/****************************************************************************/
static int message_in_parts(msg_parts, message)
MESSAGE_PART *msg_parts;
MESSAGE *message;
{
      /* Return whether message is referenced in the message parts */

      return(msg_parts != NULL && 
             (msg_parts->message == message ||
            message_in_parts(msg_parts->next, message) ||
            message_in_parts(msg_parts->parts, message)));
}
/****************************************************************************/
static int id_on_stack(id_stack, partial_id)
PARTIAL_ID *id_stack;
char *partial_id;
{
      /* Return whether partial_id is referenced on the id stack */

      PARTIAL_ID *id;

      /* Loop through the stack looking for parts */

      for (id = id_stack; id != NULL; id = id->next) {
            /* Is this the id we're looking for? */

            if (!strcmp(id->partial_id, partial_id)) {
                  return(TRUE);
            }
      }

      /* Didn't find the partial id in the stack */

      return(FALSE);
}
/****************************************************************************/
static int check_message_parts(msg_parts)
MESSAGE_PART *msg_parts;
{
      /* Check that we have all the parts of a message */

      int number = 0, total = 0;
      MESSAGE_PART *p;

      /* Now check we got all the parts of the message */

      for (p = msg_parts; p != NULL; p = p->next) {
            /* Set the total number of parts */

            total = (total) ? total : p->total;

            /* Check the part number and total */

            if (p->number != ++number || total &&
                p->total && p->total != total) {
                  /* We have a missing part */

                  return(FALSE);
            }
      }

      /* Return whether the total number of parts matches */

      return(total && total == number);
}
/****************************************************************************/
static MESSAGE *add_body_part(body_parts, message, start, end, escape, what)
MESSAGE *body_parts, *message;
TEXTLINE *start, *end;
char *escape;
int what;
{
      /*
       * Add a new message to the body_parts list, containing the
       * body part found between start and end.  The original
       * message's header information is preserved in the new
       * message.  What indicates how the body parts should be
       * interpreted.
       */

      int position;
      MESSAGE *node, *m;

      /* What will the new body part's position be? */

      position = count_messages(body_parts, TRUE);

      /* Read the body part from the original message */

      if ((node = read_body_part(message, start, end, escape,
                           position, what)) != NULL) {
            /* Now add the body-part to the list */

            for (m = body_parts; m != NULL; m = m->next) {
                  /* Are we at the end of the list? */

                  if (m->next == NULL) {
                        /* Append the new node */

                        node->prev = m;
                        m->next = node;
                        break;
                  }
            }
      }

      /* Now return the body-part list */

      return((body_parts != NULL) ? body_parts : node);
}
/****************************************************************************/
static MESSAGE *add_body_text(message, start, end)
MESSAGE *message;
TEXTLINE *start, *end;
{
      /*
       * Add the body of the message stored between start and
       * end to the text of message.
       */

      MESSAGE *node;
      TEXTLINE *body, *t;

      /* Read the body part from the original message */

      node = read_body_part(message, start, end, NULL, 0, BE_MESSAGE);

      /* Now add the text of the body-part to the list */

      body = find_body(node->text);

      /* Remove the text from this part of the message */

      for (t = node->text; t != NULL; t = t->next) {
            /* Is this the node we're looking for? */

            if (t->next == body) {
                  t->next = NULL;
                  break;
            }
      }

      /* And add it to the original message */

      for (t = message->text; t != NULL; t = t->next) {
            /* Are we at the end of the text? */

            if (t->next == NULL) {
                  t->next = body;
                  break;
            }
      }

      /* Free the new body part */

      free_messages(node);

      /* And return the updated message */

      return(message);
}
/****************************************************************************/
static MESSAGE_PART *new_message_part(message)
MESSAGE *message;
{
      /* Return a new message part based on message */

      char *ntext, *ttext;
      int number = 0, total = 0;
      MESSAGE_PART *node;

      /* Get the message's part number */

      if ((ntext = get_param(message->contype, NUMBER_PARAM)) != NULL) {
            /* Extract the number and free the text */

            number = atoi(ntext);
            free(ntext);
      }

      /* Now check the message's has a valid part number */

      if (!number) {
            /* This isn't a valid partial message */

            return(NULL);
      }

      /* Get the message's total part count */

      if ((ttext = get_param(message->contype, TOTAL_PARAM)) != NULL) {
            /* Extract the total and free the text */

            total = atoi(ttext);
            free(ttext);
      }

      /* Now create a new message part for the message */

      node = (MESSAGE_PART *) xmalloc(sizeof(MESSAGE_PART));
      node->number = number;
      node->total = total;
      node->message = message;
      node->parts = NULL;
      node->next = NULL;

      /* And return the new message part */

      return(node);
}
/****************************************************************************/
static MESSAGE_PART *add_message_part(msg_parts, part)
MESSAGE_PART *msg_parts, *part;
{
      /* Add part to the list of message parts */

      MESSAGE_PART *p;

      /* Prepend the node if it belongs at the start of the list */

      if (msg_parts == NULL || msg_parts->number > part->number) {
            /* Prepend the node to the list */

            part->next = msg_parts;
            return(part);
      }

      /* Now find the insert position for the node */

      for (p = msg_parts; p != NULL; p = p->next) {
            /* Do we want to insert the node here? */

            if (p->next == NULL || p->next->number > part->number) {
                  /* Insert the node and return the list */

                  part->next = p->next;
                  p->next = part;
                  return(msg_parts);
            } else if (p->next->number == part->number) {
                  /* Simply discard the part */

                  free_message_parts(part);
                  return(msg_parts);
            }
      }

      /* We shouldn't ever reach here */

      return(msg_parts);
}
/****************************************************************************/
static PARTIAL_ID *push_partial_id(id_stack, partial_id)
PARTIAL_ID *id_stack;
char *partial_id;
{
      /* Push a partial message id onto the id stack */

      PARTIAL_ID *node;

      /* Allocate and fill the partial id node */

      node = (PARTIAL_ID *) xmalloc(sizeof(PARTIAL_ID));
      node->partial_id = xstrdup(partial_id);
      node->next = id_stack;

      /* And then return it */

      return(node);
}
/****************************************************************************/
static PARTIAL_ID *pop_partial_id(id_stack)
PARTIAL_ID *id_stack;
{
      /* Pop a partial message id from the id stack */

      PARTIAL_ID *next;

      /* Store the next id in the stack */

      next = id_stack->next;

      /* Free the top partial id in the stack */

      free(id_stack->partial_id);
      free(id_stack);

      /* And return the new top of the stack */

      return(next);
}
/****************************************************************************/

Generated by  Doxygen 1.6.0   Back to index