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