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