use m_strdup and m_strlen that are inlined for efficiency
[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/macros.h>
21
22 #include "mutt.h"
23 #include "ascii.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     SKIPWS (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   SKIPWS (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   SKIPWS (s);
317   begin = s;
318   while (*s) {
319     if (*s == ',') {
320       if (phraselen) {
321         terminate_buffer (phrase, phraselen);
322         add_addrspec (&top, &last, phrase, comment, &commentlen,
323                       sizeof (comment) - 1);
324       }
325       else if (commentlen && last && !last->personal) {
326         terminate_buffer (comment, commentlen);
327         last->personal = m_strdup(comment);
328       }
329
330       commentlen = 0;
331       phraselen = 0;
332       s++;
333       begin = s;
334       SKIPWS (begin);
335     }
336     else if (*s == '(') {
337       if (commentlen && commentlen < sizeof (comment) - 1)
338         comment[commentlen++] = ' ';
339       if ((ps =
340            next_token (s, comment, &commentlen,
341                        sizeof (comment) - 1)) == NULL) {
342         rfc822_free_address (&top);
343         return NULL;
344       }
345       s = ps;
346     }
347     else if (*s == ':') {
348       cur = rfc822_new_address ();
349       terminate_buffer (phrase, phraselen);
350       cur->mailbox = m_strdup(phrase);
351       cur->group = 1;
352
353       if (last)
354         last->next = cur;
355       else
356         top = cur;
357       last = cur;
358
359       phraselen = 0;
360       commentlen = 0;
361       s++;
362       begin = s;
363       SKIPWS (begin);
364     }
365     else if (*s == ';') {
366       if (phraselen) {
367         terminate_buffer (phrase, phraselen);
368         add_addrspec (&top, &last, phrase, comment, &commentlen,
369                       sizeof (comment) - 1);
370       }
371       else if (commentlen && last && !last->personal) {
372         terminate_buffer (comment, commentlen);
373         last->personal = m_strdup(comment);
374       }
375
376       /* add group terminator */
377       cur = rfc822_new_address ();
378       if (last) {
379         last->next = cur;
380         last = cur;
381       }
382
383       phraselen = 0;
384       commentlen = 0;
385       s++;
386       begin = s;
387       SKIPWS (begin);
388     }
389     else if (*s == '<') {
390       terminate_buffer (phrase, phraselen);
391       cur = rfc822_new_address ();
392       if (phraselen) {
393         if (cur->personal)
394           p_delete(&cur->personal);
395         /* if we get something like "Michael R. Elkins" remove the quotes */
396         rfc822_dequote_comment (phrase);
397         cur->personal = m_strdup(phrase);
398       }
399       if ((ps =
400            parse_route_addr (s + 1, comment, &commentlen,
401                              sizeof (comment) - 1, cur)) == NULL) {
402         rfc822_free_address (&top);
403         rfc822_free_address (&cur);
404         return NULL;
405       }
406
407       if (last)
408         last->next = cur;
409       else
410         top = cur;
411       last = cur;
412
413       phraselen = 0;
414       commentlen = 0;
415       s = ps;
416     }
417     else {
418       if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
419         phrase[phraselen++] = ' ';
420       if ((ps =
421            next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) {
422         rfc822_free_address (&top);
423         return NULL;
424       }
425       s = ps;
426     }
427     ws_pending = isspace ((unsigned char) *s);
428     SKIPWS (s);
429   }
430
431   if (phraselen) {
432     terminate_buffer (phrase, phraselen);
433     terminate_buffer (comment, commentlen);
434     add_addrspec (&top, &last, phrase, comment, &commentlen,
435                   sizeof (comment) - 1);
436   }
437   else if (commentlen && last && !last->personal) {
438     terminate_buffer (comment, commentlen);
439     last->personal = m_strdup(comment);
440   }
441
442   return top;
443 }
444
445 void rfc822_qualify (ADDRESS * addr, const char *host)
446 {
447   char *p;
448
449   for (; addr; addr = addr->next)
450     if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) {
451       p = p_new(char, m_strlen(addr->mailbox) + m_strlen(host) + 2);
452       sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
453       p_delete(&addr->mailbox);
454       addr->mailbox = p;
455     }
456 }
457
458 void
459 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
460 {
461   if (strpbrk (value, specials)) {
462     char tmp[256], *pc = tmp;
463     size_t tmplen = sizeof (tmp) - 3;
464
465     *pc++ = '"';
466     for (; *value && tmplen > 1; value++) {
467       if (*value == '\\' || *value == '"') {
468         *pc++ = '\\';
469         tmplen--;
470       }
471       *pc++ = *value;
472       tmplen--;
473     }
474     *pc++ = '"';
475     *pc = 0;
476     strfcpy (buf, tmp, buflen);
477   }
478   else
479     strfcpy (buf, value, buflen);
480 }
481
482 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS * addr,
483                                   int display)
484 {
485   size_t len;
486   char *pbuf = buf;
487   char *pc;
488
489   if (!addr)
490     return;
491
492   buflen--;                     /* save room for the terminal nul */
493
494   if (addr->personal) {
495     if (strpbrk (addr->personal, RFC822Specials)) {
496       if (!buflen)
497         goto done;
498       *pbuf++ = '"';
499       buflen--;
500       for (pc = addr->personal; *pc && buflen > 0; pc++) {
501         if (*pc == '"' || *pc == '\\') {
502           if (!buflen)
503             goto done;
504           *pbuf++ = '\\';
505           buflen--;
506         }
507         if (!buflen)
508           goto done;
509         *pbuf++ = *pc;
510         buflen--;
511       }
512       if (!buflen)
513         goto done;
514       *pbuf++ = '"';
515       buflen--;
516     }
517     else {
518       if (!buflen)
519         goto done;
520       strfcpy (pbuf, addr->personal, buflen);
521       len = m_strlen(pbuf);
522       pbuf += len;
523       buflen -= len;
524     }
525
526     if (!buflen)
527       goto done;
528     *pbuf++ = ' ';
529     buflen--;
530   }
531
532   if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
533     if (!buflen)
534       goto done;
535     *pbuf++ = '<';
536     buflen--;
537   }
538
539   if (addr->mailbox) {
540     if (!buflen)
541       goto done;
542     if (ascii_strcmp (addr->mailbox, "@") && !display) {
543       strfcpy (pbuf, addr->mailbox, buflen);
544       len = m_strlen(pbuf);
545     }
546     else if (ascii_strcmp (addr->mailbox, "@") && display) {
547       strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
548       len = m_strlen(pbuf);
549     }
550     else {
551       *pbuf = '\0';
552       len = 0;
553     }
554     pbuf += len;
555     buflen -= len;
556
557     if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) {
558       if (!buflen)
559         goto done;
560       *pbuf++ = '>';
561       buflen--;
562     }
563
564     if (addr->group) {
565       if (!buflen)
566         goto done;
567       *pbuf++ = ':';
568       buflen--;
569       if (!buflen)
570         goto done;
571       *pbuf++ = ' ';
572       buflen--;
573     }
574   }
575   else {
576     if (!buflen)
577       goto done;
578     *pbuf++ = ';';
579     buflen--;
580   }
581 done:
582   /* no need to check for length here since we already save space at the
583      beginning of this routine */
584   *pbuf = 0;
585 }
586
587 /* note: it is assumed that `buf' is nul terminated! */
588 void rfc822_write_address (char *buf, size_t buflen, ADDRESS * addr,
589                            int display)
590 {
591   char *pbuf = buf;
592   size_t len = m_strlen(buf);
593
594   buflen--;                     /* save room for the terminal nul */
595
596   if (len > 0) {
597     if (len > buflen)
598       return;                   /* safety check for bogus arguments */
599
600     pbuf += len;
601     buflen -= len;
602     if (!buflen)
603       goto done;
604     *pbuf++ = ',';
605     buflen--;
606     if (!buflen)
607       goto done;
608     *pbuf++ = ' ';
609     buflen--;
610   }
611
612   for (; addr && buflen > 0; addr = addr->next) {
613     /* use buflen+1 here because we already saved space for the trailing
614        nul char, and the subroutine can make use of it */
615     rfc822_write_address_single (pbuf, buflen + 1, addr, display);
616
617     /* this should be safe since we always have at least 1 char passed into
618        the above call, which means `pbuf' should always be nul terminated */
619     len = m_strlen(pbuf);
620     pbuf += len;
621     buflen -= len;
622
623     /* if there is another address, and its not a group mailbox name or
624        group terminator, add a comma to separate the addresses */
625     if (addr->next && addr->next->mailbox && !addr->group) {
626       if (!buflen)
627         goto done;
628       *pbuf++ = ',';
629       buflen--;
630       if (!buflen)
631         goto done;
632       *pbuf++ = ' ';
633       buflen--;
634     }
635   }
636 done:
637   *pbuf = 0;
638 }
639
640 /* this should be rfc822_cpy_adr */
641 ADDRESS *rfc822_cpy_adr_real (ADDRESS * addr)
642 {
643   ADDRESS *p = rfc822_new_address ();
644
645   p->personal = m_strdup(addr->personal);
646   p->mailbox = m_strdup(addr->mailbox);
647   p->group = addr->group;
648   return p;
649 }
650
651 /* this should be rfc822_cpy_adrlist */
652 ADDRESS *rfc822_cpy_adr (ADDRESS * addr)
653 {
654   ADDRESS *top = NULL, *last = NULL;
655
656   for (; addr; addr = addr->next) {
657     if (last) {
658       last->next = rfc822_cpy_adr_real (addr);
659       last = last->next;
660     }
661     else
662       top = last = rfc822_cpy_adr_real (addr);
663   }
664   return top;
665 }
666
667 /* append list 'b' to list 'a' and return the last element in the new list */
668 ADDRESS *rfc822_append (ADDRESS ** a, ADDRESS * b)
669 {
670   ADDRESS *tmp = *a;
671
672   while (tmp && tmp->next)
673     tmp = tmp->next;
674   if (!b)
675     return tmp;
676   if (tmp)
677     tmp->next = rfc822_cpy_adr (b);
678   else
679     tmp = *a = rfc822_cpy_adr (b);
680   while (tmp && tmp->next)
681     tmp = tmp->next;
682   return tmp;
683 }