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