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