18c980ffb58aef1b48424ed9ab3f6999930f8948
[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 <lib-lib/mem.h>
19
20 #include "mutt.h"
21 #include "ascii.h"
22 #include "mutt_idna.h"
23
24 #include "lib/mem.h"
25 #include "lib/intl.h"
26 #include "lib/str.h"
27
28 #define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \
29         a[(c)] = 0; } while (0)
30
31 #define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1)
32
33
34 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
35
36 #define is_special(x) strchr(RFC822Specials,x)
37
38 int RFC822Error = 0;
39
40 /* these must defined in the same order as the numerated errors given in rfc822.h */
41 const char *RFC822Errors[] = {
42   "out of memory",
43   "mismatched parenthesis",
44   "mismatched quotes",
45   "bad route in <>",
46   "bad address in <>",
47   "bad address spec"
48 };
49
50 void rfc822_dequote_comment (char *s)
51 {
52   char *w = s;
53
54   for (; *s; s++) {
55     if (*s == '\\') {
56       if (!*++s)
57         break;                  /* error? */
58       *w++ = *s;
59     }
60     else if (*s != '\"') {
61       if (w != s)
62         *w = *s;
63       w++;
64     }
65   }
66   *w = 0;
67 }
68
69 void rfc822_free_address (ADDRESS ** p)
70 {
71   ADDRESS *t;
72
73   while (*p) {
74     t = *p;
75     *p = (*p)->next;
76     p_delete(&t->personal);
77     p_delete(&t->mailbox);
78     p_delete(&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 = str_dup (token);
212
213   if (*commentlen && !addr->personal) {
214     terminate_string (comment, *commentlen, commentmax);
215     addr->personal = str_dup (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 = str_dup ("@");
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 = str_dup (comment);
329       }
330
331       commentlen = 0;
332       phraselen = 0;
333       s++;
334       begin = s;
335       SKIPWS (begin);
336     }
337     else if (*s == '(') {
338       if (commentlen && commentlen < sizeof (comment) - 1)
339         comment[commentlen++] = ' ';
340       if ((ps =
341            next_token (s, comment, &commentlen,
342                        sizeof (comment) - 1)) == NULL) {
343         rfc822_free_address (&top);
344         return NULL;
345       }
346       s = ps;
347     }
348     else if (*s == ':') {
349       cur = rfc822_new_address ();
350       terminate_buffer (phrase, phraselen);
351       cur->mailbox = str_dup (phrase);
352       cur->group = 1;
353
354       if (last)
355         last->next = cur;
356       else
357         top = cur;
358       last = cur;
359
360       phraselen = 0;
361       commentlen = 0;
362       s++;
363       begin = s;
364       SKIPWS (begin);
365     }
366     else if (*s == ';') {
367       if (phraselen) {
368         terminate_buffer (phrase, phraselen);
369         add_addrspec (&top, &last, phrase, comment, &commentlen,
370                       sizeof (comment) - 1);
371       }
372       else if (commentlen && last && !last->personal) {
373         terminate_buffer (comment, commentlen);
374         last->personal = str_dup (comment);
375       }
376
377       /* add group terminator */
378       cur = rfc822_new_address ();
379       if (last) {
380         last->next = cur;
381         last = cur;
382       }
383
384       phraselen = 0;
385       commentlen = 0;
386       s++;
387       begin = s;
388       SKIPWS (begin);
389     }
390     else if (*s == '<') {
391       terminate_buffer (phrase, phraselen);
392       cur = rfc822_new_address ();
393       if (phraselen) {
394         if (cur->personal)
395           p_delete(&cur->personal);
396         /* if we get something like "Michael R. Elkins" remove the quotes */
397         rfc822_dequote_comment (phrase);
398         cur->personal = str_dup (phrase);
399       }
400       if ((ps =
401            parse_route_addr (s + 1, comment, &commentlen,
402                              sizeof (comment) - 1, cur)) == NULL) {
403         rfc822_free_address (&top);
404         rfc822_free_address (&cur);
405         return NULL;
406       }
407
408       if (last)
409         last->next = cur;
410       else
411         top = cur;
412       last = cur;
413
414       phraselen = 0;
415       commentlen = 0;
416       s = ps;
417     }
418     else {
419       if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
420         phrase[phraselen++] = ' ';
421       if ((ps =
422            next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) {
423         rfc822_free_address (&top);
424         return NULL;
425       }
426       s = ps;
427     }
428     ws_pending = isspace ((unsigned char) *s);
429     SKIPWS (s);
430   }
431
432   if (phraselen) {
433     terminate_buffer (phrase, phraselen);
434     terminate_buffer (comment, commentlen);
435     add_addrspec (&top, &last, phrase, comment, &commentlen,
436                   sizeof (comment) - 1);
437   }
438   else if (commentlen && last && !last->personal) {
439     terminate_buffer (comment, commentlen);
440     last->personal = str_dup (comment);
441   }
442
443   return top;
444 }
445
446 void rfc822_qualify (ADDRESS * addr, const char *host)
447 {
448   char *p;
449
450   for (; addr; addr = addr->next)
451     if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) {
452       p = p_new(char, str_len(addr->mailbox) + str_len(host) + 2);
453       sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
454       p_delete(&addr->mailbox);
455       addr->mailbox = p;
456     }
457 }
458
459 void
460 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
461 {
462   if (strpbrk (value, specials)) {
463     char tmp[256], *pc = tmp;
464     size_t tmplen = sizeof (tmp) - 3;
465
466     *pc++ = '"';
467     for (; *value && tmplen > 1; value++) {
468       if (*value == '\\' || *value == '"') {
469         *pc++ = '\\';
470         tmplen--;
471       }
472       *pc++ = *value;
473       tmplen--;
474     }
475     *pc++ = '"';
476     *pc = 0;
477     strfcpy (buf, tmp, buflen);
478   }
479   else
480     strfcpy (buf, value, buflen);
481 }
482
483 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS * addr,
484                                   int display)
485 {
486   size_t len;
487   char *pbuf = buf;
488   char *pc;
489
490   if (!addr)
491     return;
492
493   buflen--;                     /* save room for the terminal nul */
494
495   if (addr->personal) {
496     if (strpbrk (addr->personal, RFC822Specials)) {
497       if (!buflen)
498         goto done;
499       *pbuf++ = '"';
500       buflen--;
501       for (pc = addr->personal; *pc && buflen > 0; pc++) {
502         if (*pc == '"' || *pc == '\\') {
503           if (!buflen)
504             goto done;
505           *pbuf++ = '\\';
506           buflen--;
507         }
508         if (!buflen)
509           goto done;
510         *pbuf++ = *pc;
511         buflen--;
512       }
513       if (!buflen)
514         goto done;
515       *pbuf++ = '"';
516       buflen--;
517     }
518     else {
519       if (!buflen)
520         goto done;
521       strfcpy (pbuf, addr->personal, buflen);
522       len = str_len (pbuf);
523       pbuf += len;
524       buflen -= len;
525     }
526
527     if (!buflen)
528       goto done;
529     *pbuf++ = ' ';
530     buflen--;
531   }
532
533   if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
534     if (!buflen)
535       goto done;
536     *pbuf++ = '<';
537     buflen--;
538   }
539
540   if (addr->mailbox) {
541     if (!buflen)
542       goto done;
543     if (ascii_strcmp (addr->mailbox, "@") && !display) {
544       strfcpy (pbuf, addr->mailbox, buflen);
545       len = str_len (pbuf);
546     }
547     else if (ascii_strcmp (addr->mailbox, "@") && display) {
548       strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
549       len = str_len (pbuf);
550     }
551     else {
552       *pbuf = '\0';
553       len = 0;
554     }
555     pbuf += len;
556     buflen -= len;
557
558     if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
559       if (!buflen)
560         goto done;
561       *pbuf++ = '>';
562       buflen--;
563     }
564
565     if (addr->group) {
566       if (!buflen)
567         goto done;
568       *pbuf++ = ':';
569       buflen--;
570       if (!buflen)
571         goto done;
572       *pbuf++ = ' ';
573       buflen--;
574     }
575   }
576   else {
577     if (!buflen)
578       goto done;
579     *pbuf++ = ';';
580     buflen--;
581   }
582 done:
583   /* no need to check for length here since we already save space at the
584      beginning of this routine */
585   *pbuf = 0;
586 }
587
588 /* note: it is assumed that `buf' is nul terminated! */
589 void rfc822_write_address (char *buf, size_t buflen, ADDRESS * addr,
590                            int display)
591 {
592   char *pbuf = buf;
593   size_t len = str_len (buf);
594
595   buflen--;                     /* save room for the terminal nul */
596
597   if (len > 0) {
598     if (len > buflen)
599       return;                   /* safety check for bogus arguments */
600
601     pbuf += len;
602     buflen -= len;
603     if (!buflen)
604       goto done;
605     *pbuf++ = ',';
606     buflen--;
607     if (!buflen)
608       goto done;
609     *pbuf++ = ' ';
610     buflen--;
611   }
612
613   for (; addr && buflen > 0; addr = addr->next) {
614     /* use buflen+1 here because we already saved space for the trailing
615        nul char, and the subroutine can make use of it */
616     rfc822_write_address_single (pbuf, buflen + 1, addr, display);
617
618     /* this should be safe since we always have at least 1 char passed into
619        the above call, which means `pbuf' should always be nul terminated */
620     len = str_len (pbuf);
621     pbuf += len;
622     buflen -= len;
623
624     /* if there is another address, and its not a group mailbox name or
625        group terminator, add a comma to separate the addresses */
626     if (addr->next && addr->next->mailbox && !addr->group) {
627       if (!buflen)
628         goto done;
629       *pbuf++ = ',';
630       buflen--;
631       if (!buflen)
632         goto done;
633       *pbuf++ = ' ';
634       buflen--;
635     }
636   }
637 done:
638   *pbuf = 0;
639 }
640
641 /* this should be rfc822_cpy_adr */
642 ADDRESS *rfc822_cpy_adr_real (ADDRESS * addr)
643 {
644   ADDRESS *p = rfc822_new_address ();
645
646   p->personal = str_dup (addr->personal);
647   p->mailbox = str_dup (addr->mailbox);
648   p->group = addr->group;
649   return p;
650 }
651
652 /* this should be rfc822_cpy_adrlist */
653 ADDRESS *rfc822_cpy_adr (ADDRESS * addr)
654 {
655   ADDRESS *top = NULL, *last = NULL;
656
657   for (; addr; addr = addr->next) {
658     if (last) {
659       last->next = rfc822_cpy_adr_real (addr);
660       last = last->next;
661     }
662     else
663       top = last = rfc822_cpy_adr_real (addr);
664   }
665   return top;
666 }
667
668 /* append list 'b' to list 'a' and return the last element in the new list */
669 ADDRESS *rfc822_append (ADDRESS ** a, ADDRESS * b)
670 {
671   ADDRESS *tmp = *a;
672
673   while (tmp && tmp->next)
674     tmp = tmp->next;
675   if (!b)
676     return tmp;
677   if (tmp)
678     tmp->next = rfc822_cpy_adr (b);
679   else
680     tmp = *a = rfc822_cpy_adr (b);
681   while (tmp && tmp->next)
682     tmp = tmp->next;
683   return tmp;
684 }