home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / mm / mm-ccmd-0.91.tar.Z / mm-ccmd-0.91.tar / work / mm / token.c < prev    next >
C/C++ Source or Header  |  1990-12-18  |  16KB  |  869 lines

  1. /*
  2.  * Copyright (c) 1986, 1990 by The Trustees of Columbia University in
  3.  * the City of New York.  Permission is granted to any individual or
  4.  * institution to use, copy, or redistribute this software so long as it
  5.  * is not sold for profit, provided this copyright notice is retained.
  6.  */
  7.  
  8. #ifndef lint
  9. static char *rcsid = "$Header: /f/src2/encore.bin/cucca/mm/tarring-it-up/RCS/token.c,v 2.1 90/10/04 18:26:55 melissa Exp $";
  10. #endif
  11.  
  12. /*
  13.  * token.c - rudimentary RFC822 address parsing
  14.  */
  15.  
  16. #define MAIL11                /* for VMS type addresses */
  17. #define ALLOWDOT            /* allow dots in some extra places */
  18. /* #define NOTMM */            /* use to get rid of MM dependencies */
  19.  
  20. #include <stdio.h>
  21. #include "token.h"
  22. #define _CHARTYPE_ARRAY_        /* necessary for chartype.h */
  23. #ifdef NOTMM
  24. #include "chartype.h"
  25. #else
  26. #include "mm.h"
  27. #include "address.h"
  28. #endif /* NOTMM */
  29. /*
  30.  * token struct used for parsing rfc822 address lists
  31.  */
  32.  
  33. typedef struct token {
  34.     unsigned char type;            /* token type */
  35.     unsigned char ptype;        /* parse type */
  36.     unsigned short len;            /* token string length */
  37.     unsigned short plen;        /* parse type length in tokens */
  38.     short clen;                /* length of following comment */
  39.     char *text;                /* pointer to static text */
  40.     char *ctext;            /* text of following comment */
  41.     struct token *next;            /* pointer to next token */
  42. } token;
  43.  
  44. #ifdef TEST
  45. #define add_addresslist(a,b,c)
  46. #endif
  47. /*
  48.  * test for whether we're looking at whitespace -- should be a macro
  49.  */
  50.  
  51. folding (s)
  52. char *s;
  53. {
  54.     while (isblank (*s))        /* skip tabs, spaces */
  55.     ++s;
  56.     if (*s=='\r')
  57.     ++s;
  58.     if (*s == '\n' && isblank (*++s))    /* newline followed by whitespace? */
  59.     return 1;            /* yes, line continuation */
  60.     return 0;
  61. }
  62.  
  63. /*
  64.  * given a pointer to text after an opening '(', returns a pointer to the
  65.  * character following the matching ')' (or a null if the comment wasn't
  66.  * terminated properly).
  67.  */
  68.  
  69. char *
  70. eatcomment(s)
  71. char *s;
  72. {
  73.     int parencount = 1;
  74.  
  75.     if (*s == '(') ++s;
  76.  
  77.     while (*s) {
  78.     switch (*s) {
  79.       case '\\':
  80.         if (*++s) break;        /* skip unless null */
  81.         return s;
  82.       case '(':
  83.         ++parencount;        /* parens nest */
  84.         break;
  85.       case ')':
  86.         if (--parencount == 0)
  87.         return ++s;
  88.         break;
  89.       case '\r':
  90.       case '\n':            /* check for continuation */
  91.         if (!folding (s))
  92.         return s;        /* bad continuation line */
  93.         break;
  94.       default:
  95.         if (iscntrl(*s))
  96.         return s;        /* return pointer to null */
  97.     }
  98.     ++s;
  99.     }
  100.     return s;                /* return pointer to null */
  101. }
  102.  
  103. /*
  104.  * given a pointer to an opening '"', return a pointer to the char following
  105.  * the closing '"'.
  106.  */
  107.  
  108. char *
  109. eatqst(s)
  110. char *s;
  111. {
  112.     while (*++s) {
  113.     switch (*s) {
  114.       case '\\':
  115.         ++s;
  116.         break;
  117.       case '"':
  118.         return ++s;
  119.       case '\n':
  120.         if (!isblank(s[1]))
  121.         return s;        /* not a continuation line */
  122.         break;
  123.       case '\0':
  124.         return s;
  125.     }
  126.     }
  127. }
  128.  
  129. /*
  130.  * allocate a token struct and fill in the type and value
  131.  */
  132.  
  133. token *
  134. alloc_token (type, text, len)
  135. int type, len;
  136. char *text;
  137. {
  138.     register token *t;
  139.     char * calloc ();
  140.  
  141.     if (t = ((token *) calloc (1, sizeof (token)))) {
  142.     t->type = type;
  143.     t->text = text;
  144.     t->len = len;
  145.     }
  146.     return t;
  147. }
  148.  
  149. /*
  150.  * break a null-terminated string into a list of tokens and return it.
  151.  * basically what you'd expect, except that any paren-delimited comment
  152.  * string is attached as a unit to the previous token, if any.  Such
  153.  * comments are lost if they appear before any  "significant" tokens, but
  154.  * I don't think that's much to worry about.
  155.  */
  156.  
  157. token *
  158. lex (s)
  159. char *s;
  160. {
  161.     register char *p = NULL;
  162.     token head, *tail, *t = NULL;
  163.     
  164.     tail = head.next = &head;
  165.  
  166.     while (*s && tail) {
  167.     while (isblank (*s)) {
  168.         do ++s; while (isblank (*s));
  169.         if (*s == '\n') {
  170.         if (folding (++s))
  171.             continue;
  172.         else {
  173.             /*
  174.              * data pointer in T_EOH token points to unparsed text
  175.              */
  176.             tail->next = alloc_token (T_EOH, s, 0);
  177.             tail = tail->next;
  178.             tail->next = NULL;
  179.             return head.next;
  180.         }
  181.         }
  182.     }
  183.  
  184.     if (!*s && (p == 0))
  185.         return NULL;
  186.  
  187.     p = s;                /* save start of string */
  188.     if (isatom (*s)
  189. #ifdef ALLOWDOT
  190.            || *s == '.'
  191. #endif
  192.         ) {
  193.         s++;
  194.         while (isatom (*s)
  195. #ifdef ALLOWDOT
  196.            || *s == '.'
  197. #endif
  198.            )
  199.         s++;
  200.         t = alloc_token (T_ATOM, p, (int) (s-p));
  201.     }    
  202.     else if (isspecial(*s)) {
  203.         switch (*s) {
  204.           case '(':
  205.         s = eatcomment(s);
  206.         tail->ctext = p;
  207.         tail->clen = (s - p);
  208.         continue;
  209.           case ')':
  210.         t = alloc_token (T_RPAREN, s, 1);
  211.         break;
  212.           case '<':
  213.         t = alloc_token (T_LROUTE, s, 1);
  214.         break;
  215.           case '>':
  216.         t = alloc_token (T_RROUTE, s, 1);
  217.         break;
  218.           case '@':
  219.         t = alloc_token (T_AT, s, 1);
  220.         break;
  221.           case ',':
  222.         if (tail->type == T_COMMA) {
  223.             ++s;        /* multiple commas are allowed, but */
  224.             continue;        /*  aren't meaningful */
  225.         }
  226.         t = alloc_token (T_COMMA, s, 1);
  227.         break;
  228.           case ';':
  229.         t = alloc_token (T_SEMI, s, 1);
  230.         break;
  231.           case ':':
  232. #ifdef MAIL11
  233.         if (*(s+1) == ':') {
  234.             t = alloc_token(T_COLCOL, s, 2);
  235.             s++;
  236.         }
  237.         else
  238. #endif
  239.             t = alloc_token (T_COLON, s, 1);
  240.         break;
  241.           case '\\':
  242.         if (*++s)
  243.             t = alloc_token (T_QPAIR, p, 2);
  244.         else
  245.             return NULL;
  246.         break;
  247.           case '"':
  248.         s = eatqst (s);
  249.         t = alloc_token (T_QSTR, p, (int) (s-p));
  250.         --s;
  251.         break;
  252.           case '.':
  253.         t = alloc_token (T_DOT, s, 1);
  254.         break;
  255.           case '[':
  256.         t = alloc_token (T_LDOMLIT, s, 1);
  257.         break;
  258.           case ']':
  259.         t = alloc_token (T_RDOMLIT, s, 1);
  260.         }
  261.         ++s;
  262.     }
  263.     else if (*s) ++s;        /* ignore the character */
  264.  
  265.     tail = (tail->next = t);
  266.     }
  267.     if (tail)
  268.     tail->next = NULL;
  269.     return head.next;
  270. }
  271.  
  272. /*
  273.  * advance a pointer along a token chain - should probably be a macro
  274.  */
  275.  
  276. token *
  277. advance (t, n)
  278. token *t;
  279. int n;
  280. {
  281. #if TEST > 1
  282.     char *untoken ();
  283.     char *p = untoken (t, n, ' ', 0);
  284.     if (p) {
  285.     printf ("advancing past %d tokens '%s'\n", n, p);
  286.     free (p);
  287.     }
  288. #endif
  289.     while (t && (n-- > 0))
  290.     t = t->next;
  291.     return t;
  292. }
  293.  
  294. /*
  295.  * turn a token list back into an ascii string.  the string returned should
  296.  * be released with free().
  297.  */
  298.  
  299. char *
  300. untoken (t, n, stripcomments, dofree)
  301. token *t;
  302. int n, stripcomments, dofree;
  303. {
  304.     char *p, *cp, *malloc ();
  305.     int i, len = 0;
  306.     token *head = t;
  307.  
  308.     for (i = 0; t && (i < n); i++, t = t->next) {
  309.     len += t->len;
  310.     if (t->clen && !stripcomments)
  311.         len += t->clen + 1;
  312.     }
  313.  
  314.     len += n;                /* count delimiters, trailing null */
  315.  
  316.     p = cp = malloc (len+1);        /* get the space */
  317.     if (p) {
  318.     for (t = head, i = n; t && (i > 0); i--) {
  319.         strncpy (cp, t->text, t->len);
  320.         cp += t->len;
  321.         if (t->clen && !stripcomments) {
  322.         *cp = ' ';
  323.         strncpy (++cp, t->ctext, t->clen);
  324.         cp += t->clen;
  325.         if (t->next && isspace(t->ctext[t->clen]))
  326.             *cp++ = ' ';
  327.         }
  328.         else
  329.         if (!stripcomments && t->next && isblank(t->text[t->len]))
  330.             *cp++ = ' ';
  331.         if (dofree) {
  332.         token *old = t;
  333.         t = t->next;
  334.         free (old);
  335.         }
  336.         else
  337.         t = t->next;
  338.     }        
  339.     *cp = 0;
  340.     }
  341.     return p;
  342. }
  343.  
  344. /*
  345.  * are we looking at an RFC822 "phrase"?
  346.  */
  347.  
  348. int
  349. phrase (t)
  350. token *t;
  351. {
  352.     int len = 0;
  353.     while (t && (t->type == T_QSTR || t->type == T_ATOM)) {
  354.     t = advance (t, 1);
  355.     ++len;
  356.     }
  357.     return len;
  358. }
  359.  
  360. /*
  361.  * try to parse a domain name (one or more dot-delimited atoms)
  362.  */
  363.  
  364. int
  365. domain (t)
  366. token *t;
  367. {
  368.     int n = 0, needdot = 0;
  369.  
  370.     if (t && (t->type == T_LDOMLIT))
  371.     return domlit (t);
  372.  
  373.     while (t) {
  374.     if ((needdot && t->type == T_DOT) ||
  375.         (!needdot && t->type == T_ATOM))
  376.         needdot = ~needdot;
  377.     else
  378.         break;
  379.  
  380.     t = advance (t, 1);
  381.     ++n;
  382.     }        
  383.     if (n && !needdot)
  384.     --n;
  385.     return n;
  386. }
  387.  
  388. /*
  389.  * parse a domain literal, e.g. "[128.59.16.20]"
  390.  * the argument must be a pointer to a "[" token.
  391.  */ 
  392.  
  393. int
  394. domlit (t)
  395. token *t;
  396. {
  397.     int n;
  398.  
  399.     if (t->type != T_LDOMLIT || (t = t->next) == NULL)
  400.     return 0;
  401.  
  402.     n = domain (t);
  403.     t = advance (t, n);
  404.     if (t && t->type == T_RDOMLIT)
  405.     return n + 2;
  406.     return 0;
  407. }
  408.  
  409. /*
  410.  * parse a "local-part" of an rfc822 mailbox, consisting of a dot-delimited
  411.  * list of quoted-strings and/or atoms.
  412.  */
  413.  
  414. int
  415. localpart (t)
  416. token *t;
  417. {
  418.     int n = 0, needdot = 0;
  419.  
  420.     while (t) {
  421.     if ((needdot && t->type == T_DOT) ||
  422.         (!needdot && (t->type == T_QSTR || t->type == T_ATOM))) {
  423.         needdot = ~needdot;
  424.         n++;
  425.     }
  426.     else
  427.         break;
  428.     t = t->next;
  429.     }        
  430.     if (n && !needdot)
  431.     --n;                /* don't swallow trailing dot */
  432.     return n;
  433. }
  434.  
  435. /*
  436.  * parse an RFC822 addr-spec -- "localpart@domain"
  437.  */
  438.  
  439. int
  440. addrspec (t)
  441. token *t;
  442. {
  443.     token *head = t;
  444.     int n, len = 0;
  445.  
  446.     if (!t)
  447.     return 0;
  448.  
  449.     len += (n = localpart (t));
  450.     if (n == 0)
  451.     return 0;
  452.     t = advance (t, n);
  453.     if (t && t->type == T_AT) {
  454.     len += 1;
  455.     if (t = advance (t, 1)) {
  456.         if (n = domain (t))
  457.         len += n;
  458.         else if (n = domlit (t))
  459.         len += n;
  460.     }
  461.     else
  462.         return 0;
  463.     }
  464.     head->ptype = T_ADDRSPEC;
  465.     head->plen = len;
  466.     return len;
  467. }
  468.  
  469. /*
  470.  * parse a route, e.g. "@domain,...@domain:"
  471.  */
  472.  
  473. int
  474. route (t)
  475. token *t;
  476. {
  477.     int n = 0, len = 0;
  478.  
  479.     while (t && t->type == T_AT && (t = advance (t, 1))) {
  480.     if ((n = domain (t)) || (n = domlit (t))) {
  481.         t = advance (t, n);
  482.         len += n + 2;        /* commit to next token */
  483.         if (t->type == T_COLON)
  484.         return len;
  485.         else if (t->type == T_COMMA) {
  486.         t = advance (t, 1);
  487.         continue;
  488.         }
  489.         break;
  490.     }
  491.     }
  492.     return 0;
  493. }
  494.  
  495. /*
  496.  * parse a routeaddr -- "<@domain,...,domain:localpart@domain>"
  497.  */
  498.  
  499. int
  500. routeaddr (t)
  501. token *t;
  502. {
  503.     int n, len = 1;
  504.  
  505.     if (t) {
  506.     if (t->type != T_LROUTE)
  507.         return 0;
  508.     if ((t = t->next) == NULL)
  509.         return 0;
  510.     if (t->type == T_AT) {
  511.         len += (n = route (t));
  512.         if (n == 0 || ((t = advance (t, n)) == NULL))
  513.         return 0;
  514.     }
  515.     len += (n = addrspec (t));
  516.     
  517.     if (n == 0 || ((t = advance (t, n)) == NULL))
  518.         return 0;
  519.     if (t->type == T_RROUTE)
  520.         return (len + 1);
  521.     }
  522.     return 0;
  523. }
  524.  
  525. int
  526. group (t)
  527. token *t;
  528. {
  529.     int n, len = 0;
  530.     token *tp = t;
  531.  
  532.     if ((n = phrase (t)) > 0) {
  533.     if (t = advance (t, n)) {
  534.         if (t->type != T_COLON)
  535.         return 0;
  536.         len += n;
  537.         t = advance (t, 1);
  538.         len += 1;
  539.         for (;;) {
  540.         if (n = mailbox (t)) {
  541.             t = advance (t, n);
  542.             len += n;
  543.             if (t && (t->type == T_COMMA)) {
  544.             len += 1;
  545.             t = advance (t, 1);
  546.             continue;
  547.             }
  548.         }
  549.         break;
  550.         }
  551.         if (t && (t->type == T_SEMI)) {
  552.         tp->ptype = T_GROUPLIST;
  553.         tp->plen = ++len;
  554.         t->ptype = T_GROUPEND;
  555.         t->plen = 1;
  556.         return len;
  557.         }
  558.     }
  559.     }
  560.     return 0;
  561. }
  562.  
  563. #ifdef MAIL11
  564. /* 
  565.  * parse mail11 addresses
  566.  *  or at least the hostname:: part.
  567.  */
  568. int
  569. mail11_mailbox (t)
  570. token *t;
  571. {
  572.     int n;
  573.     token *head = t;
  574.     if (t->type == T_ATOM) { 
  575.     t = advance(t,1);
  576.     if (t && (t->type == T_COLCOL)) {
  577.         t = advance(t,1);
  578.         n = addrspec(t);
  579.         if (n == 0)
  580.         return(0);
  581.         head->ptype = T_MAIL11;
  582.         head->plen = n + 2;
  583.         return(n + 2);        /* addr_spec + hostname + "::" */
  584.     }
  585.     }
  586.     return(0);
  587. }
  588. #endif
  589. /*
  590.  * Parse "phrase route-addr" or "addrspec".  
  591.  */
  592.  
  593. int
  594. rfc822_mailbox (t)
  595. token *t;
  596. {
  597.     token *head = t;
  598.     int n, len;
  599.  
  600.     if (n = phrase(t)) {
  601.     len = n;
  602.     t = advance (t, n);        /* skip past it */
  603.     }
  604.     if (n = routeaddr (t)) {
  605.     head->ptype = T_PHRASEADDR;
  606.     head->plen = len + n;
  607.     return head->plen;        /* if followed by route-addr, done */
  608.     }
  609.     return addrspec (head);        /* see if it's an addrspec */
  610. }
  611.  
  612. int
  613. mailbox (t)
  614. token *t;
  615. {
  616.     int n;
  617. #ifdef MAIL11
  618.     if (n = mail11_mailbox(t)) return(n);
  619. #endif
  620.     if (n = rfc822_mailbox(t)) return(n);
  621.     return(0);
  622. }
  623.  
  624. static int
  625. addrlist (t)
  626. token *t;
  627. {
  628.     token *tp;
  629.     int n, naddrs = 0;
  630.  
  631.     while (t) {
  632.     tp = t;
  633.     if (n = group (t)) {
  634.         ++naddrs;
  635.         if (t = advance (t, n)) {
  636.         if (t->type == T_COMMA) {
  637.             t->ptype = T_COMMA;
  638.             t->plen = 1;
  639.             t = advance (t, 1);
  640.             continue;
  641.         }
  642.         }
  643.         else
  644.         break;
  645.     }
  646.     else if (n = mailbox (t)) {
  647.         ++naddrs;
  648.         if (t = advance (t, n)) {
  649.         if (t->type == T_COMMA) {
  650.             t->ptype = T_COMMA;
  651.             t->plen = 1;
  652.             t = advance (t, 1);
  653.             continue;
  654.         }
  655.         }
  656.         else
  657.         break;
  658.     }
  659.     if (t == NULL || t->type == T_EOH)
  660.         return naddrs;
  661.  
  662.     /* parse problem - mark invalid tokens and try to continue */
  663.  
  664.     tp->ptype = T_IGNORE;        /* mark token string bad */
  665.     tp->plen = 1;
  666.     t = tp->next;
  667.     
  668.     /* munch tokens till we find comma or end of string */
  669.     while (t && (t->type != T_COMMA) && (t->type != T_EOH)) {
  670.         ++tp->plen;
  671.         t = advance (t, 1);
  672.     }
  673.  
  674.     /* eat following comma */
  675.     if (t && (t->type == T_COMMA)) {
  676.         t = advance (t, 1);
  677.     }
  678.     }
  679.     return naddrs;
  680. }
  681.  
  682. char *
  683. unspace(str)
  684. char *str;
  685. {
  686.     char *cp;
  687.     while(isspace(*str)) str++;
  688.     cp = str + strlen(str) - 1;
  689.     while(isspace(*cp)) *cp-- = '\0';
  690.     return(str);
  691. }
  692.  
  693. #ifndef NOTMM
  694. match_addresses (a, buf, len)
  695. char **buf;
  696. addresslist *a;
  697. int len;
  698. {
  699.     int n;
  700.     token *t, *newt;
  701.     token *t2;
  702.     char *p,*cp;
  703.  
  704.     if (strlen (*buf) < 1)
  705.     return;
  706.     t = lex (*buf);
  707.     if ((t == 0) || (t->type == T_EOH))
  708.     return;
  709.  
  710.     for (n = 0, t2 = t; t2; t2 = t2->next)
  711.     ++n;
  712.     /*
  713.      * Note that the untoken calls free the address token structs,
  714.      * hence the use of newt to step through the list.
  715.      */
  716.     if (addrlist (newt = t)) {
  717.     int n;
  718.     token *t1;
  719.     while (t = newt) {
  720.         switch (t->ptype) {
  721.         case T_GROUPLIST:
  722.         for (n = 1, t1 = advance(t,1); t1->ptype == T_NONE;
  723.              t1 = advance(t1,1),n++) 
  724.             ;
  725.         newt = advance (t, n);
  726.         add_addresslist(a, unspace(untoken(t,n - 1,0,1)), ADR_GROUP);
  727.         break;
  728.           case T_GROUPEND:
  729.         newt = advance (t, 1);
  730.         add_addresslist(a, unspace(untoken(t,1,0,1)), ADR_GROUPEND);
  731.         break;
  732. #ifdef MAIL11
  733.           case T_MAIL11:
  734. #endif
  735.           case T_ADDRSPEC:
  736.           case T_PHRASEADDR:
  737.         newt = advance (t, t->plen);
  738.         cp = unspace(untoken(t,t->plen,0,1));
  739. #ifndef TEST
  740.         if (strcmp(cp,".") == 0)
  741.             add_addresslist(a,user_name,ADR_ADDRESS);
  742.         else if (*cp == '*')
  743.             add_addresslist(a,tilde_expand(cp+1),ADR_FILE);
  744.         else if (lookup_alias(cp))
  745.             add_addresslist(a,cp,ADR_ALIAS);
  746.         else
  747. #endif
  748.             add_addresslist(a,cp,ADR_ADDRESS);
  749.         break;
  750.           case T_IGNORE:
  751.         newt = advance (t, (t->plen ? t->plen : 1));
  752.         cp = unspace(untoken (t, t->plen, 0, 1));
  753.         if (use_address(cp))
  754.             add_addresslist(a, cp, ADR_ADDRESS);
  755.         break;
  756.           default:
  757.         newt = t->next;
  758. #ifdef TEST
  759.         if (t->type != T_COMMA)
  760.             printf ("unknown token \"%s\"\n",
  761.                 untoken (t, 1, 0, 1));
  762. #endif
  763.         break;
  764.         }
  765.     }
  766.     }
  767. }
  768.  
  769.  
  770. use_address(str) 
  771. char *str;
  772. {
  773. #ifndef TEST
  774.     extern use_invalid_address;
  775.     switch(use_invalid_address) {
  776.       case SET_YES:
  777.     return(true);
  778.       case SET_NO:
  779.     printf("Invalid address: \"%s\"\n", str);
  780.     return(false);
  781.       case SET_ASK:
  782.     printf("Invalid address: \"%s\"\n", str);
  783.     return(yesno("Use anyway? "));
  784.     }
  785. #else
  786.     printf("Invalid address: \"%s\"\n", str);
  787.     return(0);
  788. #endif
  789. }
  790.  
  791. #if TEST
  792. main(argc,argv) 
  793. int argc;
  794. char **argv;
  795. {
  796.     addresslist a;
  797.     char *buf;
  798.     
  799.     buf = (char *)malloc(512);
  800.     a.first = a.last = NULL;
  801.     while (fgets (buf, 512, stdin) != NULL) {
  802.     match_addresses(&a,&buf,strlen(buf));
  803.     }
  804. }
  805. #endif /* TEST */
  806. #else /* NOTMM */
  807. #if TEST
  808. main (argc, argv)
  809. int argc;
  810. char *argv[];
  811. {
  812.     int n;
  813.     token *t, *newt;
  814.     char buffer[512];
  815.     char *p;
  816.  
  817.     while ((p = fgets (buffer, sizeof (buffer), stdin)) != NULL) {
  818.     if (strlen (buffer) < 1)
  819.         continue;
  820.     t = lex (buffer);
  821.     if ((t == 0) || (t->type == T_EOH))
  822.         continue;
  823.     {
  824.         token *t2;
  825.         for (n = 0, t2 = t; t2; t2 = t2->next)
  826.         ++n;
  827.         printf ("n = %d, tokens = %s\n", n, untoken (t, n, 0, 0));
  828.     }
  829.     /*
  830.      * Note that the untoken calls free the address token structs,
  831.      * hence the use of newt to step through the list.
  832.      */
  833.     if (addrlist (newt = t)) {
  834.         while (t = newt) {
  835.         switch (t->ptype) {
  836.           case T_GROUPLIST:
  837.             newt = advance (t, t->plen);
  838.             printf ("group = %s\n", untoken (t, t->plen, 0, 1));
  839.             break;
  840. #ifdef MAIL11
  841.           case T_MAIL11:
  842. #endif
  843.           case T_ADDRSPEC:
  844.           case T_PHRASEADDR:
  845.             newt = advance (t, t->plen);
  846.             printf ("address = %s\n", untoken (t, t->plen, 0, 1));
  847.             break;
  848.           case T_IGNORE:
  849.             newt = advance (t, (t->plen ? t->plen : 1));
  850.             printf ("bad tokens: %s\n", untoken (t, t->plen, 0, 1));
  851.             break;
  852.           default:
  853.             newt = t->next;
  854.             if (t->type != T_COMMA)
  855.             printf ("unknown token \"%s\"\n",
  856.                 untoken (t, 1, 0, 1));
  857.             break;
  858.         }
  859.         }
  860.     }
  861.     else
  862.         printf ("no addresses found\n");
  863.     }
  864.     exit (0);
  865. }
  866. #endif /* TEST */
  867.  
  868. #endif NOTMM
  869.