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