]> git.llucax.com Git - software/mutt-debian.git/blob - rfc822.c
Update and enable NNTP patch
[software/mutt-debian.git] / rfc822.c
1 /*
2  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */ 
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26
27 #ifndef TESTING
28 #include "mutt.h"
29 #else
30 #define safe_strdup strdup
31 #define safe_malloc malloc
32 #define SKIPWS(x) while(isspace(*x))x++
33 #define FREE(x) safe_free(x)
34 #define ISSPACE isspace
35 #define strfcpy(a,b,c) {if (c) {strncpy(a,b,c);a[c-1]=0;}}
36 #define LONG_STRING 1024
37 #include "rfc822.h"
38 #endif
39
40 #include "mutt_idna.h"
41
42 #define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \
43         a[(c)] = 0; } while (0)
44
45 #define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1)
46
47
48 const char RFC822Specials[] = "@.,:;<>[]\\\"()";
49 #define is_special(x) strchr(RFC822Specials,x)
50
51 int RFC822Error = 0;
52
53 /* these must defined in the same order as the numerated errors given in rfc822.h */
54 const char *RFC822Errors[] = {
55   "out of memory",
56   "mismatched parenthesis",
57   "mismatched quotes",
58   "bad route in <>",
59   "bad address in <>",
60   "bad address spec"
61 };
62
63 void rfc822_dequote_comment (char *s)
64 {
65   char *w = s;
66
67   for (; *s; s++)
68   {
69     if (*s == '\\')
70     {
71       if (!*++s)
72         break; /* error? */
73       *w++ = *s;
74     }
75     else if (*s != '\"')
76     {
77       if (w != s)
78         *w = *s;
79       w++;
80     }
81   }
82   *w = 0;
83 }
84
85 static void free_address (ADDRESS *a)
86 {
87   FREE(&a->personal);
88   FREE(&a->mailbox);
89 #ifdef EXACT_ADDRESS
90   FREE(&a->val);
91 #endif
92   FREE(&a);
93 }
94
95 int rfc822_remove_from_adrlist (ADDRESS **a, const char *mailbox)
96 {
97   ADDRESS *p, *last = NULL, *t;
98   int rv = -1;
99
100   p = *a;
101   last = NULL;
102   while (p)
103   {
104     if (ascii_strcasecmp (mailbox, p->mailbox) == 0)
105     {
106       if (last)
107         last->next = p->next;
108       else
109         (*a) = p->next;
110       t = p;
111       p = p->next;
112       free_address (t);
113       rv = 0;
114     }
115     else
116     {
117       last = p;
118       p = p->next;
119     }
120   }
121
122   return (rv);
123 }
124
125 void rfc822_free_address (ADDRESS **p)
126 {
127   ADDRESS *t;
128
129   while (*p)
130   {
131     t = *p;
132     *p = (*p)->next;
133 #ifdef EXACT_ADDRESS
134     FREE (&t->val);
135 #endif
136     FREE (&t->personal);
137     FREE (&t->mailbox);
138     FREE (&t);
139   }
140 }
141
142 static const char *
143 parse_comment (const char *s,
144                char *comment, size_t *commentlen, size_t commentmax)
145 {
146   int level = 1;
147   
148   while (*s && level)
149   {
150     if (*s == '(')
151       level++;
152     else if (*s == ')')
153     {
154       if (--level == 0)
155       {
156         s++;
157         break;
158       }
159     }
160     else if (*s == '\\')
161     {
162       if (!*++s)
163         break;
164     }
165     if (*commentlen < commentmax)
166       comment[(*commentlen)++] = *s;
167     s++;
168   }
169   if (level)
170   {
171     RFC822Error = ERR_MISMATCH_PAREN;
172     return NULL;
173   }
174   return s;
175 }
176
177 static const char *
178 parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
179 {
180   while (*s)
181   {
182     if (*tokenlen < tokenmax)
183       token[*tokenlen] = *s;
184     if (*s == '\\')
185     {
186       if (!*++s)
187         break;
188
189       if (*tokenlen < tokenmax)
190         token[*tokenlen] = *s;
191     }
192     else if (*s == '"')
193       return (s + 1);
194     (*tokenlen)++;
195     s++;
196   }
197   RFC822Error = ERR_MISMATCH_QUOTE;
198   return NULL;
199 }
200
201 static const char *
202 next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
203 {
204   if (*s == '(')
205     return (parse_comment (s + 1, token, tokenlen, tokenmax));
206   if (*s == '"')
207     return (parse_quote (s + 1, token, tokenlen, tokenmax));
208   if (is_special (*s))
209   {
210     if (*tokenlen < tokenmax)
211       token[(*tokenlen)++] = *s;
212     return (s + 1);
213   }
214   while (*s)
215   {
216     if (ISSPACE ((unsigned char) *s) || is_special (*s))
217       break;
218     if (*tokenlen < tokenmax)
219       token[(*tokenlen)++] = *s;
220     s++;
221   }
222   return s;
223 }
224
225 static const char *
226 parse_mailboxdomain (const char *s, const char *nonspecial,
227                      char *mailbox, size_t *mailboxlen, size_t mailboxmax,
228                      char *comment, size_t *commentlen, size_t commentmax)
229 {
230   const char *ps;
231
232   while (*s)
233   {
234     SKIPWS (s);
235     if (strchr (nonspecial, *s) == NULL && is_special (*s))
236       return s;
237
238     if (*s == '(')
239     {
240       if (*commentlen && *commentlen < commentmax)
241         comment[(*commentlen)++] = ' ';
242       ps = next_token (s, comment, commentlen, commentmax);
243     }
244     else
245       ps = next_token (s, mailbox, mailboxlen, mailboxmax);
246     if (!ps)
247       return NULL;
248     s = ps;
249   }
250
251   return s;
252 }
253
254 static const char *
255 parse_address (const char *s,
256                char *token, size_t *tokenlen, size_t tokenmax,
257                char *comment, size_t *commentlen, size_t commentmax,
258                ADDRESS *addr)
259 {
260   s = parse_mailboxdomain (s, ".\"(\\",
261                            token, tokenlen, tokenmax,
262                            comment, commentlen, commentmax);
263   if (!s)
264     return NULL;
265
266   if (*s == '@')
267   {
268     if (*tokenlen < tokenmax)
269       token[(*tokenlen)++] = '@';
270     s = parse_mailboxdomain (s + 1, ".([]\\",
271                              token, tokenlen, tokenmax,
272                              comment, commentlen, commentmax);
273     if (!s)
274       return NULL;
275   }
276
277   terminate_string (token, *tokenlen, tokenmax);
278   addr->mailbox = safe_strdup (token);
279
280   if (*commentlen && !addr->personal)
281   {
282     terminate_string (comment, *commentlen, commentmax);
283     addr->personal = safe_strdup (comment);
284   }
285
286   return s;
287 }
288
289 static const char *
290 parse_route_addr (const char *s,
291                   char *comment, size_t *commentlen, size_t commentmax,
292                   ADDRESS *addr)
293 {
294   char token[LONG_STRING];
295   size_t tokenlen = 0;
296
297   SKIPWS (s);
298
299   /* find the end of the route */
300   if (*s == '@')
301   {
302     while (s && *s == '@')
303     {
304       if (tokenlen < sizeof (token) - 1)
305         token[tokenlen++] = '@';
306       s = parse_mailboxdomain (s + 1, ",.\\[](", token,
307                                &tokenlen, sizeof (token) - 1,
308                                comment, commentlen, commentmax);
309     }
310     if (!s || *s != ':')
311     {
312       RFC822Error = ERR_BAD_ROUTE;
313       return NULL; /* invalid route */
314     }
315
316     if (tokenlen < sizeof (token) - 1)
317       token[tokenlen++] = ':';
318     s++;
319   }
320
321   if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL)
322     return NULL;
323
324   if (*s != '>')
325   {
326     RFC822Error = ERR_BAD_ROUTE_ADDR;
327     return NULL;
328   }
329
330   if (!addr->mailbox)
331     addr->mailbox = safe_strdup ("@");
332
333   s++;
334   return s;
335 }
336
337 static const char *
338 parse_addr_spec (const char *s,
339                  char *comment, size_t *commentlen, size_t commentmax,
340                  ADDRESS *addr)
341 {
342   char token[LONG_STRING];
343   size_t tokenlen = 0;
344
345   s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr);
346   if (s && *s && *s != ',' && *s != ';')
347   {
348     RFC822Error = ERR_BAD_ADDR_SPEC;
349     return NULL;
350   }
351   return s;
352 }
353
354 static void
355 add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase,
356               char *comment, size_t *commentlen, size_t commentmax)
357 {
358   ADDRESS *cur = rfc822_new_address ();
359   
360   if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL)
361   {
362     rfc822_free_address (&cur);
363     return;
364   }
365
366   if (*last)
367     (*last)->next = cur;
368   else
369     *top = cur;
370   *last = cur;
371 }
372
373 ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s)
374 {
375   int ws_pending, nl;
376   const char *begin, *ps;
377   char comment[LONG_STRING], phrase[LONG_STRING];
378   size_t phraselen = 0, commentlen = 0;
379   ADDRESS *cur, *last = NULL;
380   
381   RFC822Error = 0;
382
383   last = top;
384   while (last && last->next)
385     last = last->next;
386
387   ws_pending = isspace ((unsigned char) *s);
388   if ((nl = mutt_strlen (s)))
389     nl = s[nl - 1] == '\n';
390   
391   SKIPWS (s);
392   begin = s;
393   while (*s)
394   {
395     if (*s == ',')
396     {
397       if (phraselen)
398       {
399         terminate_buffer (phrase, phraselen);
400         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
401       }
402       else if (commentlen && last && !last->personal)
403       {
404         terminate_buffer (comment, commentlen);
405         last->personal = safe_strdup (comment);
406       }
407
408 #ifdef EXACT_ADDRESS
409       if (last && !last->val)
410         last->val = mutt_substrdup (begin, s);
411 #endif
412       commentlen = 0;
413       phraselen = 0;
414       s++;
415       begin = s;
416       SKIPWS (begin);
417     }
418     else if (*s == '(')
419     {
420       if (commentlen && commentlen < sizeof (comment) - 1)
421         comment[commentlen++] = ' ';
422       if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL)
423       {
424         rfc822_free_address (&top);
425         return NULL;
426       }
427       s = ps;
428     }
429     else if (*s == '"')
430     {
431       if (phraselen && phraselen < sizeof (phrase) - 1)
432         phrase[phraselen++] = ' ';
433       if ((ps = parse_quote (s + 1, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
434       {
435         rfc822_free_address (&top);
436         return NULL;
437       }
438       s = ps;
439     }
440     else if (*s == ':')
441     {
442       cur = rfc822_new_address ();
443       terminate_buffer (phrase, phraselen);
444       cur->mailbox = safe_strdup (phrase);
445       cur->group = 1;
446
447       if (last)
448         last->next = cur;
449       else
450         top = cur;
451       last = cur;
452
453 #ifdef EXACT_ADDRESS
454       last->val = mutt_substrdup (begin, s);
455 #endif
456
457       phraselen = 0;
458       commentlen = 0;
459       s++;
460       begin = s;
461       SKIPWS (begin);
462     }
463     else if (*s == ';')
464     {
465       if (phraselen)
466       {
467         terminate_buffer (phrase, phraselen);
468         add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
469       }
470       else if (commentlen && last && !last->personal)
471       {
472         terminate_buffer (comment, commentlen);
473         last->personal = safe_strdup (comment);
474       }
475 #ifdef EXACT_ADDRESS
476       if (last && !last->val)
477         last->val = mutt_substrdup (begin, s);
478 #endif
479
480       /* add group terminator */
481       cur = rfc822_new_address ();
482       if (last)
483       {
484         last->next = cur;
485         last = cur;
486       }
487
488       phraselen = 0;
489       commentlen = 0;
490       s++;
491       begin = s;
492       SKIPWS (begin);
493     }
494     else if (*s == '<')
495     {
496       terminate_buffer (phrase, phraselen);
497       cur = rfc822_new_address ();
498       if (phraselen)
499         cur->personal = safe_strdup (phrase);
500       if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL)
501       {
502         rfc822_free_address (&top);
503         rfc822_free_address (&cur);
504         return NULL;
505       }
506
507       if (last)
508         last->next = cur;
509       else
510         top = cur;
511       last = cur;
512
513       phraselen = 0;
514       commentlen = 0;
515       s = ps;
516     }
517     else
518     {
519       if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending)
520         phrase[phraselen++] = ' ';
521       if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
522       {
523         rfc822_free_address (&top);
524         return NULL;
525       }
526       s = ps;
527     }
528     ws_pending = isspace ((unsigned char) *s);
529     SKIPWS (s);
530   }
531   
532   if (phraselen)
533   {
534     terminate_buffer (phrase, phraselen);
535     terminate_buffer (comment, commentlen);
536     add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
537   }
538   else if (commentlen && last && !last->personal)
539   {
540     terminate_buffer (comment, commentlen);
541     last->personal = safe_strdup (comment);
542   }
543 #ifdef EXACT_ADDRESS
544   if (last)
545     last->val = mutt_substrdup (begin, s - nl < begin ? begin : s - nl);
546 #endif
547
548   return top;
549 }
550
551 void rfc822_qualify (ADDRESS *addr, const char *host)
552 {
553   char *p;
554
555   for (; addr; addr = addr->next)
556     if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL)
557     {
558       p = safe_malloc (mutt_strlen (addr->mailbox) + mutt_strlen (host) + 2);
559       sprintf (p, "%s@%s", addr->mailbox, host);        /* __SPRINTF_CHECKED__ */
560       FREE (&addr->mailbox);
561       addr->mailbox = p;
562     }
563 }
564
565 void
566 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
567 {
568   if (strpbrk (value, specials))
569   {
570     char tmp[256], *pc = tmp;
571     size_t tmplen = sizeof (tmp) - 3;
572
573     *pc++ = '"';
574     for (; *value && tmplen > 1; value++)
575     {
576       if (*value == '\\' || *value == '"')
577       {
578         *pc++ = '\\';
579         tmplen--;
580       }
581       *pc++ = *value;
582       tmplen--;
583     }
584     *pc++ = '"';
585     *pc = 0;
586     strfcpy (buf, tmp, buflen);
587   }
588   else
589     strfcpy (buf, value, buflen);
590 }
591
592 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr,
593                                   int display)
594 {
595   size_t len;
596   char *pbuf = buf;
597   char *pc;
598   
599   if (!addr)
600     return;
601
602   buflen--; /* save room for the terminal nul */
603
604 #ifdef EXACT_ADDRESS
605   if (addr->val)
606   {
607     if (!buflen)
608       goto done;
609     strfcpy (pbuf, addr->val, buflen);
610     len = mutt_strlen (pbuf);
611     pbuf += len;
612     buflen -= len;
613     if (addr->group)
614     {
615       if (!buflen)
616         goto done;
617       *pbuf++ = ':';
618       buflen--;
619       *pbuf = 0;
620     }
621     return;
622   }
623 #endif
624
625   if (addr->personal)
626   {
627     if (strpbrk (addr->personal, RFC822Specials))
628     {
629       if (!buflen)
630         goto done;
631       *pbuf++ = '"';
632       buflen--;
633       for (pc = addr->personal; *pc && buflen > 0; pc++)
634       {
635         if (*pc == '"' || *pc == '\\')
636         {
637           *pbuf++ = '\\';
638           buflen--;
639         }
640         if (!buflen)
641           goto done;
642         *pbuf++ = *pc;
643         buflen--;
644       }
645       if (!buflen)
646         goto done;
647       *pbuf++ = '"';
648       buflen--;
649     }
650     else
651     {
652       if (!buflen)
653         goto done;
654       strfcpy (pbuf, addr->personal, buflen);
655       len = mutt_strlen (pbuf);
656       pbuf += len;
657       buflen -= len;
658     }
659
660     if (!buflen)
661       goto done;
662     *pbuf++ = ' ';
663     buflen--;
664   }
665
666   if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
667   {
668     if (!buflen)
669       goto done;
670     *pbuf++ = '<';
671     buflen--;
672   }
673
674   if (addr->mailbox)
675   {
676     if (!buflen)
677       goto done;
678     if (ascii_strcmp (addr->mailbox, "@") && !display)
679     {
680       strfcpy (pbuf, addr->mailbox, buflen);
681       len = mutt_strlen (pbuf);
682     }
683     else if (ascii_strcmp (addr->mailbox, "@") && display)
684     {
685       strfcpy (pbuf, mutt_addr_for_display (addr), buflen);
686       len = mutt_strlen (pbuf);
687     }
688     else
689     {
690       *pbuf = '\0';
691       len = 0;
692     }
693     pbuf += len;
694     buflen -= len;
695
696     if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
697     {
698       if (!buflen)
699         goto done;
700       *pbuf++ = '>';
701       buflen--;
702     }
703
704     if (addr->group)
705     {
706       if (!buflen)
707         goto done;
708       *pbuf++ = ':';
709       buflen--;
710       if (!buflen)
711         goto done;
712       *pbuf++ = ' ';
713       buflen--;
714     }
715   }
716   else
717   {
718     if (!buflen)
719       goto done;
720     *pbuf++ = ';';
721     buflen--;
722   }
723 done:
724   /* no need to check for length here since we already save space at the
725      beginning of this routine */
726   *pbuf = 0;
727 }
728
729 /* note: it is assumed that `buf' is nul terminated! */
730 int rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr, int display)
731 {
732   char *pbuf = buf;
733   size_t len = mutt_strlen (buf);
734   
735   buflen--; /* save room for the terminal nul */
736
737   if (len > 0)
738   {
739     if (len > buflen)
740       return pbuf - buf; /* safety check for bogus arguments */
741
742     pbuf += len;
743     buflen -= len;
744     if (!buflen)
745       goto done;
746     *pbuf++ = ',';
747     buflen--;
748     if (!buflen)
749       goto done;
750     *pbuf++ = ' ';
751     buflen--;
752   }
753
754   for (; addr && buflen > 0; addr = addr->next)
755   {
756     /* use buflen+1 here because we already saved space for the trailing
757        nul char, and the subroutine can make use of it */
758     rfc822_write_address_single (pbuf, buflen + 1, addr, display);
759
760     /* this should be safe since we always have at least 1 char passed into
761        the above call, which means `pbuf' should always be nul terminated */
762     len = mutt_strlen (pbuf);
763     pbuf += len;
764     buflen -= len;
765
766     /* if there is another address, and its not a group mailbox name or
767        group terminator, add a comma to separate the addresses */
768     if (addr->next && addr->next->mailbox && !addr->group)
769     {
770       if (!buflen)
771         goto done;
772       *pbuf++ = ',';
773       buflen--;
774       if (!buflen)
775         goto done;
776       *pbuf++ = ' ';
777       buflen--;
778     }
779   }
780 done:
781   *pbuf = 0;
782   return pbuf - buf;
783 }
784
785 /* this should be rfc822_cpy_adr */
786 ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr)
787 {
788   ADDRESS *p = rfc822_new_address ();
789
790 #ifdef EXACT_ADDRESS
791   p->val = safe_strdup (addr->val);
792 #endif
793   p->personal = safe_strdup (addr->personal);
794   p->mailbox = safe_strdup (addr->mailbox);
795   p->group = addr->group;
796   return p;
797 }
798
799 /* this should be rfc822_cpy_adrlist */
800 ADDRESS *rfc822_cpy_adr (ADDRESS *addr, int prune)
801 {
802   ADDRESS *top = NULL, *last = NULL;
803   
804   for (; addr; addr = addr->next)
805   {
806     if (prune && addr->group && (!addr->next || !addr->next->mailbox))
807     {
808       addr = addr->next;
809       continue;
810     }
811     if (last)
812     {
813       last->next = rfc822_cpy_adr_real (addr);
814       last = last->next;
815     }
816     else
817       top = last = rfc822_cpy_adr_real (addr);
818   }
819   return top;
820 }
821
822 /* append list 'b' to list 'a' and return the last element in the new list */
823 ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b, int prune)
824 {
825   ADDRESS *tmp = *a;
826
827   while (tmp && tmp->next)
828     tmp = tmp->next;
829   if (!b)
830     return tmp;
831   if (tmp)
832     tmp->next = rfc822_cpy_adr (b, prune);
833   else
834     tmp = *a = rfc822_cpy_adr (b, prune);
835   while (tmp && tmp->next)
836     tmp = tmp->next;
837   return tmp;
838 }
839
840 /* incomplete. Only used to thwart the APOP MD5 attack (#2846). */
841 int rfc822_valid_msgid (const char *msgid)
842 {
843   /* msg-id         = "<" addr-spec ">"
844    * addr-spec      = local-part "@" domain
845    * local-part     = word *("." word)
846    * word           = atom / quoted-string
847    * atom           = 1*<any CHAR except specials, SPACE and CTLs>
848    * CHAR           = ( 0.-127. )
849    * specials       = "(" / ")" / "<" / ">" / "@"
850                     / "," / ";" / ":" / "\" / <">
851                     / "." / "[" / "]"
852    * SPACE          = ( 32. )
853    * CTLS           = ( 0.-31., 127.)
854    * quoted-string  = <"> *(qtext/quoted-pair) <">
855    * qtext          = <any CHAR except <">, "\" and CR>
856    * CR             = ( 13. )
857    * quoted-pair    = "\" CHAR
858    * domain         = sub-domain *("." sub-domain)
859    * sub-domain     = domain-ref / domain-literal
860    * domain-ref     = atom
861    * domain-literal = "[" *(dtext / quoted-pair) "]"
862    */
863
864   unsigned int l, i;
865
866   if (!msgid || !*msgid)
867     return -1;
868
869   l = mutt_strlen (msgid);
870   if (l < 5) /* <atom@atom> */
871     return -1;
872   if (msgid[0] != '<' || msgid[l-1] != '>')
873     return -1;
874   if (!(strrchr (msgid, '@')))
875     return -1;
876
877   /* TODO: complete parser */
878   for (i = 0; i < l; i++)
879     if ((unsigned char)msgid[i] > 127)
880       return -1;
881
882   return 0;
883 }
884
885 #ifdef TESTING
886 int safe_free (void **p)        /* __SAFE_FREE_CHECKED__ */
887 {
888   free(*p);             /* __MEM_CHECKED__ */
889   *p = 0;
890 }
891
892 int main (int argc, char **argv)
893 {
894   ADDRESS *list;
895   char buf[256];
896 # if 0
897   char *str = "michael, Michael Elkins <me@mutt.org>, testing a really complex address: this example <@contains.a.source.route,@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)";
898 # else
899   char *str = "a b c ";
900 # endif
901   
902   list = rfc822_parse_adrlist (NULL, str);
903   buf[0] = 0;
904   rfc822_write_address (buf, sizeof (buf), list);
905   rfc822_free_address (&list);
906   puts (buf);
907   exit (0);
908 }
909 #endif