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