]> mj.ucw.cz Git - checkmail.git/blob - rfc822.c
Added sort order option
[checkmail.git] / rfc822.c
1 /*
2  * This code has been adapted from Mutt 1.5.16 which is
3  *
4  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
5  *
6  *     This program is free software; you can redistribute it and/or modify
7  *     it under the terms of the GNU General Public License as published by
8  *     the Free Software Foundation; either version 2 of the License, or
9  *     (at your option) any later version.
10  *
11  *     This program is distributed in the hope that it will be useful,
12  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *     GNU General Public License for more details.
15  *
16  *     You should have received a copy of the GNU General Public License
17  *     along with this program; if not, write to the Free Software
18  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 #include <string.h>
22 #include <ctype.h>
23 #include <stdlib.h>
24
25 #define safe_strdup xstrdup
26 #define safe_malloc xmalloc
27 #define SKIPWS(x) while(isspace(*x))x++
28 #define FREE(x) free(*(x)), *(x) = NULL
29 #define ISSPACE isspace
30 #define strfcpy(a,b,c) {if (c) {strncpy(a,b,c);a[c-1]=0;}}
31 #define STRING 128
32
33 #include "util.h"
34 #include "rfc822.h"
35
36 #define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \
37         a[(c)] = 0; } while (0)
38
39 #define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1)
40
41 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
42 #define is_special(x) strchr(RFC822Specials,x)
43
44 int RFC822Error = 0;
45
46 /* these must defined in the same order as the numerated errors given in rfc822.h */
47 const char *RFC822Errors[] = {
48   "out of memory",
49   "mismatched parenthesis",
50   "mismatched quotes",
51   "bad route in <>",
52   "bad address in <>",
53   "bad address spec"
54 };
55
56 static void rfc822_dequote_comment (char *s)
57 {
58   char *w = s;
59
60   for (; *s; s++)
61   {
62     if (*s == '\\')
63     {
64       if (!*++s)
65         break; /* error? */
66       *w++ = *s;
67     }
68     else if (*s != '\"')
69     {
70       if (w != s)
71         *w = *s;
72       w++;
73     }
74   }
75   *w = 0;
76 }
77
78 void rfc822_free_address (ADDRESS **p)
79 {
80   ADDRESS *t;
81
82   while (*p)
83   {
84     t = *p;
85     *p = (*p)->next;
86     FREE (&t->personal);
87     FREE (&t->mailbox);
88     FREE (&t);
89   }
90 }
91
92 static const char *
93 parse_comment (const char *s,
94                char *comment, size_t *commentlen, size_t commentmax)
95 {
96   int level = 1;
97
98   while (*s && level)
99   {
100     if (*s == '(')
101       level++;
102     else if (*s == ')')
103     {
104       if (--level == 0)
105       {
106         s++;
107         break;
108       }
109     }
110     else if (*s == '\\')
111     {
112       if (!*++s)
113         break;
114     }
115     if (*commentlen < commentmax)
116       comment[(*commentlen)++] = *s;
117     s++;
118   }
119   if (level)
120   {
121     RFC822Error = ERR_MISMATCH_PAREN;
122     return NULL;
123   }
124   return s;
125 }
126
127 static const char *
128 parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
129 {
130   if (*tokenlen < tokenmax)
131     token[(*tokenlen)++] = '"';
132   while (*s)
133   {
134     if (*tokenlen < tokenmax)
135       token[*tokenlen] = *s;
136     if (*s == '"')
137     {
138       (*tokenlen)++;
139       return (s + 1);
140     }
141     if (*s == '\\')
142     {
143       if (!*++s)
144         break;
145
146       if (*tokenlen < tokenmax)
147         token[*tokenlen] = *s;
148     }
149     (*tokenlen)++;
150     s++;
151   }
152   RFC822Error = ERR_MISMATCH_QUOTE;
153   return NULL;
154 }
155
156 static const char *
157 next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
158 {
159   if (*s == '(')
160     return (parse_comment (s + 1, token, tokenlen, tokenmax));
161   if (*s == '"')
162     return (parse_quote (s + 1, token, tokenlen, tokenmax));
163   if (is_special (*s))
164   {
165     if (*tokenlen < tokenmax)
166       token[(*tokenlen)++] = *s;
167     return (s + 1);
168   }
169   while (*s)
170   {
171     if (ISSPACE ((unsigned char) *s) || is_special (*s))
172       break;
173     if (*tokenlen < tokenmax)
174       token[(*tokenlen)++] = *s;
175     s++;
176   }
177   return s;
178 }
179
180 static const char *
181 parse_mailboxdomain (const char *s, const char *nonspecial,
182                      char *mailbox, size_t *mailboxlen, size_t mailboxmax,
183                      char *comment, size_t *commentlen, size_t commentmax)
184 {
185   const char *ps;
186
187   while (*s)
188   {
189     SKIPWS (s);
190     if (strchr (nonspecial, *s) == NULL && is_special (*s))
191       return s;
192
193     if (*s == '(')
194     {
195       if (*commentlen && *commentlen < commentmax)
196         comment[(*commentlen)++] = ' ';
197       ps = next_token (s, comment, commentlen, commentmax);
198     }
199     else
200       ps = next_token (s, mailbox, mailboxlen, mailboxmax);
201     if (!ps)
202       return NULL;
203     s = ps;
204   }
205
206   return s;
207 }
208
209 static const char *
210 parse_address (const char *s,
211                char *token, size_t *tokenlen, size_t tokenmax,
212                char *comment, size_t *commentlen, size_t commentmax,
213                ADDRESS *addr)
214 {
215   s = parse_mailboxdomain (s, ".\"(\\",
216                            token, tokenlen, tokenmax,
217                            comment, commentlen, commentmax);
218   if (!s)
219     return NULL;
220
221   if (*s == '@')
222   {
223     if (*tokenlen < tokenmax)
224       token[(*tokenlen)++] = '@';
225     s = parse_mailboxdomain (s + 1, ".([]\\",
226                              token, tokenlen, tokenmax,
227                              comment, commentlen, commentmax);
228     if (!s)
229       return NULL;
230   }
231
232   terminate_string (token, *tokenlen, tokenmax);
233   addr->mailbox = safe_strdup (token);
234
235   if (*commentlen && !addr->personal)
236   {
237     terminate_string (comment, *commentlen, commentmax);
238     addr->personal = safe_strdup (comment);
239   }
240
241   return s;
242 }
243
244 static const char *
245 parse_route_addr (const char *s,
246                   char *comment, size_t *commentlen, size_t commentmax,
247                   ADDRESS *addr)
248 {
249   char token[STRING];
250   size_t tokenlen = 0;
251
252   SKIPWS (s);
253
254   /* find the end of the route */
255   if (*s == '@')
256   {
257     while (s && *s == '@')
258     {
259       if (tokenlen < sizeof (token) - 1)
260         token[tokenlen++] = '@';
261       s = parse_mailboxdomain (s + 1, ",.\\[](", token,
262                                &tokenlen, sizeof (token) - 1,
263                                comment, commentlen, commentmax);
264     }
265     if (!s || *s != ':')
266     {
267       RFC822Error = ERR_BAD_ROUTE;
268       return NULL; /* invalid route */
269     }
270
271     if (tokenlen < sizeof (token) - 1)
272       token[tokenlen++] = ':';
273     s++;
274   }
275
276   if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL)
277     return NULL;
278
279   if (*s != '>')
280   {
281     RFC822Error = ERR_BAD_ROUTE_ADDR;
282     return NULL;
283   }
284
285   if (!addr->mailbox)
286     addr->mailbox = safe_strdup ("@");
287
288   s++;
289   return s;
290 }
291
292 static const char *
293 parse_addr_spec (const char *s,
294                  char *comment, size_t *commentlen, size_t commentmax,
295                  ADDRESS *addr)
296 {
297   char token[STRING];
298   size_t tokenlen = 0;
299
300   s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr);
301   if (s && *s && *s != ',' && *s != ';')
302   {
303     RFC822Error = ERR_BAD_ADDR_SPEC;
304     return NULL;
305   }
306   return s;
307 }
308
309 static void
310 add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase,
311               char *comment, size_t *commentlen, size_t commentmax)
312 {
313   ADDRESS *cur = rfc822_new_address ();
314
315   if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL)
316   {
317     rfc822_free_address (&cur);
318     return;
319   }
320
321   if (*last)
322     (*last)->next = cur;
323   else
324     *top = cur;
325   *last = cur;
326 }
327
328 ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s)
329 {
330   int ws_pending;
331   const char *begin, *ps;
332   char comment[STRING], phrase[STRING];
333   size_t phraselen = 0, commentlen = 0;
334   ADDRESS *cur, *last = NULL;
335
336   RFC822Error = 0;
337
338   last = top;
339   while (last && last->next)
340     last = last->next;
341
342   ws_pending = isspace ((unsigned char) *s);
343
344   SKIPWS (s);
345   begin = s;
346   while (*s)
347   {
348     if (*s == ',')
349     {
350       if (phraselen)
351       {
352         terminate_buffer (phrase, phraselen);
353         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
354       }
355       else if (commentlen && last && !last->personal)
356       {
357         terminate_buffer (comment, commentlen);
358         last->personal = safe_strdup (comment);
359       }
360
361       commentlen = 0;
362       phraselen = 0;
363       s++;
364       begin = s;
365       SKIPWS (begin);
366     }
367     else if (*s == '(')
368     {
369       if (commentlen && commentlen < sizeof (comment) - 1)
370         comment[commentlen++] = ' ';
371       if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL)
372       {
373         rfc822_free_address (&top);
374         return NULL;
375       }
376       s = ps;
377     }
378     else if (*s == ':')
379     {
380       cur = rfc822_new_address ();
381       terminate_buffer (phrase, phraselen);
382       cur->mailbox = safe_strdup (phrase);
383       cur->group = 1;
384
385       if (last)
386         last->next = cur;
387       else
388         top = cur;
389       last = cur;
390
391       phraselen = 0;
392       commentlen = 0;
393       s++;
394       begin = s;
395       SKIPWS (begin);
396     }
397     else if (*s == ';')
398     {
399       if (phraselen)
400       {
401         terminate_buffer (phrase, phraselen);
402         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
403       }
404       else if (commentlen && last && !last->personal)
405       {
406         terminate_buffer (comment, commentlen);
407         last->personal = safe_strdup (comment);
408       }
409
410       /* add group terminator */
411       cur = rfc822_new_address ();
412       if (last)
413       {
414         last->next = cur;
415         last = cur;
416       }
417
418       phraselen = 0;
419       commentlen = 0;
420       s++;
421       begin = s;
422       SKIPWS (begin);
423     }
424     else if (*s == '<')
425     {
426       terminate_buffer (phrase, phraselen);
427       cur = rfc822_new_address ();
428       if (phraselen)
429       {
430         if (cur->personal)
431           FREE (&cur->personal);
432         /* if we get something like "Michael R. Elkins" remove the quotes */
433         rfc822_dequote_comment (phrase);
434         cur->personal = safe_strdup (phrase);
435       }
436       if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL)
437       {
438         rfc822_free_address (&top);
439         rfc822_free_address (&cur);
440         return NULL;
441       }
442
443       if (last)
444         last->next = cur;
445       else
446         top = cur;
447       last = cur;
448
449       phraselen = 0;
450       commentlen = 0;
451       s = ps;
452     }
453     else
454     {
455       if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
456         phrase[phraselen++] = ' ';
457       if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
458       {
459         rfc822_free_address (&top);
460         return NULL;
461       }
462       s = ps;
463     }
464     ws_pending = isspace ((unsigned char) *s);
465     SKIPWS (s);
466   }
467
468   if (phraselen)
469   {
470     terminate_buffer (phrase, phraselen);
471     terminate_buffer (comment, commentlen);
472     add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
473   }
474   else if (commentlen && last && !last->personal)
475   {
476     terminate_buffer (comment, commentlen);
477     last->personal = safe_strdup (comment);
478   }
479
480   return top;
481 }