Logo Search packages:      
Sourcecode: heirloom-mailx version File versions  Download package

mime.c

/*
 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 */
/*
 * Copyright (c) 2000
 *    Gunnar Ritter.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
#ifdef      DOSCCS
static char copyright[]
= "@(#) Copyright (c) 2000, 2002 Gunnar Ritter. All rights reserved.\n";
static char sccsid[]  = "@(#)mime.c 2.70 (gritter) 3/10/09";
#endif /* DOSCCS */
#endif /* not lint */

#include "rcv.h"
#include "extern.h"
#include <ctype.h>
#include <errno.h>
#ifdef      HAVE_WCTYPE_H
#include <wctype.h>
#endif      /* HAVE_WCTYPE_H */

/*
 * Mail -- a mail program
 *
 * MIME support functions.
 */

/*
 * You won't guess what these are for.
 */
static const char basetable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static char *mimetypes_world = "/etc/mime.types";
static char *mimetypes_user = "~/.mime.types";
char *us_ascii = "us-ascii";

static int mustquote_body(int c);
static int mustquote_hdr(const char *cp, int wordstart, int wordend);
static int mustquote_inhdrq(int c);
static size_t     delctrl(char *cp, size_t sz);
static char *getcharset(int isclean);
static int has_highbit(register const char *s);
#ifdef      HAVE_ICONV
static void uppercopy(char *dest, const char *src);
static void stripdash(char *p);
static size_t iconv_ft(iconv_t cd, char **inb, size_t *inbleft,
            char **outb, size_t *outbleft);
static void invalid_seq(int c);
#endif      /* HAVE_ICONV */
static int is_this_enc(const char *line, const char *encoding);
static char *mime_tline(char *x, char *l);
static char *mime_type(char *ext, char *filename);
static enum mimeclean mime_isclean(FILE *f);
static enum conversion gettextconversion(void);
static char *ctohex(int c, char *hex);
static size_t mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int));
static void mime_str_toqp(struct str *in, struct str *out,
            int (*mustquote)(int), int inhdr);
static void mime_fromqp(struct str *in, struct str *out, int ishdr);
static size_t mime_write_tohdr(struct str *in, FILE *fo);
static size_t convhdra(char *str, size_t len, FILE *fp);
static size_t mime_write_tohdr_a(struct str *in, FILE *f);
static void addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
static void addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
static size_t fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f,
            enum tdflags flags, char *prefix, size_t prefixlen);

/*
 * Check if c must be quoted inside a message's body.
 */
static int 
mustquote_body(int c)
{
      if (c != '\n' && (c < 040 || c == '=' || c >= 0177))
            return 1;
      return 0;
}

/*
 * Check if c must be quoted inside a message's header.
 */
static int 
mustquote_hdr(const char *cp, int wordstart, int wordend)
{
      int   c = *cp & 0377;

      if (c != '\n' && (c < 040 || c >= 0177))
            return 1;
      if (wordstart && cp[0] == '=' && cp[1] == '?')
            return 1;
      if (cp[0] == '?' && cp[1] == '=' &&
                  (wordend || cp[2] == '\0' || whitechar(cp[2]&0377)))
            return 1;
      return 0;
}

/*
 * Check if c must be quoted inside a quoting in a message's header.
 */
static int 
mustquote_inhdrq(int c)
{
      if (c != '\n'
            && (c <= 040 || c == '=' || c == '?' || c == '_' || c >= 0177))
            return 1;
      return 0;
}

static size_t
delctrl(char *cp, size_t sz)
{
      size_t      x = 0, y = 0;

      while (x < sz) {
            if (!cntrlchar(cp[x]&0377))
                  cp[y++] = cp[x];
            x++;
      }
      return y;
}

/*
 * Check if a name's address part contains invalid characters.
 */
int 
mime_name_invalid(char *name, int putmsg)
{
      char *addr, *p;
      int in_quote = 0, in_domain = 0, err = 0, hadat = 0;

      if (is_fileaddr(name))
            return 0;
      addr = skin(name);

      if (addr == NULL || *addr == '\0')
            return 1;
      for (p = addr; *p != '\0'; p++) {
            if (*p == '\"') {
                  in_quote = !in_quote;
            } else if (*p < 040 || (*p & 0377) >= 0177) {
                  err = *p & 0377;
                  break;
            } else if (in_domain == 2) {
                  if ((*p == ']' && p[1] != '\0') || *p == '\0'
                              || *p == '\\' || whitechar(*p & 0377)) {
                        err = *p & 0377;
                        break;
                  }
            } else if (in_quote && in_domain == 0) {
                  /*EMPTY*/;
            } else if (*p == '\\' && p[1] != '\0') {
                  p++;
            } else if (*p == '@') {
                  if (hadat++) {
                        if (putmsg) {
                              fprintf(stderr, catgets(catd, CATSET,
                                                142,
                              "%s contains invalid @@ sequence\n"),
                                    addr);
                              putmsg = 0;
                        }
                        err = *p;
                        break;
                  }
                  if (p[1] == '[')
                        in_domain = 2;
                  else
                        in_domain = 1;
                  continue;
            } else if (*p == '(' || *p == ')' || *p == '<' || *p == '>'
                        || *p == ',' || *p == ';' || *p == ':'
                        || *p == '\\' || *p == '[' || *p == ']') {
                  err = *p & 0377;
                  break;
            }
            hadat = 0;
      }
      if (err && putmsg) {
            fprintf(stderr, catgets(catd, CATSET, 143,
                        "%s contains invalid character '"), addr);
#ifdef      HAVE_SETLOCALE
            if (isprint(err))
#else /* !HAVE_SETLOCALE */
            if (err >= 040 && err <= 0177)
#endif      /* !HAVE_SETLOCALE */
                  putc(err, stderr);
            else
                  fprintf(stderr, "\\%03o", err);
            fprintf(stderr, catgets(catd, CATSET, 144, "'\n"));
      }
      return err;
}

/*
 * Check all addresses in np and delete invalid ones.
 */
struct name *
checkaddrs(struct name *np)
{
      struct name *n = np;

      while (n != NULL) {
            if (mime_name_invalid(n->n_name, 1)) {
                  if (n->n_blink)
                        n->n_blink->n_flink = n->n_flink;
                  if (n->n_flink)
                        n->n_flink->n_blink = n->n_blink;
                  if (n == np)
                        np = n->n_flink;
            }
            n = n->n_flink;
      }
      return np;
}

static char defcharset[] = "utf-8";

/*
 * Get the character set dependant on the conversion.
 */
static char *
getcharset(int isclean)
{
      char *charset;

      if (isclean & (MIME_CTRLCHAR|MIME_HASNUL))
            charset = NULL;
      else if (isclean & MIME_HIGHBIT) {
            charset = (wantcharset && wantcharset != (char *)-1) ?
                  wantcharset : value("charset");
            if (charset == NULL) {
                  charset = defcharset;
            }
      } else {
            /*
             * This variable shall remain undocumented because
             * only experts should change it.
             */
            charset = value("charset7");
            if (charset == NULL) {
                  charset = us_ascii;
            }
      }
      return charset;
}

/*
 * Get the setting of the terminal's character set.
 */
char *
gettcharset(void)
{
      char *t;

      if ((t = value("ttycharset")) == NULL)
            if ((t = value("charset")) == NULL)
                  t = defcharset;
      return t;
}

static int 
has_highbit(const char *s)
{
      if (s) {
            do
                  if (*s & 0200)
                        return 1;
            while (*s++ != '\0');
      }
      return 0;
}

static int
name_highbit(struct name *np)
{
      while (np) {
            if (has_highbit(np->n_name) || has_highbit(np->n_fullname))
                  return 1;
            np = np->n_flink;
      }
      return 0;
}

char *
need_hdrconv(struct header *hp, enum gfield w)
{
      if (w & GIDENT) {
            if (hp->h_from && name_highbit(hp->h_from))
                  goto needs;
            else if (has_highbit(myaddrs(hp)))
                  goto needs;
            if (hp->h_organization && has_highbit(hp->h_organization))
                  goto needs;
            else if (has_highbit(value("ORGANIZATION")))
                  goto needs;
            if (hp->h_replyto && name_highbit(hp->h_replyto))
                  goto needs;
            else if (has_highbit(value("replyto")))
                  goto needs;
            if (hp->h_sender && name_highbit(hp->h_sender))
                  goto needs;
            else if (has_highbit(value("sender")))
                  goto needs;
      }
      if (w & GTO && name_highbit(hp->h_to))
            goto needs;
      if (w & GCC && name_highbit(hp->h_cc))
            goto needs;
      if (w & GBCC && name_highbit(hp->h_bcc))
            goto needs;
      if (w & GSUBJECT && has_highbit(hp->h_subject))
            goto needs;
      return NULL;
needs:      return getcharset(MIME_HIGHBIT);
}

#ifdef      HAVE_ICONV
/*
 * Convert a string, upper-casing the characters.
 */
static void 
uppercopy(char *dest, const char *src)
{
      do
            *dest++ = upperconv(*src & 0377);
      while (*src++);
}

/*
 * Strip dashes.
 */
static void 
stripdash(char *p)
{
      char *q = p;

      do
            if (*(q = p) != '-')
                  q++;
      while (*p++);
}

/*
 * An iconv_open wrapper that tries to convert between character set
 * naming conventions.
 */
iconv_t
iconv_open_ft(const char *tocode, const char *fromcode)
{
      iconv_t id;
      char *t, *f;

      /*
       * On Linux systems, this call may succeed.
       */
      if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
            return id;
      /*
       * Remove the "iso-" prefixes for Solaris.
       */
      if (ascncasecmp(tocode, "iso-", 4) == 0)
            tocode += 4;
      else if (ascncasecmp(tocode, "iso", 3) == 0)
            tocode += 3;
      if (ascncasecmp(fromcode, "iso-", 4) == 0)
            fromcode += 4;
      else if (ascncasecmp(fromcode, "iso", 3) == 0)
            fromcode += 3;
      if (*tocode == '\0' || *fromcode == '\0')
            return (iconv_t) -1;
      if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
            return id;
      /*
       * Solaris prefers upper-case charset names. Don't ask...
       */
      t = salloc(strlen(tocode) + 1);
      uppercopy(t, tocode);
      f = salloc(strlen(fromcode) + 1);
      uppercopy(f, fromcode);
      if ((id = iconv_open(t, f)) != (iconv_t)-1)
            return id;
      /*
       * Strip dashes for UnixWare.
       */
      stripdash(t);
      stripdash(f);
      if ((id = iconv_open(t, f)) != (iconv_t)-1)
            return id;
      /*
       * Add your vendor's sillynesses here.
       */
      /*
       * If the encoding names are equal at this point, they
       * are just not understood by iconv(), and we cannot
       * sensibly use it in any way. We do not perform this
       * as an optimization above since iconv() can otherwise
       * be used to check the validity of the input even with
       * identical encoding names.
       */
      if (strcmp(t, f) == 0)
            errno = 0;
      return (iconv_t)-1;
}

/*
 * Fault-tolerant iconv() function.
 */
static size_t
iconv_ft(iconv_t cd, char **inb, size_t *inbleft, char **outb, size_t *outbleft)
{
      size_t sz = 0;

      while ((sz = iconv(cd, inb, inbleft, outb, outbleft)) == (size_t)-1
                  && (errno == EILSEQ || errno == EINVAL)) {
            if (*inbleft > 0) {
                  (*inb)++;
                  (*inbleft)--;
            } else {
                  **outb = '\0';
                  break;
            }
            if (*outbleft > 0) {
                  *(*outb)++ = '?';
                  (*outbleft)--;
            } else {
                  **outb = '\0';
                  break;
            }
      }
      return sz;
}

/*
 * Print an error because of an invalid character sequence.
 */
/*ARGSUSED*/
static void 
invalid_seq(int c)
{
      /*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
}
#endif      /* HAVE_ICONV */

static int
is_this_enc(const char *line, const char *encoding)
{
      int quoted = 0, c;

      if (*line == '"')
            quoted = 1, line++;
      while (*line && *encoding)
            if (c = *line++, lowerconv(c) != *encoding++)
                  return 0;
      if (quoted && *line == '"')
            return 1;
      if (*line == '\0' || whitechar(*line & 0377))
            return 1;
      return 0;
}

/*
 * Get the mime encoding from a Content-Transfer-Encoding header field.
 */
enum mimeenc 
mime_getenc(char *p)
{
      if (is_this_enc(p, "7bit"))
            return MIME_7B;
      if (is_this_enc(p, "8bit"))
            return MIME_8B;
      if (is_this_enc(p, "base64"))
            return MIME_B64;
      if (is_this_enc(p, "binary"))
            return MIME_BIN;
      if (is_this_enc(p, "quoted-printable"))
            return MIME_QP;
      return MIME_NONE;
}

/*
 * Get the mime content from a Content-Type header field, other parameters
 * already stripped.
 */
int 
mime_getcontent(char *s)
{
      if (strchr(s, '/') == NULL)   /* for compatibility with non-MIME */
            return MIME_TEXT;
      if (asccasecmp(s, "text/plain") == 0)
            return MIME_TEXT_PLAIN;
      if (asccasecmp(s, "text/html") == 0)
            return MIME_TEXT_HTML;
      if (ascncasecmp(s, "text/", 5) == 0)
            return MIME_TEXT;
      if (asccasecmp(s, "message/rfc822") == 0)
            return MIME_822;
      if (ascncasecmp(s, "message/", 8) == 0)
            return MIME_MESSAGE;
      if (asccasecmp(s, "multipart/alternative") == 0)
            return MIME_ALTERNATIVE;
      if (asccasecmp(s, "multipart/digest") == 0)
            return MIME_DIGEST;
      if (ascncasecmp(s, "multipart/", 10) == 0)
            return MIME_MULTI;
      if (asccasecmp(s, "application/x-pkcs7-mime") == 0 ||
                        asccasecmp(s, "application/pkcs7-mime") == 0)
            return MIME_PKCS7;
      return MIME_UNKNOWN;
}

/*
 * Get a mime style parameter from a header line.
 */
char *
mime_getparam(char *param, char *h)
{
      char *p = h, *q, *r;
      int c;
      size_t sz;

      sz = strlen(param);
      if (!whitechar(*p & 0377)) {
            c = '\0';
            while (*p && (*p != ';' || c == '\\')) {
                  c = c == '\\' ? '\0' : *p;
                  p++;
            }
            if (*p++ == '\0')
                  return NULL;
      }
      for (;;) {
            while (whitechar(*p & 0377))
                  p++;
            if (ascncasecmp(p, param, sz) == 0) {
                  p += sz;
                  while (whitechar(*p & 0377))
                        p++;
                  if (*p++ == '=')
                        break;
            }
            c = '\0';
            while (*p && (*p != ';' || c == '\\')) {
                  if (*p == '"' && c != '\\') {
                        p++;
                        while (*p && (*p != '"' || c == '\\')) {
                              c = c == '\\' ? '\0' : *p;
                              p++;
                        }
                        p++;
                  } else {
                        c = c == '\\' ? '\0' : *p;
                        p++;
                  }
            }
            if (*p++ == '\0')
                  return NULL;
      }
      while (whitechar(*p & 0377))
            p++;
      q = p;
      c = '\0';
      if (*p == '"') {
            p++;
            if ((q = strchr(p, '"')) == NULL)
                  return NULL;
      } else {
            q = p;
            while (*q && !whitechar(*q & 0377) && *q != ';')
                  q++;
      }
      sz = q - p;
      r = salloc(q - p + 1);
      memcpy(r, p, sz);
      *(r + sz) = '\0';
      return r;
}

/*
 * Get the boundary out of a Content-Type: multipart/xyz header field.
 */
char *
mime_getboundary(char *h)
{
      char *p, *q;
      size_t sz;

      if ((p = mime_getparam("boundary", h)) == NULL)
            return NULL;
      sz = strlen(p);
      q = salloc(sz + 3);
      memcpy(q, "--", 2);
      memcpy(q + 2, p, sz);
      *(q + sz + 2) = '\0';
      return q;
}

/*
 * Get a line like "text/html html" and look if x matches the extension.
 */
static char *
mime_tline(char *x, char *l)
{
      char *type, *n;
      int match = 0;

      if ((*l & 0200) || alphachar(*l & 0377) == 0)
            return NULL;
      type = l;
      while (blankchar(*l & 0377) == 0 && *l != '\0')
            l++;
      if (*l == '\0')
            return NULL;
      *l++ = '\0';
      while (blankchar(*l & 0377) != 0 && *l != '\0')
            l++;
      if (*l == '\0')
            return NULL;
      while (*l != '\0') {
            n = l;
            while (whitechar(*l & 0377) == 0 && *l != '\0')
                  l++;
            if (*l != '\0')
                  *l++ = '\0';
            if (strcmp(x, n) == 0) {
                  match = 1;
                  break;
            }
            while (whitechar(*l & 0377) != 0 && *l != '\0')
                  l++;
      }
      if (match != 0) {
            n = salloc(strlen(type) + 1);
            strcpy(n, type);
            return n;
      }
      return NULL;
}

/*
 * Check the given MIME type file for extension ext.
 */
static char *
mime_type(char *ext, char *filename)
{
      FILE *f;
      char *line = NULL;
      size_t linesize = 0;
      char *type = NULL;

      if ((f = Fopen(filename, "r")) == NULL)
            return NULL;
      while (fgetline(&line, &linesize, NULL, NULL, f, 0)) {
            if ((type = mime_tline(ext, line)) != NULL)
                  break;
      }
      Fclose(f);
      if (line)
            free(line);
      return type;
}

/*
 * Return the Content-Type matching the extension of name.
 */
char *
mime_filecontent(char *name)
{
      char *ext, *content;

      if ((ext = strrchr(name, '.')) == NULL || *++ext == '\0')
            return NULL;
      if ((content = mime_type(ext, expand(mimetypes_user))) != NULL)
            return content;
      if ((content = mime_type(ext, mimetypes_world)) != NULL)
            return content;
      return NULL;
}

/*
 * Check file contents.
 */
static enum mimeclean
mime_isclean(FILE *f)
{
      long initial_pos;
      unsigned curlen = 1, maxlen = 0, limit = 950;
      enum mimeclean isclean = 0;
      char  *cp;
      int c = EOF, lastc;

      initial_pos = ftell(f);
      do {
            lastc = c;
            c = getc(f);
            curlen++;
            if (c == '\n' || c == EOF) {
                  /*
                   * RFC 821 imposes a maximum line length of 1000
                   * characters including the terminating CRLF
                   * sequence. The configurable limit must not
                   * exceed that including a safety zone.
                   */
                  if (curlen > maxlen)
                        maxlen = curlen;
                  curlen = 1;
            } else if (c & 0200) {
                  isclean |= MIME_HIGHBIT;
            } else if (c == '\0') {
                  isclean |= MIME_HASNUL;
                  break;
            } else if ((c < 040 && (c != '\t' && c != '\f')) || c == 0177) {
                  isclean |= MIME_CTRLCHAR;
            }
      } while (c != EOF);
      if (lastc != '\n')
            isclean |= MIME_NOTERMNL;
      clearerr(f);
      fseek(f, initial_pos, SEEK_SET);
      if ((cp = value("maximum-unencoded-line-length")) != NULL)
            limit = atoi(cp);
      if (limit < 0 || limit > 950)
            limit = 950;
      if (maxlen > limit)
            isclean |= MIME_LONGLINES;
      return isclean;
}

/*
 * Get the conversion that matches the encoding specified in the environment.
 */
static enum conversion
gettextconversion(void)
{
      char *p;
      int convert;

      if ((p = value("encoding")) == NULL)
            return CONV_8BIT;
      if (equal(p, "quoted-printable"))
            convert = CONV_TOQP;
      else if (equal(p, "8bit"))
            convert = CONV_8BIT;
      else {
            fprintf(stderr, catgets(catd, CATSET, 177,
                  "Warning: invalid encoding %s, using 8bit\n"), p);
            convert = CONV_8BIT;
      }
      return convert;
}

int
get_mime_convert(FILE *fp, char **contenttype, char **charset,
            enum mimeclean *isclean, int dosign)
{
      int convert;

      *isclean = mime_isclean(fp);
      if (*isclean & MIME_HASNUL ||
                  *contenttype && ascncasecmp(*contenttype, "text/", 5) ||
                  *contenttype == NULL && *isclean & MIME_CTRLCHAR) {
            convert = CONV_TOB64;
            if (*contenttype == NULL ||
                        ascncasecmp(*contenttype, "text/", 5) == 0)
                  *contenttype = "application/octet-stream";
            *charset = NULL;
      } else if (*isclean & (MIME_LONGLINES|MIME_CTRLCHAR|MIME_NOTERMNL) ||
                  dosign)
            convert = CONV_TOQP;
      else if (*isclean & MIME_HIGHBIT)
            convert = gettextconversion();
      else
            convert = CONV_7BIT;
      if (*contenttype == NULL ||
                  ascncasecmp(*contenttype, "text/", 5) == 0) {
            *charset = getcharset(*isclean);
            if (wantcharset == (char *)-1) {
                  *contenttype = "application/octet-stream";
                  *charset = NULL;
            } if (*isclean & MIME_CTRLCHAR) {
                  /*
                   * RFC 2046 forbids control characters other than
                   * ^I or ^L in text/plain bodies. However, some
                   * obscure character sets actually contain these
                   * characters, so the content type can be set.
                   */
                  if ((*contenttype = value("contenttype-cntrl")) == NULL)
                        *contenttype = "application/octet-stream";
            } else if (*contenttype == NULL)
                  *contenttype = "text/plain";
      }
      return convert;
}

/*
 * Convert c to a hexadecimal character string and store it in hex.
 */
static char *
ctohex(int c, char *hex)
{
      unsigned char d;

      hex[2] = '\0';
      d = c % 16;
      hex[1] = basetable[d];
      if (c > d)
            hex[0] = basetable[(c - d) / 16];
      else
            hex[0] = basetable[0];
      return hex;
}

/*
 * Write to a file converting to quoted-printable.
 * The mustquote function determines whether a character must be quoted.
 */
static size_t
mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int))
{
      char *p, *upper, *h, hex[3];
      int l;
      size_t sz;

      sz = in->l;
      upper = in->s + in->l;
      for (p = in->s, l = 0; p < upper; p++) {
            if (mustquote(*p&0377) ||
                        p < upper-1 && p[1] == '\n' &&
                              blankchar(p[0]&0377) ||
                        p < upper-4 && l == 0 &&
                              p[0] == 'F' && p[1] == 'r' &&
                              p[2] == 'o' && p[3] == 'm' ||
                        *p == '.' && l == 0 && p < upper-1 &&
                              p[1] == '\n') {
                  if (l >= 69) {
                        sz += 2;
                        fwrite("=\n", sizeof (char), 2, fo);
                        l = 0;
                  }
                  sz += 2;
                  putc('=', fo);
                  h = ctohex(*p&0377, hex);
                  fwrite(h, sizeof *h, 2, fo);
                  l += 3;
            } else {
                  if (*p == '\n')
                        l = 0;
                  else if (l >= 71) {
                        sz += 2;
                        fwrite("=\n", sizeof (char), 2, fo);
                        l = 0;
                  }
                  putc(*p, fo);
                  l++;
            }
      }
      return sz;
}

/*
 * Write to a stringstruct converting to quoted-printable.
 * The mustquote function determines whether a character must be quoted.
 */
static void 
mime_str_toqp(struct str *in, struct str *out, int (*mustquote)(int), int inhdr)
{
      char *p, *q, *upper;

      out->s = smalloc(in->l * 3 + 1);
      q = out->s;
      out->l = in->l;
      upper = in->s + in->l;
      for (p = in->s; p < upper; p++) {
            if (mustquote(*p&0377) || p+1 < upper && *(p + 1) == '\n' &&
                        blankchar(*p & 0377)) {
                  if (inhdr && *p == ' ') {
                        *q++ = '_';
                  } else {
                        out->l += 2;
                        *q++ = '=';
                        ctohex(*p&0377, q);
                        q += 2;
                  }
            } else {
                  *q++ = *p;
            }
      }
      *q = '\0';
}

/*
 * Write to a stringstruct converting from quoted-printable.
 */
static void 
mime_fromqp(struct str *in, struct str *out, int ishdr)
{
      char *p, *q, *upper;
      char quote[4];

      out->l = in->l;
      out->s = smalloc(out->l + 1);
      upper = in->s + in->l;
      for (p = in->s, q = out->s; p < upper; p++) {
            if (*p == '=') {
                  do {
                        p++;
                        out->l--;
                  } while (blankchar(*p & 0377) && p < upper);
                  if (p == upper)
                        break;
                  if (*p == '\n') {
                        out->l--;
                        continue;
                  }
                  if (p + 1 >= upper)
                        break;
                  quote[0] = *p++;
                  quote[1] = *p;
                  quote[2] = '\0';
                  *q = (char)strtol(quote, NULL, 16);
                  q++;
                  out->l--;
            } else if (ishdr && *p == '_')
                  *q++ = ' ';
            else
                  *q++ = *p;
      }
      return;
}

#define     mime_fromhdr_inc(inc) { \
            size_t diff = q - out->s; \
            out->s = srealloc(out->s, (maxstor += inc) + 1); \
            q = &(out->s)[diff]; \
      }
/*
 * Convert header fields from RFC 1522 format
 */
void 
mime_fromhdr(struct str *in, struct str *out, enum tdflags flags)
{
      char *p, *q, *op, *upper, *cs, *cbeg, *tcs, *lastwordend = NULL;
      struct str cin, cout;
      int convert;
      size_t maxstor, lastoutl = 0;
#ifdef      HAVE_ICONV
      iconv_t fhicd = (iconv_t)-1;
#endif

      tcs = gettcharset();
      maxstor = in->l;
      out->s = smalloc(maxstor + 1);
      out->l = 0;
      upper = in->s + in->l;
      for (p = in->s, q = out->s; p < upper; p++) {
            op = p;
            if (*p == '=' && *(p + 1) == '?') {
                  p += 2;
                  cbeg = p;
                  while (p < upper && *p != '?')
                        p++;  /* strip charset */
                  if (p >= upper)
                        goto notmime;
                  cs = salloc(++p - cbeg);
                  memcpy(cs, cbeg, p - cbeg - 1);
                  cs[p - cbeg - 1] = '\0';
#ifdef      HAVE_ICONV
                  if (fhicd != (iconv_t)-1)
                        iconv_close(fhicd);
                  if (strcmp(cs, tcs))
                        fhicd = iconv_open_ft(tcs, cs);
                  else
                        fhicd = (iconv_t)-1;
#endif
                  switch (*p) {
                  case 'B': case 'b':
                        convert = CONV_FROMB64;
                        break;
                  case 'Q': case 'q':
                        convert = CONV_FROMQP;
                        break;
                  default:    /* invalid, ignore */
                        goto notmime;
                  }
                  if (*++p != '?')
                        goto notmime;
                  cin.s = ++p;
                  cin.l = 1;
                  for (;;) {
                        if (p == upper)
                              goto fromhdr_end;
                        if (*p++ == '?' && *p == '=')
                              break;
                        cin.l++;
                  }
                  cin.l--;
                  switch (convert) {
                        case CONV_FROMB64:
                              mime_fromb64(&cin, &cout, 1);
                              break;
                        case CONV_FROMQP:
                              mime_fromqp(&cin, &cout, 1);
                              break;
                  }
                  if (lastwordend) {
                        q = lastwordend;
                        out->l = lastoutl;
                  }
#ifdef      HAVE_ICONV
                  if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
                        char *iptr, *mptr, *nptr, *uptr;
                        size_t inleft, outleft;

                  again:      inleft = cout.l;
                        outleft = maxstor - out->l;
                        mptr = nptr = q;
                        uptr = nptr + outleft;
                        iptr = cout.s;
                        if (iconv_ft(fhicd, &iptr, &inleft,
                              &nptr, &outleft) == (size_t)-1 &&
                                    errno == E2BIG) {
                              iconv(fhicd, NULL, NULL, NULL, NULL);
                              mime_fromhdr_inc(inleft);
                              goto again;
                        }
                        /*
                         * For state-dependent encodings,
                         * reset the state here, assuming
                         * that states are restricted to
                         * single encoded-word parts.
                         */
                        while (iconv(fhicd, NULL, NULL,
                              &nptr, &outleft) == (size_t)-1 &&
                                    errno == E2BIG)
                              mime_fromhdr_inc(16);
                        out->l += uptr - mptr - outleft;
                        q += uptr - mptr - outleft;
                  } else {
#endif
                        while (cout.l > maxstor - out->l)
                              mime_fromhdr_inc(cout.l -
                                          (maxstor - out->l));
                        memcpy(q, cout.s, cout.l);
                        q += cout.l;
                        out->l += cout.l;
#ifdef      HAVE_ICONV
                  }
#endif
                  free(cout.s);
                  lastwordend = q;
                  lastoutl = out->l;
            } else {
notmime:
                  p = op;
                  while (out->l >= maxstor)
                        mime_fromhdr_inc(16);
                  *q++ = *p;
                  out->l++;
                  if (!blankchar(*p&0377))
                        lastwordend = NULL;
            }
      }
fromhdr_end:
      *q = '\0';
      if (flags & TD_ISPR) {
            struct str  new;
            makeprint(out, &new);
            free(out->s);
            *out = new;
      }
      if (flags & TD_DELCTRL)
            out->l = delctrl(out->s, out->l);
#ifdef      HAVE_ICONV
      if (fhicd != (iconv_t)-1)
            iconv_close(fhicd);
#endif
      return;
}

/*
 * Convert header fields to RFC 1522 format and write to the file fo.
 */
static size_t
mime_write_tohdr(struct str *in, FILE *fo)
{
      char *upper, *wbeg, *wend, *charset, *lastwordend = NULL, *lastspc, b,
            *charset7;
      struct str cin, cout;
      size_t sz = 0, col = 0, wr, charsetlen, charset7len;
      int quoteany, mustquote, broken,
            maxcol = 65 /* there is the header field's name, too */;

      upper = in->s + in->l;
      charset = getcharset(MIME_HIGHBIT);
      if ((charset7 = value("charset7")) == NULL)
            charset7 = us_ascii;
      charsetlen = strlen(charset);
      charset7len = strlen(charset7);
      charsetlen = smax(charsetlen, charset7len);
      b = 0;
      for (wbeg = in->s, quoteany = 0; wbeg < upper; wbeg++) {
            b |= *wbeg;
            if (mustquote_hdr(wbeg, wbeg == in->s, wbeg == &upper[-1]))
                  quoteany++;
      }
      if (2 * quoteany > in->l) {
            /*
             * Print the entire field in base64.
             */
            for (wbeg = in->s; wbeg < upper; wbeg = wend) {
                  wend = upper;
                  cin.s = wbeg;
                  for (;;) {
                        cin.l = wend - wbeg;
                        if (cin.l * 4/3 + 7 + charsetlen
                                    < maxcol - col) {
                              fprintf(fo, "=?%s?B?",
                                    b&0200 ? charset : charset7);
                              wr = mime_write_tob64(&cin, fo, 1);
                              fwrite("?=", sizeof (char), 2, fo);
                              wr += 7 + charsetlen;
                              sz += wr, col += wr;
                              if (wend < upper) {
                                    fwrite("\n ", sizeof (char),
                                                2, fo);
                                    sz += 2;
                                    col = 0;
                                    maxcol = 76;
                              }
                              break;
                        } else {
                              if (col) {
                                    fprintf(fo, "\n ");
                                    sz += 2;
                                    col = 0;
                                    maxcol = 76;
                              } else
                                    wend -= 4;
                        }
                  }
            }
      } else {
            /*
             * Print the field word-wise in quoted-printable.
             */
            broken = 0;
            for (wbeg = in->s; wbeg < upper; wbeg = wend) {
                  lastspc = NULL;
                  while (wbeg < upper && whitechar(*wbeg & 0377)) {
                        lastspc = lastspc ? lastspc : wbeg;
                        wbeg++;
                        col++;
                        broken = 0;
                  }
                  if (wbeg == upper) {
                        if (lastspc)
                              while (lastspc < wbeg) {
                                    putc(*lastspc&0377, fo);
                                          lastspc++,
                                          sz++;
                                    }
                        break;
                  }
                  mustquote = 0;
                  b = 0;
                  for (wend = wbeg;
                        wend < upper && !whitechar(*wend & 0377);
                              wend++) {
                        b |= *wend;
                        if (mustquote_hdr(wend, wend == wbeg,
                                          wbeg == &upper[-1]))
                              mustquote++;
                  }
                  if (mustquote || broken || (wend - wbeg) >= 74 &&
                              quoteany) {
                        for (;;) {
                              cin.s = lastwordend ? lastwordend :
                                    wbeg;
                              cin.l = wend - cin.s;
                              mime_str_toqp(&cin, &cout,
                                          mustquote_inhdrq, 1);
                              if ((wr = cout.l + charsetlen + 7)
                                          < maxcol - col) {
                                    if (lastspc)
                                          while (lastspc < wbeg) {
                                                putc(*lastspc
                                                      &0377,
                                                      fo);
                                                lastspc++,
                                                sz++;
                                          }
                                    fprintf(fo, "=?%s?Q?", b&0200 ?
                                          charset : charset7);
                                    fwrite(cout.s, sizeof *cout.s,
                                                cout.l, fo);
                                    fwrite("?=", 1, 2, fo);
                                    sz += wr, col += wr;
                                    free(cout.s);
                                    break;
                              } else {
                                    broken = 1;
                                    if (col) {
                                          putc('\n', fo);
                                          sz++;
                                          col = 0;
                                          maxcol = 76;
                                          if (lastspc == NULL) {
                                                putc(' ', fo);
                                                sz++;
                                                maxcol--;
                                          } else
                                                maxcol -= wbeg -
                                                      lastspc;
                                    } else {
                                          wend -= 4;
                                    }
                                    free(cout.s);
                              }
                        }
                        lastwordend = wend;
                  } else {
                        if (col && wend - wbeg > maxcol - col) {
                              putc('\n', fo);
                              sz++;
                              col = 0;
                              maxcol = 76;
                              if (lastspc == NULL) {
                                    putc(' ', fo);
                                    sz++;
                                    maxcol--;
                              } else
                                    maxcol -= wbeg - lastspc;
                        }
                        if (lastspc)
                              while (lastspc < wbeg) {
                                    putc(*lastspc&0377, fo);
                                    lastspc++, sz++;
                              }
                        wr = fwrite(wbeg, sizeof *wbeg,
                                    wend - wbeg, fo);
                        sz += wr, col += wr;
                        lastwordend = NULL;
                  }
            }
      }
      return sz;
}

/*
 * Write len characters of the passed string to the passed file, 
 * doing charset and header conversion.
 */
static size_t
convhdra(char *str, size_t len, FILE *fp)
{
#ifdef      HAVE_ICONV
      char  *ip, *op;
      size_t      isz, osz;
#endif
      struct str  cin;
      size_t      cbufsz;
      char  *cbuf;
      size_t      sz;

      cbuf = ac_alloc(cbufsz = 1);
#ifdef      HAVE_ICONV
      if (iconvd == (iconv_t)-1) {
#endif
            cin.s = str;
            cin.l = len;
#ifdef      HAVE_ICONV
      } else {
      again:      ip = str;
            isz = len;
            op = cbuf;
            osz = cbufsz;
            if (iconv(iconvd, &ip, &isz, &op, &osz) == (size_t)-1) {
                  if (errno != E2BIG) {
                        ac_free(cbuf);
                        return 0;
                  }
                  cbuf = ac_alloc(cbufsz += isz);
                  goto again;
            }
            cin.s = cbuf;
            cin.l = cbufsz - osz;
      }
#endif      /* HAVE_ICONV */
      sz = mime_write_tohdr(&cin, fp);
      ac_free(cbuf);
      return sz;
}


/*
 * Write an address to a header field.
 */
static size_t
mime_write_tohdr_a(struct str *in, FILE *f)
{
      char  *cp, *lastcp;
      size_t      sz = 0;

      in->s[in->l] = '\0';
      lastcp = in->s;
      if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
            sz += convhdra(lastcp, cp - lastcp, f);
            lastcp = cp;
      } else
            cp = in->s;
      for ( ; *cp; cp++) {
            switch (*cp) {
            case '(':
                  sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
                  lastcp = ++cp;
                  cp = skip_comment(cp);
                  if (--cp > lastcp)
                        sz += convhdra(lastcp, cp - lastcp, f);
                  lastcp = cp;
                  break;
            case '"':
                  while (*cp) {
                        if (*++cp == '"')
                              break;
                        if (*cp == '\\' && cp[1])
                              cp++;
                  }
                  break;
            }
      }
      if (cp > lastcp)
            sz += fwrite(lastcp, 1, cp - lastcp, f);
      return sz;
}

static void
addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
{
      *buf = srealloc(*buf, *sz += len);
      memcpy(&(*buf)[*pos], str, len);
      *pos += len;
}

static void
addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
{
      struct str  in, out;

      in.s = str;
      in.l = len;
      mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
      addstr(buf, sz, pos, out.s, out.l);
      free(out.s);
}

/*
 * Interpret MIME strings in parts of an address field.
 */
char *
mime_fromaddr(char *name)
{
      char  *cp, *lastcp;
      char  *res = NULL;
      size_t      ressz = 1, rescur = 0;

      if (name == NULL || *name == '\0')
            return name;
      if ((cp = routeaddr(name)) != NULL && cp > name) {
            addconv(&res, &ressz, &rescur, name, cp - name);
            lastcp = cp;
      } else
            cp = lastcp = name;
      for ( ; *cp; cp++) {
            switch (*cp) {
            case '(':
                  addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
                  lastcp = ++cp;
                  cp = skip_comment(cp);
                  if (--cp > lastcp)
                        addconv(&res, &ressz, &rescur, lastcp,
                                    cp - lastcp);
                  lastcp = cp;
                  break;
            case '"':
                  while (*cp) {
                        if (*++cp == '"')
                              break;
                        if (*cp == '\\' && cp[1])
                              cp++;
                  }
                  break;
            }
      }
      if (cp > lastcp)
            addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
      res[rescur] = '\0';
      cp = savestr(res);
      free(res);
      return cp;
}

/*
 * fwrite whilst adding prefix, if present.
 */
size_t
prefixwrite(void *ptr, size_t size, size_t nmemb, FILE *f,
            char *prefix, size_t prefixlen)
{
      static FILE *lastf;
      static char lastc = '\n';
      size_t rsz, wsz = 0;
      char *p = ptr;

      if (nmemb == 0)
            return 0;
      if (prefix == NULL) {
            lastf = f;
            lastc = ((char *)ptr)[size * nmemb - 1];
            return fwrite(ptr, size, nmemb, f);
      }
      if (f != lastf || lastc == '\n') {
            if (*p == '\n' || *p == '\0')
                  wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
            else {
                  fputs(prefix, f);
                  wsz += strlen(prefix);
            }
      }
      lastf = f;
      for (rsz = size * nmemb; rsz; rsz--, p++, wsz++) {
            putc(*p, f);
            if (*p != '\n' || rsz == 1) {
                  continue;
            }
            if (p[1] == '\n' || p[1] == '\0')
                  wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
            else {
                  fputs(prefix, f);
                  wsz += strlen(prefix);
            }
      }
      lastc = p[-1];
      return wsz;
}

/*
 * fwrite while checking for displayability.
 */
static size_t
fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f, enum tdflags flags,
            char *prefix, size_t prefixlen)
{
      char *upper;
      size_t sz, csize;
#ifdef      HAVE_ICONV
      char *iptr, *nptr;
      size_t inleft, outleft;
#endif
      char *mptr, *xmptr, *mlptr = NULL;
      size_t mptrsz;

      csize = size * nmemb;
      mptrsz = csize;
      mptr = xmptr = ac_alloc(mptrsz + 1);
#ifdef      HAVE_ICONV
      if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
      again:      inleft = csize;
            outleft = mptrsz;
            nptr = mptr;
            iptr = ptr;
            if (iconv_ft(iconvd, &iptr, &inleft, &nptr, &outleft) ==
                              (size_t)-1 &&
                        errno == E2BIG) {
                  iconv(iconvd, NULL, NULL, NULL, NULL);
                  ac_free(mptr);
                  mptrsz += inleft;
                  mptr = ac_alloc(mptrsz + 1);
                  goto again;
            }
            nmemb = mptrsz - outleft;
            size = sizeof (char);
            ptr = mptr;
            csize = size * nmemb;
      } else
#endif
      {
            memcpy(mptr, ptr, csize);
      }
      upper = mptr + csize;
      *upper = '\0';
      if (flags & TD_ISPR) {
            struct str  in, out;
            in.s = mptr;
            in.l = csize;
            makeprint(&in, &out);
            mptr = mlptr = out.s;
            csize = out.l;
      }
      if (flags & TD_DELCTRL)
            csize = delctrl(mptr, csize);
      sz = prefixwrite(mptr, sizeof *mptr, csize, f, prefix, prefixlen);
      ac_free(xmptr);
      free(mlptr);
      return sz;
}

/*
 * fwrite performing the given MIME conversion.
 */
size_t
mime_write(void *ptr, size_t size, FILE *f,
            enum conversion convert, enum tdflags dflags,
            char *prefix, size_t prefixlen,
            char **restp, size_t *restsizep)
{
      struct str in, out;
      size_t sz, csize;
      int is_text = 0;
#ifdef      HAVE_ICONV
      char mptr[LINESIZE * 6];
      char *iptr, *nptr;
      size_t inleft, outleft;
#endif

      if (size == 0)
            return 0;
      csize = size;
#ifdef      HAVE_ICONV
      if (csize < sizeof mptr && (dflags & TD_ICONV)
                  && iconvd != (iconv_t)-1
                  && (convert == CONV_TOQP || convert == CONV_8BIT ||
                        convert == CONV_TOB64 ||
                        convert == CONV_TOHDR)) {
            inleft = csize;
            outleft = sizeof mptr;
            nptr = mptr;
            iptr = ptr;
            if (iconv(iconvd, &iptr, &inleft,
                        &nptr, &outleft) != (size_t)-1) {
                  in.l = sizeof mptr - outleft;
                  in.s = mptr;
            } else {
                  if (errno == EILSEQ || errno == EINVAL)
                        invalid_seq(*iptr);
                  return 0;
            }
      } else {
#endif
            in.s = ptr;
            in.l = csize;
#ifdef      HAVE_ICONV
      }
#endif
      switch (convert) {
      case CONV_FROMQP:
            mime_fromqp(&in, &out, 0);
            sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
                        prefix, prefixlen);
            free(out.s);
            break;
      case CONV_TOQP:
            sz = mime_write_toqp(&in, f, mustquote_body);
            break;
      case CONV_8BIT:
            sz = prefixwrite(in.s, sizeof *in.s, in.l, f,
                        prefix, prefixlen);
            break;
      case CONV_FROMB64_T:
            is_text = 1;
            /*FALLTHROUGH*/
      case CONV_FROMB64:
            mime_fromb64_b(&in, &out, is_text, f);
            if (is_text && out.s[out.l-1] != '\n' && restp && restsizep) {
                  *restp = ptr;
                  *restsizep = size;
                  sz = 0;
            } else {
                  sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
                        prefix, prefixlen);
            }
            free(out.s);
            break;
      case CONV_TOB64:
            sz = mime_write_tob64(&in, f, 0);
            break;
      case CONV_FROMHDR:
            mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
            sz = fwrite_td(out.s, sizeof *out.s, out.l, f,
                        dflags&TD_DELCTRL, prefix, prefixlen);
            free(out.s);
            break;
      case CONV_TOHDR:
            sz = mime_write_tohdr(&in, f);
            break;
      case CONV_TOHDR_A:
            sz = mime_write_tohdr_a(&in, f);
            break;
      default:
            sz = fwrite_td(in.s, sizeof *in.s, in.l, f, dflags,
                        prefix, prefixlen);
      }
      return sz;
}

Generated by  Doxygen 1.6.0   Back to index