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