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