don't hide macro.
[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 void address_wipe(address_t *addr)
41 {
42     p_delete(&addr->personal);
43     p_delete(&addr->mailbox);
44     address_delete(&addr->next);
45 }
46
47
48 void rfc822_qualify(address_t *addr, const char *host)
49 {
50     char *p;
51
52     for (; addr; addr = addr->next) {
53         if (!addr->group && addr->mailbox && !strchr(addr->mailbox, '@')) {
54             p = p_new(char, m_strlen(addr->mailbox) + m_strlen(host) + 2);
55             sprintf(p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
56             p_delete(&addr->mailbox);
57             addr->mailbox = p;
58         }
59     }
60 }
61
62 address_t *address_dup(address_t *addr)
63 {
64     address_t *res = address_new();
65
66     res->personal = m_strdup(addr->personal);
67     res->mailbox  = m_strdup(addr->mailbox);
68     res->group    = addr->group;
69     return res;
70 }
71
72 address_t *address_list_dup(address_t *addr)
73 {
74     address_t *res = NULL, **resp = &res;
75
76     for (; addr; addr = addr->next) {
77         *resp = address_dup(addr);
78         resp = &(*resp)->next;
79     }
80
81     return res;
82 }
83
84
85 /****************************************************************************/
86 /* Parsing functions                                                        */
87 /****************************************************************************/
88
89 typedef struct static_buf {
90     char buf[STRING];
91     int  len;
92 } static_buf;
93
94 static inline void stbuf_append(static_buf *buf, int c) {
95     if (buf->len < ssizeof(buf->buf) - 1) {
96         buf->buf[buf->len++] = c;
97         buf->buf[buf->len]   = '\0';
98     }
99 }
100
101 static inline void stbuf_append_sp(static_buf *buf) {
102     if (buf->len)
103         stbuf_append(buf, ' ');
104 }
105
106 static char *rfc822_dequote_comment(static_buf *buf)
107 {
108     char *res = p_new(char, buf->len + 1);
109     char *q   = res;
110     char *p   = buf->buf;
111     int i;
112
113     for (i = 0; i < buf->len; i++) {
114         if (p[i] == '\"')
115             continue;
116
117         if (p[i] == '\\') {
118             if (++i >= buf->len) /* should not happen */
119                 break;
120         }
121
122         *q++ = p[i];
123     }
124
125     *q++ = '\0';
126     return res;
127 }
128
129 static const char *parse_comment(const char *s, static_buf *buf)
130 {
131     int level = 1;
132
133     for (; *s; s++) {
134         switch (*s) {
135           case '(':
136             level++;
137             break;
138
139           case ')':
140             level--;
141             if (!level)
142                 return s;
143             break;
144
145           case '\\':
146             s++; /* if *++s is NUL it will be an error anyway */
147             break;
148
149           default:
150             break;
151         }
152
153         stbuf_append(buf, *s);
154     }
155
156     return NULL;
157 }
158
159 static const char *parse_quote(const char *s, static_buf *buf)
160 {
161     for (; *s; s++) {
162         switch (*s) {
163           case '"':
164             stbuf_append(buf, *s);
165             return s + 1;
166
167           case '\\':
168             if (!*++s)
169                 return NULL;
170             /* fallthrough */
171
172           default:
173             stbuf_append(buf, *s);
174             break;
175         }
176     }
177
178     return NULL;
179 }
180
181 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
182
183 static const char *next_phrase(const char *s, static_buf *buf)
184 {
185     if (*s == '"') {
186         stbuf_append(buf, '"');
187         return parse_quote(s + 1, buf);
188     }
189
190     if (strchr(RFC822Specials, *s)) {
191         stbuf_append(buf, *s);
192         return s + 1;
193     }
194
195     while (*s) {
196         if (ISSPACE(*s) || strchr(RFC822Specials, *s))
197             break;
198         stbuf_append(buf, *s++);
199     }
200
201     return s;
202 }
203
204 static const char *
205 parse_mailboxdomain(const char *s, const char *nonspecial, static_buf *mbox,
206                     static_buf *comment)
207 {
208     while (*s) {
209         s = skipspaces(s);
210
211         if (!strchr(nonspecial, *s) && strchr(RFC822Specials, *s))
212             return s;
213
214         if (*s == '(') {
215             stbuf_append_sp(comment);
216             s = parse_comment(s + 1, comment);
217         } else {
218             s = next_phrase(s, mbox);
219         }
220
221         if (!s)
222             return NULL;
223     }
224
225     return s;
226 }
227
228 static const char *
229 parse_address(const char *s, static_buf *comment, address_t *cur)
230 {
231     static_buf token = {"", 0};
232
233     s = parse_mailboxdomain(s, ".\"(\\", &token, comment);
234     if (!s)
235         return NULL;
236
237     if (*s == '@') {
238         stbuf_append(&token, '@');
239         s = parse_mailboxdomain(s + 1, ".([]\\", &token, comment);
240         if (!s)
241             return NULL;
242     }
243
244     cur->mailbox = p_dupstr(token.buf, token.len);
245
246     if (comment->len && !cur->personal) {
247         cur->personal = p_dupstr(comment->buf, comment->len);
248     }
249
250     return s;
251 }
252
253 address_t **rfc822_eotoken(address_t **last, static_buf *phrase, static_buf *comment)
254 {
255     if (phrase->len) {
256         const char *s;
257         address_t *cur = address_new();
258
259         s = parse_address(phrase->buf, comment, cur);
260         if (s && *s && *s != ',' && *s != ';') {
261             address_delete(&cur);
262             return last;
263         }
264
265         *last = cur;
266         return &(*last)->next;
267     }
268
269     return last;
270 }
271
272 address_t *rfc822_parse_adrlist(address_t *top, const char *s)
273 {
274     static_buf comment = {"", 0};
275     static_buf phrase  = {"", 0};
276
277     address_t **last = address_list_last(&top);
278     int ws_pending = 0;
279
280     for (;;) {
281         ws_pending = ISSPACE(*s);
282         s = skipspaces(s);
283
284         switch (*s) {
285             address_t *cur;
286
287           default:
288             if (ws_pending)
289                 stbuf_append_sp(&phrase);
290             s = next_phrase(s, &phrase);
291             if (!s) {
292                 address_delete(&top);
293                 return NULL;
294             }
295             continue;
296
297           case '(':
298             stbuf_append_sp(&comment);
299             s = parse_comment(s + 1, &comment);
300             if (!s) {
301                 address_delete(&top);
302                 return NULL;
303             }
304             continue;
305
306
307           case '<':
308             cur = address_new();
309             if (phrase.len) {
310                 /* if we get something like "Michael R. Elkins" remove the quotes */
311                 cur->personal = rfc822_dequote_comment(&phrase);
312             }
313
314             s = parse_address(skipspaces(s + 1), &comment, cur);
315             if (!s || *s != '>' || !cur->mailbox) {
316                 address_delete(&top);
317                 address_delete(&cur);
318                 return NULL;
319             }
320
321             *last = cur;
322             last = &(*last)->next;
323             break;
324
325           case ',':
326             last = rfc822_eotoken(last, &phrase, &comment);
327             break;
328
329           case ':': /* group start */
330             *last = address_new();
331             (*last)->mailbox = p_dupstr(phrase.buf, phrase.len);
332             (*last)->group = 1;
333             last = &(*last)->next;
334             break;
335
336           case ';':
337             last = rfc822_eotoken(last, &phrase, &comment);
338             /* add group terminator */
339             *last = address_new();
340             last = &(*last)->next;
341             break;
342
343           case '\0':
344             last = rfc822_eotoken(last, &phrase, &comment);
345             return top;
346         }
347
348         comment.len = phrase.len  = 0;
349         s++;
350     }
351
352     return NULL;
353 }
354
355
356 /****************************************************************************/
357 /* Output functions                                                         */
358 /****************************************************************************/
359
360 ssize_t
361 rfc822_strcpy(char *buf, ssize_t buflen, const char *p, const char *specials)
362 {
363     if (strpbrk(p, specials)) {
364         ssize_t pos = 0;
365
366         buf[pos++] = '"';
367
368         while (*p && pos < buflen - 2) {
369             if (*p == '\\' || *p == '"') {
370                 if (pos >= buflen - 4)
371                     break;
372                 buf[pos++] = '\\';
373             }
374
375             buf[pos++] = *p++;
376         }
377
378         buf[pos++] = '"';
379         buf[pos]   = '\0';
380         return pos;
381     } else {
382         return m_strcpy(buf, buflen, p);
383     }
384 }
385
386 ssize_t rfc822_write_address_single(char *buf, ssize_t buflen,
387                                     address_t *addr, int display)
388 {
389     ssize_t pos = 0;
390
391     if (!addr)
392         return 0;
393
394     buflen--;                     /* save room for the terminal nul */
395
396     if (addr->personal) {
397         pos = rfc822_strcpy(buf, buflen, addr->personal, RFC822Specials);
398         if (pos + 2 >= buflen)
399             goto done;
400
401         buf[pos++] = ' ';
402         buf[pos++] = '<';
403     }
404
405     if (addr->mailbox) {
406         if (!display) {
407             pos += m_strcpy(buf + pos, buflen - pos, addr->mailbox);
408         } else {
409             pos += m_strcpy(buf + pos, buflen - pos, mutt_addr_for_display(addr));
410         }
411
412         if (addr->personal) {
413             if (pos + 1 >= buflen)
414                 goto done;
415             buf[pos++] = '>';
416         }
417
418         if (addr->group) {
419             if (pos + 1 >= buflen)
420                 goto done;
421             buf[pos++] = ':';
422         }
423     } else {
424         if (pos + 1 >= buflen)
425             goto done;
426         buf[pos++] = ';';
427     }
428
429   done:
430     /* no need to check for length here since we already save space at the
431        beginning of this routine */
432     buf[pos] = 0;
433     return pos;
434 }
435
436 /* note: it is assumed that `buf' is nul terminated! */
437 ssize_t
438 rfc822_write_address(char *buf, ssize_t buflen, address_t *addr, int display)
439 {
440     ssize_t pos;
441
442     buflen--;                     /* save room for the terminal nul */
443     pos = m_strnlen(buf, buflen);
444
445     if (pos) {
446         if (pos + 2 >= buflen)
447             goto done;
448
449         buf[pos++] = ',';
450         buf[pos++] = ' ';
451     }
452
453     for (; addr; addr = addr->next) {
454         pos += rfc822_write_address_single(buf + pos, buflen + 1 - pos,
455                                            addr, display);
456
457         if (!addr->group && addr->next && addr->next->mailbox) {
458             /* if there is another address, and its not a group mailbox name or
459                group terminator, add a comma to separate the addresses */
460             if (pos + 2 >= buflen)
461                 break;
462
463             buf[pos++] = ',';
464             buf[pos++] = ' ';
465         }
466     }
467
468   done:
469     buf[pos] = '\0';
470     return pos;
471 }
472