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