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