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