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