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