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