8d192884d6d6e7f8bcddc6b5606777b5fbcc0c9d
[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 ssize_t
363 rfc822_strcpy(char *buf, ssize_t buflen, const char *p, const char *specials)
364 {
365     if (strpbrk(p, specials)) {
366         ssize_t pos = 0;
367
368         buf[pos++] = '"';
369
370         while (*p && pos < buflen - 2) {
371             if (*p == '\\' || *p == '"') {
372                 if (pos >= buflen - 4)
373                     break;
374                 buf[pos++] = '\\';
375             }
376
377             buf[pos++] = *p++;
378         }
379
380         buf[pos++] = '"';
381         buf[pos]   = '\0';
382         return pos;
383     } else {
384         return m_strcpy(buf, buflen, p);
385     }
386 }
387
388 ssize_t rfc822_write_address_single(char *buf, ssize_t buflen,
389                                     address_t *addr, int display)
390 {
391     ssize_t pos = 0;
392
393     if (!addr)
394         return 0;
395
396     buflen--;                     /* save room for the terminal nul */
397
398     if (addr->personal) {
399         pos = rfc822_strcpy(buf, buflen, addr->personal, RFC822Specials);
400         if (pos + 2 >= buflen)
401             goto done;
402
403         buf[pos++] = ' ';
404         buf[pos++] = '<';
405     }
406
407     if (addr->mailbox) {
408         if (!display) {
409             pos += m_strcpy(buf + pos, buflen - pos, addr->mailbox);
410         } else {
411             pos += m_strcpy(buf + pos, buflen - pos, mutt_addr_for_display(addr));
412         }
413
414         if (addr->personal) {
415             if (pos + 1 >= buflen)
416                 goto done;
417             buf[pos++] = '>';
418         }
419
420         if (addr->group) {
421             if (pos + 1 >= buflen)
422                 goto done;
423             buf[pos++] = ':';
424         }
425     } else {
426         if (pos + 1 >= buflen)
427             goto done;
428         buf[pos++] = ';';
429     }
430
431   done:
432     /* no need to check for length here since we already save space at the
433        beginning of this routine */
434     buf[pos] = 0;
435     return pos;
436 }
437
438 /* note: it is assumed that `buf' is nul terminated! */
439 ssize_t
440 rfc822_write_address(char *buf, ssize_t buflen, address_t *addr, int display)
441 {
442     ssize_t pos;
443
444     buflen--;                     /* save room for the terminal nul */
445     pos = m_strnlen(buf, buflen);
446
447     if (pos) {
448         if (pos + 2 >= buflen)
449             goto done;
450
451         buf[pos++] = ',';
452         buf[pos++] = ' ';
453     }
454
455     for (; addr; addr = addr->next) {
456         pos += rfc822_write_address_single(buf + pos, buflen + 1 - pos,
457                                            addr, display);
458
459         if (!addr->group && addr->next && addr->next->mailbox) {
460             /* if there is another address, and its not a group mailbox name or
461                group terminator, add a comma to separate the addresses */
462             if (pos + 2 >= buflen)
463                 break;
464
465             buf[pos++] = ',';
466             buf[pos++] = ' ';
467         }
468     }
469
470   done:
471     buf[pos] = '\0';
472     return pos;
473 }
474