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