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

mime.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2010 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#pragma prototyped

/*
 * Glenn Fowler
 * AT&T Research
 *
 * mime/mailcap support library
 */

static const char id[] = "\n@(#)$Id: mime library (AT&T Research) 2002-10-29 $\0\n";

static const char lib[] = "libast:mime";

#include "mimelib.h"

typedef struct Att_s
{
      struct Att_s*     next;
      char*       name;
      char*       value;
} Att_t;

typedef struct Cap_s
{
      struct Cap_s*     next;
      unsigned long     flags;
      Att_t       att;
      char*       test;
      char        data[1];
} Cap_t;

typedef struct
{
      Dtlink_t    link;
      Cap_t*            cap;
      Cap_t*            pac;
      char        name[1];
} Ent_t;

typedef struct
{
      char*       data;
      int         size;
} String_t;

typedef struct
{
      char*       next;
      String_t    name;
      String_t    value;
} Parse_t;

typedef struct
{
      const char* pattern;
      int         prefix;
      Sfio_t*           fp;
      int         hit;
} Walk_t;

/*
 * convert c to lower case
 */

static int
lower(register int c)
{
      return isupper(c) ? tolower(c) : c;
}

/*
 * Ent_t case insensitive comparf
 */

static int
order(Dt_t* dt, void* a, void* b, Dtdisc_t* disc)
{
      return strcasecmp(a, b);
}

/*
 * Cap_t free
 */

static void
dropcap(register Cap_t* cap)
{
      register Att_t*   att;

      while (att = cap->att.next)
      {
            cap->att.next = att->next;
            free(att);
      }
      free(cap);
}

/*
 * Ent_t freef
 */

static void
drop(Dt_t* dt, void* object, Dtdisc_t* disc)
{
      register Ent_t*   ent = (Ent_t*)object;
      register Cap_t*   cap;

      while (cap = ent->cap)
      {
            ent->cap = cap->next;
            dropcap(cap);
      }
      free(ent);
}

/*
 * add mime type entry in s to mp
 */

int
mimeset(Mime_t* mp, register char* s, unsigned long flags)
{
      register Ent_t*   ent;
      register Cap_t*   cap;
      register Att_t*   att;
      register char*    t;
      register char*    v;
      register char*    k;
      char*       x;
      Att_t*            tta;
      int         q;

      for (; isspace(*s); s++);
      if (*s && *s != '#')
      {
            cap = 0;
            for (v = s; *v && *v != ';'; v++)
                  if (isspace(*v) || *v == '/' && *(v + 1) == '*')
                        *v = 0;
            if (*v)
            {
                  *v++ = 0;
                  do
                  {
                        for (; isspace(*v); v++);
                        if (cap)
                        {
                              for (t = v; *t && !isspace(*t) && *t != '='; t++);
                              for (k = t; isspace(*t); t++);
                              if (!*t || *t == '=' || *t == ';')
                              {
                                    if (*t)
                                          while (isspace(*++t));
                                    *k = 0;
                                    k = v;
                                    v = t;
                              }
                              else
                                    k = 0;
                        }
                        if (*v == '"')
                              q = *v++;
                        else
                              q = 0;
                        for (t = v; *t; t++)
                              if (*t == '\\')
                              {
                                    switch (*(t + 1))
                                    {
                                    case 0:
                                    case '\\':
                                    case '%':
                                          *t = *(t + 1);
                                          break;
                                    default:
                                          *t = ' ';
                                          break;
                                    }
                                    if (!*++t)
                                          break;
                              }
                              else if (*t == q)
                              {
                                    *t = ' ';
                                    q = 0;
                              }
                              else if (*t == ';' && !q)
                              {
                                    *t = ' ';
                                    break;
                              }
                        for (; t > v && isspace(*(t - 1)); t--);
                        if (t <= v && (!cap || !k))
                              break;
                        if (!cap)
                        {
                              if (!(cap = newof(0, Cap_t, 1, strlen(v) + 1)))
                                    return -1;
                              if (*t)
                                    *t++ = 0;
                              tta = &cap->att;
                              tta->name = "default";
                              x = strcopy(tta->value = cap->data, v) + 1;
                        }
                        else if (k)
                        {
                              if (*t)
                                    *t++ = 0;
                              if (!(att = newof(0, Att_t, 1, 0)))
                                    return -1;
                              x = strcopy(att->name = x, k) + 1;
                              x = strcopy(att->value = x, v) + 1;
                              tta = tta->next = att;
                              if (!strcasecmp(k, "test"))
                                    cap->test = att->value;
                        }
                  } while (*(v = t));
            }
            ent = (Ent_t*)dtmatch(mp->cap, s);
            if (cap)
            {
                  if (ent)
                  {
                        register Cap_t*   dup;
                        register Cap_t*   pud;

                        for (pud = 0, dup = ent->cap; dup; pud = dup, dup = dup->next)
                              if (!cap->test && !dup->test || cap->test && dup->test && streq(cap->test, dup->test))
                              {
                                    if (flags & MIME_REPLACE)
                                    {
                                          if (pud)
                                                pud->next = cap;
                                          else
                                                ent->cap = cap;
                                          if (!(cap->next = dup->next))
                                                ent->pac = cap;
                                          cap = dup;
                                    }
                                    dropcap(cap);
                                    return 0;
                              }
                        ent->pac = ent->pac->next = cap;
                  }
                  else if (!(ent = newof(0, Ent_t, 1, strlen(s) + 1)))
                        return -1;
                  else
                  {
                        strcpy(ent->name, s);
                        ent->cap = ent->pac = cap;
                        dtinsert(mp->cap, ent);
                  }
            }
            else if (ent && (flags & MIME_REPLACE))
                  dtdelete(mp->cap, ent);
      }
      return 0;
}

/*
 * load mime type files into mp
 */

int
mimeload(Mime_t* mp, const char* file, unsigned long flags)
{
      register char*    s;
      register char*    t;
      register char*    e;
      register int      n;
      Sfio_t*           fp;

      if (!(s = (char*)file))
      {
            flags |= MIME_LIST;
            if (!(s = getenv(MIME_FILES_ENV)))
                  s = MIME_FILES;
      }
      for (;;)
      {
            if (!(flags & MIME_LIST))
                  e = 0;
            else if (e = strchr(s, ':'))
            {
                  /*
                   * ok, so ~ won't work for the last list element
                   * we do it for MIME_FILES_ENV anyway
                   */

                  if ((strneq(s, "~/", n = 2) || strneq(s, "$HOME/", n = 6) || strneq(s, "${HOME}/", n = 8)) && (t = getenv("HOME")))
                  {
                        sfputr(mp->buf, t, -1);
                        s += n - 1;
                  }
                  sfwrite(mp->buf, s, e - s);
                  if (!(s = sfstruse(mp->buf)))
                        return -1;
            }
            if (fp = tokline(s, SF_READ, NiL))
            {
                  while (t = sfgetr(fp, '\n', 1))
                        if (mimeset(mp, t, flags))
                              break;
                  sfclose(fp);
            }
            else if (!(flags & MIME_LIST))
                  return -1;
            if (!e)
                  break;
            s = e + 1;
      }
      return 0;
}

/*
 * mimelist walker
 */

static int
list(Dt_t* dt, void* object, void* context)
{
      register Walk_t*  wp = (Walk_t*)context;
      register Ent_t*         ent = (Ent_t*)object;
      register Cap_t*         cap;
      register Att_t*         att;

      if (!wp->pattern || !strncasecmp(ent->name, wp->pattern, wp->prefix) && (!ent->name[wp->prefix] || ent->name[wp->prefix] == '/'))
      {
            wp->hit++;
            for (cap = ent->cap; cap; cap = cap->next)
            {
                  sfprintf(wp->fp, "%s", ent->name);
                  for (att = &cap->att; att; att = att->next)
                  {
                        sfprintf(wp->fp, "\n\t");
                        if (att != &cap->att)
                        {
                              sfprintf(wp->fp, "%s", att->name);
                              if (*att->value)
                                    sfprintf(wp->fp, " = ");
                        }
                        sfputr(wp->fp, att->value, -1);
                  }
                  sfprintf(wp->fp, "\n");
            }
      }
      return 0;
}

/*
 * find entry matching type
 * if exact match fails then left and right x- and right version number
 * permutations are attempted
 */

static Ent_t*
find(Mime_t* mp, const char* type)
{
      register char*    lp;
      register char*    rp;
      register char*    rb;
      register char*    rv;
      register int      rc;
      register int      i;
      char*       s;
      Ent_t*            ent;
      char        buf[256];

      static const char*      prefix[] = { "", "", "x-", "x-", "" };

      if ((ent = (Ent_t*)dtmatch(mp->cap, type)) ||
          !(rp = strchr(lp = (char*)type, '/')) ||
          strlen(lp) >= sizeof(buf))
            return ent;
      strcpy(buf, type);
      rp = buf + (rp - lp);
      *rp++ = 0;
      if (*rp == 'x' && *(rp + 1) == '-')
            rp += 2;
      lp = buf;
      if (*lp == 'x' && *(lp + 1) == '-')
            lp += 2;
      rb = rp;
      for (rv = rp + strlen(rp); rv > rp && (isdigit(*(rv - 1)) || *(rv - 1) == '.'); rv--);
      rc = *rv;
      do
      {
            rp = rb;
            do
            {
                  for (i = 0; i < elementsof(prefix) - 1; i++)
                  {
                        sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp);
                        if (!(s = sfstruse(mp->buf)))
                              return 0;
                        if (ent = (Ent_t*)dtmatch(mp->cap, s))
                              return ent;
                        if (rc)
                        {
                              *rv = 0;
                              sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp);
                              if (!(s = sfstruse(mp->buf)))
                                    return 0;
                              if (ent = (Ent_t*)dtmatch(mp->cap, s))
                                    return ent;
                              *rv = rc;
                        }
                  }
                  while (*rp && *rp++ != '-');
            } while (*rp);
            while (*lp && *lp++ != '-');
      } while (*lp);
      return (Ent_t*)dtmatch(mp->cap, buf);
}

/*
 * list mime <type,data> for pat on fp
 */

int
mimelist(Mime_t* mp, Sfio_t* fp, register const char* pattern)
{
      Ent_t*      ent;
      Walk_t      ws;

      ws.fp = fp;
      ws.hit = 0;
      ws.prefix = 0;
      if (ws.pattern = pattern)
      {
            while (*pattern && *pattern++ != '/');
            if (!*pattern || *pattern == '*' && !*(pattern + 1))
                  ws.prefix = pattern - ws.pattern;
            else if (ent = find(mp, ws.pattern))
            {
                  ws.pattern = 0;
                  list(mp->cap, ent, &ws);
                  return ws.hit;
            }
      }
      dtwalk(mp->cap, list, &ws);
      return ws.hit;
}

/*
 * get next arg in pp
 * 0 returned if no more args
 */

static int
arg(register Parse_t* pp, int first)
{
      register char*    s;
      register int      c;
      register int      q;
      int         x;

      for (s = pp->next; isspace(*s) && *s != '\n'; s++);
      if (!*s || *s == '\n')
      {
            pp->next = s;
            return 0;
      }
      pp->name.data = s;
      pp->value.data = 0;
      q = 0;
      x = 0;
      while ((c = *s++) && c != ';' && c != '\n')
      {
            if (c == '"')
            {
                  q = 1;
                  if (pp->value.data)
                  {
                        pp->value.data = s;
                        if (x)
                              x = -1;
                        else
                              x = 1;
                  }
                  else if (!x && pp->name.data == (s - 1))
                  {
                        x = 1;
                        pp->name.data = s;
                  }
                  do
                  {
                        if (!(c = *s++) || c == '\n')
                        {
                              s--;
                              break;
                        }
                  } while (c != '"');
                  if (first < 0 || x > 0)
                  {
                        c = ';';
                        break;
                  }
            }
            else if (c == '=' && !first)
            {
                  first = 1;
                  pp->name.size = s - pp->name.data - 1;
                  pp->value.data = s;
            }
            else if (first >= 0 && isspace(c))
                  break;
      }
      pp->next = s - (c != ';');
      if (first >= 0 || !q)
            for (s--; s > pp->name.data && isspace(*(s - 1)); s--);
      if (pp->value.data)
            pp->value.size = s - pp->value.data - (q && first < 0);
      else
      {
            pp->value.size = 0;
            pp->name.size = s - pp->name.data - (q && first < 0);
      }
      if (first >= 0 && pp->name.size > 0 && pp->name.data[pp->name.size - 1] == ':')
            return 0;
      return pp->name.size > 0;
}

/*
 * low level for mimeview()
 */

static char*
expand(Mime_t* mp, register char* s, const char* name, const char* type, const char* opts)
{
      register char*    t;
      register int      c;
      Parse_t           pp;

      mp->disc->flags |= MIME_PIPE;
      for (;;)
      {
            switch (c = *s++)
            {
            case 0:
            case '\n':
                  break;
            case '%':
                  switch (c = *s++)
                  {
                  case 's':
                        sfputr(mp->buf, (char*)name, -1);
                        mp->disc->flags &= ~MIME_PIPE;
                        continue;
                  case 't':
                        sfputr(mp->buf, (char*)type, -1);
                        continue;
                  case '{':
                        for (t = s; *s && *s != '}'; s++);
                        if (*s && (c = s++ - t) && (pp.next = (char*)opts))
                              while (arg(&pp, 0))
                                    if (pp.name.size == c && !strncasecmp(pp.name.data, t, c))
                                    {
                                          if (pp.value.size)
                                                sfwrite(mp->buf, pp.value.data, pp.value.size);
                                          break;
                                    }
                        continue;
                  }
                  /*FALLTHROUGH*/
            default:
                  sfputc(mp->buf, c);
                  continue;
            }
            break;
      }
      return sfstruse(mp->buf);
}

/*
 * return expanded command/path/value for <view,name,type,opts>
 * return value valid until next mime*() call
 */

char*
mimeview(Mime_t* mp, const char* view, const char* name, const char* type, const char* opts)
{
      register Ent_t*   ent;
      register Cap_t*   cap;
      register Att_t*   att;
      register char*    s;
      int         c;

      if (ent = find(mp, type))
      {
            cap = ent->cap;
            if (!view || strcasecmp(view, "test"))
                  while (s = cap->test)
                  {
                        if (s = expand(mp, s, name, type, opts))
                        {
                              Parse_t     a1;
                              Parse_t     a2;
                              Parse_t     a3;
                              Parse_t     a4;

                              /*
                               * try to do a few common cases here
                               * mailcap consistency is a winning
                               * strategy
                               */

                              a1.next = s;
                              if (arg(&a1, -1))
                              {
                                    if ((c = *a1.name.data == '!') && --a1.name.size <= 0 && !arg(&a1, -1))
                                          goto lose;
                                    if (a1.name.size == 6 && strneq(a1.name.data, "strcmp", 6) || a1.name.size == 10 && strneq(a1.name.data, "strcasecmp", 10))
                                    {
                                          a2.next = a1.next;
                                          if (!arg(&a2, -1))
                                                goto lose;
                                          a3.next = a2.next;
                                          if (!arg(&a3, -1))
                                                goto lose;
                                          if (a2.name.size != a3.name.size)
                                                c ^= 0;
                                          else c ^= (a1.name.size == 6 ? strncmp : strncasecmp)(a2.name.data, a3.name.data, a2.name.size) == 0;
                                          if (c)
                                                break;
                                          goto skip;
                                    }
                                    else if (a1.name.size == 4 && strneq(a1.name.data, "test", 4))
                                    {
                                          if (!arg(&a1, -1))
                                                goto lose;
                                          a2.next = a1.next;
                                          if (!arg(&a2, -1) || a2.name.size > 2 || a2.name.size == 1 && *a2.name.data != '=' || a2.name.size == 2 && (!strneq(a1.name.data, "!=", 2) || !strneq(a2.name.data, "==", 2)))
                                                goto lose;
                                          a3.next = a2.next;
                                          if (!arg(&a3, -1))
                                                goto lose;
                                          if (*a3.name.data == '`' && *(a3.name.data + a3.name.size - 1) == '`')
                                          {
                                                a4 = a3;
                                                a3 = a1;
                                                a1 = a4;
                                          }
                                          if (*a1.name.data == '`' && *(a1.name.data + a1.name.size - 1) == '`')
                                          {
                                                a1.next = a1.name.data + 1;
                                                if (!arg(&a1, -1) || a1.name.size != 4 || !strneq(a1.name.data, "echo", 4) || !arg(&a1, -1))
                                                      goto lose;
                                                a4.next = a1.next;
                                                if (!arg(&a4, 1) || a4.name.size < 21 || !strneq(a4.name.data, "| tr '[A-Z]' '[a-z]'`", 21))
                                                      goto lose;
                                          }
                                          else
                                                a4.name.size = 0;
                                          c = *a2.name.data == '!';
                                          if (a1.name.size != a3.name.size)
                                                c ^= 0;
                                          else c ^= (a4.name.size ? strncasecmp : strncmp)(a1.name.data, a3.name.data, a1.name.size) == 0;
                                          if (c)
                                                break;
                                          goto skip;
                                    }
                              }
                        lose:
                              if (!system(s))
                                    break;
                        }
                  skip:
                        if (!(cap = cap->next))
                              return 0;
                  }
            att = &cap->att;
            if (view && *view && !streq(view, "-"))
                  while (strcasecmp(view, att->name))
                        if (!(att = att->next))
                              return 0;
            return expand(mp, att->value, name, type, opts);
      }
      return 0;
}

/*
 * lower case identifier prefix strcmp
 * if e!=0 then it will point to the next char after the match
 */

int
mimecmp(register const char* s, register const char* v, char** e)
{
      register int      n;

      while (isalnum(*v) || *v == *s && (*v == '_' || *v == '-' || *v == '/'))
            if (n = lower(*s++) - lower(*v++))
                  return n;
      if (!isalnum(*s) && *s != '_' && *s != '-')
      {
            if (e)
                  *e = (char*)s;
            return 0;
      }
      return lower(*s) - lower(*v);
}

/*
 * parse mime headers in strsearch(tab,num,siz) from s
 * return >0 if mime header consumed
 */

int
mimehead(Mime_t* mp, void* tab, size_t num, size_t siz, register char* s)
{
      register void*    p;
      char*       e;
      Parse_t           pp;
      Mimevalue_f set;

      set = mp->disc->valuef;
      if (!strncasecmp(s, "original-", 9))
            s += 9;
      if (!strncasecmp(s, "content-", 8))
      {
            s += 8;
            if ((p = strsearch(tab, num, siz, (Strcmp_f)mimecmp, s, &e)) && *e == ':')
            {
                  pp.next = e + 1;
                  if (arg(&pp, 1))
                  {
                        if ((*set)(mp, p, pp.name.data, pp.name.size, mp->disc))
                              return 0;
                        while (arg(&pp, 0))
                              if (pp.value.size &&
                                  (p = strsearch(tab, num, siz, (Strcmp_f)mimecmp, pp.name.data, &e)) &&
                                  (*set)(mp, p, pp.value.data, pp.value.size, mp->disc))
                                    return 0;
                        return 1;
                  }
            }
            else if (strchr(s, ':'))
                  return 1;
      }
      return !strncasecmp(s, "x-", 2);
}

/*
 * open a mime library handle
 */

Mime_t*
mimeopen(Mimedisc_t* disc)
{
      register Mime_t*  mp;

      if (!(mp = newof(0, Mime_t, 1, 0)))
            return 0;
      mp->id = lib;
      mp->disc = disc;
      mp->dict.key = offsetof(Ent_t, name);
      mp->dict.comparf = order;
      mp->dict.freef = drop;
      if (!(mp->buf = sfstropen()) || !(mp->cap = dtopen(&mp->dict, Dtorder)))
      {
            mimeclose(mp);
            return 0;
      }
      return mp;
}

/*
 * close a mimeopen() handle
 */

int
mimeclose(Mime_t* mp)
{
      if (mp)
      {
            if (mp->buf)
                  sfclose(mp->buf);
            if (mp->cap)
                  dtclose(mp->cap);
            if (mp->freef)
                  (*mp->freef)(mp);
            free(mp);
      }
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index