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     /* dirty fix to un-break EXACT_ADDRESS */
518     if (pbuf[len-1] == '\n')
519       pbuf[--len] = '\0';
520     pbuf += len;
521     buflen -= len;
522     if (addr->group) {
523       if (!buflen)
524         goto done;
525       *pbuf++ = ':';
526       buflen--;
527       *pbuf = 0;
528     }
529     return;
530   }
531 #endif
532
533   if (addr->personal) {
534     if (strpbrk (addr->personal, RFC822Specials)) {
535       if (!buflen)
536         goto done;
537       *pbuf++ = '"';
538       buflen--;
539       for (pc = addr->personal; *pc && buflen > 0; pc++) {
540         if (*pc == '"' || *pc == '\\') {
541           if (!buflen)
542             goto done;
543           *pbuf++ = '\\';
544           buflen--;
545         }
546         if (!buflen)
547           goto done;
548         *pbuf++ = *pc;
549         buflen--;
550       }
551       if (!buflen)
552         goto done;
553       *pbuf++ = '"';
554       buflen--;
555     }
556     else {
557       if (!buflen)
558         goto done;
559       strfcpy (pbuf, addr->personal, buflen);
560       len = safe_strlen (pbuf);
561       pbuf += len;
562       buflen -= len;
563     }
564
565     if (!buflen)
566       goto done;
567     *pbuf++ = ' ';
568     buflen--;
569   }
570
571   if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
572     if (!buflen)
573       goto done;
574     *pbuf++ = '<';
575     buflen--;
576   }
577
578   if (addr->mailbox) {
579     if (!buflen)
580       goto done;
581     if (ascii_strcmp (addr->mailbox, "@") && !display) {
582       strfcpy (pbuf, addr->mailbox, buflen);
583       len = safe_strlen (pbuf);
584     }
585     else if (ascii_strcmp (addr->mailbox, "@") && display) {
586       strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
587       len = safe_strlen (pbuf);
588     }
589     else {
590       *pbuf = '\0';
591       len = 0;
592     }
593     pbuf += len;
594     buflen -= len;
595
596     if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
597       if (!buflen)
598         goto done;
599       *pbuf++ = '>';
600       buflen--;
601     }
602
603     if (addr->group) {
604       if (!buflen)
605         goto done;
606       *pbuf++ = ':';
607       buflen--;
608       if (!buflen)
609         goto done;
610       *pbuf++ = ' ';
611       buflen--;
612     }
613   }
614   else {
615     if (!buflen)
616       goto done;
617     *pbuf++ = ';';
618     buflen--;
619   }
620 done:
621   /* no need to check for length here since we already save space at the
622      beginning of this routine */
623   *pbuf = 0;
624 }
625
626 /* note: it is assumed that `buf' is nul terminated! */
627 void rfc822_write_address (char *buf, size_t buflen, ADDRESS * addr,
628                            int display)
629 {
630   char *pbuf = buf;
631   size_t len = safe_strlen (buf);
632
633   buflen--;                     /* save room for the terminal nul */
634
635   if (len > 0) {
636     if (len > buflen)
637       return;                   /* safety check for bogus arguments */
638
639     pbuf += len;
640     buflen -= len;
641     if (!buflen)
642       goto done;
643     *pbuf++ = ',';
644     buflen--;
645     if (!buflen)
646       goto done;
647     *pbuf++ = ' ';
648     buflen--;
649   }
650
651   for (; addr && buflen > 0; addr = addr->next) {
652     /* use buflen+1 here because we already saved space for the trailing
653        nul char, and the subroutine can make use of it */
654     rfc822_write_address_single (pbuf, buflen + 1, addr, display);
655
656     /* this should be safe since we always have at least 1 char passed into
657        the above call, which means `pbuf' should always be nul terminated */
658     len = safe_strlen (pbuf);
659     pbuf += len;
660     buflen -= len;
661
662     /* if there is another address, and its not a group mailbox name or
663        group terminator, add a comma to separate the addresses */
664     if (addr->next && addr->next->mailbox && !addr->group) {
665       if (!buflen)
666         goto done;
667       *pbuf++ = ',';
668       buflen--;
669       if (!buflen)
670         goto done;
671       *pbuf++ = ' ';
672       buflen--;
673     }
674   }
675 done:
676   *pbuf = 0;
677 }
678
679 /* this should be rfc822_cpy_adr */
680 ADDRESS *rfc822_cpy_adr_real (ADDRESS * addr)
681 {
682   ADDRESS *p = rfc822_new_address ();
683
684 #ifdef EXACT_ADDRESS
685   p->val = safe_strdup (addr->val);
686 #endif
687   p->personal = safe_strdup (addr->personal);
688   p->mailbox = safe_strdup (addr->mailbox);
689   p->group = addr->group;
690   return p;
691 }
692
693 /* this should be rfc822_cpy_adrlist */
694 ADDRESS *rfc822_cpy_adr (ADDRESS * addr)
695 {
696   ADDRESS *top = NULL, *last = NULL;
697
698   for (; addr; addr = addr->next) {
699     if (last) {
700       last->next = rfc822_cpy_adr_real (addr);
701       last = last->next;
702     }
703     else
704       top = last = rfc822_cpy_adr_real (addr);
705   }
706   return top;
707 }
708
709 /* append list 'b' to list 'a' and return the last element in the new list */
710 ADDRESS *rfc822_append (ADDRESS ** a, ADDRESS * b)
711 {
712   ADDRESS *tmp = *a;
713
714   while (tmp && tmp->next)
715     tmp = tmp->next;
716   if (!b)
717     return tmp;
718   if (tmp)
719     tmp->next = rfc822_cpy_adr (b);
720   else
721     tmp = *a = rfc822_cpy_adr (b);
722   while (tmp && tmp->next)
723     tmp = tmp->next;
724   return tmp;
725 }