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