jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

rfc822.c (15781B)


      1 /*
      2  * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
      3  * 
      4  *     This program is free software; you can redistribute it and/or modify
      5  *     it under the terms of the GNU General Public License as published by
      6  *     the Free Software Foundation; either version 2 of the License, or
      7  *     (at your option) any later version.
      8  * 
      9  *     This program is distributed in the hope that it will be useful,
     10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  *     GNU General Public License for more details.
     13  * 
     14  *     You should have received a copy of the GNU General Public License
     15  *     along with this program; if not, write to the Free Software Foundation,
     16  *     Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA.
     17  */ 
     18 
     19 /* $Id: rfc822.c,v 1.5 2005-10-29 14:48:11 roland Exp $ */
     20 
     21 #include <string.h>
     22 #include <ctype.h>
     23 #include <stdlib.h>
     24 #include <stdio.h>
     25 
     26 #include "helpers.h"
     27 #include "rfc822.h"
     28 
     29 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
     30 #define is_special(x) strchr(RFC822Specials,x)
     31 
     32 int RFC822Error = 0;
     33 
     34 /* these must defined in the same order as the numerated errors given in rfc822.h */
     35 const char *RFC822Errors[] = {
     36   "out of memory",
     37   "mismatched parenthesis",
     38   "mismatched quotes",
     39   "bad route in <>",
     40   "bad address in <>",
     41   "bad address spec"
     42 };
     43 
     44 void rfc822_dequote_comment (char *s)
     45 {
     46   char *w = s;
     47 
     48   for (; *s; s++)
     49   {
     50     if (*s == '\\')
     51     {
     52       if (!*++s)
     53 	break; /* error? */
     54       *w++ = *s;
     55     }
     56     else if (*s != '\"')
     57     {
     58       if (w != s)
     59 	*w = *s;
     60       w++;
     61     }
     62   }
     63   *w = 0;
     64 }
     65 
     66 void rfc822_free_address (ADDRESS **p)
     67 {
     68   ADDRESS *t;
     69 
     70   while (*p)
     71   {
     72     t = *p;
     73     *p = (*p)->next;
     74 #ifdef EXACT_ADDRESS
     75     FREE (&t->val);
     76 #endif
     77     FREE (&t->personal);
     78     FREE (&t->mailbox);
     79     FREE (&t);
     80   }
     81 }
     82 
     83 static const char *
     84 parse_comment (const char *s,
     85 	       char *comment, size_t *commentlen, size_t commentmax)
     86 {
     87   int level = 1;
     88   
     89   while (*s && level)
     90   {
     91     if (*s == '(')
     92       level++;
     93     else if (*s == ')')
     94     {
     95       if (--level == 0)
     96       {
     97 	s++;
     98 	break;
     99       }
    100     }
    101     else if (*s == '\\')
    102     {
    103       if (!*++s)
    104 	break;
    105     }
    106     if (*commentlen < commentmax)
    107       comment[(*commentlen)++] = *s;
    108     s++;
    109   }
    110   if (level)
    111   {
    112     RFC822Error = ERR_MISMATCH_PAREN;
    113     return NULL;
    114   }
    115   return s;
    116 }
    117 
    118 static const char *
    119 parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
    120 {
    121   if (*tokenlen < tokenmax)
    122     token[(*tokenlen)++] = '"';
    123   while (*s)
    124   {
    125     if (*tokenlen < tokenmax)
    126       token[(*tokenlen)++] = *s;
    127     if (*s == '"')
    128       return (s + 1);
    129     if (*s == '\\')
    130     {
    131       if (!*++s)
    132 	break;
    133     }
    134     s++;
    135   }
    136   RFC822Error = ERR_MISMATCH_QUOTE;
    137   return NULL;
    138 }
    139 
    140 static const char *
    141 next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
    142 {
    143   if (*s == '(')
    144     return (parse_comment (s + 1, token, tokenlen, tokenmax));
    145   if (*s == '"')
    146     return (parse_quote (s + 1, token, tokenlen, tokenmax));
    147   if (is_special (*s))
    148   {
    149     if (*tokenlen < tokenmax)
    150       token[(*tokenlen)++] = *s;
    151     return (s + 1);
    152   }
    153   while (*s)
    154   {
    155     if (isspace (*s) || is_special (*s))
    156       break;
    157     if (*tokenlen < tokenmax)
    158       token[(*tokenlen)++] = *s;
    159     s++;
    160   }
    161   return s;
    162 }
    163 
    164 static const char *
    165 parse_mailboxdomain (const char *s, const char *nonspecial,
    166 		     char *mailbox, size_t *mailboxlen, size_t mailboxmax,
    167 		     char *comment, size_t *commentlen, size_t commentmax)
    168 {
    169   const char *ps;
    170 
    171   while (*s)
    172   {
    173     SKIPWS (s);
    174     if (strchr (nonspecial, *s) == NULL && is_special (*s))
    175       return s;
    176 
    177     if (*s == '(')
    178     {
    179       if (*commentlen && *commentlen < commentmax)
    180 	comment[(*commentlen)++] = ' ';
    181       ps = next_token (s, comment, commentlen, commentmax);
    182     }
    183     else
    184       ps = next_token (s, mailbox, mailboxlen, mailboxmax);
    185     if (!ps)
    186       return NULL;
    187     s = ps;
    188   }
    189 
    190   return s;
    191 }
    192 
    193 static const char *
    194 parse_address (const char *s,
    195                char *token, size_t *tokenlen, size_t tokenmax,
    196 	       char *comment, size_t *commentlen, size_t commentmax,
    197 	       ADDRESS *addr)
    198 {
    199   s = parse_mailboxdomain (s, ".\"(\\",
    200 			   token, tokenlen, tokenmax,
    201 			   comment, commentlen, commentmax);
    202   if (!s)
    203     return NULL;
    204 
    205   if (*s == '@')
    206   {
    207     if (*tokenlen < tokenmax)
    208       token[(*tokenlen)++] = '@';
    209     s = parse_mailboxdomain (s + 1, ".([]\\",
    210 			     token, tokenlen, tokenmax,
    211 			     comment, commentlen, commentmax);
    212     if (!s)
    213       return NULL;
    214   }
    215 
    216   token[*tokenlen] = 0;
    217   addr->mailbox = safe_strdup (token);
    218 
    219   if (*commentlen && !addr->personal)
    220   {
    221     comment[*commentlen] = 0;
    222     addr->personal = safe_strdup (comment);
    223   }
    224 
    225   return s;
    226 }
    227 
    228 static const char *
    229 parse_route_addr (const char *s,
    230 		  char *comment, size_t *commentlen, size_t commentmax,
    231 		  ADDRESS *addr)
    232 {
    233   char token[STRING];
    234   size_t tokenlen = 0;
    235 
    236   SKIPWS (s);
    237 
    238   /* find the end of the route */
    239   if (*s == '@')
    240   {
    241     while (s && *s == '@')
    242     {
    243       if (tokenlen < sizeof (token) - 1)
    244 	token[tokenlen++] = '@';
    245       s = parse_mailboxdomain (s + 1, ".\\[](", token,
    246 			       &tokenlen, sizeof (token) - 1,
    247 			       comment, commentlen, commentmax);
    248     }
    249     if (!s || *s != ':')
    250     {
    251       RFC822Error = ERR_BAD_ROUTE;
    252       return NULL; /* invalid route */
    253     }
    254 
    255     if (tokenlen < sizeof (token) - 1)
    256       token[tokenlen++] = ':';
    257     s++;
    258   }
    259 
    260   if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL)
    261     return NULL;
    262 
    263   if (*s != '>' || !addr->mailbox)
    264   {
    265     RFC822Error = ERR_BAD_ROUTE_ADDR;
    266     return NULL;
    267   }
    268 
    269   s++;
    270   return s;
    271 }
    272 
    273 static const char *
    274 parse_addr_spec (const char *s,
    275 		 char *comment, size_t *commentlen, size_t commentmax,
    276 		 ADDRESS *addr)
    277 {
    278   char token[STRING];
    279   size_t tokenlen = 0;
    280 
    281   s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr);
    282   if (s && *s && *s != ',' && *s != ';')
    283   {
    284     RFC822Error = ERR_BAD_ADDR_SPEC;
    285     return NULL;
    286   }
    287   return s;
    288 }
    289 
    290 static void
    291 add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase,
    292 	      char *comment, size_t *commentlen, size_t commentmax)
    293 {
    294   ADDRESS *cur = rfc822_new_address ();
    295   
    296   if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL)
    297     return;
    298 
    299   if (*last)
    300     (*last)->next = cur;
    301   else
    302     *top = cur;
    303   *last = cur;
    304 }
    305 
    306 ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s)
    307 {
    308   const char *begin, *ps;
    309   char comment[STRING], phrase[STRING];
    310   size_t phraselen = 0, commentlen = 0;
    311   ADDRESS *cur, *last = NULL;
    312   
    313   RFC822Error = 0;
    314 
    315   last = top;
    316   while (last && last->next)
    317     last = last->next;
    318 
    319   SKIPWS (s);
    320   begin = s;
    321   while (*s)
    322   {
    323     if (*s == ',')
    324     {
    325       if (phraselen)
    326       {
    327 	phrase[phraselen] = 0;
    328 	add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
    329       }
    330       else if (commentlen && last && !last->personal)
    331       {
    332 	comment[commentlen] = 0;
    333 	last->personal = safe_strdup (comment);
    334       }
    335 
    336 #ifdef EXACT_ADDRESS
    337       if (last && !last->val)
    338 	last->val = mutt_substrdup (begin, s);
    339 #endif
    340       commentlen = 0;
    341       phraselen = 0;
    342       s++;
    343       begin = s;
    344       SKIPWS (begin);
    345     }
    346     else if (*s == '(')
    347     {
    348       if (commentlen && commentlen < sizeof (comment) - 1)
    349 	comment[commentlen++] = ' ';
    350       if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL)
    351       {
    352 	rfc822_free_address (&top);
    353 	return NULL;
    354       }
    355       s = ps;
    356     }
    357     else if (*s == ':')
    358     {
    359       cur = rfc822_new_address ();
    360       phrase[phraselen] = 0;
    361       cur->mailbox = safe_strdup (phrase);
    362       cur->group = 1;
    363 
    364       if (last)
    365 	last->next = cur;
    366       else
    367 	top = cur;
    368       last = cur;
    369 
    370 #ifdef EXACT_ADDRESS
    371       last->val = mutt_substrdup (begin, s);
    372 #endif
    373 
    374       phraselen = 0;
    375       commentlen = 0;
    376       s++;
    377       begin = s;
    378       SKIPWS (begin);
    379     }
    380     else if (*s == ';')
    381     {
    382       if (phraselen)
    383       {
    384 	phrase[phraselen] = 0;
    385 	add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
    386       }
    387       else if (commentlen && !last->personal)
    388       {
    389 	comment[commentlen] = 0;
    390 	last->personal = safe_strdup (comment);
    391       }
    392 #ifdef EXACT_ADDRESS
    393       if (last && !last->val)
    394 	last->val = mutt_substrdup (begin, s);
    395 #endif
    396 
    397       /* add group terminator */
    398       cur = rfc822_new_address ();
    399       if (last)
    400       {
    401 	last->next = cur;
    402 	last = cur;
    403       }
    404 
    405       phraselen = 0;
    406       commentlen = 0;
    407       s++;
    408       begin = s;
    409       SKIPWS (begin);
    410     }
    411     else if (*s == '<')
    412     {
    413       phrase[phraselen] = 0;
    414       cur = rfc822_new_address ();
    415       if (phraselen)
    416       {
    417 	if (cur->personal)
    418 	  FREE (&cur->personal);
    419 	/* if we get something like "Michael R. Elkins" remove the quotes */
    420 	rfc822_dequote_comment (phrase);
    421 	cur->personal = safe_strdup (phrase);
    422       }
    423       if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL)
    424       {
    425 	rfc822_free_address (&top);
    426 	rfc822_free_address (&cur);
    427 	return NULL;
    428       }
    429 
    430       if (last)
    431 	last->next = cur;
    432       else
    433 	top = cur;
    434       last = cur;
    435 
    436       phraselen = 0;
    437       commentlen = 0;
    438       s = ps;
    439     }
    440     else
    441     {
    442       if (phraselen && phraselen < sizeof (phrase) - 1)
    443 	phrase[phraselen++] = ' ';
    444       if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
    445       {
    446 	rfc822_free_address (&top);
    447 	return NULL;
    448       }
    449       s = ps;
    450     }
    451     SKIPWS (s);
    452   }
    453   
    454   if (phraselen)
    455   {
    456     phrase[phraselen] = 0;
    457     comment[commentlen] = 0;
    458     add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
    459   }
    460   else if (commentlen && last && !last->personal)
    461   {
    462     comment[commentlen] = 0;
    463     last->personal = safe_strdup (comment);
    464   }
    465 #ifdef EXACT_ADDRESS
    466   if (last)
    467     last->val = mutt_substrdup (begin, s);
    468 #endif
    469 
    470   return top;
    471 }
    472 
    473 void rfc822_qualify (ADDRESS *addr, const char *host)
    474 {
    475   char *p;
    476 
    477   for (; addr; addr = addr->next)
    478     if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL)
    479     {
    480       p = safe_malloc (strlen (addr->mailbox) + strlen (host) + 2);
    481       sprintf (p, "%s@%s", addr->mailbox, host);
    482       safe_free (&addr->mailbox);
    483       addr->mailbox = p;
    484     }
    485 }
    486 
    487 void
    488 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
    489 {
    490   if (strpbrk (value, specials))
    491   {
    492     char tmp[256], *pc = tmp;
    493     size_t tmplen = sizeof (tmp) - 3;
    494 
    495     *pc++ = '"';
    496     for (; *value && tmplen > 1; value++)
    497     {
    498       if (*value == '\\' || *value == '"')
    499       {
    500 	*pc++ = '\\';
    501 	tmplen--;
    502       }
    503       *pc++ = *value;
    504       tmplen--;
    505     }
    506     *pc++ = '"';
    507     *pc = 0;
    508     strfcpy (buf, tmp, buflen);
    509   }
    510   else
    511     strfcpy (buf, value, buflen);
    512 }
    513 
    514 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr)
    515 {
    516   size_t len;
    517   char *pbuf = buf;
    518   char *pc;
    519   
    520   if (!addr)
    521     return;
    522 
    523   buflen--; /* save room for the terminal nul */
    524 
    525 #ifdef EXACT_ADDRESS
    526   if (addr->val)
    527   {
    528     if (!buflen)
    529       goto done;
    530     strfcpy (pbuf, addr->val, buflen);
    531     len = strlen (pbuf);
    532     pbuf += len;
    533     buflen -= len;
    534     if (addr->group)
    535     {
    536       if (!buflen)
    537 	goto done;
    538       *pbuf++ = ':';
    539       buflen--;
    540       *pbuf = 0;
    541     }
    542     return;
    543   }
    544 #endif
    545 
    546   if (addr->personal)
    547   {
    548     if (strpbrk (addr->personal, RFC822Specials))
    549     {
    550       if (!buflen)
    551 	goto done;
    552       *pbuf++ = '"';
    553       buflen--;
    554       for (pc = addr->personal; *pc && buflen > 0; pc++)
    555       {
    556 	if (*pc == '"' || *pc == '\\')
    557 	{
    558 	  if (!buflen)
    559 	    goto done;
    560 	  *pbuf++ = '\\';
    561 	  buflen--;
    562 	}
    563 	if (!buflen)
    564 	  goto done;
    565 	*pbuf++ = *pc;
    566 	buflen--;
    567       }
    568       if (!buflen)
    569 	goto done;
    570       *pbuf++ = '"';
    571       buflen--;
    572     }
    573     else
    574     {
    575       if (!buflen)
    576 	goto done;
    577       strfcpy (pbuf, addr->personal, buflen);
    578       len = strlen (pbuf);
    579       pbuf += len;
    580       buflen -= len;
    581     }
    582 
    583     if (!buflen)
    584       goto done;
    585     *pbuf++ = ' ';
    586     buflen--;
    587   }
    588 
    589   if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
    590   {
    591     if (!buflen)
    592       goto done;
    593     *pbuf++ = '<';
    594     buflen--;
    595   }
    596 
    597   if (addr->mailbox)
    598   {
    599     if (!buflen)
    600       goto done;
    601     strfcpy (pbuf, addr->mailbox, buflen);
    602     len = strlen (pbuf);
    603     pbuf += len;
    604     buflen -= len;
    605 
    606     if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
    607     {
    608       if (!buflen)
    609 	goto done;
    610       *pbuf++ = '>';
    611       buflen--;
    612     }
    613 
    614     if (addr->group)
    615     {
    616       if (!buflen)
    617 	goto done;
    618       *pbuf++ = ':';
    619       buflen--;
    620       if (!buflen)
    621 	goto done;
    622       *pbuf++ = ' ';
    623       buflen--;
    624     }
    625   }
    626   else
    627   {
    628     if (!buflen)
    629       goto done;
    630     *pbuf++ = ';';
    631     buflen--;
    632   }
    633 done:
    634   /* no need to check for length here since we already save space at the
    635      beginning of this routine */
    636   *pbuf = 0;
    637 }
    638 
    639 /* note: it is assumed that `buf' is nul terminated! */
    640 void rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr)
    641 {
    642   char *pbuf = buf;
    643   size_t len = strlen (buf);
    644   
    645   buflen--; /* save room for the terminal nul */
    646 
    647   if (len > 0)
    648   {
    649     if (len > buflen)
    650       return; /* safety check for bogus arguments */
    651 
    652     pbuf += len;
    653     buflen -= len;
    654     if (!buflen)
    655       goto done;
    656     *pbuf++ = ',';
    657     buflen--;
    658     if (!buflen)
    659       goto done;
    660     *pbuf++ = ' ';
    661     buflen--;
    662   }
    663 
    664   for (; addr && buflen > 0; addr = addr->next)
    665   {
    666     /* use buflen+1 here because we already saved space for the trailing
    667        nul char, and the subroutine can make use of it */
    668     rfc822_write_address_single (pbuf, buflen + 1, addr);
    669 
    670     /* this should be safe since we always have at least 1 char passed into
    671        the above call, which means `pbuf' should always be nul terminated */
    672     len = strlen (pbuf);
    673     pbuf += len;
    674     buflen -= len;
    675 
    676     /* if there is another address, and its not a group mailbox name or
    677        group terminator, add a comma to separate the addresses */
    678     if (addr->next && addr->next->mailbox && !addr->group)
    679     {
    680       if (!buflen)
    681 	goto done;
    682       *pbuf++ = ',';
    683       buflen--;
    684       if (!buflen)
    685 	goto done;
    686       *pbuf++ = ' ';
    687       buflen--;
    688     }
    689   }
    690 done:
    691   *pbuf = 0;
    692 }
    693 
    694 /* this should be rfc822_cpy_adr */
    695 ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr)
    696 {
    697   ADDRESS *p = rfc822_new_address ();
    698 
    699 #ifdef EXACT_ADDRESS
    700   p->val = safe_strdup (addr->val);
    701 #endif
    702   p->personal = safe_strdup (addr->personal);
    703   p->mailbox = safe_strdup (addr->mailbox);
    704   p->group = addr->group;
    705   return p;
    706 }
    707 
    708 /* this should be rfc822_cpy_adrlist */
    709 ADDRESS *rfc822_cpy_adr (ADDRESS *addr)
    710 {
    711   ADDRESS *top = NULL, *last = NULL;
    712   
    713   for (; addr; addr = addr->next)
    714   {
    715     if (last)
    716     {
    717       last->next = rfc822_cpy_adr_real (addr);
    718       last = last->next;
    719     }
    720     else
    721       top = last = rfc822_cpy_adr_real (addr);
    722   }
    723   return top;
    724 }
    725 
    726 /* append list 'b' to list 'a' and return the last element in the new list */
    727 ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b)
    728 {
    729   ADDRESS *tmp = *a;
    730 
    731   while (tmp && tmp->next)
    732     tmp = tmp->next;
    733   if (!b)
    734     return tmp;
    735   if (tmp)
    736     tmp->next = rfc822_cpy_adr (b);
    737   else
    738     tmp = *a = rfc822_cpy_adr (b);
    739   while (tmp && tmp->next)
    740     tmp = tmp->next;
    741   return tmp;
    742 }
    743 
    744 #ifdef TESTING
    745 void safe_free (void *ptr)
    746 { 
    747   void **p = (void **)ptr;
    748   if (*p)
    749   { 
    750     free (*p);
    751     *p = 0;
    752   }
    753 }
    754 
    755 int main (int argc, char **argv)
    756 {
    757   ADDRESS *list;
    758   char buf[256];
    759   char *str = "michael, Michael Elkins <me@cs.hmc.edu>, testing a really complex address: this example <@contains.a.source.route@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)";
    760   
    761   list = rfc822_parse_adrlist (NULL, str);
    762   buf[0] = 0;
    763   rfc822_write_address (buf, sizeof (buf), list);
    764   rfc822_free_address (&list);
    765   puts (buf);
    766   exit (0);
    767 }
    768 #endif