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