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